diff options
300 files changed, 82087 insertions, 0 deletions
diff --git a/.hg_archival.txt b/.hg_archival.txt new file mode 100644 index 0000000..22a0b4f --- /dev/null +++ b/.hg_archival.txt @@ -0,0 +1,2 @@ +repo: e3ffdd7ae81c596b2be7e1e110d2c1255161340e +node: 0a7add1d9996b6d869d441da6c82fb7b8abcef4f diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..8ce7ca5 --- /dev/null +++ b/.hgignore @@ -0,0 +1,92 @@ +syntax: glob + +# Autofoo entries +*.o +*.la +*.lo +*.loT +*.pyc +.libs +.deps +*.cache +.cvsignore +compile +configure +configure.status +configure.lineno +depcomp +aclocal.m4 +libtool +ltmain.sh +ltconfig +libltdl +mkinstalldirs +install-sh +missing +py-compile +autom4te* +libtool.m4 +ltdl.m4 +libltdl.tar +autoconf +autoheader +automake +include/glue_config.h +include/stamp-h1 +include/pils/plugin.h +include/stamp-h2 +ylwrap + +# BEAM Entries +*.beam +parser-messages +MISC_ERRORS +cscope.files +cscope.out +patches +updates +logs + +# OS and Editor Artifacts +.DS_Store +*.diff +*.patch +*~ + +# Project build targets +lib/clplumbing/base64_md5_test +lib/clplumbing/ipctest +lib/clplumbing/ipctransientclient +lib/clplumbing/ipctransientserver +logd/ha_logd +logd/ha_logger +logd/logtest +lrm/admin/lrmadmin +lrm/lrmd/lrmd +lrm/test/apitest +lrm/test/callbacktest +lrm/test/plugintest +lrm/test/lrmregtest +lrm/test/lrmregtest-heartbeat +lrm/test/lrmregtest-lsb +lrm/test/regression.sh +lrm/test/LRMBasicSanityCheck +lrm/test/simple_ops + +# Misc +GPATH +GRTAGS +GSYMS +GTAGS +HTML +TAGS +.gres.* +*.orig +.gdb_history + +# Entries better done as regexp's to avoid matching too broadly +syntax: regexp +^config\.* +README$ +Makefile$ +Makefile.in$ @@ -0,0 +1,4 @@ +b6dca003bb176978af803eeb33019b6aef3c58b0 0 iEYEABECAAYFAktnGJAACgkQWnQN9wr0w1ywBACghXYwYkv/70Xg5AQMzVjRWKZecIoAnjRUytRoYl+dhhqbhfdXSD+/Bfvw +6007185b487e3f2dc3b24674a9105761b2cde6ea 0 iEYEABECAAYFAktoWfsACgkQWnQN9wr0w1ySZwCfQILyC2VJrCnVEU2zvTIyI7ustDAAn37hhb9JM8JQVKLfPEbqIloz1m3m +979c4ffae287976631a30d10258903aea6fb28fa 0 iEYEABECAAYFAktoY38ACgkQWnQN9wr0w1wHxgCeMZyOt8ccxmIsvIHg4/y6KmqtTVAAn2jn7dOmFMjA8m4ju59YaQ1Bznhb +798645ead29e20b361af883fce695b85caf3392b 0 iEYEABECAAYFAlPJCM4ACgkQWnQN9wr0w1wv+QCeJQOjaYNXNJZA61n7Fu8f63CeVBEAnja4WqiYC+TS4HvmRJz6oNi6p48u @@ -0,0 +1,68 @@ +01ecac8670a6c2e47202a9ce2f5e27e9dcdbeff6 STABLE-1.1.3 +19d11d8d62c270c48a3feab5ed66b18897c9cc8d sle11-rc9 +2185d55c12e37c48abc239dd1f8b3b9ef012fd6b obs-2.1.2-1 +235a71009062702c906cc68f23904ddcbe17535f STABLE-2.0.6 +29540582671a9e33ae2122d319c68258346f1a3f STABLE-2.1.1 +2cb36a1c01c76ef3e3a449f16b13730c761efff2 STABLE-2.1.3 +2ece20ad31a4271076e5c43dd3f2ea25caa55635 Series-Root-1.0 +3b8dc33a402daaf7e3754acadd1898c0fe69072f STABLE-2.0.3 +45c377d7a35dba92d46321d2f824bc0d9b17f54e obs-2.1.2-24 +67a443d135f128ca28f15e4e8999d3e0caabed61 sle11-rc3 +68de68ef5f0a7b97a4ff0d9806c598527c8659b8 STABLE-2.0.4 +705b21e4b623f7d2fc5c83d99beffb709905c996 STABLE-0.4.9c +7190f69e29a08350bcec753509eb37f53593334a beta-2.99.0 +7f90244e5c25372e70178f77f44c76a8564e1665 SLE10-SP1 +7f90244e5c25372e70178f77f44c76a8564e1665 STABLE-2.1.0 +823208439a98179d7c01d6eed1db50dd96802663 sle11-rc7 +86fa06f08a123868eb272a20bf850cec1805a12f STABLE-2.0.8 +97d025dd33648a1e50a3a1bf40573669440dd1b6 obs-2.1.2-4 +9b34f480b8e8966e9ed4276507cc562564763720 beta-2.99.2 +9eb2a4db4ff595d18302426029b03153fad77ef7 beta-2.99.1 +a230062a445096b89cf75bef85e285ee55626a78 obs-2.1.2-2 +af867b71bcc645f3d3c56fe8fdd883b17a851e46 STABLE-2.0.0 +b4a0a0ffd15eb2dd1285bdbd86ea9716a9d0bf36 STABLE-2.0.5 +b906db882c37647abfd21fa1473950445ad7813c STABLE-2.1.2 +ba476a3948ea0cf52098fa050a27a8856a214825 sle11-rc2 +be0d49da51a810e870356b7f2a52013e5c775c0d Beta-0.4.9a +c77ad4549888539e7fc9a6b56cccdb1403749198 STABLE-0.4.9e +c7d672b9f3ece79ad26fb8a7df20265bcb596515 sle11-beta6 +cf0265eed1b5b3b3f25f7e56eb807d21ca261d68 SLES10-GA-2.0.7-1.5 +cf0265eed1b5b3b3f25f7e56eb807d21ca261d68 STABLE-2.0.7 +d1899e1eecc09b7a6e66a02609408272bb856c6a STABLE-1.1.5 +dae6b0b3e109afc5df29a7127ab6dd9e1bd0a20a sle11-rc5 +e3691501a2d0631c3796b6a728fadf7d90691203 obs-2.1.2-15 +e3855af19554339204b5b2b2a199a7bc31e22843 STABLE-2.0.1 +e3855af19554339204b5b2b2a199a7bc31e22843 STABLE-2.0.2 +e6637f62c87ada212a83942ec5b2a4bf30b98c3f Series-Root-1.2 +f6c2cd2593f365f984ce051db61466738ac05dcd Beta-0.4.9f +940fa13f6a0a929d15a01af9a0b62c16e4d2706a glue-1.0 +130b1d7af88912d077d32a7c386c3c94d0b2da16 glue-1.0.2-rc1 +78894a112c0a134dc709d2a8772085180444c40c glue-1.0.2-rc2 +7700902a4de3ee84fa2007a4b4602693c5ac26a8 glue-1.0.2-rc2a +97fcdf789e174b0a0b23e28dcabe2f7d579d426f glue-1.0.2 +0a64e6f77894da1364b17dc3c73b65561717f4aa glue-1.0.3 +0a64e6f77894da1364b17dc3c73b65561717f4aa glue-1.0.3 +0000000000000000000000000000000000000000 glue-1.0.3 +0000000000000000000000000000000000000000 glue-1.0.3 +979c4ffae287976631a30d10258903aea6fb28fa glue-1.0.3 +979c4ffae287976631a30d10258903aea6fb28fa glue-1.0.3 +0000000000000000000000000000000000000000 glue-1.0.3 +0000000000000000000000000000000000000000 glue-1.0.3 +9bcd134f1ebff7baf80f4b21c3b5f620b0ee976e glue-1.0.3 +9bcd134f1ebff7baf80f4b21c3b5f620b0ee976e glue-1.0.3 +0000000000000000000000000000000000000000 glue-1.0.3 +0000000000000000000000000000000000000000 glue-1.0.3 +2e33ecd820b2673755d1280a259489a026921f63 glue-1.0.3 +761edff8c35ea2cdf3e1bd37d600b06233e61d4f glue-1.0.4-rc1 +3229873980e1028bf05de81f5bafccb3a92b9aa4 glue-1.0.4 +3af80b93d9e5d5e441f3f4c3aad16775ea27d2d9 glue-1.0.5 +1c87a0c58c59fc384b93ec11476cefdbb6ddc1e1 glue-1.0.6 +61200fbe18358e420cdc2037d87e803e150c1eac glue-1.0.7-rc1 +5e06b2ddd24b37ad6c1c25d958d7a9dda7d02f93 glue-1.0.7 +5740338816e1ff07d0e37f36214f442e183984d7 glue-1.0.8-rc1 +c69dc6ace936f501776df92dab3d611c2405f69e glue-1.0.8 +0a08a469fdc8a0db1875369497bc83c0523ceb21 glue-1.0.9 +12055ca2b025ab250a544701edaa1f5aaf63aef1 glue-1.0.10 +02bdcf58f9a098b717784746308e199e12eeb005 glue-1.0.11 +c64d6e96f20ad5ba245f7fb9e1295b14fa179e29 glue-1.0.12-rc1 +d05229decc34d66c4752536dc7c9d812d1e6d5ca glue-1.0.12 @@ -0,0 +1,19 @@ +Alan Robertson <alanr@unix.sh> +Andreas Mock <andreas.mock@web.de> +Andrew Beekhof <andrew@beekhof.net> +Dave Blaschke <debltc@us.ibm.com> +David Lee <t.d.lee@durham.ac.uk> +Dejan Muhamedagic <dejan@hello-penguin.com> +Hannes Eder <heder@google.com> +Huang Zhen <zhenhltc@cn.ibm.com> +Junko Ikeda <ikedaj@intellilink.co.jp> +Lars Marowsky-Bree <lmb@suse.de> +Martin Bene <martin.bene@icomedias.com> +Phil Carns <carns@mcs.anl.gov> +Satomi Taniguchi <taniguchis@intellilink.co.jp> +Sean Reifschneider <jafo@tummy.com> +Sebastian Reitenbach <itlistuser@rapideye.de> +Serge Dubrouski <sergeyfd@gmail.com> +Simon Horman <horms@verge.net.au> +Xinwei Hu <hxinwei@gmail.com> + @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/COPYING.LIB b/COPYING.LIB new file mode 100644 index 0000000..602bfc9 --- /dev/null +++ b/COPYING.LIB @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + 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 Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +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 other code 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. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + 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, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser 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 combine 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) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) 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. + + d) 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. + + e) 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 materials to be 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 with +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 Lesser 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 + + 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. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..4366ee3 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,262 @@ +* Fri Jul 18 2014 Dejan Muhamedagic <dejan@suse.de>, Lars Ellenberg <lars.ellenberg@linbit.com>, and many others +- stable release 1.0.12 +- hb_report: add -Q option for quick runs +- hb_report: dot is not illegal in file names (bnc#884079, deb#715391) +- build: update spec files for systemd +- hb_report: update interface to zypper +- hb_report: support logs with varied timestamps +- stonith: external/vcenter: add parameter for SSL hostname + verification (bnc#851990) +- hb_report: fix ssh passwords again (bnc#867365) +- hb_report: Don't use deprecated corosync-fplay (bnc#870913) +- logd: Add systemd unit file for logd (bnc#863248) +- hb_report: Add support for xz compression (bnc#854060) + +* Thu Sep 26 2013 Dejan Muhamedagic <dejan@suse.de>, Lars Ellenberg <lars.ellenberg@linbit.com>, and many others +- release candidate 1.0.12-rc1 +- clplumbing: increase listen backlog to 128 +- hb_report: add -X option for extra ssh options +- hb_report: add support for the rfc5424 syslog date format +- stonith: external/libvirt: fix exit code in reset +- stonith: external/vcenter: do not list vms in status + (bnc#825765) +- stonith: fix memory leak in external.c +- hb_report: enable ssh to prompt for passwords (bnc#808373) +- hb_report: collect RA trace files +- hb_report: look for nodes in the archived CIB if pacemaker is + not running +- sbd plugin now lives at http://hg.linux-ha.org/sbd/ +- stonith: if debug's not set, do not send debug messages to the + logger (bnc#792124) +- stonith: log status message at the debug level (bnc#792124) +- stonith: don't always log debug level messages (bnc#792124) +- stonith: external/vcenter: fix gethosts to produce list of + nodes not vms (bnc#792704) + +* Mon Oct 15 2012 Dejan Muhamedagic <dejan@suse.de>, Lars Ellenberg <lars.ellenberg@linbit.com>, and many others +- stable release 1.0.11 +- lrmd: set max-children depending on the number of processors +- lrmd: don't send parameters from ops back to crmd +- stonith: external/libvirt: support for reboot reset method +- hb_report: node's type got optional +- hb_report: make use of bash trace features +- hb_report: compatibility code for pacemaker v1.1.8 +- build: link libstonith with stonith2 agents + +* Mon Jul 16 2012 Dejan Muhamedagic <dejan@suse.de>, Lars Ellenberg <lars.ellenberg@linbit.com>, and many others +- stable release 1.0.10 +- clplumbing: ipc: fix message size checks (bnc#752231) +- clplumbing: load bz2 compression module by default +- clplumbing: cl_msg: try compressing message before rejecting it + as too big +- clplumbing: cl_msg: don't use traditional compression by default +- clplumbing: cl_msg: increase compression threshold +- clplumbing: fix memleak for Gmain_timeout +- LRM: lrmd: add basic authentication (lf#2547) +- LRM: lrmd: use the resource timeout as an override to the + default dbus timeout for upstart RA +- LRM: lrmd: if set, get max-children from the LRMD_MAX_CHILDREN + environment var +- stonith: add CRM stonith resource name to log messages (bnc#728579) +- stonith: adjust timeouts in the meta-data template (bnc#733337) +- stonith: external/vcenter: return list of configured hosts on + gethosts +- stonith: external/libvirt: add more search strings for domain + start and stop +- stonith: rhcs: pass the action via stdin too +- stonith: rhcs: avoid false error if parameter isn't set +- logd: remove runlevel 4 from the LSB info section in the logd + init script (bnc#744120) +- logd: add try-restart action to the logd init script +- sbd: Use async IO for disk reads to increase resilience against + hung IO (bnc#738295) +- sbd: Handle IO errors during slot allocation properly (bnc#753559) +- sbd: Debug mode added (bnc#753559, bnc#738295) +- hb_report: improve performance +- hb_report: get corosync blackbox records if available +- hb_report: add node time information + +* Mon Nov 28 2011 Dejan Muhamedagic <dejan@suse.de>, Lars Ellenberg <lars.ellenberg@linbit.com>, and many others +- stable release 1.0.9 +- stonith: external/ipmi: add missing double quote +- stonith: external/ipmi: add the priv parameter (ipmitool -L) +- LRM: lrmd: set op status to cancelled for running monitor operations +- ha_log: increase MAXENTITY size to accommodate long stonith strings +- hb_report: improve destination directory handling (bnc#727295) +* Tue Oct 18 2011 Dejan Muhamedagic <dejan@suse.de>, Lars Ellenberg <lars.ellenberg@linbit.com>, and many others +- stable release 1.0.8 +- cl_log: log spamming control +- LRM: raexecocf: list resource agents properly (bnc#664409) +- LRM: lrmd: allow storing parameters in local files (lf#2415) +- LRM: lrmd: limit number of "stayed too long in operation list" + log messages (bnc#636576) +- stonith: external/libvirt: new plugin for libvirt virtualization technologies +- stonith: external/vcenter: new plugin +- stonith: external/hetzner: new plugin +- stonith: sbd: support for multiple devices +- stonith: sbd: Fix timeout setting on archs where int != long (bnc#635690) +- stonith: sbd: abort start if watchdog cannot be initialized (bnc#680109) +- stonith: sbd: Make failing to set the watchdog timeout non-fatal but annoying +- stonith: sbd: Make the restart interval for servants configurable +- stonith: sbd: Maximize scheduler and IO priority in the child processes (bnc#702907) +- stonith: external/sbd: Fix ordering of arguments in reset +- stonith: external/ipmi: fix unique parameters' attributes +- stonith: external/rackpdu: split off assignment from local to + make it work with non-bash shells +- stonith: external: avoid false error if parameter isn't set (bnc#646205) +- hb_report: add .info files with the last byte pos for all logs +- hb_report: use sudo for remove collectors if connecting with + user other than root +- hb_report: install debuginfo packages on platforms with zypper (bnc#641979) +- hb_report: improve detecting ssh user + +* Tue Nov 30 2010 Dejan Muhamedagic <dejan@suse.de>, Lars Ellenberg <lars.ellenberg@linbit.com>, and many others +- stable release 1.0.7 +- clplumbing: ipc: adjust socket buffers size when adjusting ipc queue length +- logd: add a SIGHUP signal handler to timely close/open log files +- logd: use buffered io with fflush and fsync +- logd: reopen logfiles on inode change (logrotate) +- clplumbing: cl_log: keep logfiles open, but default to non-buffered io (lf#2470) +- clplumbing: cl_log: add new optional common syslog message prefix +- stonith: use ST_DEVICEID for the short description in meta-data +- stonith: external: interpret properly exit codes from external stonith + plugins (bnc#630357) +- stonith: external: avoid false out of memory error if a parameter isn't set (bnc#646205) +- stonith: external: check if PATH already contains GLUE_SHARED_DIR + (memory leak, lf#2484) +- stonith(8): reduce the number of stonith plugin invocations (bnc#630357) +- stonith(8): use cl_log for logging if invoked by stonithd (pcmk 1.1) +- stonith: external/sbd: make sbd use realtime priority for IO (works only with CFQ) +- stonith: cyclades: add the serial_port parameter to the meta-data +- stonith: external/riloe: add support for http proxies +- stonith: external/ipmi: provide opt param "passwd_method" to hide + the ipmi password from config and logs +- stonith: external/nut: support for the Network UPS Tools +- stonith: external/rackpdu: remove displaced local command +- stonith: rcd_serial: rename dtr|rts parameter to dtr_rts +- configure: test for POSIX signals (fixes rcd_serial) + +* Fri Jul 9 2010 Dejan Muhamedagic <dejan@suse.de> +- stable release 1.0.6 +- clplumbing: Add identity info of the user on the other side of socket +- ha_logger: log strings longer than 1024 +- lrmd: remove operation history on client unregister (lf#2161) +- lrmd: don't allow cancelled operations to get back to the repeating op list (lf#2417) +- lrmd: exclude stonith resources from child count (bnc#612387) +- lrmd,clientlib: asynchronous resource delete notification (lf#2439) +- stonith: add -V (version) to stonith +- stonith: add -E option to get the configuration from the environment +- stonith: ha_log: feed the message to stdout and not on command line +- stonith: external/sbd,xen0: fix wrong reference from ha_log to ha_log.sh (deb#585120) +- stonith: external/sbd: reduce monitoring +- stonith: external/rackpdu: check the snmpset and snmpwalk exit codes +- hb_report: create cib.txt after sanitizing the CIB (lf#2415) + +* Mon Apr 15 2010 Dejan Muhamedagic <dejan@suse.de> +- stable release 1.0.5 +- clplumbing: revert changeset 81ad41d14f72 which breaks the ABI + +* Mon Apr 12 2010 Dejan Muhamedagic <dejan@suse.de> +- stable release 1.0.4 +- clplumbing: fix memory leak in cl_msg/lrmd (lf#1841,2389) +- clplumbing: Add identity info of the user on the other side of socket +- clplumbing: Fix erroneous "Stack hogger failed 0xffffffff" warnings +- lrmd: fix possible null pointer dereference +- lrmd: raise severity from debug to info for some log messages +- lrmd: on shutdown exit once all operations finished (lf#2340) +- lrmd: don't add the cancel option in flush to the running operations (bnc#578644) +- lrmd: check if tables exist before free_str_table and prevent + segfault (bnc#587887) +- stonith: new external/ippower9258 plugin +- stonith: external/sbd: fix status operation +- stonith: external/sbd: add support for heartbeat +- stonith: external/ibmrsa-telnet: fix ha_log.sh invocation +- stonith: external/ibmrsa-telnet: fix expect regex +- stonith: external/ipmi: make reset work when the node is off +- stonith: external/riloe: log error message on unrecognized power method +- hb_report: don't create dot files if there are more than 20 PE files +- hb_report: make dot and png files for PE inputs (if there are + not too many) +- hb_report: do not filter CIB/PE files by default (use -s to + force filtering) +- hb_report: add -Z option to force destination directory cleanup +- hb_report: allow for default destination +- hb_report: when creating cts reports get information from the log +- hb_report: new option -d to keep the directory +- hb_report: don't give up early when creating backtraces (lf#2350) + +* Tue Feb 02 2010 Dejan Muhamedagic <dejan@suse.de> +- bugfix release 1.0.3 +- lrmd: don't flush operations which don't belong to the requesting client (lf#2161) + +* Mon Feb 01 2010 Dejan Muhamedagic <dejan@suse.de> and MANY others +- stable release 1.0.2 +- clplumbing: fix a potential resource leak in cl_random (bnc#525393) +- clplumbing: change the default log format to syslog format +- lrmd: log outcome of monitor once an hour +- lrmd: lookup clients by name (lf#2161) +- lrmd: remove operation history on client unregister (lf#2161) +- lrmd: fix return code on LSB class RA exec failure (lf#2194) +- lrmd: close the logd fd too when executing agents (lf#2267) +- lrmd: restore reset scheduler for children (bnc#551971,lf#2296) +- lrmd: reset scheduler and priority for children (resource operations) +- lrmadmin: fix -E option +- lrmadmin moved to the sbindir +- stonith: support for RHCS fence agents +- stonith: external/dracmc-telnet: stonith plugin for Dell + Drac/MC Blade Enclosure and Cyclades terminal server +- stonith: sbd plugin +- stonith: apcmastersnmp plugin (bnc#518689) +- stonith: bladehpi plugin (bnc#510299) +- stonith: WTS MPC: new SNMP based plugin +- stonith: meatclient: add -w option to wait until we can connect +- stonith: add -m option to stonith(8) to display metadata (lf#2279) +- stonith: external: log using ha_log.sh (lf#2294,1971) +- stonith: external: log output of plugins (bnc#548699,bnc#553340) +- stonith: external: log messages immediately on manage and status calls +- stonith: external: remove dependency on .ocf-shellfuncs (lf#2249) +- stonith: external/riloe: make sure that host is turned on after power + off/on reset (lf#2282) +- stonith: external/riloe: fix check for ilo_can_reset +- stonith: external/riloe: workaround for the iLO double close in RIBCL (bnc#553340) +- stonith: external/ipmi: add explanation on reset and power off (LF 2071) +- stonith: external/ibmrsa-telnet: add support for later RSA cards +- stonith: cyclades: fix for support for newer PM10 firmware (lf#1938) +- stonith: wti_nps: add support for internet power switch model (bnc#539912) +- stonith: wti_mpc: support for MIB versions 1 and 3 +- stonith: external/sbd: fix definition of sector_size for s390x (bnc#542827) +- stonith: external/sbd: make nodename comparison case insensitive (bnc#534445) +- stonith: external/sbd: describe "dump" command in help (bnc#529575) +- stonith: external/sbd: Accept -h (bnc#529574) +- stonith: external/xen0: add run_dump parameter to dump core before resetting a node +- hb_report: add man page hb_report.8 +- hb_report: add -V (version) option +- hb_report: add support for corosync +- hb_report: add -v option (debugging) +- hb_report: options -C and -D are obsoleted +- hb_report: combine log/events if there is no loghost +- hb_report: extract important events from the logs +- logd: add init script +- rpm spec: start logd by default +- doc: new README for wti_mpc +- doc: move stonith README files to the doc directory +- doc: convert man pages to xml +- build: /usr/share/heartbeat replaced by /usr/share/cluster-glue +- build: enable IPMI and hpi support +- build: include time.h in ipcsocket.c and proctrack.c (lf#2263) +- build: output documentation directory from configure (lf#2276) + +* Thu Oct 23 2008 Lars Marowsky-Bree <lmb@suse.de> and MANY others +- beta release 2.99.2 +- stonith: external/kdumpcheck: new plugin +- stonith: external/drac5: new plugin +- stonith: drac3: initialize curl properly and workaround xml parsing problem (lf#1730) +- stonith external/riloe: a new implementation for HP iLO devices + +* Tue Sep 23 2008 Lars Marowsky-Bree <lmb@suse.de> and MANY others +- beta release 2.99.1 +- stonith: bladehpi: fix a mix of a threaded library and not threaded stonithd (bnc#389344) +- stonith: external/riloe: fix check for ilo_can_reset + +* Tue Aug 19 2008 Andrew Beekhof <abeekhof@suse.de> and MANY others +- beta release 2.99.0 diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..a641d9c --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,47 @@ +# +# Copyright (C) 2008 Andrew Beekhof +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +-include Makefile + +PACKAGE ?= cluster-glue +TARFILE = $(PACKAGE).tar.bz2 + +RPM_ROOT = $(shell pwd) +RPM_OPTS = --define "_sourcedir $(RPM_ROOT)" \ + --define "_specdir $(RPM_ROOT)" \ + --define "_srcrpmdir $(RPM_ROOT)" + + +getdistro = $(shell test -e /etc/SuSE-release || echo fedora; test -e /etc/SuSE-release && echo suse) +DISTRO ?= $(call getdistro) +TAG ?= tip + +hgarchive: + rm -f $(TARFILE) + hg archive -t tbz2 -r $(TAG) $(TARFILE) + echo `date`: Rebuilt $(TARFILE) + +srpm: hgarchive + rm -f *.src.rpm + @echo To create custom builds, edit the flags and options in $(PACKAGE)-$(DISTRO).spec first + rpmbuild -bs --define "dist .$(DISTRO)" $(RPM_OPTS) $(PACKAGE)-$(DISTRO).spec + +rpm: srpm + rpmbuild $(RPM_OPTS) --rebuild $(RPM_ROOT)/*.src.rpm + + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..93dbaf6 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,39 @@ +# +# Copyright (C) 2008 Andrew Beekhof +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +DISTCHECK_CONFIGURE_FLAGS = \ + --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir) + +MAINTAINERCLEANFILES = Makefile.in aclocal.m4 configure DRF/config-h.in \ + DRF/stamp-h.in libtool.m4 ltdl.m4 libltdl.tar + +SUBDIRS = include $(LIBLTDL_DIR) replace lib lrm logd \ + hb_report doc config + +install-exec-local: + $(INSTALL) -d $(DESTDIR)/$(HA_COREDIR) + -$(INSTALL) -d -m 700 -o root $(DESTDIR)/$(HA_COREDIR)/root + -$(INSTALL) -d -m 700 -o nobody $(DESTDIR)/$(HA_COREDIR)/nobody + $(INSTALL) -d -m 700 $(DESTDIR)/$(HA_COREDIR)/$(GLUE_DAEMON_USER) + -chown $(GLUE_DAEMON_USER) $(DESTDIR)/$(HA_COREDIR)/$(GLUE_DAEMON_USER) +# Use chown because $(GLUE_DAEMON_USER) may not exist yet + +dist-clean-local: + rm -f autoconf automake autoheader $(TARFILE) + +.PHONY: @@ -0,0 +1 @@ + @@ -0,0 +1 @@ + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..7464c9d --- /dev/null +++ b/autogen.sh @@ -0,0 +1,193 @@ +#!/bin/sh +# +# License: GNU General Public License (GPL) +# Copyright 2001 horms <horms@vergenet.net> +# (heavily mangled by alanr) +# +# bootstrap: set up the project and get it ready to make +# +# Basically, we run autoconf, automake and libtool in the +# right way to get things set up for this environment. +# +# We also look and see if those tools are installed, and +# tell you where to get them if they're not. +# +# Our goal is to not require dragging along anything +# more than we need. If this doesn't work on your system, +# (i.e., your /bin/sh is broken) send us a patch. +# +# This code loosely based on the corresponding named script in +# enlightenment, and also on the sort-of-standard autoconf +# bootstrap script. + +# Run this to generate all the initial makefiles, etc. + +testProgram() +{ + cmd=$1 + + if [ -z "$cmd" ]; then + return 1; + fi + + arch=`uname -s` + + # Make sure the which is in an if-block... on some platforms it throws exceptions + # + # The ERR trap is not executed if the failed command is part + # of an until or while loop, part of an if statement, part of a && + # or || list. + if + which $cmd </dev/null >/dev/null 2>&1 + then + : + else + return 1 + fi + + # The GNU standard is --version + if + $cmd --version </dev/null >/dev/null 2>&1 + then + return 0 + fi + + # Maybe it suppports -V instead + if + $cmd -V </dev/null >/dev/null 2>&1 + then + return 0 + fi + + # Nope, the program seems broken + return 1 +} + +gnu="ftp://ftp.gnu.org/pub/gnu" + +for command in autoconf213 autoconf253 autoconf259 autoconf +do + if + testProgram $command == 1 + then + autoconf=$command + autoheader=`echo "$autoconf" | sed -e 's/autoconf/autoheader/'` + autom4te=`echo "$autoconf" | sed -e 's/autoconf/autmo4te/'` + autoreconf=`echo "$autoconf" | sed -e 's/autoconf/autoreconf/'` + autoscan=`echo "$autoconf" | sed -e 's/autoconf/autoscan/'` + autoupdate=`echo "$autoconf" | sed -e 's/autoconf/autoupdate/'` + ifnames=`echo "$autoconf" | sed -e 's/autoconf/ifnames/'` + fi +done + +for command in automake14 automake-1.4 automake15 automake-1.5 automake17 automake-1.7 automake19 automake-1.9 automake-1.11 automake +do + if + testProgram $command + then + : OK $pkg is installed + automake=$command + aclocal=`echo "$automake" | sed -e 's/automake/aclocal/'` + fi +done + +for command in libtool14 libtool15 libtool glibtool +do + URL=$gnu/$pkg/ + if + testProgram $command + then + libtool=$command + libtoolize=`echo "$libtool" | sed -e 's/libtool/libtoolize/'` + fi +done + +if [ -z $autoconf ]; then + echo You must have autoconf installed to compile the cluster-glue package. + echo Download the appropriate package for your system, + echo or get the source tarball at: $gnu/autoconf/ + exit 1 + +elif [ -z $automake ]; then + echo You must have automake installed to compile the cluster-glue package. + echo Download the appropriate package for your system, + echo or get the source tarball at: $gnu/automake/ + exit 1 + +elif [ -z $libtool ]; then + echo You must have libtool installed to compile the cluster-glue package. + echo Download the appropriate package for your system, + echo or get the source tarball at: $gnu/libtool/ + exit 1 +fi + +oneline() { + read x; echo "$x" +} + +LT_version=`$libtool --version | oneline | sed -e 's%^[^0-9]*%%' -e s'% .*%%'` +LT_majvers=`echo "$LT_version" | sed -e 's%\..*%%'` +LT_minvers=`echo "$LT_version" | sed -e 's%^[^.]*\.%%' ` +LT_minnum=`echo "$LT_minvers" | sed -e 's%[^0-9].*%%'` + +if + [ $LT_majvers -lt 1 ] || [ $LT_majvers = 1 -a $LT_minnum -lt 4 ] +then + echo "Minimum version of libtool is 1.4. You have $LT_version installed." + exit 1 +fi + +# Create local copies so that the incremental updates will work. +rm -f ./autoconf ./automake ./autoheader ./libtool +ln -s `which $libtool` ./libtool +ln -s `which $autoconf` ./autoconf +ln -s `which $automake` ./automake +ln -s `which $autoheader` ./autoheader + +printf "$autoconf:\t" +$autoconf --version | head -n 1 + +printf "$automake:\t" +$automake --version | head -n 1 + +rm -rf libltdl libltdl.tar +echo $libtoolize --ltdl --force --copy +# Unset GREP_OPTIONS as any coloring can mess up the AC_CONFIG_AUX_DIR matching patterns +GREP_OPTIONS= $libtoolize --ltdl --force --copy + +arch=`uname -s` +# Disable the errors on FreeBSD until a fix can be found. +if [ ! "$arch" = "FreeBSD" ]; then +set -e +# +# All errors are fatal from here on out... +# The shell will complain and exit on any "uncaught" error code. +# +# +# And the trap will ensure sure some kind of error message comes out. +# +trap 'echo ""; echo "$0 exiting due to error (sorry!)." >&2' 0 +fi + +# Emulate the old --ltdl-tar option... +# If the libltdl directory is required we will unpack it later +tar -cf libltdl.tar libltdl +rm -rf libltdl + +echo $aclocal $ACLOCAL_FLAGS +$aclocal $ACLOCAL_FLAGS + +echo $autoheader +$autoheader + +echo $automake --add-missing --include-deps --copy +$automake --add-missing --include-deps --copy + +echo $autoconf +$autoconf + +test -f libtool.m4 || touch libtool.m4 +test -f ltdl.m4 || touch ltdl.m4 + +echo Now run ./configure +trap '' 0 diff --git a/cluster-glue-fedora.spec b/cluster-glue-fedora.spec new file mode 100644 index 0000000..b480ff5 --- /dev/null +++ b/cluster-glue-fedora.spec @@ -0,0 +1,249 @@ +# Keep around for when/if required +## define alphatag XXX + +%define gname haclient +%define uname hacluster +%define nogroup nobody + +# Directory where we install documentation +%global glue_docdir %{_defaultdocdir}/%{name}-%{version} + +# When downloading directly from Mercurial, it will automatically add this prefix +# Invoking 'hg archive' wont but you can add one with: hg archive -t tgz -p "Reusable-Cluster-Components-" -r $upstreamversion $upstreamversion.tar.gz +%global upstreamprefix Reusable-Cluster-Components- +%global upstreamversion d97b9dea436e + +Name: cluster-glue +Summary: Reusable cluster components +Version: 1.0.12 +Release: 1%{?dist} +License: GPLv2+ and LGPLv2+ +Url: http://www.linux-ha.org/wiki/Cluster_Glue +Group: System Environment/Base +Source0: cluster-glue.tar.bz2 +Requires: perl-TimeDate +Requires: cluster-glue-libs = %{version}-%{release} + +# Directives to allow upgrade from combined heartbeat packages in Fedora11 +Provides: heartbeat-stonith = 3.0.0-1 +Provides: heartbeat-pils = 3.0.0-1 +Obsoletes: heartbeat-stonith < 3.0.0-1 +Obsoletes: heartbeat-pils < 3.0.0-1 +Obsoletes: heartbeat-common + +## Setup/build bits + +BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) + +# Build dependencies +BuildRequires: automake autoconf libtool pkgconfig which +BuildRequires: bzip2-devel glib2-devel python-devel libxml2-devel libaio-devel +BuildRequires: OpenIPMI-devel openssl-devel +BuildRequires: libxslt docbook-dtds docbook-style-xsl +BuildRequires: help2man +BuildRequires: asciidoc + +%if 0%{?fedora} +BuildRequires: libcurl-devel libnet-devel +%endif + +%if 0%{?fedora} || 0%{?centos} > 4 || 0%{?rhel} > 4 +BuildRequires: libtool-ltdl-devel openhpi-devel +BuildRequires: net-snmp-devel >= 5.4 +%else +BuildRequires: gcc-c++ +%endif + +%if 0%{?fedora} > 11 || 0%{?centos} > 5 || 0%{?rhel} > 5 +BuildRequires: libuuid-devel +%else +BuildRequires: e2fsprogs-devel +%endif + +%if %{defined systemd_requires} +BuildRequires: systemd +%{?systemd_requires} +%endif + +%prep +%setup -q -n cluster-glue + +./autogen.sh + +# RHEL <= 5 does not support ./configure --docdir=, +# hence, use this ugly hack +%if 0%{?centos} <= 5 || 0%{?rhel} <= 5 +export docdir=%{glue_docdir} +%configure \ + --enable-fatal-warnings=yes \ + --with-daemon-group=%{gname} \ + --with-daemon-user=%{uname} \ + --localstatedir=%{_var} \ + --libdir=%{_libdir} +%else +%configure \ + --enable-fatal-warnings=yes \ + --with-daemon-group=%{gname} \ + --with-daemon-user=%{uname} \ + --localstatedir=%{_var} \ + --libdir=%{_libdir} \ +%if %{defined _unitdir} + --with-systemdsystemunitdir=%{_unitdir} \ +%endif + --docdir=%{glue_docdir} +%endif + +%build +make %{?jobs:-j%jobs} docdir=%{glue_docdir} + +%install +rm -rf %{buildroot} +make install DESTDIR=%{buildroot} docdir=%{glue_docdir} + + +## tree fix up +# Dont package static libs +find %{buildroot} -name '*.a' -exec rm {} \; +find %{buildroot} -name '*.la' -exec rm {} \; + +%clean +rm -rf %{buildroot} + +# cluster-glue + +%description +A collection of common tools that are useful for writing cluster managers +such as Pacemaker. +Provides a local resource manager that understands the OCF and LSB +standards, and an interface to common STONITH devices. + +%files +%defattr(-,root,root) +%dir %{_datadir}/%{name} +%if %{defined _unitdir} +%{_unitdir}/logd.service +%else +%{_sysconfdir}/init.d/logd +%endif +%{_datadir}/%{name}/ha_cf_support.sh +%{_datadir}/%{name}/openais_conf_support.sh +%{_datadir}/%{name}/utillib.sh +%{_datadir}/%{name}/ha_log.sh + +%{_sbindir}/ha_logger +%{_sbindir}/hb_report +%{_sbindir}/lrmadmin +%{_sbindir}/cibsecret +%{_sbindir}/meatclient +%{_sbindir}/stonith +%dir %{_libdir}/heartbeat +%dir %{_libdir}/heartbeat/plugins +%dir %{_libdir}/heartbeat/plugins/RAExec +%dir %{_libdir}/heartbeat/plugins/InterfaceMgr +%dir %{_libdir}/heartbeat/plugins/compress +%{_libdir}/heartbeat/lrmd +%{_libdir}/heartbeat/ha_logd +%{_libdir}/heartbeat/plugins/RAExec/*.so +%{_libdir}/heartbeat/plugins/InterfaceMgr/*.so +%{_libdir}/heartbeat/plugins/compress/*.so +%dir %{_libdir}/stonith +%dir %{_libdir}/stonith/plugins +%dir %{_libdir}/stonith/plugins/stonith2 +%{_libdir}/stonith/plugins/external +%{_libdir}/stonith/plugins/stonith2/*.so +%{_libdir}/stonith/plugins/stonith2/*.py* +%exclude %{_libdir}/stonith/plugins/external/ssh +%exclude %{_libdir}/stonith/plugins/stonith2/null.so +%exclude %{_libdir}/stonith/plugins/stonith2/ssh.so +%{_libdir}/stonith/plugins/xen0-ha-dom0-stonith-helper +%dir %{_var}/lib/heartbeat +%dir %{_var}/lib/heartbeat/cores +%dir %attr (0700, root, root) %{_var}/lib/heartbeat/cores/root +%dir %attr (0700, nobody, %{nogroup}) %{_var}/lib/heartbeat/cores/nobody +%dir %attr (0700, %{uname}, %{gname}) %{_var}/lib/heartbeat/cores/%{uname} +%{_mandir}/man1/* +%{_mandir}/man8/* +%doc doc/stonith/README* +%doc logd/logd.cf +%doc AUTHORS +%doc COPYING +%doc ChangeLog + +# cluster-glue-libs + +%package -n cluster-glue-libs +Summary: Reusable cluster libraries +Group: Development/Libraries +Obsoletes: libheartbeat2 + +%description -n cluster-glue-libs +A collection of libraries that are useful for writing cluster managers +such as Pacemaker. + +%pre +getent group %{gname} >/dev/null || groupadd -r %{gname} +getent passwd %{uname} >/dev/null || \ +useradd -r -g %{gname} -d %{_var}/lib/heartbeat/cores/hacluster -s /sbin/nologin \ +-c "cluster user" %{uname} +exit 0 + +%if %{defined _unitdir} +%post +%systemd_post logd.service + +%preun +%systemd_preun logd.service + +%postun +%systemd_postun_with_restart logd.service +%endif + +%post -n cluster-glue-libs -p /sbin/ldconfig + +%postun -n cluster-glue-libs -p /sbin/ldconfig + +%files -n cluster-glue-libs +%defattr(-,root,root) +%{_libdir}/lib*.so.* +%doc AUTHORS +%doc COPYING.LIB + +# cluster-glue-libs-devel + +%package -n cluster-glue-libs-devel +Summary: Headers and libraries for writing cluster managers +Group: Development/Libraries +Requires: cluster-glue-libs = %{version}-%{release} +Obsoletes: libheartbeat-devel + +%description -n cluster-glue-libs-devel +Headers and shared libraries for a useful for writing cluster managers +such as Pacemaker. + +%files -n cluster-glue-libs-devel +%defattr(-,root,root) +%dir %{_libdir}/heartbeat/plugins +%dir %{_libdir}/heartbeat/plugins/test +%dir %{_libdir}/heartbeat +%dir %{_datadir}/%{name} +%{_libdir}/lib*.so +%{_libdir}/heartbeat/ipctest +%{_libdir}/heartbeat/ipctransientclient +%{_libdir}/heartbeat/ipctransientserver +%{_libdir}/heartbeat/transient-test.sh +%{_libdir}/heartbeat/base64_md5_test +%{_libdir}/heartbeat/logtest +%{_includedir}/clplumbing +%{_includedir}/heartbeat +%{_includedir}/stonith +%{_includedir}/pils +%{_datadir}/%{name}/lrmtest +%{_libdir}/heartbeat/plugins/test/test.so +%{_libdir}/stonith/plugins/external/ssh +%{_libdir}/stonith/plugins/stonith2/null.so +%{_libdir}/stonith/plugins/stonith2/ssh.so +%doc AUTHORS +%doc COPYING +%doc COPYING.LIB + +%changelog diff --git a/cluster-glue-suse.spec b/cluster-glue-suse.spec new file mode 100644 index 0000000..e2ca7c7 --- /dev/null +++ b/cluster-glue-suse.spec @@ -0,0 +1,307 @@ +# +# Copyright (c) 2009 SUSE LINUX Products GmbH, Nuernberg, Germany. +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# + +# norootforbuild + +# +# Since this spec file supports multiple distributions, ensure we +# use the correct group for each. +# + +%define uid 90 +%define gname haclient +%define uname hacluster + +# Directory where we install documentation +%global glue_docdir %{_defaultdocdir}/%{name} + +Name: cluster-glue +Summary: Reusable cluster components +Version: 1.0.12 +Release: 1%{?dist} +License: GPL v2 or later; LGPL v2.1 or later +Url: http://www.linux-ha.org/wiki/Cluster_Glue +Group: Productivity/Clustering/HA +Source: cluster-glue.tar.bz2 +BuildRoot: %{_tmppath}/%{name}-%{version}-build +AutoReqProv: on +BuildRequires: automake autoconf libtool e2fsprogs-devel glib2-devel pkgconfig python-devel libxml2-devel +BuildRequires: libnet net-snmp-devel OpenIPMI-devel openhpi-devel +BuildRequires: libxslt docbook_4 docbook-xsl-stylesheets +BuildRequires: help2man +BuildRequires: asciidoc +BuildRequires: libbz2-devel libaio-devel + +Obsoletes: heartbeat-common +Provides: heartbeat-common +Requires(pre): /usr/sbin/groupadd /usr/bin/getent /usr/sbin/useradd + +# SLES10 needs tcpd-devel but doesn't have libcurl +# in SLES10 docbook has no dependency on sgml-skel +%if 0%{?suse_version} < 1020 +BuildRequires: tcpd-devel +BuildRequires: sgml-skel +%else +BuildRequires: libcurl-devel +%endif + +%if %{defined systemd_requires} +BuildRequires: systemd +%{?systemd_requires} +%endif + +%description +A collection of common tools derived from the Heartbeat project that are +useful for writing cluster managers such as Pacemaker. +Provides a local resource manager that understands the OCF and LSB +standards, and an interface to common STONITH devices. + +%package -n libglue2 +License: GPL v2 only; GPL v2 or later; LGPL v2.1 or later +Summary: The Pacemaker scalable High-Availability cluster resource manager +Group: Productivity/Clustering/HA +Obsoletes: libheartbeat2 +Provides: libheartbeat2 +Requires: %{name} = %{version}-%{release} + +%description -n libglue2 +A collection of libraries that are useful for writing cluster managers +such as Pacemaker. + +%package -n libglue-devel +License: GPL v2 only; GPL v2 or later; LGPL v2.1 or later +Summary: The Pacemaker scalable High-Availability cluster resource manager +Group: Development/Libraries/C and C++ +Requires: %{name} = %{version}-%{release} +Requires: libglue2 = %{version}-%{release} +Obsoletes: libheartbeat-devel +Provides: libheartbeat-devel + +%description -n libglue-devel +Headers and shared libraries for a useful for writing cluster managers +such as Pacemaker. + +%prep +########################################################### +%setup -n cluster-glue -q +########################################################### + +%build +CFLAGS="${CFLAGS} ${RPM_OPT_FLAGS}" +export CFLAGS + +./autogen.sh +# SLES <= 10 does not support ./configure --docdir=, +# hence, use this ugly hack +%if 0%{?suse_version} < 1020 +export docdir=%{glue_docdir} +%configure \ + --enable-fatal-warnings=yes \ + --with-package-name=%{name} \ + --with-daemon-group=%{gname} \ + --with-daemon-user=%{uname} +%else +%configure \ + --enable-fatal-warnings=yes \ + --with-package-name=%{name} \ + --with-daemon-group=%{gname} \ + --with-daemon-user=%{uname} \ + --with-rundir=%{_rundir} \ +%if %{defined _unitdir} + --with-systemdsystemunitdir=%{_unitdir} \ +%endif + --docdir=%{glue_docdir} +%endif + +make %{?_smp_mflags} docdir=%{glue_docdir} +########################################################### + +%install +########################################################### +make DESTDIR=$RPM_BUILD_ROOT docdir=%{glue_docdir} install +# Dont package static libs or compiled python +find $RPM_BUILD_ROOT -name '*.a' -type f -print0 | xargs -0 rm -f +find $RPM_BUILD_ROOT -name '*.la' -type f -print0 | xargs -0 rm -f +find $RPM_BUILD_ROOT -name '*.pyc' -type f -print0 | xargs -0 rm -f +find $RPM_BUILD_ROOT -name '*.pyo' -type f -print0 | xargs -0 rm -f + +%if %{defined _unitdir} +ln -s /usr/sbin/service %{buildroot}%{_sbindir}/rclogd +%else +test -d $RPM_BUILD_ROOT/sbin || mkdir $RPM_BUILD_ROOT/sbin +( + cd $RPM_BUILD_ROOT/sbin + ln -s /etc/init.d/logd rclogd +) +%endif + +########################################################### + +%clean +########################################################### +if + [ -n "${RPM_BUILD_ROOT}" -a "${RPM_BUILD_ROOT}" != "/" ] +then + rm -rf $RPM_BUILD_ROOT +fi +rm -rf $RPM_BUILD_DIR/cluster-glue +########################################################### + +%pre +if + getent group %{gname} >/dev/null +then + : OK group haclient already present +else + /usr/sbin/groupadd -o -r -g %{uid} %{gname} 2>/dev/null || : +fi +if + getent passwd %{uname} >/dev/null +then + : OK hacluster user already present +else + /usr/sbin/useradd -r -g %{gname} -c "heartbeat processes" \ + -d %{_var}/lib/heartbeat/cores/%{uname} -o -u %{uid} \ + %{uname} 2>/dev/null || : +fi +%if %{defined _unitdir} + %service_add_pre logd.service +%endif + +%if %{defined _unitdir} +%post +%service_add_post logd.service + +%preun +%service_del_preun logd.service + +%postun +%service_del_postun logd.service +%else +%preun +%stop_on_removal logd + +%post +%{insserv_force_if_yast logd} + +%postun +%insserv_cleanup +%endif + +%post -n libglue2 +/sbin/ldconfig + +%postun -n libglue2 +/sbin/ldconfig + +%files +########################################################### +%defattr(-,root,root) + +%dir %{_libdir}/heartbeat +%dir %{_var}/lib/heartbeat +%dir %{_var}/lib/heartbeat/cores +%dir %attr (0700, root, root) %{_var}/lib/heartbeat/cores/root +%dir %attr (0700, nobody, nobody) %{_var}/lib/heartbeat/cores/nobody +%dir %attr (0700, %{uname}, %{gname}) %{_var}/lib/heartbeat/cores/%{uname} + +%dir %{_libdir}/heartbeat/plugins +%dir %{_libdir}/heartbeat/plugins/RAExec +%dir %{_libdir}/heartbeat/plugins/InterfaceMgr +%dir %{_libdir}/heartbeat/plugins/compress + +%dir %{_libdir}/stonith +%dir %{_libdir}/stonith/plugins +%dir %{_libdir}/stonith/plugins/stonith2 + +%dir %{_datadir}/%{name} +%{_datadir}/%{name}/ha_cf_support.sh +%{_datadir}/%{name}/openais_conf_support.sh +%{_datadir}/%{name}/utillib.sh +%{_datadir}/%{name}/ha_log.sh + +%{_sbindir}/ha_logger +%{_sbindir}/hb_report +%{_sbindir}/lrmadmin +%{_sbindir}/cibsecret +%{_sbindir}/meatclient +%{_sbindir}/stonith + +%if %{defined _unitdir} +%{_unitdir}/logd.service +%{_sbindir}/rclogd +%else +%{_sysconfdir}/init.d/logd +/sbin/rclogd +%endif + +%doc %{_mandir}/man1/* +%doc %{_mandir}/man8/* +%doc AUTHORS +%doc COPYING +%doc ChangeLog +%doc logd/logd.cf +%doc doc/stonith/README* + +%{_libdir}/heartbeat/lrmd +%{_libdir}/heartbeat/ha_logd + +%{_libdir}/heartbeat/plugins/RAExec/*.so +%{_libdir}/heartbeat/plugins/InterfaceMgr/*.so +%{_libdir}/heartbeat/plugins/compress/*.so + +%{_libdir}/stonith/plugins/external +%{_libdir}/stonith/plugins/stonith2/*.so +%{_libdir}/stonith/plugins/stonith2/*.py +%{_libdir}/stonith/plugins/xen0-ha-dom0-stonith-helper +%exclude %{_libdir}/stonith/plugins/external/ssh +%exclude %{_libdir}/stonith/plugins/stonith2/null.so +%exclude %{_libdir}/stonith/plugins/stonith2/ssh.so + +%files -n libglue2 +%defattr(-,root,root) +%{_libdir}/lib*.so.* +%doc AUTHORS +%doc COPYING.LIB + +%files -n libglue-devel +%defattr(-,root,root) + +%dir %{_libdir}/heartbeat +%dir %{_libdir}/heartbeat/plugins +%dir %{_libdir}/heartbeat/plugins/test +%dir %{_datadir}/%{name} + +%{_libdir}/lib*.so +%{_libdir}/heartbeat/ipctest +%{_libdir}/heartbeat/ipctransientclient +%{_libdir}/heartbeat/ipctransientserver +%{_libdir}/heartbeat/transient-test.sh +%{_libdir}/heartbeat/base64_md5_test +%{_libdir}/heartbeat/logtest +%{_includedir}/clplumbing +%{_includedir}/heartbeat +%{_includedir}/stonith +%{_includedir}/pils +%{_datadir}/%{name}/lrmtest +%{_libdir}/heartbeat/plugins/test/test.so +%{_libdir}/stonith/plugins/external/ssh +%{_libdir}/stonith/plugins/stonith2/null.so +%{_libdir}/stonith/plugins/stonith2/ssh.so +%doc AUTHORS +%doc COPYING +%doc COPYING.LIB + +%changelog diff --git a/config/Makefile.am b/config/Makefile.am new file mode 100644 index 0000000..fa41516 --- /dev/null +++ b/config/Makefile.am @@ -0,0 +1,19 @@ +# +# Copyright (C) 2005 Guochun Shi (gshi@ncsa.uiuc.edu) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +MAINTAINERCLEANFILES = Makefile.in +EXTRA_DIST = byteorder_test.c diff --git a/config/byteorder_test.c b/config/byteorder_test.c new file mode 100644 index 0000000..0583803 --- /dev/null +++ b/config/byteorder_test.c @@ -0,0 +1,15 @@ +#include <stdio.h> + +int +main () +{ + unsigned int a = 0x1234; + + if ( (unsigned int) ( ((unsigned char *)&a)[0]) == 0x34 ) { + printf("little-endian\n"); + return 0; + } else { + printf("big-endian\n"); + return 1; + } +} diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..36bcf12 --- /dev/null +++ b/configure.ac @@ -0,0 +1,1439 @@ +dnl +dnl autoconf for Pacemaker +dnl +dnl License: GNU General Public License (GPL) + +dnl =============================================== +dnl Bootstrap +dnl =============================================== +AC_PREREQ(2.53) + +dnl Suggested structure: +dnl information on the package +dnl checks for programs +dnl checks for libraries +dnl checks for header files +dnl checks for types +dnl checks for structures +dnl checks for compiler characteristics +dnl checks for library functions +dnl checks for system services + +AC_INIT(cluster-glue, 1.0.12, linux-ha-dev@lists.linux-ha.org) + +FEATURES="" +HB_PKG=heartbeat + +AC_CONFIG_AUX_DIR(.) +AC_CANONICAL_HOST + +dnl Where #defines go (e.g. `AC_CHECK_HEADERS' below) +dnl +dnl Internal header: include/config.h +dnl - Contains ALL defines +dnl - include/config.h.in is generated automatically by autoheader +dnl - NOT to be included in any header files except lha_internal.h +dnl (which is also not to be included in any other header files) +dnl +dnl External header: include/crm_config.h +dnl - Contains a subset of defines checked here +dnl - Manually edit include/crm_config.h.in to have configure include +dnl new defines +dnl - Should not include HAVE_* defines +dnl - Safe to include anywhere +AM_CONFIG_HEADER(include/config.h include/glue_config.h) +ALL_LINGUAS="en fr" + +AC_ARG_WITH(version, + [ --with-version=version Override package version (if you're a packager needing to pretend) ], + [ PACKAGE_VERSION="$withval" ]) + +AC_ARG_WITH(pkg-name, + [ --with-pkg-name=name Override package name (if you're a packager needing to pretend) ], + [ PACKAGE_NAME="$withval" ]) + +PKG_PROG_PKG_CONFIG +AC_ARG_WITH([systemdsystemunitdir], + [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],, + [with_systemdsystemunitdir=auto]) +AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [ + def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd) + + AS_IF([test "x$def_systemdsystemunitdir" = "x"], + [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"], + [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])]) + with_systemdsystemunitdir=no], + [with_systemdsystemunitdir="$def_systemdsystemunitdir"])]) +AS_IF([test "x$with_systemdsystemunitdir" != "xno"], + [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])]) +AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"]) + +AM_INIT_AUTOMAKE($PACKAGE_NAME, $PACKAGE_VERSION) +AC_DEFINE_UNQUOTED(GLUE_VERSION, "$PACKAGE_VERSION", Current version of the glue library) + +CC_IN_CONFIGURE=yes +export CC_IN_CONFIGURE + +LDD=ldd + +dnl ======================================================================== +dnl Compiler characteristics +dnl ======================================================================== + +AC_PROG_CC dnl Can force other with environment variable "CC". +AM_PROG_CC_C_O +AC_PROG_CC_STDC + +AC_LIBTOOL_DLOPEN dnl Enable dlopen support... +AC_LIBLTDL_CONVENIENCE dnl make libltdl a convenience lib +AC_PROG_LIBTOOL + +AC_C_STRINGIZE +AC_TYPE_SIZE_T +AC_CHECK_SIZEOF(char) +AC_CHECK_SIZEOF(short) +AC_CHECK_SIZEOF(int) +AC_CHECK_SIZEOF(long) +AC_CHECK_SIZEOF(long long) +AC_CHECK_SIZEOF(clock_t, [], [#include <sys/times.h>]) +AC_STRUCT_TIMEZONE + +dnl =============================================== +dnl Helpers +dnl =============================================== +cc_supports_flag() { + local CFLAGS="$@" + AC_MSG_CHECKING(whether $CC supports "$@") + AC_COMPILE_IFELSE([AC_LANG_SOURCE(int main(){return 0;})] ,[RC=0; AC_MSG_RESULT(yes)],[RC=1; AC_MSG_RESULT(no)]) + return $RC +} + +dnl =============================================== +dnl Configure Options +dnl =============================================== + +dnl Some systems, like Solaris require a custom package name +AC_ARG_WITH(pkgname, + [ --with-pkgname=name name for pkg (typically for Solaris) ], + [ PKGNAME="$withval" ], + [ PKGNAME="LXHAhb" ], + ) +AC_SUBST(PKGNAME) + +AC_ARG_ENABLE([ansi], +[ --enable-ansi force GCC to compile to ANSI/ANSI standard for older compilers. + [default=yes]]) + +AC_ARG_ENABLE([fatal-warnings], +[ --enable-fatal-warnings very pedantic and fatal warnings for gcc + [default=yes]]) + +AC_ARG_ENABLE([pretty], +[ --enable-pretty + Pretty-print compiler output unless there is an error + [default=no]]) + +AC_ARG_ENABLE([quiet], +[ --enable-quiet + Supress make output unless there is an error + [default=no]]) + +AC_ARG_ENABLE([thread-safe], +[ --enable-thread-safe Enable some client libraries to be thread safe. + [default=no]]) + +AC_ARG_ENABLE([bundled-ltdl], +[ --enable-bundled-ltdl Configure, build and install the standalone ltdl library bundled with ${PACKAGE} [default=no]]) +LTDL_LIBS="" + +AC_ARG_ENABLE([upstart], +AS_HELP_STRING([--enable-upstart], + [Enable Upstart support in lrmd. [default=no]])) + +INITDIR="" +AC_ARG_WITH(initdir, + [ --with-initdir=DIR directory for init (rc) scripts [${INITDIR}]], + [ INITDIR="$withval" ]) + +OCF_ROOT_DIR="/usr/lib/ocf" +AC_ARG_WITH(ocf-root, + [ --with-ocf-root=DIR directory for OCF scripts [${OCF_ROOT_DIR}]], + [ if test x"$withval" = xprefix; then OCF_ROOT_DIR=${prefix}; else + OCF_ROOT_DIR="$withval"; fi ]) + +AC_ARG_WITH( + daemon-group, + [ --with-daemon-group=GROUP_NAME + Group to run our programs as. [default=haclient] ], + [ GLUE_DAEMON_GROUP="$withval" ], + [ GLUE_DAEMON_GROUP="haclient" ], + ) + +AC_ARG_WITH( + daemon-user, + [ --with-daemon-user=USER_NAME + User to run privileged non-root things as. [default=hacluster] ], + [ GLUE_DAEMON_USER="$withval" ], + [ GLUE_DAEMON_USER="hacluster" ], + ) + + +AC_ARG_WITH( + rundir, + [ --with-rundir=DIR + directory to store state information [default=localstatedir/run] ], + [ GLUE_STATE_DIR="$withval" ], + [ GLUE_STATE_DIR="${localstatedir}/run" ], + ) + +dnl =============================================== +dnl General Processing +dnl =============================================== + +AC_SUBST(HB_PKG) + +INIT_EXT="" +echo Our Host OS: $host_os/$host + +if test "X$OCF_ROOT_DIR" = X; then + OCF_ROOT_DIR="/usr/lib/ocf" +fi + +AC_MSG_NOTICE(Sanitizing prefix: ${prefix}) +case $prefix in + NONE) prefix=/usr;; +esac + +AC_MSG_NOTICE(Sanitizing exec_prefix: ${exec_prefix}) +case $exec_prefix in + dnl For consistency with Heartbeat, map NONE->$prefix + NONE) exec_prefix=$prefix;; + prefix) exec_prefix=$prefix;; +esac + +AC_MSG_NOTICE(Sanitizing INITDIR: ${INITDIR}) +case $INITDIR in + prefix) INITDIR=$prefix;; + "") + AC_MSG_CHECKING(which init (rc) directory to use) + for initdir in /etc/init.d /etc/rc.d/init.d /sbin/init.d \ + /usr/local/etc/rc.d /etc/rc.d + do + if + test -d $initdir + then + INITDIR=$initdir + break + fi + done + AC_MSG_RESULT($INITDIR);; +esac +AC_SUBST(INITDIR) + +AC_MSG_NOTICE(Sanitizing libdir: ${libdir}) +case $libdir in + dnl For consistency with Heartbeat, map NONE->$prefix + *prefix*|NONE) + AC_MSG_CHECKING(which lib directory to use) + for aDir in lib64 lib + do + trydir="${exec_prefix}/${aDir}" + if + test -d ${trydir} + then + libdir=${trydir} + break + fi + done + AC_MSG_RESULT($libdir); + ;; +esac + +DLOPEN_FORCE_FLAGS="" +AC_SUBST(DLOPEN_FORCE_FLAGS) + + +dnl Expand autoconf variables so that we dont end up with '${prefix}' +dnl in #defines and python scripts +dnl NOTE: Autoconf deliberately leaves them unexpanded to allow +dnl make exec_prefix=/foo install +dnl No longer being able to do this seems like no great loss to me... + +eval prefix="`eval echo ${prefix}`" +eval exec_prefix="`eval echo ${exec_prefix}`" +eval bindir="`eval echo ${bindir}`" +eval sbindir="`eval echo ${sbindir}`" +eval libexecdir="`eval echo ${libexecdir}`" +eval datadir="`eval echo ${datadir}`" +eval sysconfdir="`eval echo ${sysconfdir}`" +eval sharedstatedir="`eval echo ${sharedstatedir}`" +eval localstatedir="`eval echo ${localstatedir}`" +eval libdir="`eval echo ${libdir}`" +eval includedir="`eval echo ${includedir}`" +eval oldincludedir="`eval echo ${oldincludedir}`" +eval infodir="`eval echo ${infodir}`" +eval mandir="`eval echo ${mandir}`" + +dnl docdir is a recent addition to autotools +eval docdir="`eval echo ${docdir}`" +if test "x$docdir" = "x"; then + docdir="`eval echo ${datadir}/doc`" +fi +AC_SUBST(docdir) + +AC_MSG_CHECKING(for the location of the lock directory) +for HA_VARLOCKDIR in ${localstatedir}/lock ${localstatedir}/spool/lock ${localstatedir}/spool/locks ${localstatedir}/lock +do + if + test -d "$HA_VARLOCKDIR" + then + AC_MSG_RESULT($HA_VARLOCKDIR) + break + fi +done + +AC_SUBST(HA_VARLOCKDIR) +AC_DEFINE_UNQUOTED(HA_VARLOCKDIR,"$HA_VARLOCKDIR", System lock directory) + +dnl Home-grown variables +eval INITDIR="${INITDIR}" + +for j in prefix exec_prefix bindir sbindir libexecdir datadir sysconfdir \ + sharedstatedir localstatedir libdir includedir oldincludedir infodir \ + mandir INITDIR docdir HA_VARLOCKDIR +do + dirname=`eval echo '${'${j}'}'` + if + test ! -d "$dirname" + then + AC_MSG_WARN([$j directory ($dirname) does not exist!]) + fi +done + +dnl This OS-based decision-making is poor autotools practice; +dnl feature-based mechanisms are strongly preferred. +dnl +dnl So keep this section to a bare minimum; regard as a "necessary evil". + +ON_LINUX=0 +REBOOT_OPTIONS="-f" +POWEROFF_OPTIONS="-f" + +case "$host_os" in +*bsd*) LIBS="-L/usr/local/lib" + CPPFLAGS="$CPPFLAGS -I/usr/local/include" + INIT_EXT=".sh" + ;; +*solaris*) + REBOOT_OPTIONS="-n" + POWEROFF_OPTIONS="-n" + ;; +*linux*) + ON_LINUX=1 + REBOOT_OPTIONS="-nf" + POWEROFF_OPTIONS="-nf" + AC_DEFINE_UNQUOTED(ON_LINUX, $ON_LINUX, Compiling for Linux platform) + ;; +darwin*) + AC_DEFINE_UNQUOTED(ON_DARWIN, 1, Compiling for Darwin platform) + LIBS="$LIBS -L${prefix}/lib" + CFLAGS="$CFLAGS -I${prefix}/include" + ;; +esac + +AM_CONDITIONAL(ON_LINUX, test $ON_LINUX = 1) + +dnl Eventually remove this +dnl CFLAGS="$CFLAGS -I${prefix}/include/heartbeat" + +AC_SUBST(INIT_EXT) +AC_DEFINE_UNQUOTED(HA_LOG_FACILITY, LOG_DAEMON, Default logging facility) + +AC_MSG_NOTICE(Host CPU: $host_cpu) + +case "$host_cpu" in + ppc64|powerpc64) + case $CFLAGS in + *powerpc64*) ;; + *) if test "$GCC" = yes; then + CFLAGS="$CFLAGS -m64" + fi ;; + esac +esac + +AC_MSG_CHECKING(which format is needed to print uint64_t) +case "$host_cpu" in + s390x)U64T="%lu";; + *64*) U64T="%lu";; + *) U64T="%llu";; +esac +AC_MSG_RESULT($U64T) +AC_DEFINE_UNQUOTED(U64T, "$U64T", Correct printf format for logging uint64_t) + +dnl Variables needed for substitution +AC_DEFINE_UNQUOTED(GLUE_DAEMON_USER,"$GLUE_DAEMON_USER", User to run daemons as) +AC_SUBST(GLUE_DAEMON_USER) + +AC_DEFINE_UNQUOTED(GLUE_DAEMON_GROUP,"$GLUE_DAEMON_GROUP", Group to run daemons as) +AC_SUBST(GLUE_DAEMON_GROUP) + +dnl Eventually move out of the heartbeat dir tree and create symlinks when needed +GLUE_DAEMON_DIR=$libdir/heartbeat +AC_DEFINE_UNQUOTED(GLUE_DAEMON_DIR,"$GLUE_DAEMON_DIR", Location for daemons) +AC_SUBST(GLUE_DAEMON_DIR) + +GLUE_STATE_DIR=${localstatedir}/run +AC_DEFINE_UNQUOTED(GLUE_STATE_DIR,"$GLUE_STATE_DIR", Where to keep state files and sockets) +AC_SUBST(GLUE_STATE_DIR) + +GLUE_SHARED_DIR=${datadir}/"$PACKAGE_NAME" +AC_DEFINE_UNQUOTED(GLUE_SHARED_DIR,"$GLUE_SHARED_DIR", Location for scripts) +AC_SUBST(GLUE_SHARED_DIR) + +AC_DEFINE_UNQUOTED(HA_VARRUNDIR,"$GLUE_STATE_DIR", Where Heartbeat keeps state files and sockets - old name) +AC_SUBST(HA_VARRUNDIR) + +HA_VARLIBHBDIR=${localstatedir}/lib/heartbeat +AC_DEFINE_UNQUOTED(HA_VARLIBHBDIR,"$HA_VARLIBHBDIR", Whatever this used to mean) +AC_SUBST(HA_VARLIBHBDIR) +AC_DEFINE_UNQUOTED(HA_VARLIBDIR,"$HA_VARLIBHBDIR", Whatever this used to mean) +AC_SUBST(HA_VARLIBDIR) + +AC_DEFINE_UNQUOTED(OCF_ROOT_DIR,"$OCF_ROOT_DIR", OCF root directory - specified by the OCF standard) +AC_SUBST(OCF_ROOT_DIR) + +OCF_RA_DIR="${OCF_ROOT_DIR}/resource.d/" +AC_DEFINE_UNQUOTED(OCF_RA_DIR,"$OCF_RA_DIR", Location for OCF RAs) +AC_SUBST(OCF_RA_DIR) + +HA_LOGDAEMON_IPC="${localstatedir}/lib/heartbeat/log_daemon" +AC_DEFINE_UNQUOTED(HA_LOGDAEMON_IPC, "$HA_LOGDAEMON_IPC", Logging Daemon IPC socket name) +AC_SUBST(HA_LOGDAEMON_IPC) + +HA_URLBASE="http://linux-ha.org/wiki/" +AC_DEFINE_UNQUOTED(HA_URLBASE, "$HA_URLBASE", Web site base URL) +AC_SUBST(HA_URLBASE) + +HA_COREDIR="${localstatedir}/lib/heartbeat/cores" +AC_DEFINE_UNQUOTED(HA_COREDIR,"$HA_COREDIR", top directory of area to drop core files in) +AC_SUBST(HA_COREDIR) + +LRM_VARLIBDIR="${localstatedir}/lib/heartbeat/lrm" +AC_DEFINE_UNQUOTED(LRM_VARLIBDIR,"$LRM_VARLIBDIR", LRM directory) +AC_SUBST(LRM_VARLIBDIR) + +LRM_CIBSECRETS="${localstatedir}/lib/heartbeat/lrm/secrets" +AC_DEFINE_UNQUOTED(LRM_CIBSECRETS,"$LRM_CIBSECRETS", CIB secrets location) +AC_SUBST(LRM_CIBSECRETS) + +AC_DEFINE_UNQUOTED(PILS_BASE_PLUGINDIR,"$libdir/heartbeat/plugins", Default plugin search path) +AC_DEFINE_UNQUOTED(HA_PLUGIN_DIR,"$libdir/heartbeat/plugins", Where to find plugins) +AC_DEFINE_UNQUOTED(LRM_PLUGIN_DIR,"$libdir/heartbeat/plugins/RAExec", Where to find LRM plugins) + +AC_DEFINE_UNQUOTED(LSB_RA_DIR,"$INITDIR", Location for LSB RAs) +LSB_RA_DIR=$INITDIR +AC_SUBST(LSB_RA_DIR) + +AC_DEFINE_UNQUOTED(HA_SYSCONFDIR, "$sysconfdir", Location of system configuration files) + +HA_HBCONF_DIR=${sysconfdir}/ha.d/ +AC_DEFINE_UNQUOTED(HA_HBCONF_DIR,"$HA_HBCONF_DIR", Location for v1 Heartbeat configuration) +AC_SUBST(HA_HBCONF_DIR) + +HB_RA_DIR=${sysconfdir}/ha.d/resource.d/ +AC_DEFINE_UNQUOTED(HB_RA_DIR,"$HB_RA_DIR", Location for v1 Heartbeat RAs) +AC_SUBST(HB_RA_DIR) + +stonith_plugindir="${libdir}/stonith/plugins" +stonith_ext_plugindir="${stonith_plugindir}/external" +stonith_rhcs_plugindir="${stonith_plugindir}/rhcs" +AC_DEFINE_UNQUOTED(ST_TEXTDOMAIN, "stonith", Stonith plugin domain) +AC_DEFINE_UNQUOTED(STONITH_MODULES, "$stonith_plugindir", Location of stonith plugins) +AC_DEFINE_UNQUOTED(STONITH_EXT_PLUGINDIR, "$stonith_ext_plugindir", Location of non-plugin stonith scripts) +AC_DEFINE_UNQUOTED(STONITH_RHCS_PLUGINDIR, "$stonith_rhcs_plugindir", Location of RHCS fence scripts) +AC_SUBST(stonith_plugindir) +AC_SUBST(stonith_ext_plugindir) +AC_SUBST(stonith_rhcs_plugindir) + +dnl Old names for new things +AC_DEFINE_UNQUOTED(HA_CCMUSER, "$GLUE_DAEMON_USER", User to run daemons as) +AC_DEFINE_UNQUOTED(HA_APIGROUP, "$GLUE_DAEMON_GROUP", Group to run daemons as) +AC_DEFINE_UNQUOTED(HA_LIBHBDIR, "$GLUE_DAEMON_DIR", Location for daemons) + +LRM_DIR=lrm +AC_SUBST(LRM_DIR) + +AC_PATH_PROGS(HG, hg false) +AC_MSG_CHECKING(build version) +GLUE_BUILD_VERSION=unknown +if test -f $srcdir/.hg_archival.txt; then + GLUE_BUILD_VERSION=`cat $srcdir/.hg_archival.txt | awk '/node:/ { print $2 }'` +elif test -x $HG -a -d .hg; then + GLUE_BUILD_VERSION=`$HG id -itb` + if test $? != 0; then + GLUE_BUILD_VERSION=unknown + fi +fi + +AC_DEFINE_UNQUOTED(GLUE_BUILD_VERSION, "$GLUE_BUILD_VERSION", Build version) +AC_MSG_RESULT($GLUE_BUILD_VERSION) +AC_SUBST(GLUE_BUILD_VERSION) + +dnl check byte order +AC_MSG_CHECKING(for byteorder) +AC_C_BIGENDIAN( +[AC_MSG_RESULT(big-endian); AC_DEFINE(CONFIG_BIG_ENDIAN, 1, [big-endian])], +[AC_MSG_RESULT(little-endian); AC_DEFINE(CONFIG_LITTLE_ENDIAN, 1, [little-endian])], +) + + +dnl =============================================== +dnl Program Paths +dnl =============================================== + +PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin" +export PATH + + +dnl Replacing AC_PROG_LIBTOOL with AC_CHECK_PROG because LIBTOOL +dnl was NOT being expanded all the time thus causing things to fail. +AC_CHECK_PROGS(LIBTOOL, glibtool libtool libtool15 libtool13) + +AM_PATH_PYTHON +AC_CHECK_PROGS(MAKE, gmake make) +AC_PATH_PROGS(HTML2TXT, lynx w3m) +AC_PATH_PROGS(HELP2MAN, help2man) +AC_PATH_PROGS(POD2MAN, pod2man, pod2man) +AC_PATH_PROGS(SSH, ssh, /usr/bin/ssh) +AC_PATH_PROGS(SCP, scp, /usr/bin/scp) +AC_PATH_PROGS(HG, hg, /bin/false) +AC_PATH_PROGS(TAR, tar) +AC_PATH_PROGS(MD5, md5) +AC_PATH_PROGS(RPM, rpm) +AC_PATH_PROGS(TEST, test) +AC_PATH_PROGS(PING, ping, /bin/ping) +AC_PATH_PROGS(IFCONFIG, ifconfig, /sbin/ifconfig) +AC_PATH_PROGS(MAILCMD, mailx mail) +AC_PATH_PROGS(EGREP, egrep) +AC_PATH_PROGS(PKGCONFIG, pkg-config) +AC_PATH_PROGS(XML2CONFIG, xml2-config) + +AC_ARG_ENABLE([doc], + AS_HELP_STRING([--enable-doc], [build documentation (default is yes)]), + [], [enable_doc=yes]) +if test "x$enable_doc" != "xno"; then + AC_PATH_PROGS(XSLTPROC, xsltproc) + if test "x$XSLTPROC" = "x"; then + AC_MSG_WARN([xsltproc not installed, unable to (re-)build manual pages]) + fi + AC_PATH_PROGS(ASCIIDOC, asciidoc) + if test "x$ASCIIDOC" = "x"; then + AC_MSG_WARN([asciidoc not installed, unable to (re-)build manual pages]) + fi +fi +AM_CONDITIONAL(BUILD_DOC, test "x$XSLTPROC" != "x" ) + +AC_PATH_PROGS(VALGRIND_BIN, valgrind, /usr/bin/valgrind) +AC_DEFINE_UNQUOTED(VALGRIND_BIN, "$VALGRIND_BIN", Valgrind command) + +AC_SUBST(MAILCMD) +AC_SUBST(EGREP) +AC_SUBST(SHELL) +AC_SUBST(PING) +AC_SUBST(TEST) +AC_SUBST(RPM) +AC_SUBST(XSLTPROC) + +AC_MSG_CHECKING(ifconfig option to list interfaces) +for IFCONFIG_A_OPT in "-A" "-a" "" +do + $IFCONFIG $IFCONFIG_A_OPT > /dev/null 2>&1 + if + test "$?" = 0 + then + AC_DEFINE_UNQUOTED(IFCONFIG_A_OPT, "$IFCONFIG_A_OPT", option for ifconfig command) + AC_MSG_RESULT($IFCONFIG_A_OPT) + break + fi +done + +AC_SUBST(IFCONFIG_A_OPT) + +if test x"${LIBTOOL}" = x""; then + AC_MSG_ERROR(You need (g)libtool installed in order to build ${PACKAGE}) +fi +if test x"${MAKE}" = x""; then + AC_MSG_ERROR(You need (g)make installed in order to build ${PACKAGE}) +fi + +AM_CONDITIONAL(BUILD_HELP, test x"${HELP2MAN}" != x"") +if test x"${HELP2MAN}" != x""; then + FEATURES="$FEATURES manpages" +fi + +dnl =============================================== +dnl Libraries +dnl =============================================== +AC_CHECK_LIB(socket, socket) +AC_CHECK_LIB(c, dlopen) dnl if dlopen is in libc... +AC_CHECK_LIB(dl, dlopen) dnl for Linux +AC_CHECK_LIB(rt, sched_getscheduler) dnl for Tru64 +AC_CHECK_LIB(gnugetopt, getopt_long) dnl if available +AC_CHECK_LIB(uuid, uuid_parse) dnl e2fsprogs +AC_CHECK_LIB(uuid, uuid_create) dnl ossp +AC_CHECK_LIB(posix4, sched_getscheduler) + +if test x"${PKGCONFIG}" = x""; then + AC_MSG_ERROR(You need pkgconfig installed in order to build ${PACKAGE}) +fi + +dnl +dnl On many systems libcrypto is needed when linking against libsnmp. +dnl Check to see if it exists, and if so use it. +dnl +AC_CHECK_LIB(crypto, CRYPTO_free, CRYPTOLIB="-lcrypto",) +AC_SUBST(CRYPTOLIB) + +if test "x${enable_thread_safe}" = "xyes"; then + GPKGNAME="gthread-2.0" +else + GPKGNAME="glib-2.0" +fi + +if + $PKGCONFIG --exists $GPKGNAME +then + GLIBCONFIG="$PKGCONFIG $GPKGNAME" +else + set -x + echo PKG_CONFIG_PATH=$PKG_CONFIG_PATH + $PKGCONFIG --exists $GPKGNAME; echo $? + $PKGCONFIG --cflags $GPKGNAME; echo $? + $PKGCONFIG $GPKGNAME; echo $? + set +x + + AC_MSG_ERROR(You need glib2-devel installed in order to build ${PACKAGE}) +fi +AC_MSG_RESULT(using $GLIBCONFIG) + +# +# Where is dlopen? +# +if test "$ac_cv_lib_c_dlopen" = yes; then + LIBADD_DL="" +elif test "$ac_cv_lib_dl_dlopen" = yes; then + LIBADD_DL=-ldl +else + LIBADD_DL=${lt_cv_dlopen_libs} +fi +dnl +dnl Check for location of gettext +dnl +dnl On at least Solaris 2.x, where it is in libc, specifying lintl causes +dnl grief. Ensure minimal result, not the sum of all possibilities. +dnl And do libc first. +dnl Known examples: +dnl c: Linux, Solaris 2.6+ +dnl intl: BSD, AIX + +AC_CHECK_LIB(c, gettext) +if test x$ac_cv_lib_c_gettext != xyes; then + AC_CHECK_LIB(intl, gettext) +fi + +if test x$ac_cv_lib_c_gettext != xyes -a x$ac_cv_lib_intl_gettext != xyes; then + AC_MSG_ERROR(You need gettext installed in order to build ${PACKAGE}) +fi + +if test "X$GLIBCONFIG" != X; then + AC_MSG_CHECKING(for special glib includes: ) + GLIBHEAD=`$GLIBCONFIG --cflags` + AC_MSG_RESULT($GLIBHEAD) + CPPFLAGS="$CPPFLAGS $GLIBHEAD" + + AC_MSG_CHECKING(for glib library flags) + GLIBLIB=`$GLIBCONFIG --libs` + AC_MSG_RESULT($GLIBLIB) + LIBS="$LIBS $GLIBLIB" +fi + +dnl ======================================================================== +dnl Headers +dnl ======================================================================== + +AC_HEADER_STDC +AC_CHECK_HEADERS(arpa/inet.h) +AC_CHECK_HEADERS(asm/types.h) +AC_CHECK_HEADERS(assert.h) +AC_CHECK_HEADERS(auth-client.h) +AC_CHECK_HEADERS(ctype.h) +AC_CHECK_HEADERS(dirent.h) +AC_CHECK_HEADERS(errno.h) +AC_CHECK_HEADERS(fcntl.h) +AC_CHECK_HEADERS(getopt.h) +AC_CHECK_HEADERS(glib.h) +AC_CHECK_HEADERS(grp.h) +AC_CHECK_HEADERS(limits.h) +AC_CHECK_HEADERS(linux/errqueue.h,,, + [#ifdef HAVE_LINUX_TYPES_H + # include <linux/types.h> + #endif + ]) +AC_CHECK_HEADERS(malloc.h) +AC_CHECK_HEADERS(netdb.h) +AC_CHECK_HEADERS(netinet/in.h) +AC_CHECK_HEADERS(netinet/ip.h) +AC_CHECK_HEADERS(pthread.h) +AC_CHECK_HEADERS(pwd.h) +AC_CHECK_HEADERS(sgtty.h) +AC_CHECK_HEADERS(signal.h) +AC_CHECK_HEADERS(stdarg.h) +AC_CHECK_HEADERS(stddef.h) +AC_CHECK_HEADERS(stdio.h) +AC_CHECK_HEADERS(stdlib.h) +AC_CHECK_HEADERS(string.h) +AC_CHECK_HEADERS(strings.h) +AC_CHECK_HEADERS(sys/dir.h) +AC_CHECK_HEADERS(sys/ioctl.h) +AC_CHECK_HEADERS(sys/param.h) +AC_CHECK_HEADERS(sys/poll.h) +AC_CHECK_HEADERS(sys/reboot.h) +AC_CHECK_HEADERS(sys/resource.h) +AC_CHECK_HEADERS(sys/select.h) +AC_CHECK_HEADERS(sys/socket.h) +AC_CHECK_HEADERS(sys/sockio.h) +AC_CHECK_HEADERS(sys/stat.h) +AC_CHECK_HEADERS(sys/time.h) +AC_CHECK_HEADERS(sys/timeb.h) +AC_CHECK_HEADERS(sys/types.h) +AC_CHECK_HEADERS(sys/uio.h) +AC_CHECK_HEADERS(sys/un.h) +AC_CHECK_HEADERS(sys/utsname.h) +AC_CHECK_HEADERS(sys/wait.h) +AC_CHECK_HEADERS(time.h) +AC_CHECK_HEADERS(unistd.h) +AC_CHECK_HEADERS(winsock.h) +AC_CHECK_HEADERS(sys/termios.h) +AC_CHECK_HEADERS(termios.h) + +dnl These headers need prerequisits before the tests will pass +dnl AC_CHECK_HEADERS(net/if.h) +dnl AC_CHECK_HEADERS(netinet/icmp6.h) +dnl AC_CHECK_HEADERS(netinet/ip6.h) +dnl AC_CHECK_HEADERS(netinet/ip_icmp.h) + +AC_MSG_CHECKING(for special libxml2 includes) +if test "x$XML2CONFIG" = "x"; then + AC_MSG_ERROR(libxml2 config not found) +else + XML2HEAD="`$XML2CONFIG --cflags`" + AC_MSG_RESULT($XML2HEAD) + AC_CHECK_LIB(xml2, xmlReadMemory) +fi + +CPPFLAGS="$CPPFLAGS $XML2HEAD" + +AC_CHECK_HEADERS(libxml/xpath.h) +if test "$ac_cv_header_libxml_xpath_h" != "yes"; then + AC_MSG_ERROR(The libxml developement headers were not found) +fi + +dnl Check syslog.h for 'facilitynames' table +dnl +AC_CACHE_CHECK([for facilitynames in syslog.h],ac_cv_HAVE_SYSLOG_FACILITYNAMES,[ +AC_TRY_COMPILE([ +#define SYSLOG_NAMES +#include <stdlib.h> +#include <syslog.h> +], +[ void *fnames; fnames = facilitynames; ], +ac_cv_HAVE_SYSLOG_FACILITYNAMES=yes,ac_cv_HAVE_SYSLOG_FACILITYNAMES=no,ac_cv_HAVE_SYSLOG_FACILITYNAMES=cross)]) +if test x"$ac_cv_HAVE_SYSLOG_FACILITYNAMES" = x"yes"; then + AC_DEFINE(HAVE_SYSLOG_FACILITYNAMES,1,[ ]) +fi + +dnl Check for POSIX signals +dnl +AC_CACHE_CHECK([have POSIX signals],ac_cv_HAVE_POSIX_SIGNALS,[ +AC_TRY_COMPILE([ +#include <signal.h> +], +[ struct sigaction act, oact; sigaction(0, &act, &oact); return 0;], +ac_cv_HAVE_POSIX_SIGNALS=yes,ac_cv_HAVE_POSIX_SIGNALS=no,ac_cv_HAVE_POSIX_SIGNALS=cross)]) +if test x"$ac_cv_HAVE_POSIX_SIGNALS" = x"yes"; then + AC_DEFINE(HAVE_POSIX_SIGNALS,1,[ ]) +fi + +dnl 'reboot()' system call: one argument (e.g. Linux) or two (e.g. Solaris)? +dnl +AC_CACHE_CHECK([number of arguments in reboot system call], + ac_cv_REBOOT_ARGS,[ + AC_TRY_COMPILE( + [#include <sys/reboot.h>], + [(void)reboot(0);], + ac_cv_REBOOT_ARGS=1, + [AC_TRY_COMPILE( + [#include <sys/reboot.h>], + [(void)reboot(0,(void *)0);], + ac_cv_REBOOT_ARGS=2, + ac_cv_REBOOT_ARGS=0 + )], + ac_cv_REBOOT_ARGS=0 + ) + ] +) +dnl Argument count of 0 suggests no known 'reboot()' call. +if test "$ac_cv_REBOOT_ARGS" -ge "1"; then + AC_DEFINE_UNQUOTED(REBOOT_ARGS,$ac_cv_REBOOT_ARGS,[number of arguments for reboot system call]) +fi + +AC_PATH_PROGS(REBOOT, reboot, /sbin/reboot) +AC_SUBST(REBOOT) +AC_SUBST(REBOOT_OPTIONS) +AC_DEFINE_UNQUOTED(REBOOT, "$REBOOT", path to the reboot command) +AC_DEFINE_UNQUOTED(REBOOT_OPTIONS, "$REBOOT_OPTIONS", reboot options) + +AC_PATH_PROGS(POWEROFF_CMD, poweroff, /sbin/poweroff) +AC_SUBST(POWEROFF_CMD) +AC_SUBST(POWEROFF_OPTIONS) +AC_DEFINE_UNQUOTED(POWEROFF_CMD, "$POWEROFF_CMD", path to the poweroff command) +AC_DEFINE_UNQUOTED(POWEROFF_OPTIONS, "$POWEROFF_OPTIONS", poweroff options) + +dnl Sockets are our preferred and supported comms mechanism. But the +dnl implementation needs to be able to convey credentials: some don't. +dnl So on a few OSes, credentials-carrying streams might be a better choice. +dnl +dnl Solaris releases up to and including "9" fall into this category +dnl (its sockets don't carry credentials; streams do). +dnl +dnl At Solaris 10, "getpeerucred()" is available, for both sockets and +dnl streams, so it should probably use (preferred) socket mechanism. + +AC_CHECK_HEADERS(stropts.h) dnl streams available (fallback option) + +AC_CHECK_HEADERS(ucred.h) dnl e.g. Solaris 10 decl. of "getpeerucred()" +AC_CHECK_FUNCS(getpeerucred) + +dnl ************************************************************************ +dnl checks for headers needed by clplumbing On BSD +AC_CHECK_HEADERS(sys/syslimits.h) +if test "$ac_cv_header_sys_param_h" = no; then + AC_CHECK_HEADERS(sys/ucred.h) +else + AC_CHECK_HEADERS(sys/ucred.h,[],[],[#include <sys/param.h>]) +fi + +dnl ************************************************************************ +dnl checks for headers needed by clplumbing On Solaris +AC_CHECK_HEADERS(sys/cred.h xti.h) + +dnl ************************************************************************ +dnl checks for headers needed by clplumbing On FreeBSD/Solaris +AC_CHECK_HEADERS(sys/filio.h) + +dnl ======================================================================== +dnl Structures +dnl ======================================================================== + +AC_CHECK_MEMBERS([struct tm.tm_gmtoff],,,[[#include <time.h>]]) +AC_CHECK_TYPES([nfds_t],,,[[#include <poll.h>]]) + +AC_MSG_CHECKING(if clock_t is long enough) +if test $ac_cv_sizeof_clock_t -ge 8; then + AC_MSG_RESULT(yes) + AC_DEFINE(CLOCK_T_IS_LONG_ENOUGH, 1, [Set if CLOCK_T is adequate by itself for the "indefinite future" (>= 100 years)]) +else + AC_MSG_RESULT(no) +fi + +dnl ======================================================================== +dnl Functions +dnl ======================================================================== + +AC_CHECK_FUNCS(g_log_set_default_handler) +AC_CHECK_FUNCS(getopt, AC_DEFINE(HAVE_DECL_GETOPT, 1, [Have getopt function])) +AC_CHECK_FUNCS(getpeereid) + +dnl ********************************************************************** +dnl Check for various argv[] replacing functions on various OSs +dnl +dnl Borrowed from Proftpd +dnl Proftpd is Licenced under the terms of the GNU General Public Licence +dnl and is available from http://www.proftpd.org/ +dnl + +AC_CHECK_FUNCS(setproctitle) +AC_CHECK_HEADERS(libutil.h) +AC_CHECK_LIB(util, setproctitle, + [AC_DEFINE(HAVE_SETPROCTITLE,1,[ ]) + ac_cv_func_setproctitle="yes" ; LIBS="$LIBS -lutil"]) + +if test "$ac_cv_func_setproctitle" = "yes"; then + pf_argv_set="PF_ARGV_NONE" +fi + +if test "$pf_argv_set" = ""; then + AC_CHECK_HEADERS(sys/pstat.h) + if test "$ac_cv_header_pstat_h" = "yes"; then + AC_CHECK_FUNCS(pstat) + + if test "$ac_cv_func_pstat" = "yes"; then + pf_argv_set="PF_ARGV_PSTAT" + else + pf_argv_set="PF_ARGV_WRITEABLE" + fi + fi + + if test "$pf_argv_set" = ""; then + AC_EGREP_HEADER([#define.*PS_STRINGS.*],sys/exec.h, + have_psstrings="yes",have_psstrings="no") + if test "$have_psstrings" = "yes"; then + pf_argv_set="PF_ARGV_PSSTRINGS" + fi + fi + + if test "$pf_argv_set" = ""; then + AC_CACHE_CHECK(whether __progname and __progname_full are available, + pf_cv_var_progname, + AC_TRY_LINK([extern char *__progname, *__progname_full;], + [__progname = "foo"; __progname_full = "foo bar";], + pf_cv_var_progname="yes", pf_cv_var_progname="no")) + + if test "$pf_cv_var_progname" = "yes"; then + AC_DEFINE(HAVE___PROGNAME,1,[ ]) + fi + + AC_CACHE_CHECK(which argv replacement method to use, + pf_cv_argv_type, + AC_EGREP_CPP(yes,[ +#if defined(__GNU_HURD__) + yes +#endif + ],pf_cv_argv_type="new", pf_cv_argv_type="writeable")) + + if test "$pf_cv_argv_type" = "new"; then + pf_argv_set="PF_ARGV_NEW" + fi + + if test "$pf_argv_set" = ""; then + pf_argv_set="PF_ARGV_WRITEABLE" + fi + fi +fi +AC_DEFINE_UNQUOTED(PF_ARGV_TYPE, $pf_argv_set, + mechanism to pretty-print ps output: setproctitle-equivalent) + +dnl End of tests borrowed from Proftpd + +dnl ======================================================================== +dnl ltdl +dnl ======================================================================== + +AC_CHECK_LIB(ltdl, lt_dlopen, [LTDL_foo=1]) +if test "x${enable_bundled_ltdl}" = "xyes"; then + if test $ac_cv_lib_ltdl_lt_dlopen = yes; then + AC_MSG_NOTICE([Disabling usage of installed ltdl]) + fi + ac_cv_lib_ltdl_lt_dlopen=no +fi + +LIBLTDL_DIR="" +if test $ac_cv_lib_ltdl_lt_dlopen != yes ; then + AC_MSG_NOTICE([Installing local ltdl]) + LIBLTDL_DIR=libltdl + ( cd $srcdir ; $TAR -xvf libltdl.tar ) + if test "$?" -ne 0; then + AC_MSG_ERROR([$TAR of libltdl.tar in $srcdir failed]) + fi + AC_CONFIG_SUBDIRS(libltdl) +else + LIBS="$LIBS -lltdl" + AC_MSG_NOTICE([Using installed ltdl]) + INCLTDL="" + LIBLTDL="" +fi + +AC_SUBST(INCLTDL) +AC_SUBST(LIBLTDL) +AC_SUBST(LIBLTDL_DIR) + +dnl ======================================================================== +dnl libnet +dnl ======================================================================== + +AC_ARG_ENABLE([libnet], + [ --enable-libnet Use libnet for ARP based funcationality, [default=try]], + [], [enable_libnet=try]) + +libnet="" +libnet_version="none" +LIBNETLIBS="" +LIBNETDEFINES="" + +AC_MSG_CHECKING(if libnet is required) +libnet_fatal=$enable_libnet +case $enable_libnet in + no) ;; + yes|libnet10|libnet11|10|11) libnet_fatal=yes;; + try) + case $host_os in + *Linux*|*linux*) libnet_fatal=no;; + *) libnet_fatal=yes;; dnl legacy behavior + esac + ;; + *) libnet_fatal=yes; enable_libnet=try;; +esac +AC_MSG_RESULT($libnet_fatal) + +if test "x$enable_libnet" != "xno"; then + AC_PATH_PROGS(LIBNETCONFIG, libnet-config) + + AC_CHECK_LIB(nsl, t_open) dnl -lnsl + AC_CHECK_LIB(socket, socket) dnl -lsocket + AC_CHECK_LIB(net, libnet_get_hwaddr, LIBNETLIBS=" -lnet", []) + fi + +AC_MSG_CHECKING(for libnet) +if test "x$LIBNETLIBS" != "x" -o "x$enable_libnet" = "xlibnet11"; then + LIBNETDEFINES="" + if test "$ac_cv_lib_nsl_t_open" = yes; then + LIBNETLIBS="-lnsl $LIBNETLIBS" + fi + if test "$ac_cv_lib_socket_socket" = yes; then + LIBNETLIBS="-lsocket $LIBNETLIBS" + fi + + libnet=net + libnet_version="libnet1.1" +fi + +if test "x$enable_libnet" = "xtry" -o "x$enable_libnet" = "xlibnet10"; then + if test "x$LIBNETLIBS" = x -a "x${LIBNETCONFIG}" != "x" ; then + LIBNETDEFINES="`$LIBNETCONFIG --defines` `$LIBNETCONFIG --cflags`"; + LIBNETLIBS="`$LIBNETCONFIG --libs`"; + libnet_version="libnet1.0 (old)" + case $LIBNETLIBS in + *-l*) libnet=`echo $LIBNETLIBS | sed 's%.*-l%%'`;; + *) libnet_version=none;; + esac + + CPPFLAGS="$CPPFLAGS $LIBNETDEFINES" + + AC_CHECK_HEADERS(libnet.h) + if test "$ac_cv_header_libnet_h" = no; then + libnet_version=none + fi + fi +fi +AC_MSG_RESULT(found $libnet_version) + +if test "$libnet_version" = none; then + LIBNETLIBS="" + LIBNETDEFINES="" + if test $libnet_fatal = yes; then + AC_MSG_ERROR(libnet not found) + fi + +else + AC_CHECK_LIB($libnet,libnet_init, + [new_libnet=yes; AC_DEFINE(HAVE_LIBNET_1_1_API, 1, Libnet 1.1 API)], + [new_libnet=no; AC_DEFINE(HAVE_LIBNET_1_0_API, 1, Libnet 1.0 API)],$LIBNETLIBS) +fi + +dnl ************************************************************************ +dnl * Check for netinet/icmp6.h to enable the IPv6addr resource agent +AC_CHECK_HEADERS(netinet/icmp6.h,[],[],[#include <sys/types.h>]) +AM_CONDITIONAL(USE_IPV6ADDR, test "$ac_cv_header_netinet_icmp6_h" = yes -a "$new_libnet" = yes ) + + +dnl ======================================================================== +dnl SNMP +dnl ======================================================================== + +SNMPLIB="" +SNMPCONFIG="" + +ENABLE_SNMP="yes" +if test "x${enable_snmp}" = "xno"; then + ENABLE_SNMP="no" +fi + +AC_CHECK_HEADERS(ucd-snmp/snmp.h,[],[],[#include <sys/types.h> +#include <ucd-snmp/asn1.h>]) +AC_CHECK_HEADERS(net-snmp/net-snmp-config.h) + +if test "x${ENABLE_SNMP}" = "xno"; then + # nothing + : +elif test "x${ac_cv_header_net_snmp_net_snmp_config_h}" = "xyes"; then + AC_PATH_PROGS(SNMPCONFIG, net-snmp-config) + if test "X${SNMPCONFIG}" = "X"; then + AC_MSG_RESULT(You need the net_snmp development package to continue.) + ENABLE_SNMP="no" + else + AC_MSG_CHECKING(for special snmp libraries) + SNMPLIB=`${SNMPCONFIG} --libs` + AC_MSG_RESULT($SNMPLIB) + fi +elif test "x${ac_cv_header_ucd_snmp_snmp_h}" = "xyes"; then + # UCD SNMP + # ucd-snmp-config does not seem to exist, so just + # rely on people having their LDFLAGS set to the path where + AC_CHECK_LIB(snmp, init_snmp, SNMPLIB="-lsnmp") + if test "X${SNMPLIB}" = "X"; then + AC_CHECK_LIB(ucdsnmp, init_snmp, SNMPLIB="-lucdsnmp") + fi + if test "X${SNMPLIB}" = "X"; then + ENABLE_SNMP="no" + AC_MSG_RESULT("Could not find ucdsnmp libary." + "Please make sure that libsnmp or libucdsnmp" + "are in your library path. Or the path to LDFLAGS") + fi +else + ENABLE_SNMP="no" +fi + +AC_SUBST(SNMPLIB) + +dnl ======================================================================== +dnl Stonith Devices +dnl ======================================================================== + +if test "x${enable_ipmilan}" = "x"; then + enable_ipmilan="yes" +fi +if test "x${enable_ipmilan}" = "xyes" -o "x${enable_ipmilan}" = "xtry"; then + AC_MSG_CHECKING(For libOpenIPMI version 1.4 or greater) + AC_TRY_COMPILE([#include <OpenIPMI/ipmiif.h>], + [ #if (OPENIPMI_VERSION_MAJOR == 1) && (OPENIPMI_VERSION_MINOR < 4) + #error "Too Old" + #endif ], + AC_MSG_RESULT("yes"); enable_ipmilan="yes", + AC_MSG_RESULT("no"); enable_ipmilan="no") +else + enable_ipmilan="no" +fi + +AC_CHECK_HEADERS(curl/curl.h) +AC_CHECK_HEADERS(openhpi/SaHpi.h) +AC_CHECK_HEADERS(vacmclient_api.h) + +AM_CONDITIONAL(USE_APC_SNMP, test "$ENABLE_SNMP" = "yes") +AM_CONDITIONAL(USE_VACM, test "$ac_cv_header_vacmclient_api_h" = yes) +AM_CONDITIONAL(USE_DRAC3, test "$ac_cv_header_curl_curl_h" = yes -a "$ac_cv_header_libxml_xpath_h" = yes) +AM_CONDITIONAL(USE_OPENHPI, test "$ac_cv_header_openhpi_SaHpi_h" = yes && pkg-config --atleast-version 2.6 openhpi) +AM_CONDITIONAL(IPMILAN_BUILD, test "X$enable_ipmilan" = "Xyes") + +dnl ======================================================================== +dnl ZLIB and BZ2 +dnl ======================================================================== + +dnl check if header file and lib are there for zlib +zlib_installed="yes" +AC_CHECK_HEADERS(zlib.h, , [zlib_installed="no"],) +AC_CHECK_LIB(z, compress , , [zlib_installed="no"]) +AM_CONDITIONAL(BUILD_ZLIB_COMPRESS_MODULE, test "x${zlib_installed}" = "xyes") +if test "x${zlib_installed}" = "xno"; then + FatalMissingThing "zlib" \ + "The zlib library is missing" +fi + +bz2_installed="yes" +AC_CHECK_HEADERS(bzlib.h, , [bz2_installed="no"],) +AC_CHECK_LIB(bz2, BZ2_bzBuffToBuffCompress , , [bz2_installed="no"]) +AM_CONDITIONAL(BUILD_BZ2_COMPRESS_MODULE, test "x${bz2_installed}" = "xyes") + +#if test x$ac_cv_lib_bz2_BZ2_bzBuffToBuffCompress != xyes ; then +# AC_MSG_ERROR(BZ2 libraries not found) +#fi + +if test x$ac_cv_header_bzlib_h != xyes; then + AC_MSG_ERROR(BZ2 Development headers not found) +fi + +dnl ======================================================================== +dnl Upstart via DBus +dnl ======================================================================== + +if test x$enable_upstart = xyes; then + PKG_CHECK_MODULES(DBUS, [dbus-1, dbus-glib-1]) + AC_SUBST(DBUS_CFLAGS) + AC_SUBST(DBUS_LIBS) + AC_PATH_PROGS(DBUS_BINDING_TOOL, dbus-binding-tool) +fi +AM_CONDITIONAL(UPSTART, test x$enable_upstart = xyes) + + +dnl ======================================================================== +dnl checks for library functions to replace them +dnl +dnl NoSuchFunctionName: +dnl is a dummy function which no system supplies. It is here to make +dnl the system compile semi-correctly on OpenBSD which doesn't know +dnl how to create an empty archive +dnl +dnl scandir: Only on BSD. +dnl System-V systems may have it, but hidden and/or deprecated. +dnl A replacement function is supplied for it. +dnl +dnl setenv: is some bsdish function that should also be avoided (use +dnl putenv instead) +dnl On the other hand, putenv doesn't provide the right API for the +dnl code and has memory leaks designed in (sigh...) Fortunately this +dnl A replacement function is supplied for it. +dnl +dnl strerror: returns a string that corresponds to an errno. +dnl A replacement function is supplied for it. +dnl +dnl unsetenv: is some bsdish function that should also be avoided (No +dnl replacement) +dnl A replacement function is supplied for it. +dnl +dnl strnlen: is a gnu function similar to strlen, but safer. +dnl We wrote a tolearably-fast replacement function for it. +dnl +dnl strndup: is a gnu function similar to strdup, but safer. +dnl We wrote a tolearably-fast replacement function for it. +dnl +dnl daemon: is a GNU function. The daemon() function is for programs wishing to +dnl detach themselves from the controlling terminal and run in the +dnl background as system daemon +dnl A replacement function is supplied for it. + +AC_REPLACE_FUNCS(alphasort inet_pton NoSuchFunctionName scandir setenv strerror unsetenv strnlen strndup daemon strlcpy strlcat) + +dnl ======================================================================== +dnl Compiler flags +dnl ======================================================================== + +dnl Make sure that CFLAGS is not exported. If the user did +dnl not have CFLAGS in their environment then this should have +dnl no effect. However if CFLAGS was exported from the user's +dnl environment, then the new CFLAGS will also be exported +dnl to sub processes. + +CC_ERRORS="" +CC_EXTRAS="" + +if export | fgrep " CFLAGS=" > /dev/null; then + SAVED_CFLAGS="$CFLAGS" + unset CFLAGS + CFLAGS="$SAVED_CFLAGS" + unset SAVED_CFLAGS +fi + +if test "$GCC" != yes; then + CFLAGS="$CFLAGS -g" + enable_fatal_warnings=no +else + CFLAGS="$CFLAGS -ggdb" + + # We had to eliminate -Wnested-externs because of libtool changes + EXTRA_FLAGS="-fgnu89-inline + -fstack-protector-all + -Wall + -Waggregate-return + -Wbad-function-cast + -Wcast-qual + -Wcast-align + -Wdeclaration-after-statement + -Wendif-labels + -Wfloat-equal + -Wformat=2 + -Wformat-security + -Wformat-nonliteral + -Winline + -Wmissing-prototypes + -Wmissing-declarations + -Wmissing-format-attribute + -Wnested-externs + -Wno-long-long + -Wno-strict-aliasing + -Wpointer-arith + -Wstrict-prototypes + -Wunsigned-char + -Wwrite-strings" + +# Additional warnings it might be nice to enable one day +# -Wshadow +# -Wunreachable-code + + for j in $EXTRA_FLAGS + do + if + cc_supports_flag $j + then + CC_EXTRAS="$CC_EXTRAS $j" + fi + done + +dnl In lib/ais/Makefile.am there's a gcc option available as of v4.x + + GCC_MAJOR=`gcc -v 2>&1 | awk 'END{print $3}' | sed 's/[.].*//'` + AM_CONDITIONAL(GCC_4, test "${GCC_MAJOR}" = 4) + +dnl System specific options + + case "$host_os" in + *linux*|*bsd*) + if test "${enable_fatal_warnings}" = "unknown"; then + enable_fatal_warnings=yes + fi + ;; + esac + + if test "x${enable_fatal_warnings}" != xno && cc_supports_flag -Werror ; then + enable_fatal_warnings=yes + else + enable_fatal_warnings=no + fi + + if test "x${enable_ansi}" != xno && cc_supports_flag -std=iso9899:199409 ; then + AC_MSG_NOTICE(Enabling ANSI Compatibility) + CC_EXTRAS="$CC_EXTRAS -ansi -D_GNU_SOURCE -DANSI_ONLY" + fi + + AC_MSG_NOTICE(Activated additional gcc flags: ${CC_EXTRAS}) +fi + +CFLAGS="$CFLAGS $CC_EXTRAS" + +NON_FATAL_CFLAGS="$CFLAGS" +AC_SUBST(NON_FATAL_CFLAGS) + +dnl +dnl We reset CFLAGS to include our warnings *after* all function +dnl checking goes on, so that our warning flags don't keep the +dnl AC_*FUNCS() calls above from working. In particular, -Werror will +dnl *always* cause us troubles if we set it before here. +dnl +dnl +if test "x${enable_fatal_warnings}" = xyes ; then + AC_MSG_NOTICE(Enabling Fatal Warnings) + CFLAGS="$CFLAGS -Werror" +fi +AC_SUBST(CFLAGS) + +dnl This is useful for use in Makefiles that need to remove one specific flag +CFLAGS_COPY="$CFLAGS" +AC_SUBST(CFLAGS_COPY) + +AC_SUBST(LIBADD_DL) dnl extra flags for dynamic linking libraries +AC_SUBST(LIBADD_INTL) dnl extra flags for GNU gettext stuff... + +AC_SUBST(LOCALE) + +dnl Options for cleaning up the compiler output +PRETTY_CC="" +QUIET_LIBTOOL_OPTS="" +QUIET_MAKE_OPTS="" +if test x"${enable_pretty}" = "xyes"; then + enable_quiet="yes" + echo "install_sh: ${install_sh}" + PRETTY_CC="`pwd`/tools/ccdv" + dnl It would be nice if this was rebuilt when needed too... + mkdir `pwd`/tools/ 2>/dev/null + ${CC} $CFLAGS -o `pwd`/tools/ccdv ${srcdir}/tools/ccdv.c + CC="\$(PRETTY_CC) ${CC}" +fi +if test "x${enable_quiet}" = "xyes"; then + QUIET_LIBTOOL_OPTS="--quiet" + QUIET_MAKE_OPTS="--quiet" +fi + +AC_MSG_RESULT(Supress make details: ${enable_quiet}) +AC_MSG_RESULT(Pretty print compiler output: ${enable_pretty}) + +dnl Put the above variables to use +LIBTOOL="${LIBTOOL} --tag=CC \$(QUIET_LIBTOOL_OPTS)" +MAKE="${MAKE} \$(QUIET_MAKE_OPTS)" + +AC_SUBST(CC) +AC_SUBST(MAKE) +AC_SUBST(LIBTOOL) +AC_SUBST(PRETTY_CC) +AC_SUBST(QUIET_MAKE_OPTS) +AC_SUBST(QUIET_LIBTOOL_OPTS) + +dnl The Makefiles and shell scripts we output +AC_CONFIG_FILES(Makefile \ +config/Makefile \ +include/Makefile \ + include/pils/Makefile \ + include/pils/plugin.h \ + include/clplumbing/Makefile \ + include/lrm/Makefile \ + include/stonith/Makefile \ +lib/Makefile \ + lib/pils/Makefile \ + lib/clplumbing/Makefile \ + lib/stonith/Makefile \ + lib/lrm/Makefile \ + lib/plugins/Makefile \ + lib/plugins/InterfaceMgr/Makefile \ + lib/plugins/compress/Makefile \ + lib/plugins/lrm/Makefile \ + lib/plugins/lrm/dbus/Makefile \ + lib/plugins/stonith/Makefile \ + lib/plugins/stonith/ribcl.py \ + lib/plugins/stonith/external/Makefile \ + lib/plugins/stonith/external/drac5 \ + lib/plugins/stonith/external/kdumpcheck \ + lib/plugins/stonith/external/ssh \ + lib/plugins/stonith/external/ippower9258 \ + lib/plugins/stonith/external/xen0-ha \ +lrm/Makefile \ + lrm/lrmd/Makefile \ + lrm/admin/Makefile \ + lrm/admin/cibsecret \ + lrm/test/Makefile \ + lrm/test/regression.sh \ + lrm/test/lrmregtest \ + lrm/test/LRMBasicSanityCheck \ + lrm/test/testcases/Makefile \ +logd/Makefile \ +logd/logd \ +logd/logd.service \ +replace/Makefile \ +hb_report/Makefile \ + hb_report/hb_report \ +doc/Makefile \ + doc/ha_logd.xml \ + doc/ha_logger.xml \ + doc/stonith.xml \ + doc/meatclient.xml \ + doc/stonith/Makefile +) + +dnl Now process the entire list of files added by previous +dnl calls to AC_CONFIG_FILES() +AC_OUTPUT() + +dnl ***************** +dnl Configure summary +dnl ***************** + +AC_MSG_RESULT([]) +AC_MSG_RESULT([$PACKAGE configuration:]) +AC_MSG_RESULT([ Version = ${VERSION} (Build: $GLUE_BUILD_VERSION)]) +AC_MSG_RESULT([ Features =${FEATURES}]) +AC_MSG_RESULT([]) +AC_MSG_RESULT([ Prefix = ${prefix}]) +AC_MSG_RESULT([ Executables = ${sbindir}]) +AC_MSG_RESULT([ Man pages = ${mandir}]) +AC_MSG_RESULT([ Libraries = ${libdir}]) +AC_MSG_RESULT([ Header files = ${includedir}]) +AC_MSG_RESULT([ Arch-independent files = ${datadir}]) +AC_MSG_RESULT([ Documentation = ${docdir}]) +AC_MSG_RESULT([ State information = ${localstatedir}]) +AC_MSG_RESULT([ System configuration = ${sysconfdir}]) +AC_MSG_RESULT([]) +AC_MSG_RESULT([ Use system LTDL = ${ac_cv_lib_ltdl_lt_dlopen}]) +AC_MSG_RESULT([]) +AC_MSG_RESULT([ HA group name = ${GLUE_DAEMON_GROUP}]) +AC_MSG_RESULT([ HA user name = ${GLUE_DAEMON_USER}]) +AC_MSG_RESULT([]) +AC_MSG_RESULT([ CFLAGS = ${CFLAGS}]) +AC_MSG_RESULT([ Libraries = ${LIBS}]) +AC_MSG_RESULT([ Stack Libraries = ${CLUSTERLIBS}]) + diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..c8d67a8 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,53 @@ +# +# heartbeat: Linux-HA heartbeat code +# +# Copyright (C) 2001 Michael Moerz +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in hb_report.xml ha_logd.xml ha_logger.xml stonith.xml meatclient.xml + +CLEANFILES = $(man_MANS) + +SUBDIRS = stonith + +hanoarchdir = $(datadir)/heartbeat + +man_MANS = + +if BUILD_DOC +man_MANS += hb_report.8 ha_logd.8 ha_logger.1 stonith.8 meatclient.8 + +EXTRA_DIST = $(man_MANS) + +STYLESHEET_PREFIX ?= http://docbook.sourceforge.net/release/xsl/current +MANPAGES_STYLESHEET ?= $(STYLESHEET_PREFIX)/manpages/docbook.xsl +HTML_STYLESHEET ?= $(STYLESHEET_PREFIX)/xhtml/docbook.xsl +FO_STYLESHEET ?= $(STYLESHEET_PREFIX)/fo/docbook.xsl + +XSLTPROC_OPTIONS ?= --xinclude +XSLTPROC_MANPAGES_OPTIONS ?= $(XSLTPROC_OPTIONS) +XSLTPROC_HTML_OPTIONS ?= $(XSLTPROC_OPTIONS) +XSLTPROC_FO_OPTIONS ?= $(XSLTPROC_OPTIONS) + +%.5 %.8 %.1: %.xml + $(XSLTPROC) \ + $(XSLTPROC_MANPAGES_OPTIONS) \ + $(MANPAGES_STYLESHEET) $< + +hb_report.8: hb_report.8.txt + a2x -f manpage $< + +endif diff --git a/doc/ha_logd.xml.in b/doc/ha_logd.xml.in new file mode 100644 index 0000000..368f06d --- /dev/null +++ b/doc/ha_logd.xml.in @@ -0,0 +1,134 @@ +<?xml version="1.0"?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> +<refentry id="re-ha_logd"> + <refentryinfo> + <date>December 8, 2009</date> + <productname>@PACKAGE_NAME@</productname> + <productnumber>@VERSION@</productnumber> + <authorgroup> + <author> + <firstname>Alan</firstname> + <surname>Robertson</surname> + <contrib>ha_logd</contrib> + <email>alanr@unix.sh</email> + </author> + <author> + <surname>Shi</surname> + <firstname>Guochun</firstname> + <contrib>ha_logd</contrib> + <email>gshi@ncsa.uiuc.edu</email> + </author> + <author> + <surname>Lars</surname> + <firstname>Marowsky-Bree</firstname> + <contrib>ha_logd</contrib> + <email>lmb@suse.de</email> + </author> + <author> + <firstname>Florian</firstname> + <surname>Haas</surname> + <contrib>man page</contrib> + <email>florian.haas@linbit.com</email> + </author> + </authorgroup> + </refentryinfo> + <refmeta> + <refentrytitle>ha_logd</refentrytitle> + <manvolnum>8</manvolnum> + <refmiscinfo class="manual">System administration utilities</refmiscinfo> + </refmeta> + <refnamediv> + <refname>ha_logd</refname> + <refpurpose>Logging Daemon for High-Availability Linux</refpurpose> + </refnamediv> + <refsynopsisdiv> + <cmdsynopsis> + <command>ha_logd</command> + <arg choice="opt"><option>-s</option></arg> + <arg choice="opt"><option>-k</option></arg> + <arg choice="opt"><option>-d</option></arg> + <arg choice="opt"><option>-h</option></arg> + <arg choice="opt"><option>-c</option> <replaceable>file</replaceable></arg> + </cmdsynopsis> + </refsynopsisdiv> + <refsection id="rs-ha_logd-description"> + <title>Description</title> + <para><command>ha_logd</command> is a logging daemon for + Linux-HA. It receives messages from a local domain socket + <filename>@HA_LOGDAEMON_IPC@</filename>, and writes them to + appropriate files and syslog if enabled. The reason for utilizing + this logging daemon is that occasionally Heartbeat suffers from + disk I/O delays. By sending log messages to a logging daemon, + heartbeat can avoid such I/O delays.</para> + </refsection> + <refsection id="rs-ha_logd-options"> + <title>Options</title> + <para>The following options are supported:</para> + <variablelist> + <varlistentry> + <term> + <option>-s</option> + </term> + <listitem> + <para>Show <command>ha_logd</command> status (either + <token>running</token> or <token>stopped</token>)</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-k</option> + </term> + <listitem> + <para>Stop (kill) the daemon</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-d</option> + </term> + <listitem> + <para>Daemonize (without this option, + <command>ha_logd</command> will run in the + foreground)</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-h</option> + </term> + <listitem> + <para>Show a brief usage message</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-c</option> <replaceable>file</replaceable> + </term> + <listitem> + <para>Configuration file. You may configure a regular log + file, debug log file, log facility, and entity. For details, + see the example <filename>ha_logd.cf</filename> file found + in the documentation.</para> + </listitem> + </varlistentry> + </variablelist> + </refsection> + <refsection id="rs-ha_logd-files"> + <title>Files</title> + <itemizedlist> + <listitem> + <para><filename>@GLUE_STATE_DIR@/ha_logd.pid</filename> – PID file</para> + </listitem> + <listitem> + <para><filename>ha_logd.cf</filename> – example configuration file</para> + </listitem> + </itemizedlist> + </refsection> + <refsection id="rs-ha_logd-seealso"> + <title>See also</title> + <para> + <citerefentry><refentrytitle>heartbeat</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>ha_logger</refentrytitle><manvolnum>1</manvolnum></citerefentry> + </para> + </refsection> +</refentry> diff --git a/doc/ha_logger.xml.in b/doc/ha_logger.xml.in new file mode 100644 index 0000000..dce7fe2 --- /dev/null +++ b/doc/ha_logger.xml.in @@ -0,0 +1,110 @@ +<?xml version="1.0"?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> +<refentry id="re-ha_logger"> + <refentryinfo> + <date>December 8, 2009</date> + <productname>@PACKAGE_NAME@</productname> + <productnumber>@VERSION@</productnumber> + <authorgroup> + <author> + <firstname>Alan</firstname> + <surname>Robertson</surname> + <contrib>ha_logd</contrib> + <email>alanr@unix.sh</email> + </author> + <author> + <surname>Shi</surname> + <firstname>Guochun</firstname> + <contrib>ha_logd</contrib> + <email>gshi@ncsa.uiuc.edu</email> + </author> + <author> + <surname>Lars</surname> + <firstname>Marowsky-Bree</firstname> + <contrib>ha_logd</contrib> + <email>lmb@suse.de</email> + </author> + <author> + <firstname>Florian</firstname> + <surname>Haas</surname> + <contrib>man page</contrib> + <email>florian.haas@linbit.com</email> + </author> + </authorgroup> + </refentryinfo> + <refmeta> + <refentrytitle>ha_logger</refentrytitle> + <manvolnum>1</manvolnum> + <refmiscinfo class="manual">User commands</refmiscinfo> + </refmeta> + <refnamediv> + <refname>ha_logger</refname> + <refpurpose>Log a message to files and/or syslog through the HA + Logging Daemon</refpurpose> + </refnamediv> + <refsynopsisdiv> + <cmdsynopsis> + <command>ha_logger</command> + <arg choice="opt"> + <option>-D</option> + <group choice="plain"> + <arg>ha-log</arg> + <arg>ha-debug</arg> + </group> + </arg> + <arg choice="opt"> + <option>-t</option> + <replaceable>tag</replaceable> + </arg> + <arg choice="plain" rep="repeat"> + <replaceable>message</replaceable> + </arg> + </cmdsynopsis> + </refsynopsisdiv> + <refsection id="rs-ha_logger-description"> + <title>Description</title> + <para><command>ha_logger</command> is used to log a message to + files/syslog through the HA Logging Daemon.</para> + </refsection> + <refsection id="rs-ha_logger-options"> + <title>Options</title> + <para>The following options are supported:</para> + <variablelist> + <varlistentry> + <term> + <option>-D</option> <token>ha-log</token>|<token>ha-debug</token> + </term> + <listitem> + <para>Log the message to different + files. <token>ha-log</token> will log the message to the log + file and the debug file, while <token>ha-debug</token> will + log the message to the debug file only.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-t</option> <replaceable>tag</replaceable> + </term> + <listitem> + <para>Mark every line in the log with the specified + <replaceable>tag</replaceable>.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <replaceable>message</replaceable> + </term> + <listitem> + <para>The message that should be logged.</para> + </listitem> + </varlistentry> + </variablelist> + </refsection> + <refsection id="rs-ha_logger-seealso"> + <title>See also</title> + <para> + <citerefentry><refentrytitle>heartbeat</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>ha_logd</refentrytitle><manvolnum>8</manvolnum></citerefentry> + </para> + </refsection> +</refentry> diff --git a/doc/hb_report.8.txt b/doc/hb_report.8.txt new file mode 100644 index 0000000..5efbc32 --- /dev/null +++ b/doc/hb_report.8.txt @@ -0,0 +1,478 @@ +:man source: hb_report +:man version: 1.2 +:man manual: Pacemaker documentation + +hb_report(8) +============ + + +NAME +---- +hb_report - create report for CRM based clusters (Pacemaker) + + +SYNOPSIS +-------- +*hb_report* -f {time|"cts:"testnum} [-t time] [-u user] [-l file] + [-n nodes] [-E files] [-p patt] [-L patt] [-e prog] + [-MSDCZAQVsvhd] [dest] + + +DESCRIPTION +----------- +The hb_report(1) is a utility to collect all information (logs, +configuration files, system information, etc) relevant to +Pacemaker (CRM) over the given period of time. + + +OPTIONS +------- +dest:: + The report name. It can also contain a path where to put the + report tarball. If left out, the tarball is created in the + current directory named "hb_report-current_date", for instance + hb_report-Wed-03-Mar-2010. + +*-d*:: + Don't create the compressed tar, but leave the result in a + directory. + +*-f* { time | "cts:"testnum }:: + The start time from which to collect logs. The time is in the + format as used by the Date::Parse perl module. For cts tests, + specify the "cts:" string followed by the test number. This + option is required. + +*-t* time:: + The end time to which to collect logs. Defaults to now. + +*-n* nodes:: + A list of space separated hostnames (cluster members). + hb_report may try to find out the set of nodes by itself, but + if it runs on the loghost which, as it is usually the case, + does not belong to the cluster, that may be difficult. Also, + OpenAIS doesn't contain a list of nodes and if Pacemaker is + not running, there is no way to find it out automatically. + This option is cumulative (i.e. use -n "a b" or -n a -n b). + +*-l* file:: + Log file location. If, for whatever reason, hb_report cannot + find the log files, you can specify its absolute path. + +*-E* files:: + Extra log files to collect. This option is cumulative. By + default, /var/log/messages are collected along with the + cluster logs. + +*-M*:: + Don't collect extra log files, but only the file containing + messages from the cluster subsystems. + +*-L* patt:: + A list of regular expressions to match in log files for + analysis. This option is additive (default: "CRIT: ERROR:"). + +*-p* patt:: + Additional patterns to match parameter name which contain + sensitive information. This option is additive (default: "passw.*"). + +*-Q*:: + Quick run. Gathering some system information can be expensive. + With this option, such operations are skipped and thus + information collecting sped up. The operations considered + I/O or CPU intensive: verifying installed packages content, + sanitizing files for sensitive information, and producing dot + files from PE inputs. + +*-A*:: + This is an OpenAIS cluster. hb_report has some heuristics to + find the cluster stack, but that is not always reliable. + By default, hb_report assumes that it is run on a Heartbeat + cluster. + +*-u* user:: + The ssh user. hb_report will try to login to other nodes + without specifying a user, then as "root", and finally as + "hacluster". If you have another user for administration over + ssh, please use this option. + +*-X* ssh-options:: + Extra ssh options. These will be added to every ssh + invocation. Alternatively, use `$HOME/.ssh/config` to setup + desired ssh connection options. + +*-S*:: + Single node operation. Run hb_report only on this node and + don't try to start slave collectors on other members of the + cluster. Under normal circumstances this option is not + needed. Use if ssh(1) does not work to other nodes. + +*-Z*:: + If the destination directory exist, remove it instead of + exiting (this is default for CTS). + +*-V*:: + Print the version including the last repository changeset. + +*-v*:: + Increase verbosity. Normally used to debug unexpected + behaviour. + +*-h*:: + Show usage and some examples. + +*-D* (obsolete):: + Don't invoke editor to fill the description text file. + +*-e* prog (obsolete):: + Your favourite text editor. Defaults to $EDITOR, vim, vi, + emacs, or nano, whichever is found first. + +*-C* (obsolete):: + Remove the destination directory once the report has been put + in a tarball. + +EXAMPLES +-------- +Last night during the backup there were several warnings +encountered (logserver is the log host): + + logserver# hb_report -f 3:00 -t 4:00 -n "node1 node2" report + +collects everything from all nodes from 3am to 4am last night. +The files are compressed to a tarball report.tar.bz2. + +Just found a problem during testing: + + # note the current time + node1# date + Fri Sep 11 18:51:40 CEST 2009 + node1# /etc/init.d/heartbeat start + node1# nasty-command-that-breaks-things + node1# sleep 120 #wait for the cluster to settle + node1# hb_report -f 18:51 hb1 + + # if hb_report can't figure out that this is corosync + node1# hb_report -f 18:51 -A hb1 + + # if hb_report can't figure out the cluster members + node1# hb_report -f 18:51 -n "node1 node2" hb1 + +The files are compressed to a tarball hb1.tar.bz2. + +INTERPRETING RESULTS +-------------------- +The compressed tar archive is the final product of hb_report. +This is one example of its content, for a CTS test case on a +three node OpenAIS cluster: + + $ ls -RF 001-Restart + + 001-Restart: + analysis.txt events.txt logd.cf s390vm13/ s390vm16/ + description.txt ha-log.txt openais.conf s390vm14/ + + 001-Restart/s390vm13: + STOPPED crm_verify.txt hb_uuid.txt openais.conf@ sysinfo.txt + cib.txt dlm_dump.txt logd.cf@ pengine/ sysstats.txt + cib.xml events.txt messages permissions.txt + + 001-Restart/s390vm13/pengine: + pe-input-738.bz2 pe-input-740.bz2 pe-warn-450.bz2 + pe-input-739.bz2 pe-warn-449.bz2 pe-warn-451.bz2 + + 001-Restart/s390vm14: + STOPPED crm_verify.txt hb_uuid.txt openais.conf@ sysstats.txt + cib.txt dlm_dump.txt logd.cf@ permissions.txt + cib.xml events.txt messages sysinfo.txt + + 001-Restart/s390vm16: + STOPPED crm_verify.txt hb_uuid.txt messages sysinfo.txt + cib.txt dlm_dump.txt hostcache openais.conf@ sysstats.txt + cib.xml events.txt logd.cf@ permissions.txt + +The top directory contains information which pertains to the +cluster or event as a whole. Files with exactly the same content +on all nodes will also be at the top, with per-node links created +(as it is in this example the case with openais.conf and logd.cf). + +The cluster log files are named ha-log.txt regardless of the +actual log file name on the system. If it is found on the +loghost, then it is placed in the top directory. If not, the top +directory ha-log.txt contains all nodes logs merged and sorted by +time. Files named messages are excerpts of /var/log/messages from +nodes. + +Most files are copied verbatim or they contain output of a +command. For instance, cib.xml is a copy of the CIB found in +/var/lib/heartbeat/crm/cib.xml. crm_verify.txt is output of the +crm_verify(8) program. + +Some files are result of a more involved processing: + + *analysis.txt*:: + A set of log messages matching user defined patterns (may be + provided with the -L option). + + *events.txt*:: + A set of log messages matching event patterns. It should + provide information about major cluster motions without + unnecessary details. These patterns are devised by the + cluster experts. Currently, the patterns cover membership + and quorum changes, resource starts and stops, fencing + (stonith) actions, and cluster starts and stops. events.txt + is always generated for each node. In case the central + cluster log was found, also combined for all nodes. + + *permissions.txt*:: + One of the more common problem causes are file and directory + permissions. hb_report looks for a set of predefined + directories and checks their permissions. Any issues are + reported here. + + *backtraces.txt*:: + gdb generated backtrace information for cores dumped + within the specified period. + + *sysinfo.txt*:: + Various release information about the platform, kernel, + operating system, packages, and anything else deemed to be + relevant. The static part of the system. + + *sysstats.txt*:: + Output of various system commands such as ps(1), uptime(1), + netstat(8), and ifconfig(8). The dynamic part of the system. + +description.txt should contain a user supplied description of the +problem, but since it is very seldom used, it will be dropped +from the future releases. + +PREREQUISITES +------------- + +ssh:: + It is not strictly required, but you won't regret having a + password-less ssh. It is not too difficult to setup and will save + you a lot of time. If you can't have it, for example because your + security policy does not allow such a thing, or you just prefer + menial work, then you will have to resort to the semi-manual + semi-automated report generation. See below for instructions. + + + If you need to supply a password for your passphrase/login, then + always use the `-u` option. + + + For extra ssh(1) options, if you're too lazy to setup + $HOME/.ssh/config, use the `-X` option. Do not forget to put + the options in quotes. + +sudo:: + If the ssh user (as specified with the `-u` option) is other + than `root`, then `hb_report` uses `sudo` to collect the + information which is readable only by the `root` user. In that + case it is required to setup the `sudoers` file properly. The + user (or group to which the user belongs) should have the + following line: + + + <user> ALL = NOPASSWD: /usr/sbin/hb_report + + + See the `sudoers(5)` man page for more details. + +Times:: + In order to find files and messages in the given period and to + parse the `-f` and `-t` options, `hb_report` uses perl and one of the + `Date::Parse` or `Date::Manip` perl modules. Note that you need + only one of these. Furthermore, on nodes which have no logs and + where you don't run `hb_report` directly, no date parsing is + necessary. In other words, if you run this on a loghost then you + don't need these perl modules on the cluster nodes. + + + On rpm based distributions, you can find `Date::Parse` in + `perl-TimeDate` and on Debian and its derivatives in + `libtimedate-perl`. + +Core dumps:: + To backtrace core dumps gdb is needed and the packages with + the debugging info. The debug info packages may be installed + at the time the report is created. Let's hope that you will + need this really seldom. + +TIMES +----- + +Specifying times can at times be a nuisance. That is why we have +chosen to use one of the perl modules--they do allow certain +freedom when talking dates. You can either read the instructions +at the +http://search.cpan.org/dist/TimeDate/lib/Date/Parse.pm#EXAMPLE_DATES[Date::Parse +examples page]. +or just rely on common sense and try stuff like: + + 3:00 (today at 3am) + 15:00 (today at 3pm) + 2007/9/1 2pm (September 1st at 2pm) + Tue Sep 15 20:46:27 CEST 2009 (September 15th etc) + +`hb_report` will (probably) complain if it can't figure out what do +you mean. + +Try to delimit the event as close as possible in order to reduce +the size of the report, but still leaving a minute or two around +for good measure. + +`-f` is not optional. And don't forget to quote dates when they +contain spaces. + + +Should I send all this to the rest of Internet? +----------------------------------------------- + +By default, the sensitive data in CIB and PE files is not mangled +by `hb_report` because that makes PE input files mostly useless. +If you still have no other option but to send the report to a +public mailing list and do not want the sensitive data to be +included, use the `-s` option. Without this option, `hb_report` +will issue a warning if it finds information which should not be +exposed. By default, parameters matching 'passw.*' are considered +sensitive. Use the `-p` option to specify additional regular +expressions to match variable names which may contain information +you don't want to leak. For example: + + # hb_report -f 18:00 -p "user.*" -p "secret.*" /var/tmp/report + +Heartbeat's ha.cf is always sanitized. Logs and other files are +not filtered. + +LOGS +---- + +It may be tricky to find syslog logs. The scheme used is to log a +unique message on all nodes and then look it up in the usual +syslog locations. This procedure is not foolproof, in particular +if the syslog files are in a non-standard directory. We look in +/var/log /var/logs /var/syslog /var/adm /var/log/ha +/var/log/cluster. In case we can't find the logs, please supply +their location: + + # hb_report -f 5pm -l /var/log/cluster1/ha-log -S /tmp/report_node1 + +If you have different log locations on different nodes, well, +perhaps you'd like to make them the same and make life easier for +everybody. + +Files starting with "ha-" are preferred. In case syslog sends +messages to more than one file, if one of them is named ha-log or +ha-debug those will be favoured over syslog or messages. + +hb_report supports also archived logs in case the period +specified extends that far in the past. The archives must reside +in the same directory as the current log and their names must +be prefixed with the name of the current log (syslog-1.gz or +messages-20090105.bz2). + +If there is no separate log for the cluster, possibly unrelated +messages from other programs are included. We don't filter logs, +but just pick a segment for the period you specified. + +MANUAL REPORT COLLECTION +------------------------ + +So, your ssh doesn't work. In that case, you will have to run +this procedure on all nodes. Use `-S` so that `hb_report` doesn't +bother with ssh: + + # hb_report -f 5:20pm -t 5:30pm -S /tmp/report_node1 + +If you also have a log host which is not in the cluster, then +you'll have to copy the log to one of the nodes and tell us where +it is: + + # hb_report -f 5:20pm -t 5:30pm -l /var/tmp/ha-log -S /tmp/report_node1 + +OPERATION +--------- +hb_report collects files and other information in a fairly +straightforward way. The most complex tasks are discovering the +log file locations (if syslog is used which is the most common +case) and coordinating the operation on multiple nodes. + +The instance of hb_report running on the host where it was +invoked is the master instance. Instances running on other nodes +are slave instances. The master instance communicates with slave +instances by ssh. There are multiple ssh invocations per run, so +it is essential that the ssh works without password, i.e. with +the public key authentication and authorized_keys. + +The operation consists of three phases. Each phase must finish +on all nodes before the next one can commence. The first phase +consists of logging unique messages through syslog on all nodes. +This is the shortest of all phases. + +The second phase is the most involved. During this phase all +local information is collected, which includes: + +- logs (both current and archived if the start time is far in the past) +- various configuration files (corosync, heartbeat, logd) +- the CIB (both as xml and as represented by the crm shell) +- pengine inputs (if this node was the DC at any point in + time over the given period) +- system information and status +- package information and status +- dlm lock information +- backtraces (if there were core dumps) + +The third phase is collecting information from all nodes and +analyzing it. The analyzis consists of the following tasks: + +- identify files equal on all nodes which may then be moved to + the top directory +- save log messages matching user defined patterns + (defaults to ERRORs and CRITical conditions) +- report if there were coredumps and by whom +- report crm_verify(8) results +- save log messages matching major events to events.txt +- in case logging is configured without loghost, node logs and + events files are combined using a perl utility + + +BUGS +---- +Finding logs may at times be extremely difficult, depending on +how weird the syslog configuration. It would be nice to ask +syslog-ng developers to provide a way to find out the log +destination based on facility and priority. + +If you think you found a bug, please rerun with the -v option and +attach the output to bugzilla. + +hb_report can function in a satisfactory way only if ssh works to +all nodes using authorized_keys (without password). + +There are way too many options. + + +AUTHOR +------ +Written by Dejan Muhamedagic, <dejan@suse.de> + + +RESOURCES +--------- +Pacemaker: <http://clusterlabs.org/> + +Heartbeat and other Linux HA resources: <http://linux-ha.org/wiki> + +OpenAIS: <http://www.openais.org/> + +Corosync: <http://www.corosync.org/> + + +SEE ALSO +-------- +Date::Parse(3) + + +COPYING +------- +Copyright \(C) 2007-2009 Dejan Muhamedagic. Free use of this +software is granted under the terms of the GNU General Public License (GPL). + diff --git a/doc/meatclient.xml.in b/doc/meatclient.xml.in new file mode 100644 index 0000000..778a57c --- /dev/null +++ b/doc/meatclient.xml.in @@ -0,0 +1,77 @@ +<?xml version="1.0"?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> +<refentry id="re-meatclient"> + <refentryinfo> + <date>December 4, 2009</date> + <productname>Cluster Glue</productname> + <productnumber>@VERSION@</productnumber> + <authorgroup> + <author> + <firstname>Gregor</firstname> + <surname>Binder</surname> + <contrib>meatclient</contrib> + <email>gbinder@sysfive.com</email> + </author> + <author> + <firstname>Michael</firstname> + <surname>Mörz</surname> + <contrib>man page</contrib> + <email>mimem@debian.org</email> + </author> + <author> + <firstname>Simon</firstname> + <surname>Horman</surname> + <contrib>man page</contrib> + <email>horms@vergenet.net</email> + </author> + <author> + <firstname>Florian</firstname> + <surname>Haas</surname> + <contrib>man page</contrib> + <email>florian.haas@linbit.com</email> + </author> + </authorgroup> + </refentryinfo> + <refmeta> + <refentrytitle>meatclient</refentrytitle> + <manvolnum>8</manvolnum> + <refmiscinfo class="manual">System administration utilities</refmiscinfo> + </refmeta> + <refnamediv> + <refname>meatclient</refname> + <refpurpose>Manually confirm that a node has been removed from the + cluster</refpurpose> + </refnamediv> + <refsynopsisdiv> + <para><command>meatclient</command> <option>-c</option> <replaceable>nodename</replaceable></para> + </refsynopsisdiv> + <refsection id="rs-meatclient-description"> + <title>Description</title> + <para><command>meatclient</command> confirms that a node has been + manually removed from the cluster. It instructs the cluster + manager, via the meatware STONITH plugin, that it is safe to + continue cluster operations.</para> + </refsection> + <refsection id="rs-meatclient-options"> + <title>Options</title> + <para>The following options are supported:</para> + <variablelist> + <varlistentry> + <term> + <option>-c</option> <replaceable>nodename</replaceable> + </term> + <listitem> + <para><replaceable>nodename</replaceable> is the name of the + cluster node that has been fenced.</para> + </listitem> + </varlistentry> + </variablelist> + </refsection> + <refsection id="rs-meatclient-seealso"> + <title>See also</title> + <para> + <citerefentry><refentrytitle>heartbeat</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>stonith</refentrytitle><manvolnum>8</manvolnum></citerefentry> + </para> + </refsection> +</refentry> diff --git a/doc/stonith.xml.in b/doc/stonith.xml.in new file mode 100644 index 0000000..575c339 --- /dev/null +++ b/doc/stonith.xml.in @@ -0,0 +1,315 @@ +<?xml version="1.0"?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> +<refentry id="re-stonith"> + <refentryinfo> + <date>December 7, 2009</date> + <productname>@PACKAGE_NAME@</productname> + <productnumber>@VERSION@</productnumber> + <authorgroup> + <author> + <firstname>Alan</firstname> + <surname>Robertson</surname> + <contrib>stonith</contrib> + <email>alanr@unix.sh</email> + </author> + <author> + <firstname>Simon</firstname> + <surname>Horman</surname> + <contrib>man page</contrib> + <email>horms@vergenet.net</email> + </author> + <author> + <firstname>Florian</firstname> + <surname>Haas</surname> + <contrib>man page</contrib> + <email>florian.haas@linbit.com</email> + </author> + </authorgroup> + </refentryinfo> + <refmeta> + <refentrytitle>stonith</refentrytitle> + <manvolnum>8</manvolnum> + <refmiscinfo class="manual">System administration utilities</refmiscinfo> + </refmeta> + <refnamediv> + <refname>stonith</refname> + <refpurpose>extensible interface for remotely powering down a node + in the cluster</refpurpose> + </refnamediv> + <refsynopsisdiv> + <cmdsynopsis> + <command>stonith</command> + <arg choice="plain"><option>-h</option></arg> + </cmdsynopsis> + <cmdsynopsis> + <command>stonith</command> + <arg choice="opt"><option>-s</option></arg> + <arg choice="opt"><option>-h</option></arg> + <arg choice="plain"><option>-L</option></arg> + </cmdsynopsis> + <cmdsynopsis> + <command>stonith</command> + <arg choice="opt"><option>-s</option></arg> + <arg choice="opt"><option>-h</option></arg> + <arg choice="plain"><option>-t</option> <replaceable>stonith-device-type</replaceable></arg> + <arg choice="plain"><option>-n</option></arg> + </cmdsynopsis> + <cmdsynopsis> + <command>stonith</command> + <arg choice="opt"><option>-s</option></arg> + <arg choice="opt"><option>-h</option></arg> + <arg choice="plain"><option>-t</option> <replaceable>stonith-device-type</replaceable></arg> + <group choice="req" rep="norepeat"> + <group choice="plain" rep="repeat"> + <arg choice="plain"><replaceable>name</replaceable>=<replaceable>value</replaceable></arg> + </group> + <arg choice="plain"><option>-p</option> <replaceable>stonith-device-parameters</replaceable></arg> + <arg choice="plain"><option>-F</option> <replaceable>stonith-device-parameters-file</replaceable></arg> + </group> + <arg choice="opt"><option>-c</option> <replaceable>count</replaceable></arg> + <arg choice="opt"><option>-l</option></arg> + <arg choice="opt"><option>-S</option></arg> + </cmdsynopsis> + <cmdsynopsis> + <command>stonith</command> + <arg choice="opt"><option>-s</option></arg> + <arg choice="opt"><option>-h</option></arg> + <arg choice="plain"><option>-t</option> <replaceable>stonith-device-type</replaceable></arg> + <group choice="req" rep="norepeat"> + <group choice="plain" rep="repeat"> + <arg choice="plain"><replaceable>name</replaceable>=<replaceable>value</replaceable></arg> + </group> + <arg choice="plain"><option>-p</option> <replaceable>stonith-device-parameters</replaceable></arg> + <arg choice="plain"><option>-F</option> <replaceable>stonith-device-parameters-file</replaceable></arg> + </group> + <arg choice="opt"><option>-c</option> <replaceable>count</replaceable></arg> + <arg choice="opt"><option>-T</option> + <group choice="req"> + <arg choice="plain">reset</arg> + <arg choice="plain">on</arg> + <arg choice="plain">off</arg> + </group> + </arg> + <arg><replaceable>nodename</replaceable></arg> + </cmdsynopsis> + </refsynopsisdiv> + <refsection id="rs-stonith-description"> + <title>Description</title> + <para>The STONITH module provides an extensible interface for + remotely powering down a node in the cluster (STONITH = Shoot The + Other Node In The Head). The idea is quite simple: when the + software running on one machine wants to make sure another machine + in the cluster is not using a resource, pull the plug on the other + machine. It's simple and reliable, albeit admittedly + brutal.</para> + </refsection> + <refsection id="rs-stonith-options"> + <title>Options</title> + <para>The following options are supported:</para> + <variablelist> + <varlistentry> + <term> + <option>-c</option> <replaceable>count</replaceable> + </term> + <listitem> + <para>Perform any actions identified by the + <option>-l</option>, <option>-S</option> and + <option>-T</option> options <replaceable>count</replaceable> + times.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-F</option> <replaceable>stonith-device-parameters-file</replaceable> + </term> + <listitem> + <para>Path of file specifying parameters for a stonith + device. To determine the syntax of the parameters file for a + given device type run:</para> + <screen><computeroutput># </computeroutput><userinput>stonith -t stonith-device-type -n</userinput></screen> + <para>All of the listed parameters need to appear in order + on a single line in the parameters file and be delimited by + whitespace.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-h</option> + </term> + <listitem> + <para>Display detailed information about a stonith device + including description, configuration information, parameters + and any other related information. When specified without a + stonith-device-type, detailed information on all stonith + devices is displayed.</para> + <para>If you don't yet own a stonith device and want to know + more about the ones we support, this information is likely + to be helpful.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-L</option> + </term> + <listitem> + <para>List the valid stonith device types, suitable for + passing as an argument to the <option>-t</option> + option.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-l</option> + </term> + <listitem> + <para>List the hosts controlled by the stonith device.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-n</option> + </term> + <listitem> + <para>Output the parameter names of the stonith device.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <replaceable>name</replaceable>=<replaceable>value</replaceable> + </term> + <listitem> + <para>Parameter, in the form of a name/value pair, to pass + directly to the stonith device. To determine the syntax of + the parameters for a given device type run:</para> + <screen><computeroutput># </computeroutput><userinput>stonith -t stonith-device-type -n</userinput></screen> + <para>All of the listed parameter names need to be passed + with their corresponding values.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-p</option> <replaceable>stonith-device-parameters</replaceable> + </term> + <listitem> + <para>Parameters to pass directly to the stonith device. To + determine the syntax of the parameters for a given device + type run:</para> + <screen><computeroutput># </computeroutput><userinput>stonith -t stonith-device-type -n</userinput></screen> + <para>All of the listed parameter names need to appear in + order and be delimited by whitespace.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-S</option> + </term> + <listitem> + <para>Show the status of the stonith device.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-s</option> + </term> + <listitem> + <para>Silent operation. Suppress logging of error messages to standard error.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-T</option> <replaceable>action</replaceable> + </term> + <listitem> + <para>The stonith action to perform on the node identified + by nodename. Chosen from <token>reset</token>, + <token>on</token>, and <token>off</token>.</para> + <note> + <para>If a nodename is specified without the + <option>-T</option> option, the stonith action defaults to + <token>reset</token>.</para> + </note> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-t</option> <replaceable>stonith-device-type</replaceable> + </term> + <listitem> + <para>The type of the stonith device to be used to effect + stonith. A list of supported devices for an installation may + be obtained using the <option>-L</option> option.</para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-v</option> + </term> + <listitem> + <para>Ignored.</para> + </listitem> + </varlistentry> + </variablelist> + </refsection> + <refsection id="rs-stonith-examples"> + <title>Examples</title> + <para>To determine which stonith devices are available on your installation, use the <option>-L</option> option:</para> + <screen><computeroutput># </computeroutput><userinput>stonith -L</userinput></screen> + <para>All of the supported devices will be displayed one per line. + Choose one from this list that is best for your environment - + let's use <code>wti_nps</code> for the rest of this example. To get detailed + information about this device, use the <option>-h</option> option:</para> + <screen><computeroutput># </computeroutput><userinput>stonith -t wti_nps -h</userinput></screen> + <para>Included in the output is the list of valid parameter names + for <code>wti_nps</code>. To get <emphasis>just</emphasis> the + list of valid parameter names, use the <option>-n</option> option + instead:</para> + <screen><computeroutput># </computeroutput><userinput>stonith -t wti_nps -n</userinput></screen> + <para>All of the required parameter names will be displayed one + per line. For <code>wti_nps</code> the output is:</para> + <screen><computeroutput>ipaddr</computeroutput> +<computeroutput>password</computeroutput></screen> + <para>There are three ways to pass these parameters to the device. + The first (and preferred) way is by passing name/value pairs on + the <command>stonith</command> command line:</para> + <screen><computeroutput># </computeroutput><userinput>stonith -t wti_nps ipaddr=my-dev-ip password=my-dev-pw ...</userinput></screen> + <para>The second way, which is maintained only for backward + compatibility with legacy clusters, is passing the values + <emphasis>in order</emphasis> on the <command>stonith</command> + command line with the <option>-p</option> option:</para> + <screen><computeroutput># </computeroutput><userinput>stonith -t wti_nps -p "my-dev-ip my-dev-pw" ...</userinput></screen> + <para>The third way, which is also maintained only for backward + compatibility with legacy clusters, is placing the values <emphasis>in order</emphasis> + on a single line in a config file:</para> + <programlisting>my-dev-ip my-dev-pw</programlisting> + <para>... and passing the name of the file on the stonith command + line with the <option>-F</option> option:</para> + <screen><computeroutput># </computeroutput><userinput>stonith -t wti_nps -F ~/my-wtinps-config ...</userinput></screen> + <para>To make sure you have the configuration set up correctly and + that the device is available for stonith operations, use the + <option>-S</option> option:</para> + <screen><computeroutput># </computeroutput><userinput>stonith -t wti_nps ipaddr=my-dev-ip password=my-dev-pw -S</userinput></screen> + <para>If all is well at this point, you should see something similar to:</para> + <screen><computeroutput>stonith: wti_nps device OK.</computeroutput></screen> + <para>If you don't, some debugging may be necessary to determine + if the config info is correct, the device is powered on, etc. The + <option>-d</option> option can come in handy here - you can add it + to any <command>stonith</command> command to cause it to generate + debug output.</para> + <para>To get the list of hosts controlled by the device, use the + <option>-l</option> option:</para> + <screen><computeroutput># </computeroutput><userinput>stonith -t wti_nps ipaddr=my-dev-ip password=my-dev-pw -l</userinput></screen> + <para>All of the hosts controlled by the device will be displayed one per line. For <code>wti_nps</code> the output could be:</para> + <screen><computeroutput>node1</computeroutput> + <computeroutput>node2</computeroutput> + <computeroutput>node3</computeroutput></screen> + <para>To power off one of these hosts, use the <option>-T</option> option: + <screen><computeroutput># </computeroutput><userinput>stonith -t wti_nps ipaddr=my-dev-ip password=my-dev-pw -T off <replaceable>node</replaceable></userinput></screen></para> + </refsection> + <refsection id="rs-stonith-seealso"> + <title>See also</title> + <para> + <citerefentry><refentrytitle>heartbeat</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>meatclient</refentrytitle><manvolnum>8</manvolnum></citerefentry> + </para> + </refsection> +</refentry> diff --git a/doc/stonith/Makefile.am b/doc/stonith/Makefile.am new file mode 100644 index 0000000..4c9b76f --- /dev/null +++ b/doc/stonith/Makefile.am @@ -0,0 +1,37 @@ +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +stdocdir = $(docdir)/stonith + +stdoc_DATA = README.bladehpi \ + README.cyclades \ + README.drac3 \ + README.dracmc \ + README.external \ + README.ibmrsa \ + README.ibmrsa-telnet \ + README.meatware \ + README.rackpdu \ + README.rcd_serial \ + README.riloe \ + README.vacm \ + README.wti_mpc \ + README_kdumpcheck.txt \ + README.vcenter + +if IPMILAN_BUILD +stdoc_DATA += README.ipmilan +endif diff --git a/doc/stonith/README.bladehpi b/doc/stonith/README.bladehpi new file mode 100644 index 0000000..3119ef7 --- /dev/null +++ b/doc/stonith/README.bladehpi @@ -0,0 +1,101 @@ + +STONITH module for IBM BladeCenter via OpenHPI +---------------------------------------------- + +Requirements: + Linux-HA bladehpi STONITH plugin requires OpenHPI 2.6+ + OpenHPI requires Net-SNMP 5.0+ + OpenHPI requires BladeCenter Management Module 1.08+ + +This STONITH module talks to IBM BladeCenters via SNMP through use of +the OpenHPI BladeCenter plugin (snmp_bc). For more information about +installing OpenHPI, setting up the BladeCenter SNMP agent, etc. please +visit http://www.openhpi.org/. Once OpenHPI is installed properly, +the STONITH plugin will automatically be built the next time Linux-HA +is built. + +Use the OpenHPI configuration file (i.e. /etc/openhpi/openhpi.conf) +to configure the BladeCenters of interest to STONITH. For example, +the following excerpt: + + plugin libsnmp_bc + + handler libsnmp_bc { + entity_root = "{SYSTEM_CHASSIS,1}" # Required + host = "9.254.253.252" # Required + community = "community" # Version 1 Required. + version = "3" # Required. SNMP protocol version (1|3) + security_name = "userid" # Version 3 Required. + passphrase = "userpass" # Version 3. Required if security_level is authNoPriv or authPriv. + auth_type = "MD5" # Version 3. Passphrase encoding (MD5|SHA) + security_level = "authNoPriv" # Version 3. (noAuthNoPriv|authNoPriv|authPriv) + } + +defines how to access the BladeCenter at 9.254.253.252 using SNMPV3 +with an ID/password of userid/userpass. The entity_root must be +passed to the STONITH bladehpi plugin as its single required parameter. +For example, to query the list of blades present in the BladeCenter +configured above, run: + + stonith -t bladehpi -p "{SYSTEM_CHASSIS,1}" -l + +which is the same as: + + stonith -t bladehpi "entity_root={SYSTEM_CHASSIS,1}" -l + +Use the BladeCenter Management Module web interface to set the Blade +Information to match "uname -n" for each blade in the cluster. For +example, with the BladeCeter configured above use a brower to access +http://9.254.253.252, login with userid/userpass, and then go to +Blade Tasks -> Configuration -> Blade Information, enter the proper +names, and select Save. Be aware that heartbeat must be restarted +before these changes take effect or, if using the OpenHPI daemon, +the daemon must be restarted. + +More than one BladeCenter can be placed in the OpenHPI configuration +file by using different numbers with the entity_root. For example, + + plugin libsnmp_bc + + handler libsnmp_bc { + entity_root = "{SYSTEM_CHASSIS,1}" # Required + host = "9.254.253.252" # Required + : + } + handler libsnmp_bc { + entity_root = "{SYSTEM_CHASSIS,2}" # Required + host = "9.254.253.251" # Required + : + } + +There is an optional parameter, soft_reset, that is true|1 if bladehpi +should use soft reset (power cycle) to reset nodes or false|0 if it +should use hard reset (power off, wait, power on); the default is +false. As an example, to override the default value the above stonith +command would become: + + stonith -t bladehpi -p "{SYSTEM_CHASSIS,1} true" -l + +which is the same as: + + stonith -t bladehpi "entity_root={SYSTEM_CHASSIS,1} soft_reset=true" -l + +The difference between the two is that a soft reset is much quicker +but may return before the node has been reset because bladehpi relies +on BladeCenter firmware to cycle the node's power, while a hard reset +is slower but guaranteed not to return until the node is dead because +bladehpi powers off the node, waits until it is off, then powers it +on again. + +NOTE: Set the OPENHPI_CONF environment variable to contain the +fully-qualified path of the OpenHPI configuration file, for example: + + export OPENHPI_CONF=/etc/openhpi/openhpi.conf + +NOTE: If OpenHPI is not configured with --disable-daemon before being +built and installed, make sure that the OpenHPI daemon is running +before using the bladehpi plugin. + +NOTE: If debugging of the environment is needed, configure OpenHPI +with --enable-debuggable and rebuild/reinstall, export +OPENHPI_DEBUG=YES, and run stonith commands with the -d option. diff --git a/doc/stonith/README.cyclades b/doc/stonith/README.cyclades new file mode 100644 index 0000000..3ccf9db --- /dev/null +++ b/doc/stonith/README.cyclades @@ -0,0 +1,61 @@ +STONITH module for Cyclades AlterPath PM +---------------------------------------- + +This STONITH module talks to Cyclades AlterPath PM series of power managers +via TS, ACS or KVM equipment. + +Access to the frontend device (TS, ACS or KVM) is done via root user with +passwordless ssh. + +For that, it is necessary to create a public/private keypar with _empty_ +passphrase on _each_ machine which is part of the cluster. + +Small HOWTO follows: + +# ssh-keygen -t rsa +Generating public/private rsa key pair. +Enter file in which to save the key (/root/.ssh/id_rsa): +Created directory '/home/root/.ssh'. +Enter passphrase (empty for no passphrase): +Enter same passphrase again: +Your identification has been saved in /root/.ssh/id_rsa. +Your public key has been saved in /root/.ssh/id_rsa.pub. +The key fingerprint is: +dc:e0:71:55:fd:2a:b0:19:d6:3c:48:e5:45:db:b4:be root@hostname.network + +Next step is to append the public key (/root/.ssh/id_rsa.pub) +to the authorized_keys file on the TS/ACS/KVM box. The authorized +keys file location is set at the SSH daemon configuration file. +The default location is /etc/ssh/authorized_keys, so: + +[root@clusterhost]# scp /root/.ssh/id_rsa.pub root@alterpath:/tmp + +login to the TS/ACS/KVM box normally and append the public key. + +# ssh root@alterpath +Password: .... + +[root@CAS root]# cat /tmp/id_rsa.pub >> /etc/ssh/authorized_keys + +The following entries must be present on /etc/ssh/sshd_config for the +passwordless scheme to work properly: + +RSAAuthentication yes +PubkeyAuthentication yes +AuthorizedKeysFile /etc/ssh/authorized_keys + +Next step is to test if the configuration has been done successfully: + +[root@clusterhost root]# ssh root@alterpath +[root@CAS root]# + +If it logins automatically without asking for a password, then everything +has been done correctly! + +Note that such configuration procedure (including generation of the key pair) +has to be done for each machine in the cluster which intends to use the +AlterPath PM as a STONITH device. + +------ +Any questions please contact Cyclades support at <support@cyclades.com> +or <marcelo.tosatti@cyclades.com> diff --git a/doc/stonith/README.drac3 b/doc/stonith/README.drac3 new file mode 100644 index 0000000..e3c071b --- /dev/null +++ b/doc/stonith/README.drac3 @@ -0,0 +1,18 @@ +Stonith module for Dell DRACIII remote access card +-------------------------------------------------- + +This module uses the Dell DRACIII PCI card as a stonith device. +It sends the XML commands over HTTPS to the DRACIII web server. + +The card firmware must be version 2.0 at least, with support for SSL based +service and many bug fixes over 1.x versions. + +This module uses libcurl, libxml2 (gnome xml libs) and libssl. + +Any hints, bug reports, improvements, etc. will be apreciated. + +--- +Roberto Moreda <moreda@alfa21.com> http://www.alfa21.com +Alfa21 A Coruña (Spain) +UNIX, Linux & TCP/IP Services - High Availability Solutions + diff --git a/doc/stonith/README.dracmc b/doc/stonith/README.dracmc new file mode 100644 index 0000000..761f5ad --- /dev/null +++ b/doc/stonith/README.dracmc @@ -0,0 +1,87 @@ +dracmc-telnet - External stonith plugin for HAv2 (http://linux-ha.org/wiki) + Connects to Dell Drac/MC Blade Enclosure via a Cyclades + terminal server with telnet and switches power of named + blade servers appropriatelly. + +Description: + Dell offers the Drac/MC in their blade enclosures. The +Drac/MC can be accessed in different ways. One way to interface to it +is to connect the blade enclosure's Drac/MC serial port to a Cyclades +terminal server. You can then access the Drac/MC via telnet via the +Cyclades. Once logged in, you can use 'help' to show all available +commands. With the 'serveraction' command, you can control both +hard and soft resets as well as power to a particular blade. The +blades are named 'Server-X', where 'X' is a number which corresponds +to the blade number in the enclosure. This plugin allows using the +Drac/MC with stonith. It uses python's standards 'telnetlib' library +to log in and issue commands. The code is very similar to the original +ibmrsa-telnet plugin released by Andreas and was quite easy to +modify for this application. + One complication is that the Cyclades only allows one active +connection. If someone or something has a connection active, the +terminal server closes the new attempted connection. Since this +situation can be common, for example if trying to stonith two blades +or when the plugin is started by multiple cluster nodes, there is a +built in retry mechanism for login. On 10 retries, the code gives up +and throws. + When running this resource, it is best to not run it as a clone, +rather as a normal, single-instance resource. Make sure to create a +location constraint that excludes the node that is to be fenced. + +Required parameters: + nodename: The name of the server you want to touch on your network + cyclades_ip: The IP address of the cyclades terminal server + cyclades_port: The port for telnet to access on the cyclades (i.e. 7032) + servername: The DRAC/MC server name of the blade (i.e. Server-7) + username: The login user name for the DRAC/MC + password: The login password for the DRAC/MC + +Example configuration + +These are examples: you should adjust parameters, scores and +timeout values to fit your environment. + +crm shell: + + primitive fence_node1 stonith:external/dracmc-telnet \ + nodename=node1 cyclades_ip=10.0.0.1 cyclades_port=7001 \ + servername=Server-1 username=USERID password=PASSWORD \ + op monitor interval="200m" timeout="60s" + location loc-fence_node1 fence_node1 -inf: node1 + +XML: + +<?xml version="1.0" ?> +<cib> + <configuration> + <resources> + <primitive id="r_stonith-node01" class="stonith" type="external/dracmc-telnet" provider="heartbeat" resource_stickiness="0"> + <operations> + <op name="monitor" interval="200m" timeout="60s" prereq="nothing" id="r_stonith-node01-mon"/> + <op name="start" timeout="180" id="r_stonith-node01-start"/> + <op name="stop" timeout="180" id="r_stonith-node01-stop"/> + </operations> + <instance_attributes id="r_stonith-node01"> + <attributes> + <nvpair id="r_stonith-node01-nodename" name="nodename" value="node01"/> + <nvpair id="r_stonith-node01-cyclades_ip" name="cyclades_ip" value="192.168.0.1"/> + <nvpair id="r_stonith-node01-cyclades_port" name="cyclades_port" value="7032"/> + <nvpair id="r_stonith-node01-servername" name="servername" value="Server-7"/> + <nvpair id="r_stonith-node01-username" name="username" value="USERID"/> + <nvpair id="r_stonith-node01-password" name="password" value="PASSWORD"/> + <nvpair id="r_stonith-node01-type" name="type" value="dellblade"/> + </attributes> + </instance_attributes> + </primitive> + </resources> + <constraints> + <rsc_location id="r_stonith-node01_prefer_node02" rsc="r_stonith-node01"> + <rule id="r_stonith-node01_prefer_node02_rule" score="50"> + <expression attribute="#uname" id="r_stonith-node01_prefer_node02_expr" operation="eq" value="node02"/> + </rule> + </rsc_location> + </constraints> + + </configuration> +</cib> + diff --git a/doc/stonith/README.external b/doc/stonith/README.external new file mode 100644 index 0000000..a70ccde --- /dev/null +++ b/doc/stonith/README.external @@ -0,0 +1,90 @@ +EXTERNAL module for Linux-HA STONITH + + +This stonith plugin runs an external command written in your favorite +language to shutdown the given host. The external command should return +a zero exit status after a successful shutdown, or non-zero exit status +for a shutdown failure. Failures notifications will be sent to syslog. + +To create your own external plugin, write a script that supports the +following actions: + + reset + on (optional) + off (optional) + gethosts + status + getconfignames + getinfo-devid + getinfo-devname + getinfo-devdescr + getinfo-devurl + getinfo-xml + +and place it in the /usr/lib/stonith/plugins/external directory - the +script must be a regular executable file that is NOT writable by group +or others in order to be recognized as an external plugin. If the +action requires information to be returned, such as the list of hosts +or config names or any of the getinfo calls, simply write the +information to stdout. When complete, return zero to indicate the +action succeeded or non-zero to indicate the action failed. You can +use the ssh (sh) and riloe (pyhton) scripts already in that directory +as working examples. + +To make sure that your external plugin is recognized, run "stonith -L" +and look for its name in the output, something along the lines of: + + external/yourplugin + +To configure the plugin on an R1 (legacy) cluster, add a line similar +to the following to /etc/ha.d/ha.cf: + + stonith external/yourplugin /etc/ha.d/yourplugin.cfg + +where /etc/ha.d/yourplugin.cfg contains a single line with all of your +plugin's parameters: + + parm1-value parm2-value ... + +Another way to configure the plugin on a legacy cluster is to add a line +similiar to the following to /etc/ha.d/ha.cf instead: + + stonith_host * external/yourplugin parm1-value parm2-value ... + +where all of your plugin's parameters are placed at the end of the line. + +Please note that all parameters come in to the plugin in name/value +(environment variable) form, but in R1 configurations, they appear as a +list of parameters. They are ordered in the config file or on the +stonith_host line according to the ordering specified in the output of +the getconfignames operation. + +To configure the plugin on an R2 cluster, place lines similar to the +following into the <resources> section of your CIB, which is contained +in /var/lib/heartbeat/crm/cib.xml: + + <clone id="DoFencing"> + <instance_attributes> + <nvpair name="clone_max" value="2"/> + <nvpair name="clone_node_max" value="1"/> + </instance_attributes> + <primitive id="child_DoFencing" class="stonith" type="external/yourplugin" provider="heartbeat"> + <operations> + <op name="monitor" interval="5s" timeout="20s" requires="nothing"/> + <op name="start" timeout="20s" requires="nothing"/> + </operations> + <instance_attributes> + <nvpair name="parm1-name" value="parm1-value"/> + <nvpair name="parm2-name" value="parm2-value"/> + <!-- ... --> + </instance_attributes> + </primitive> + </clone> + +Whatever <nvpair> parameters specified in the <attributes> section of +the CIB are passed to the script as environment variables. For the +example above, the parameters are passed as parm1-name=parm1-value, +parm2-name=parm2-value and so on. + +Additional information can be found at +http://linux-ha.org/wiki/ExternalStonithPlugins. diff --git a/doc/stonith/README.ibmrsa b/doc/stonith/README.ibmrsa new file mode 100644 index 0000000..b34031b --- /dev/null +++ b/doc/stonith/README.ibmrsa @@ -0,0 +1,9 @@ +See + +ftp://ftp.software.ibm.com/systems/support/system_x_pdf/d3basmst.pdf +ftp://ftp.software.ibm.com/systems/support/system_x_pdf/88p9248.pdf +http://www.redbooks.ibm.com/abstracts/sg246495.html + +for documentation about IBM management processors and the +IBMmpcli utility. + diff --git a/doc/stonith/README.ibmrsa-telnet b/doc/stonith/README.ibmrsa-telnet new file mode 100644 index 0000000..109bdd9 --- /dev/null +++ b/doc/stonith/README.ibmrsa-telnet @@ -0,0 +1,55 @@ +ibmrsa-telnet - External stonith plugin for HAv2 (http://linux-ha.org/wiki) + Connects to IBM RSA Board via telnet and switches power + of server appropriately. + +Description: + + IBM offers Remote Supervisor Adapters II for several + servers. These RSA boards can be accessed in different ways. + One of that is via telnet. Once logged in you can use 'help' to + show all available commands. With 'power' you can reset, power on and + off the controlled server. This command is used in combination + with python's standard library 'telnetlib' to do it automatically. + +Code snippet for cib + + It's useful to give a location preference so that the stonith agent + is run on the/an other node. This is not necessary as one node can kill + itself via RSA Board. But: If this node becomes crazy my experiences + showed that the node is not able to shoot itself anymore properly. + + You have to adjust parameters, scores and timeout values to fit your + HA environment. + +<?xml version="1.0" ?> +<cib> + <configuration> + <resources> + <primitive id="r_stonith-node01" class="stonith" type="external/ibmrsa" provider="heartbeat" resource_stickiness="0"> + <operations> + <op name="monitor" interval="60" timeout="300" prereq="nothing" id="r_stonith-node01-mon"/> + <op name="start" timeout="180" id="r_stonith-node01-start"/> + <op name="stop" timeout="180" id="r_stonith-node01-stop"/> + </operations> + <instance_attributes id="r_stonith-node01"> + <attributes> + <nvpair id="r_stonith-node01-nodename" name="nodename" value="node01"/> + <nvpair id="r_stonith-node01-ipaddr" name="ipaddr" value="192.168.0.1"/> + <nvpair id="r_stonith-node01-userid" name="userid" value="userid"/> + <nvpair id="r_stonith-node01-passwd" name="passwd" value="password"/> + <nvpair id="r_stonith-node01-type" name="type" value="ibm"/> + </attributes> + </instance_attributes> + </primitive> + </resources> + <constraints> + <rsc_location id="r_stonith-node01_not_on_node01" rsc="r_stonith-node01"> + <rule id="r_stonith-node01_not_on_node01_rule" score="-INFINITY"> + <expression attribute="#uname" id="r_stonith-node01_not_on_node01_expr" operation="eq" value="node01"/> + </rule> + </rsc_location> + </constraints> + + </configuration> +</cib> + diff --git a/doc/stonith/README.ipmilan b/doc/stonith/README.ipmilan new file mode 100644 index 0000000..eef86cf --- /dev/null +++ b/doc/stonith/README.ipmilan @@ -0,0 +1,131 @@ + IPMILAN STONITH Module + Copyright (c) 2003 Intel Corp. + yixiong.zou@intel.com + +1. Intro + +IPMILAN STONITH module works by sending a node an IPMI message, in particular, +a 'chassis control' command. Currently the message is sent over the LAN. + +2. Hardware Requirement + +In order to use this module, the node has to be IPMI v1.5 compliant and +also supports IPMI over LAN. For example, the Intel Langley platform. + +Note: IPMI over LAN is an optional feature defined by IPMI v1.5 spec. +So even if a system is IPMI compliant/compatible, it might still not +support IPMI over LAN. If you are sure this is your case and you still +want to try this plugin, read section 6, IPMI v1.5 without IPMI over +LAN Support. + +3. Software Requirement + +This module needs OpenIPMI (http://openipmi.sf.net) to compile. +Version 1.4.x or 2.0.x is supported. + +4. Hardware Configuration + +How to configure the node so it accepts IPMI lan packets is beyond the +scope of this document. Consult your product manual for this. + +5. STONITH Configuration + +Each node in the cluster has to be configured individually. So normally there +would be at least two entries, unless you want to use a different STONITH +device for the other nodes in the cluster. ;) + +The configuration file syntax looks like this: + + <node1> <ip> <port> <auth> <priv> <user> <pass> <reset_method> + <node2> <ip> <port> <auth> <priv> <user> <pass> <reset_method> + ... + + node: the hostname. + + ip: the IP address of the node. If a node has more than one IP addresses, + this is the IP address of the interface which accepts IPMI messages. :) + + port: the port number to send the IPMI message to. The default is 623. + But it could be different or even configurable. + + auth: the authorization type of the IPMI session. Valid choices are + "none", "straight", "md2", and "md5". + + priv: the privilege level of the user. Valid choices are "operator" + or "admin". These are the privilege levels required to run the + 'chassis control' command. + + user: the username. use "" if it is empty. Cannot exceed 16 characters. + + pass: the password. use "" if it is empty. Cannot exceed 16 characters. + + reset_method: (optional) which IPMI chassis control to send + to reset the host. Possible values are power_cycle (default) + and hard_reset. + +Each line is white-space delimited and lines begins with '#' are ignored. + +6. IPMI v1.5 without IPMI over LAN Support + +If somehow your computer have a BMC but without LAN support, you might +still be able to use this module. + + 0) Make sure OpenIPMI is installed. OpenIPMI 1.0.3 should work. + + 1) Create a /etc/ipmi_lan.conf file. + + Here's a sample of how this file should look like + + addr 172.16.1.249 999 + PEF_alerting on + per_msg_auth off + priv_limit admin + allowed_auths_admin none md2 md5 + user 20 on "" "" admin 5 md2 md5 none + + If you do not understand what each line means, do a man on ipmilan. + + 2) run ipmilan as root. + + 3) Try send youself an IPMI packet over the network using ipmicmd see + if it works. + + ipmicmd -k "0f 00 06 01" lan 172.16.1.249 999 none admin "" "" + + The result should be something like: + + Connection 0 to the BMC is up0f 07 00 01 00 01 80 01 19 01 8f 77 00 00 4b 02 + + 4) Configure your system so everytime it boots up, the ipmi device + drivers are all loaded and ipmilan is run. This is all OS dependent + so I can't tell you what to do. + + The major draw back of this is that you will not be able to power it up + once it's power down, which for a real IPMI, you could. + + +7. Bugs + +Some IPMI device does not return 0x0, success, to the host who issued the reset +command. A timeout, 0xc3, could be returned instead. So I am counting that +also as a "successful reset". + +Note: This behavior is not fully IPMI v1.5 compliant. Based on the IPMI v1.5 +spec, the IPMI device should return the appropriate return code. And it is +even allowed to return the appropriate return code before performing the +action. + + +8. TODO + +1) Right now the timeout on each host is hard coded to be 10 seconds. It will + be nice to be able to set this value for individual host. + +2) A better way of detecting the success of the reset operation will be good. A + lot of times the host which carried out the reset does not return a success. + +3) The os_handler should be contributed back to the OpenIPMI project so that + we do not need to maintain it here. It does not make sense for every little + app like this to write its own os_handler. A generic one like in this + program should be sufficient. + diff --git a/doc/stonith/README.ippower9258 b/doc/stonith/README.ippower9258 new file mode 100644 index 0000000..6873efd --- /dev/null +++ b/doc/stonith/README.ippower9258 @@ -0,0 +1,68 @@ +IP Power 9258 as external stonith device. +========================================= + +Device Information +================== + + Warning: + ======== + + Aviosys provides different types and versions of IP Power 9258. + The device is currently available with four or eight power outlets. + This script was tested with firmware version: V1.55 2009/12/22 + + Especially "IP Power 9258 HP" uses a different http command interface. + ====================================================================== + + Resources for device documentation: + + Manufacturer URL: http://www.aviosys.com/ippower9258.htm + Manual URL: http://www.aviosys.com/manual.htm + Manual current version URL: + http://www.aviosys.com/images/9258_manual_20081104.pdf + +The documentation of the http command interface defines three +supported commands: + + GetPower - useful for testing status of the device and of each port + SetPower - used to control status of each power outlet + SetSchedule+Power - useless for stonith + +Common documented structure of these three commands is + + http://username:password@a.b.c.d/Set.cmd?CMD=command[+param=value...] + param is one or more of P60 to P67 and value is 0 or 1 + expected response for GetPower is of the format + <html>P60=1,P61=0,P62=1,P63=1,P64=0,P65=0,P66=0,P67=0</html> + SetPower does respond with the same format but restricts the list + to the modified ports. + P60 to P67 represent the status of the power outlet 1 to 8: 0 <=> + power off; 1 <=> power on. + +IP Power 9258 allows to assign port names (pw1Name to pw8Name) to each +port. These names can be used with the web interface (web form with +post-method). + +Script specific notes +===================== + +There is no documented http command to retrieve port names via the +http command interface. We try to get the hostlist via the web +interface. + +This script assumes a one to one mapping between names of hostlist and +port attributes of power outlet: + + 1st hostname in hostlist connected to 1st power outlet with port + status P60 and port name pw1Name. + ... + 8th hostname in hostlist connected to 8th power outlet with port + status P67 and port name pw8Name. + +If the hostlist parameter is not defined, then all assigned outlets +are inserted into the hostlist. Unused outlets should have empty +names. The node names obviously have to match the corresponding outlet +names. A reserved hostname is "*not-defined*". This is a +sript-internal placeholder for unused outlets. It does not appear in +the hostlist. + diff --git a/doc/stonith/README.meatware b/doc/stonith/README.meatware new file mode 100644 index 0000000..0b9b15d --- /dev/null +++ b/doc/stonith/README.meatware @@ -0,0 +1,26 @@ + +MEATWARE Module for Linux-HA STONITH + +ABOUT: + + This is a port of the "meatware" stomith method found in the GFS + distribution (see http://globalfilesystem.org/) to the Linux-HA + project. It notifies operators if a node needs to be reset and + waits for confirmation. + +USAGE: + + The module can be used like any other stonith module. It will + syslog a message at CRIT level if it needs an operator to power-cycle + a node on its behalf. + To confirm that a manual reset has been done, execute + + "meatclient -c <host>". + + If you abort the confirmation, the module will report that the reset + has failed. + +AUTHOR: + + Gregor Binder <gbinder@sysfive.com> + diff --git a/doc/stonith/README.rackpdu b/doc/stonith/README.rackpdu new file mode 100644 index 0000000..69a0f44 --- /dev/null +++ b/doc/stonith/README.rackpdu @@ -0,0 +1,21 @@ +APC Rack PDU + +The product information pages: + +http://www.apcc.com/products/family/index.cfm?id=70 + +The User's Guide: + +http://www.apcmedia.com/salestools/ASTE-6Z6KAV_R1_EN.pdf + +Apparently, an existing http or telnet session will make the +plugin fail. + +In case your nodes are equipped with multiple power supplies, the +PDU supports synchronous operation on multiple outlets on up to +four Switched Rack PDUs. See the User's Guide for more +information on how to setup outlet groups. + +NB: There has been a report by one user that in case a link +between two PDUs in the chain is broken, the PDU returns success +even though it failed. This needs to be verified. diff --git a/doc/stonith/README.rcd_serial b/doc/stonith/README.rcd_serial new file mode 100644 index 0000000..8b4abb4 --- /dev/null +++ b/doc/stonith/README.rcd_serial @@ -0,0 +1,186 @@ +rcd_serial - RC Delayed Serial +------------------------------ + +This stonith plugin uses one (or both) of the control lines of a serial +device (on the stonith host) to reboot another host (the stonith'ed host) +by closing its reset switch. A simple idea with one major problem - any +glitch which occurs on the serial line of the stonith host can potentially +cause a reset of the stonith'ed host. Such "glitches" can occur when the +stonith host is powered up or reset, during BIOS detection of the serial +ports, when the kernel loads up the serial port driver, etc. + +To fix this, you need to introduce a delay between the assertion of the +control signal on the serial port and the closing of the reset switch. +Then any glitches will be dissipated. When you really want to do the +business, you hold the control signal high for a "long time" rather than +just tickling it "glitch-fashion" by, e.g., using the rcd_serial plugin. + +As the name of the plugin suggests, one way to achieve the required delay is +to use a simple RC circuit and an npn transistor: + + + . . + RTS . . ----------- +5V + or ---------- . | + DTR . | . Rl reset + . | T1 . | |\logic + . Rt | ------RWL--------| -------> + . | b| /c . |/ + . |---Rb---|/ . + . | |\ . (m/b wiring typical + . C | \e . only - YMMV!) + . | | . + . | | . + SG ---------------------------RWG----------- 0V + . . + . . stonith'ed host + stonith host --->.<----- RC circuit ----->.<---- RWL = reset wire live + (serial port) . . RWG = reset wire ground + + +The characteristic delay (in seconds) is given by the product of Rt (in ohms) +and C (in Farads). Suitable values for the 4 components of the RC circuit +above are: + +Rt = 20k +C = 47uF +Rb = 360k +T1 = BC108 + +which gives a delay of 20 x 10e3 x 47 x 10e-6 = 0.94s. In practice the +actual delay achieved will depend on the pull-up load resistor Rl if Rl is +small: for Rl greater than 3k there is no significant dependence but lower +than this and the delay will increase - to about 1.4s at 1k and 1.9s at 0.5k. + +This circuit will work but it is a bit dangerous for the following reasons: + +1) If by mistake you open the serial port with minicom (or virtually any +other piece of software) you will cause a stonith reset ;-(. This is +because opening the port will by default cause the assertion of both DTR +and RTS, and a program like minicom will hold them high thenceforth (unless +and until a receive buffer overflow pulls RTS down). + +2) Some motherboards have the property that when held in the reset state, +all serial outputs are driven high. Thus, if you have the circuit above +attached to a serial port on such a motherboard, if you were to press the +(manual) reset switch and hold it in for more than a second or so, you will +cause a stonith reset of the attached system ;-(. + +This problem can be solved by adding a second npn transistor to act as a +shorting switch across the capacitor, driven by the other serial output: + + + . . + . . ----------- +5V + RTS ----------------- . | + . | . Rl reset + . | T1 . | |\logic + . Rt | ------RWL--------| -------> + . | b| /c . |/ + . T2 --|---Rb---|/ . + . | / | |\ . (m/b wiring typical + . b| /c | | \e . only - YMMV!) + DTR ------Rb--|/ C | . + . |\ | | . + . | \e | | . + . | | | . + SG ----------------------------------RWG------------- 0V + . . + . . stonith'ed host +stonith->.<--------- RC circuit ------->.<---- RWL = reset wire live + host . . RWG = reset wire ground + + +Now when RTS goes high it can only charge up C and cause a reset if DTR is +simultaneously kept low - if DTR goes high, T2 will switch on and discharge +the capacitor. Only a very unusual piece of software e.g. the rcd_serial +plugin, is going to achieve this (rather bizarre) combination of signals +(the "meaning" of which is something along the lines of "you are clear to +send but I'm not ready"!). T2 can be another BC108 and with Rb the same. + +RS232 signal levels are typically +-8V to +-12V so a 16V rating or greater +for the capacitor is sufficient BUT NOTE that a _polarised_ electrolytic should +not be used because the voltage switches around as the capacitor charges. +Nitai make a range of non-polar aluminium electrolytic capacitors. A 16V 47uF +radial capacitor measures 6mm diameter by 11mm long and along with the 3 +resistors (1/8W are fine) and the transistors, the whole circuit can be built +in the back of a DB9 serial "plug" so that all that emerges from the plug are +the 2 reset wires to go to the stonith'ed host's m/b reset pins. + +NOTE that with these circuits the reset wires are now POLARISED and hence +they are labelled RWG and RWL above. You cannot connect to the reset pins +either way round as you can when connecting a manual reset switch! You'll +soon enough know if you've got it the wrong way round because your machine +will be in permanent reset state ;-( + + +How to find out if your motherboard can be reset by these circuits +------------------------------------------------------------------ + +You can either build it first and then suck it and see, or, you need a +multimeter. The 0V rail of your system is available in either +of the 2 black wires in the middle of a spare power connector (one of +those horrible 4-way plugs which you push with difficulty into the back +of hard disks, etc. Curse IBM for ever specifying such a monstrosity!). +Likewise, the +5V rail is the red wire. (The yellow one is +12V, ignore +this.) + +First, with the system powered down and the meter set to read ohms: + + check that one of the reset pins is connected to 0V - this then + is the RWG pin; + + check that the other pin (RWL) has a high resistance wrt 0V + (probably > 2M) and has a small resistance wrt to +5V - between + 0.5k and 10k (or higher, doesn't really matter) will be fine. + +Second, with the system powered up and the meter set to read Volts: + + check that RWG is indeed that i.e. there should be 0V between it + and the 0V rail; + + check that RWL is around +5V wrt the 0V rail. + +If all this checks out, you are _probably_ OK. However, I've got one +system which checks out fine but actually won't work. The reason is that +when you short the reset pins, the actual current drain is much higher than +one would expect. Why, I don't know, but there is a final test you can do +to detect this kind of system. + +With the system powered up and the meter set to read milliamps: + + short the reset pins with the meter i.e. reset the system, and + note how much current is actually drained when the system is in + the reset state. + +Mostly you will find that the reset current is 1mA or less and this is +fine. On the system I mention above, it is 80mA! If the current is +greater than 20mA or so, you have probably had it with the simple circuits +above, although reducing the base bias resistor will get you a bit further. +Otherwise, you have to use an analog switch (like the 4066 - I had to use 4 +of these in parallel to reset my 80mA system) which is tedious because then +you need a +5V supply rail to the circuit so you can no longer just build it +in the back of a serial plug. Mail me if you want the details. + +With the circuit built and the rcd_serial plugin compiled, you can use: + +stonith -t rcd_serial -p "testhost /dev/ttyS0 rts XXX" testhost + +to test it. XXX is the duration in millisecs so just keep increasing this +until you get a reset - but wait a few secs between each attempt because +the capacitor takes time to discharge. Once you've found the minimum value +required to cause a reset, add say 200ms for safety and use this value +henceforth. + +Finally, of course, all the usual disclaimers apply. If you follow my +advice and destroy your system, sorry. But it's highly unlikely: serial +port outputs are internally protected against short circuits, and reset pins +are designed to be short circuited! The only circumstance in which I can +see a possibility of damaging something by incorrect wiring would be if the +2 systems concerned were not at the same earth potential. Provided both +systems are plugged into the same mains system (i.e. are not miles apart +and connected only by a very long reset wire ;-) this shouldn't arise. + +John Sutton +john@scl.co.uk +October 2002 diff --git a/doc/stonith/README.riloe b/doc/stonith/README.riloe new file mode 100644 index 0000000..4befe95 --- /dev/null +++ b/doc/stonith/README.riloe @@ -0,0 +1,36 @@ +Note for iLO 3 users + +This plugin doesn't support the iLO version 3. Please use ipmilan +or external/ipmi, iLO3 should support IPMI. + +Alain St-Denis wrote the riloe plugin. Here is short usage: + +primitive st0 stonith:external/riloe \ + hostlist=target-node \ + ilo_hostname=ilo-ip-address \ + ilo_user=admin ilo_password=secret ilo_protocol=2.0 + +The following additional parameters are available: + +ilo_can_reset: + Set to "1" if the ilo is capable of rebooting the host. + Defaults to '0'. + +ilo_protocol: + Defaults to 1.2. Set to the protocol version ilo supports. + +ilo_powerdown_method: + "button" or "power", the former simulates pressing the + button, the latter pulling the power plug. Defaults to + "power". The "button" method is easier on the host, but + requires ACPI. "power" should be more reliable, but not to + be used excessively for testing. + +ilo_proxyhost (string): Proxy hostname + proxy hostname if required to access ILO board + +ilo_proxyport (string, [3128]): Proxy port + proxy port if required to access ILO board + parameter will be ignored if proxy hostname is not set + + diff --git a/doc/stonith/README.vacm b/doc/stonith/README.vacm new file mode 100644 index 0000000..c9083ee --- /dev/null +++ b/doc/stonith/README.vacm @@ -0,0 +1,40 @@ +20 December 2000 + +I (rather poorly) integrated this contributed stonith driver into the +linux-ha-stonith release. There is a problem that needs to be +resolved by autoconf in that the driver will not compile unless +libvacmclient is installed on the system. + +For now, what I've done is included a line in stonith/Makefile that you can +uncomment if you want to compile the vacm stonith module. Look in the +Makefile in this directory for the following lines and do like it says + + +# If you want the VA Linux Cluster stonith module installed, +# uncomment the following line. You must have the vacmclient library +#VACM_STONITH = vacm_stonith.so + +Please direct questions about the operation of the stonith module to +Mike Tilstra (see the announcement to the linux-ha-dev mailing list +attached below.) + + +-Eric. +eric.ayers@compgen.com + +------------------------------------------------------------------------------ + +From: Mike Tilstra <conrad@sistina.com> +Sender: linux-ha-dev-admin@lists.tummy.com +To: linux-ha-dev@lists.tummy.com +Subject: [Linux-ha-dev] stonith module for VACM +Date: Tue, 19 Dec 2000 16:41:38 -0600 + +This was in need for some testing I'm doing, so I hacked this up quick. It +works for me, but I'm willing to bet there's atleast one bug in it. + +Figured others might like it. + +... +-- +Mike Tilstra conrad@sistina.com
\ No newline at end of file diff --git a/doc/stonith/README.vcenter b/doc/stonith/README.vcenter new file mode 100644 index 0000000..e6cc9a5 --- /dev/null +++ b/doc/stonith/README.vcenter @@ -0,0 +1,90 @@ +VMware vCenter/ESX STONITH Module +================================= + +1. Intro +-------- + +VMware vCenter/ESX STONITH Module is intended to provide STONITH support to +clusters in VMware Virtual Infrastructures. It is able to deal with virtual +machines running on physically different HostSystems (e.g. ESX/ESXi) by using +VMware vSphere Web Services SDK http://www.vmware.com/support/developer/vc-sdk/ +and connecting directly on each HostSystem or through a VMware vCenter: in this +last case the module locates the specified virtual machine in the Virtual +Infrastructure and performs actions required by cluster policies. + +2. Software requirements +------------------------ + +VMware vSphere CLI, which includes both CLI tools and Perl SDK +http://www.vmware.com/support/developer/vcli/ . The plugin has been tested with +version 4.1 http://www.vmware.com/download/download.do?downloadGroup=VCLI41 + + +3. vCenter/ESX authentication settings +-------------------------------------- + +Create the credentials file with credstore_admin.pl: + +/usr/lib/vmware-vcli/apps/general/credstore_admin.pl \ + -s 10.1.1.1 -u myuser -p mypass + +This should create $HOME/.vmware/credstore/vicredentials.xml +Copy it to a system folder, e.g. /etc + +cp -p $HOME/.vmware/credstore/vicredentials.xml /etc + + +4. Testing +---------- + +The plugin can be invoked directly to perform a very first connection test +(replace all the provided sample values): + +VI_SERVER=10.1.1.1 \ + VI_CREDSTORE=/etc/vicredentials.xml \ + HOSTLIST="hostname1=vmname1;hostname2=vmname2" \ + RESETPOWERON=0 \ + /usr/lib/stonith/plugins/external/vcenter gethosts + +If everything works correctly you should get: + +hostname1 +hostname2 + +When invoked in this way, the plugin connects to VI_SERVER, authenticates with +credentials stored in VI_CREDSTORE and tries to retrieve the list of virtual +machines (case insensitive) matching vmname1 and vmname2 (and any other listed). +When finished, it reports the list back by mapping virtual machine names to +hostnames as provided in HOSTLIST. If you see the full list of hostnames as a +result, then everything is going well. If otherwise you are having a partial or +empty list, you have to check parameters. + +You can even test "reset", "off" and "on" commands, to test (carefully!) the +full chain. E.g. + +VI_SERVER=10.1.1.1 \ + VI_CREDSTORE=/etc/vicredentials.xml \ + HOSTLIST="hostname1=vmname1;hostname2=vmname2" \ + RESETPOWERON=0 \ + /usr/lib/stonith/plugins/external/vcenter reset hostname2 + +In the above examples the referring infrastructure is a vCenter with several +ESXi nodes. Server IP and credentials are referred to vCenter. + +5. CRM configuration +-------------------- + +The following is a sample procedure to setup STONITH for an HA 2-node cluster +(replace all the provided sample values): + +crm configure primitive vfencing stonith::external/vcenter params \ + VI_SERVER="10.1.1.1" VI_CREDSTORE="/etc/vicredentials.xml" \ + HOSTLIST="hostname1=vmname1;hostname2=vmname2" RESETPOWERON="0" \ + op monitor interval="60s" + +crm configure clone Fencing vfencing + +crm configure property stonith-enabled="true" + + + diff --git a/doc/stonith/README.wti_mpc b/doc/stonith/README.wti_mpc new file mode 100644 index 0000000..050953d --- /dev/null +++ b/doc/stonith/README.wti_mpc @@ -0,0 +1,85 @@ +STONITH module for WTI MPC +-------------------------- + + +****Introduction. + +wti_mpc module uses snmp for controlling the MPC power distribution unit. It has +been tested with MPC-8H and MPC-18H and should be compatible with the whole +MPC series: + * MPC-20* + * MPC-16* + * MPC-18* + * MPC-8* + +****Unit configuration. + +wti_mpc STONITH modules uses SNMP v1, therefore it should be configured on the +device side. To do so, you should login to device, go to "Network +configuration" (/N), select "SNMP access" (25) and turn it on (enable/1). At the +SNMP access screen set "Version" (2) to "V1/V2 Only", set "Read only" (3) to +"No and set any "Community" (10) you want. You may also set other options as +you need. You may check your setup by issuing the following command: + + snmpwalk -v1 -c <community> <host> .1.3.6.1.2.1.1.1.0 + +and result should be something like this: + + SNMPv2-MIB::sysDescr.0 = STRING: Linux 85.195.135.236 2.4.18_mvl30-cllf #1991 Sun Mar 16 14:39:29 PST 2008 ppc + + +****Plugin configuration. + + Plugin declares the following configuration variables: + + *ipaddr - ip address or hostname of a MPC unit. + *port - ip port, should be 161, as MPC listens for incoming SNMP + packets on that port. It is made for future use actually. + *community - Community that you've specified on previous step. + *mib_version - Should be 3 for MPC devices with firmware version 1.62 + and later. 1 is for firmware version 1.44 and below. + 2 is unused right now, if you have device, with mib V2 + feel free to contact me and I'll add it. + +****MIB version issue + + WTI guys have several time changed OIDs, used by MPC devices. I own two +types of the devices: + *With firmware v 1.44 which is compatible with MIB version 1 + *With firmware v 1.62 which is compatible with MIB version 3 + +I suppose there are exist MIB v2, but i cannot find it and I'd not able +to test it. +Anyway, this plugin supports both V1 and V3 versions, and the correct version +is selected by the "mib-version" configuration parameter. Default value is "1", +so if you do not specify this parameter or assign a unsupported value to it, +it will fall back to mib version 1. + +****Outlets and groups + + MPC devices forces unique names of the outlets. This is a big problem +for STONITH plugin, cause it uses nodes unames as outlet names, so in case +you have a node with several power plugs, you should have set the node uname +as name of all the plugs. The MPC device simply doesn't allows this. + So, this plugin works with a GROUPS instead of a PLUGS. You may give +any unique names for your physical outlets on the MPC, but you MUST create +a plug group, name it using node's uname and include plugs, corresponding to +that particular node to this group. It should be done even for node with +single power supply. Some example: + + Let's pretend you have a node "atest", with two power cords, connected +to plugs A1 and B1. You have to create a group ("Plug grouping parameters" (/G) +-> Add Plug Group to directory (2)), name it "atest" ("Plug Group Name (1)) and +assign plugs A1 and B1 to that group ("Plug access" (2)). Now save your +configuration and try to retrieve host list: + + stonith -t wti_mpc ipaddr=<host> port=161 community=<community> mib-version=<version> -l + +result should be: + + atest + + +------------------ +(C) Denis Chapligin <chollya@satgate.net>, SatGate, 2009 + diff --git a/doc/stonith/README_kdumpcheck.txt b/doc/stonith/README_kdumpcheck.txt new file mode 100644 index 0000000..cc8787c --- /dev/null +++ b/doc/stonith/README_kdumpcheck.txt @@ -0,0 +1,151 @@ + Kdump check STONITH plugin "kdumpcheck" +1. Introduction + This plugin's purpose is to avoid STONITH for a node which is doing kdump. + It confirms whether the node is doing kdump or not when STONITH reset or + off operation is executed. + If the target node is doing kdump, this plugin considers that STONITH + succeeded. If not, it considers that STONITH failed. + + NOTE: This plugin has no ability to shutdown or startup a node. + So it has to be used with other STONITH plugin. + Then, when this plugin failed, the next plugin which can kill a node + is executed. + NOTE: This plugin works only on Linux. + +2. The way to check + When STONITH reset or off is executed, kdumpcheck connects to the target + node, and checks the size of /proc/vmcore. + It judges that the target node is _not_ doing kdump when the size of + /proc/vmcore on the node is zero, or the file doesn't exist. + Then kdumpcheck returns "STONITH failed" to stonithd, and the next plugin + is executed. + +3. Expanding mkdumprd + This plugin requires non-root user and ssh connection even on 2nd kernel. + So, you need to apply mkdumprd_for_kdumpcheck.patch to /sbin/mkdumprd. + This patch is tested with mkdumprd version 5.0.39. + The patch adds the following functions: + i) Start udevd with specified .rules files. + ii) Bring the specified network interface up. + iii) Start sshd. + iv) Add the specified user to the 2nd kernel. + The user is to check whether the node is doing kdump or not. + v) Execute sync command after dumping. + + NOTE: i) to iv) expandings are only for the case that filesystem partition + is specified as the location where the vmcore should be dumped. + +4. Parameters + kdumpcheck's parameters are the following. + hostlist : The list of hosts that the STONITH device controls. + delimiter is "," or " ". + indispensable setting. (default:none) + identity_file: a full-path of the private key file for the user + who checks doing kdump. + (default: $HOME/.ssh/id_rsa, $HOME/.ssh/id_dsa and + $HOME/.ssh/identity) + + NOTE: To execute this plugin first, set the highest priority to this plugin + in all STONITH resources. + +5. How to Use + To use this tool, do the following steps at all nodes in the cluster. + 1) Add an user to check doing kdump. + ex.) + # useradd kdumpchecker + # passwd kdumpchecker + 2) Allow passwordless login from the node which will do STONITH to all + target nodes for the user added at step 1). + ex.) + $ cd + $ mkdir .ssh + $ chmod 700 .ssh + $ cd .ssh + $ ssh-keygen (generate authentication keys with empty passphrase) + $ scp id_rsa.pub kdumpchecker@target_node:"~/.ssh/." + $ ssh kdumpchecker@target_node + $ cd ~/.ssh + $ cat id_rsa.pub >> authorized_keys + $ chmod 600 autorized_keys + $ rm id_rsa.pub + 3) Limit the command that the user can execute. + Describe the following commands in a line at the head of the user's + public key in target node's authorized_keys file. + [command="test -s /proc/vmcore"] + And describe some options (like no-pty, no-port-forwarding and so on) + according to your security policy. + ex.) + $ vi ~/.ssh/authorized_keys + command="test -s /proc/vmcore",no-port-forwarding,no-X11-forwarding, + no-agent-forwarding,no-pty ssh-rsa AAA..snip..== kdumpchecker@node1 + 4) Add settings in /etc/kdump.conf. + network_device : network interface name to check doing kdump. + indispensable setting. (default: none) + kdump_check_user : user name to check doing kdump. + specify non-root user. + (default: "kdumpchecker") + udev_rules : .rules files' names. + specify if you use udev for mapping devices. + specified files have to be in /etc/udev/rules.d/. + you can specify two or more files. + delimiter is "," or " ". (default: none) + ex.) + # vi /etc/kdump.conf + ext3 /dev/sda1 + network_device eth0 + kdump_check_user kdumpchecker + udev_rules 10-if.rules + 5) Apply the patch to /sbin/mkdumprd. + # cd /sbin + # patch -p 1 < mkdumprd_for_kdumpcheck.patch + 6) Restart kdump service. + # service kdump restart + 7) Describe cib.xml to set STONITH plugin. + (See "2. Parameters" and "6. Appendix") + +6. Appendix + A sample cib.xml. + <clone id="clnStonith"> + <instance_attributes id="instance_attributes.id238245a"> + <nvpair id="clone0_clone_max" name="clone_max" value="2"/> + <nvpair id="clone0_clone_node_max" name="clone_node_max" value="1"/> + </instance_attributes> + <group id="grpStonith"> + <instance_attributes id="instance_attributes.id2382455"/> + <primitive id="grpStonith-kdumpcheck" class="stonith" type="external/kd + umpcheck"> + <instance_attributes id="instance_attributes.id238240a"> + <nvpair id="nvpair.id238240b" name="hostlist" value="node1,node2"/> + <nvpair id="nvpair.id238240c" name="priority" value="1"/> + <nvpair id="nvpair.id2382408b" name="stonith-timeout" value="30s"/> + </instance_attributes> + <operations> + <op id="grpStonith-kdumpcheck-start" name="start" interval="0" tim + eout="300" on-fail="restart"/> + <op id="grpStonith-kdumpcheck-monitor" name="monitor" interval="10" + timeout="60" on-fail="restart"/> + <op id="grpStonith-kdumpcheck-stop" name="stop" interval="0" timeou + t="300" on-fail="block"/> + </operations> + <meta_attributes id="primitive-grpStonith-kdump-check.meta"/> + </primitive> + <primitive id="grpStonith-ssh" class="stonith" type="external/ssh"> + <instance_attributes id="instance_attributes.id2382402a"> + <nvpair id="nvpair.id2382408a" name="hostlist" value="node1,node2"/ + > + <nvpair id="nvpair.id238066b" name="priority" value="2"/> + <nvpair id="nvpair.id2382408c" name="stonith-timeout" value="60s"/> + </instance_attributes> + <operations> + <op id="grpStonith-ssh-start" name="start" interval="0" timeout="30 + 0" on-fail="restart"/> + <op id="grpStonith-ssh-monitor" name="monitor" interval="10" timeou + t="60" on-fail="restart"/> + <op id="grpStonith-ssh-stop" name="stop" interval="0" timeout="300" + on-fail="block"/> + </operations> + <meta_attributes id="primitive-grpStonith-ssh.meta"/> + </primitive> + </group> + </clone> + diff --git a/hb_report/Makefile.am b/hb_report/Makefile.am new file mode 100644 index 0000000..cd4ad65 --- /dev/null +++ b/hb_report/Makefile.am @@ -0,0 +1,26 @@ +# +# heartbeat: Linux-HA heartbeat code +# +# Copyright (C) 2001 Michael Moerz +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +hanoarchdir = $(datadir)/$(PACKAGE_NAME) + +hanoarch_DATA = utillib.sh ha_cf_support.sh openais_conf_support.sh +sbin_SCRIPTS = hb_report + diff --git a/hb_report/ha_cf_support.sh b/hb_report/ha_cf_support.sh new file mode 100644 index 0000000..7b35c98 --- /dev/null +++ b/hb_report/ha_cf_support.sh @@ -0,0 +1,83 @@ + # Copyright (C) 2007 Dejan Muhamedagic <dmuhamedagic@suse.de> + # + # 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.1 of the License, or (at your option) any later version. + # + # This software is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + # General Public License for more details. + # + # You should have received a copy of the GNU General Public + # License along with this library; if not, write to the Free Software + # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + # + +# +# Stack specific part (heartbeat) +# ha.cf/logd.cf parsing +# +getcfvar() { + [ -f "$CONF" ] || return + sed 's/#.*//' < $CONF | + grep -w "^$1" | + sed 's/^[^[:space:]]*[[:space:]]*//' +} +iscfvarset() { + test "`getcfvar $1`" +} +iscfvartrue() { + getcfvar "$1" | + egrep -qsi "^(true|y|yes|on|1)" +} +uselogd() { + iscfvartrue use_logd && + return 0 # if use_logd true + iscfvarset logfacility || + iscfvarset logfile || + iscfvarset debugfile || + return 0 # or none of the log options set + false +} +get_hb_logvars() { + # unless logfacility is set to none, heartbeat/ha_logd are + # going to log through syslog + HA_LOGFACILITY=`getcfvar logfacility` + [ "" = "$HA_LOGFACILITY" ] && HA_LOGFACILITY=$DEFAULT_HA_LOGFACILITY + [ none = "$HA_LOGFACILITY" ] && HA_LOGFACILITY="" + HA_LOGFILE=`getcfvar logfile` + HA_DEBUGFILE=`getcfvar debugfile` +} +getlogvars() { + HA_LOGFACILITY=${HA_LOGFACILITY:-$DEFAULT_HA_LOGFACILITY} + HA_LOGLEVEL="info" + cfdebug=`getcfvar debug` # prefer debug level if set + isnumber $cfdebug || cfdebug="" + [ "$cfdebug" ] && [ $cfdebug -gt 0 ] && + HA_LOGLEVEL="debug" + if uselogd; then + [ -f "$LOGD_CF" ] || { + debug "logd used but logd.cf not found: using defaults" + return # no configuration: use defaults + } + debug "reading log settings from $LOGD_CF" + get_logd_logvars + else + debug "reading log settings from $CONF" + get_hb_logvars + fi +} +cluster_info() { + echo "heartbeat version: `$HA_BIN/heartbeat -V`" +} +essential_files() { + cat<<EOF +d $HA_VARLIB 0755 root root +d $HA_VARLIB/ccm 0750 hacluster haclient +d $PCMK_LIB 0750 hacluster haclient +d $PE_STATE_DIR 0750 hacluster haclient +d $CIB_DIR 0750 hacluster haclient +EOF +} diff --git a/hb_report/hb_report.in b/hb_report/hb_report.in new file mode 100755 index 0000000..a3f9c66 --- /dev/null +++ b/hb_report/hb_report.in @@ -0,0 +1,1445 @@ +#!/bin/sh + + # Copyright (C) 2007 Dejan Muhamedagic <dmuhamedagic@suse.de> + # + # 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.1 of the License, or (at your option) any later version. + # + # This software is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + # General Public License for more details. + # + # You should have received a copy of the GNU General Public + # License along with this library; if not, write to the Free Software + # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + # + +. @OCF_ROOT_DIR@/lib/heartbeat/ocf-shellfuncs + +HA_NOARCHBIN=@datadir@/@PACKAGE_NAME@ + +. $HA_NOARCHBIN/utillib.sh + +unset LANG +export LC_ALL=POSIX + +PROG=`basename $0` + +# the default syslog facility is not (yet) exported by heartbeat +# to shell scripts +# +DEFAULT_HA_LOGFACILITY="daemon" +export DEFAULT_HA_LOGFACILITY +LOGD_CF=`findlogdcf @sysconfdir@ $HA_DIR` +export LOGD_CF + +SSH_PASSWORD_NODES="" +: ${SSH_OPTS="-o StrictHostKeyChecking=no -o EscapeChar=none"} +LOG_PATTERNS="CRIT: ERROR:" +# PEINPUTS_PATT="peng.*PEngine Input stored" + +# Important events +# +# Patterns format: +# title extended_regexp +# NB: don't use spaces in titles or regular expressions! +EVENT_PATTERNS=" +membership crmd.*ccm_event.*(NEW|LOST)|pcmk_peer_update.*(lost|memb): +quorum crmd.*crm_update_quorum:.Updating.quorum.status|crmd.*ais.disp.*quorum.(lost|ac?quir) +pause Process.pause.detected +resources lrmd.*rsc:(start|stop) +stonith crmd.*te_fence_node.*Exec|stonith-ng.*log_oper.*reboot|stonithd.*(requests|(Succeeded|Failed).to.STONITH|result=) +start_stop Configuration.validated..Starting.heartbeat|Corosync.Cluster.Engine|Executive.Service.RELEASE|crm_shutdown:.Requesting.shutdown|pcmk_shutdown:.Shutdown.complete +" + +init_tmpfiles + +# +# the instance where user runs hb_report is the master +# the others are slaves +# +if [ x"$1" = x__slave ]; then + SLAVE=1 +fi + +usage() { + cat<<EOF +usage: hb_report -f {time|"cts:"testnum} [-t time] + [-u user] [-X ssh-options] [-l file] [-n nodes] [-E files] + [-p patt] [-L patt] [-e prog] [-MSDZAQVsvhd] [dest] + + -f time: time to start from or a CTS test number + -t time: time to finish at (dflt: now) + -d : don't compress, but leave result in a directory + -n nodes: node names for this cluster; this option is additive + (use either -n "a b" or -n a -n b) + if you run $PROG on the loghost or use autojoin, + it is highly recommended to set this option + -u user: ssh user to access other nodes (dflt: empty, root, hacluster) + -X ssh-options: extra ssh(1) options + -l file: log file + -E file: extra logs to collect; this option is additive + (dflt: /var/log/messages) + -s : sanitize the PE and CIB files + -p patt: regular expression to match variables containing sensitive data; + this option is additive (dflt: "passw.*") + -L patt: regular expression to match in log files for analysis; + this option is additive (dflt: $LOG_PATTERNS) + -e prog: your favourite editor + -Q : don't run resource intensive operations (speed up) + -M : don't collect extra logs (/var/log/messages) + -D : don't invoke editor to write description + -Z : if destination directories exist, remove them instead of exiting + (this is default for CTS) + -A : this is an OpenAIS cluster + -S : single node operation; don't try to start report + collectors on other nodes + -v : increase verbosity + -V : print version + dest : report name (may include path where to store the report) +EOF + +[ "$1" != short ] && + cat<<EOF + + . the multifile output is stored in a tarball {dest}.tar.bz2 + . the time specification is as in either Date::Parse or + Date::Manip, whatever you have installed; Date::Parse is + preferred + . we try to figure where is the logfile; if we can't, please + clue us in ('-l') + . we collect only one logfile and /var/log/messages; if you + have more than one logfile, then use '-E' option to supply + as many as you want ('-M' empties the list) + + Examples + + hb_report -f 2pm report_1 + hb_report -f "2007/9/5 12:30" -t "2007/9/5 14:00" report_2 + hb_report -f 1:00 -t 3:00 -l /var/log/cluster/ha-debug report_3 + hb_report -f "09sep07 2:00" -u hbadmin report_4 + hb_report -f 18:00 -p "usern.*" -p "admin.*" report_5 + hb_report -f cts:133 ctstest_133 + + . WARNING . WARNING . WARNING . WARNING . WARNING . WARNING . + + We won't sanitize the CIB and the peinputs files, because + that would make them useless when trying to reproduce the + PE behaviour. You may still choose to obliterate sensitive + information if you use the -s and -p options, but in that + case the support may be lacking as well. The logs and the + crm_mon, ccm_tool, and crm_verify output are *not* sanitized. + + Additional system logs (/var/log/messages) are collected in + order to have a more complete report. If you don't want that + specify -M. + + IT IS YOUR RESPONSIBILITY TO PROTECT THE DATA FROM EXPOSURE! +EOF + exit +} +version() { + echo "@PACKAGE_NAME@: @PACKAGE_VERSION@ (@GLUE_BUILD_VERSION@)" + exit +} +# +# these are "global" variables +# +setvarsanddefaults() { + local now=`perl -e 'print time()'` + # used by all + DEST="" + FROM_TIME="" + CTS="" + TO_TIME=0 + HA_LOG="" + UNIQUE_MSG="Mark:HB_REPORT:$now" + SANITIZE="passw.*" + DO_SANITIZE="" + FORCE_REMOVE_DEST="" + COMPRESS="1" + # logs to collect in addition + # NB: they all have to be in syslog format + # + EXTRA_LOGS="/var/log/messages" + # used only by the master + NO_SSH="" + SSH_USER="" + TRY_SSH="root hacluster" + SLAVEPIDS="" + NO_DESCRIPTION="1" + SKIP_LVL=0 + VERBOSITY=0 +} +# +# caller may skip collecting information if the skip level +# exceeds the given value +# +skip_lvl() { + [ $SKIP_LVL -ge $1 ] +} +chkname() { + [ "$1" ] || usage short + echo $1 | grep -qs '[^a-zA-Z0-9@_+=:.-]' && + fatal "$1 contains illegal characters" +} +set_dest() { + # default DEST has already been set earlier (if the + # argument is missing) + if [ $# -eq 1 ]; then + DEST=`basename $1` + DESTDIR=`dirname $1` + fi + chkname $DEST + if [ -z "$COMPRESS" -a -e "$DESTDIR/$DEST" ]; then + if [ "$FORCE_REMOVE_DEST" -o "$CTS" ]; then + rm -rf $DESTDIR/$DEST + else + fatal "destination directory $DESTDIR/$DEST exists, please cleanup or use -Z" + fi + fi +} +chktime() { + [ "$1" ] || fatal "bad time specification: $2 (try 'YYYY-M-D H:M:S') " +} +no_dir() { + fatal "could not create the working directory $WORKDIR" +} +time2str() { + perl -e "use POSIX; print strftime('%x %X',localtime($1));" +} +# try to figure out where pacemaker ... etc +get_pe_state_dir() { + PE_STATE_DIR=`strings $CRM_DAEMON_DIR/pengine | + awk 'NF==1&&/var\/lib\/.*pengine$/'` + test -d "$PE_STATE_DIR" +} +get_cib_dir() { + CIB_DIR=`strings $CRM_DAEMON_DIR/crmd | + awk 'NF==1&&/var\/lib\/.*(cib|crm)$/'` + test -d "$CIB_DIR" +} +get_pe_state_dir2() { + # PE_STATE_DIR + local localstatedir lastf + localstatedir=`dirname $HA_VARLIB` + lastf=$(2>/dev/null ls -rt `2>/dev/null find /var/lib -name pengine -type d | + sed 's,$,/*.last,'` | tail -1) + if [ -f "$lastf" ]; then + PE_STATE_DIR=`dirname $lastf` + else + for p in pacemaker/pengine pengine heartbeat/pengine; do + if [ -d $localstatedir/$p ]; then + debug "setting PE_STATE_DIR to $localstatedir/$p" + PE_STATE_DIR=$localstatedir/$p + break + fi + done + fi +} +get_cib_dir2() { + # CIB + # HA_VARLIB is normally set to {localstatedir}/heartbeat + local localstatedir + localstatedir=`dirname $HA_VARLIB` + for p in pacemaker/cib heartbeat/crm; do + if [ -f $localstatedir/$p/cib.xml ]; then + debug "setting CIB_DIR to $localstatedir/$p" + CIB_DIR=$localstatedir/$p + break + fi + done +} +get_crm_daemon_dir() { + # CRM_DAEMON_DIR + local libdir p + libdir=`dirname $HA_BIN` + for p in pacemaker heartbeat; do + if [ -x $libdir/$p/crmd ]; then + debug "setting CRM_DAEMON_DIR to $libdir/$p" + CRM_DAEMON_DIR=$libdir/$p + return 0 + fi + done + return 1 +} +get_crm_daemon_dir2() { + # CRM_DAEMON_DIR again (brute force) + local p d d2 + for p in /usr /usr/local /opt; do + for d in libexec lib64 lib; do + for d2 in pacemaker heartbeat; do + if [ -x $p/$d/$d2/crmd ]; then + debug "setting CRM_DAEMON_DIR to $p/$d/$d2" + CRM_DAEMON_DIR=$p/$d/$d2 + break + fi + done + done + done +} +compatibility_pcmk() { + get_crm_daemon_dir || get_crm_daemon_dir2 + if [ ! -d "$CRM_DAEMON_DIR" ]; then + fatal "cannot find pacemaker daemon directory!" + fi + get_pe_state_dir || get_pe_state_dir2 + get_cib_dir || get_cib_dir2 + debug "setting PCMK_LIB to `dirname $CIB_DIR`" + PCMK_LIB=`dirname $CIB_DIR` + # PTEST + PTEST=`echo_ptest_tool` + export PE_STATE_DIR CIB_DIR CRM_DAEMON_DIR PCMK_LIB PTEST +} + +# +# find log files +# +logmark() { + logger -p $* + debug "run: logger -p $*" +} +# +# first try syslog files, if none found then use the +# logfile/debugfile settings +# +findlog() { + local logf="" + if [ "$HA_LOGFACILITY" ]; then + logf=`findmsg $UNIQUE_MSG | awk '{print $1}'` + fi + if [ -f "$logf" ]; then + echo $logf + else + echo ${HA_DEBUGFILE:-$HA_LOGFILE} + [ "${HA_DEBUGFILE:-$HA_LOGFILE}" ] && + debug "will try with ${HA_DEBUGFILE:-$HA_LOGFILE}" + fi +} + +# +# find log slices +# + +find_decompressor() { + if echo $1 | grep -qs 'xz$'; then + echo "xz -dc" + elif echo $1 | grep -qs 'bz2$'; then + echo "bzip2 -dc" + elif echo $1 | grep -qs 'gz$'; then + echo "gzip -dc" + else + echo "cat" + fi +} +# +# check if the log contains a piece of our segment +# +is_our_log() { + local logf=$1 + local from_time=$2 + local to_time=$3 + + local cat=`find_decompressor $logf` + local first_time="`$cat $logf | head -10 | find_first_ts`" + local last_time="`$cat $logf | tail -10 | tac | find_first_ts`" + if [ x = "x$first_time" -o x = "x$last_time" ]; then + return 0 # skip (empty log?) + fi + if [ $from_time -gt $last_time ]; then + # we shouldn't get here anyway if the logs are in order + return 2 # we're past good logs; exit + fi + if [ $from_time -ge $first_time ]; then + return 3 # this is the last good log + fi + # have to go further back + if [ $to_time -eq 0 -o $to_time -ge $first_time ]; then + return 1 # include this log + else + return 0 # don't include this log + fi +} +# +# go through archived logs (timewise backwards) and see if there +# are lines belonging to us +# (we rely on untouched log files, i.e. that modify time +# hasn't been changed) +# +arch_logs() { + local next_log + local logf=$1 + local from_time=$2 + local to_time=$3 + + # look for files such as: ha-log-20090308 or + # ha-log-20090308.gz (.bz2) or ha-log.0, etc + ls -t $logf $logf*[0-9z] 2>/dev/null | + while read next_log; do + is_our_log $next_log $from_time $to_time + case $? in + 0) ;; # noop, continue + 1) echo $next_log # include log and continue + debug "found log $next_log" + ;; + 2) break;; # don't go through older logs! + 3) echo $next_log # include log and continue + debug "found log $next_log" + break + ;; # don't go through older logs! + esac + done +} +# +# print part of the log +# +print_log() { + local cat=`find_decompressor $1` + $cat $1 +} +print_logseg() { + if test -x $HA_NOARCHBIN/print_logseg; then + $HA_NOARCHBIN/print_logseg "$1" "$2" "$3" + return + fi + + local logf=$1 + local from_time=$2 + local to_time=$3 + local tmp sourcef + + # uncompress to a temp file (if necessary) + local cat=`find_decompressor $logf` + if [ "$cat" != "cat" ]; then + tmp=`mktemp` || + fatal "disk full" + add_tmpfiles $tmp + $cat $logf > $tmp || + fatal "disk full" + sourcef=$tmp + else + sourcef=$logf + tmp="" + fi + + if [ "$from_time" = 0 ]; then + FROM_LINE=1 + else + FROM_LINE=`findln_by_time $sourcef $from_time` + fi + if [ -z "$FROM_LINE" ]; then + warning "couldn't find line for time $from_time; corrupt log file?" + return + fi + + TO_LINE="" + if [ "$to_time" != 0 ]; then + TO_LINE=`findln_by_time $sourcef $to_time` + if [ -z "$TO_LINE" ]; then + warning "couldn't find line for time $to_time; corrupt log file?" + return + fi + fi + dumplog $sourcef $FROM_LINE $TO_LINE + debug "including segment [$FROM_LINE-$TO_LINE] from $logf" +} +# +# print some log info (important for crm history) +# +loginfo() { + local logf=$1 + local fake=$2 + local nextpos=`python -c "f=open('$logf');f.seek(0,2);print f.tell()+1"` + if [ "$fake" ]; then + echo "synthetic:$logf $nextpos" + else + echo "$logf $nextpos" + fi +} +# +# find log/set of logs which are interesting for us +# +dumplogset() { + local logf=$1 + local from_time=$2 + local to_time=$3 + + local logf_set=`arch_logs $logf $from_time $to_time` + if [ x = "x$logf_set" ]; then + return + fi + + local num_logs=`echo "$logf_set" | wc -l` + local oldest=`echo $logf_set | awk '{print $NF}'` + local newest=`echo $logf_set | awk '{print $1}'` + local mid_logfiles=`echo $logf_set | awk '{for(i=NF-1; i>1; i--) print $i}'` + + # the first logfile: from $from_time to $to_time (or end) + # logfiles in the middle: all + # the last logfile: from beginning to $to_time (or end) + case $num_logs in + 1) print_logseg $newest $from_time $to_time;; + *) + print_logseg $oldest $from_time 0 + for f in $mid_logfiles; do + print_log $f + debug "including complete $f logfile" + done + print_logseg $newest 0 $to_time + ;; + esac +} + +# +# cts_findlogseg finds lines for the CTS test run (FROM_LINE and +# TO_LINE) and then extracts the timestamps to get FROM_TIME and +# TO_TIME +# +cts_findlogseg() { + local testnum=$1 + local logf=$2 + if [ "x$logf" = "x" ]; then + logf=`findmsg "Running test.*\[ *$testnum\]" | awk '{print $1}'` + fi + getstampproc=`find_getstampproc < $logf` + export getstampproc # used by linetime + + FROM_LINE=`grep -n "Running test.*\[ *$testnum\]" $logf | tail -1 | sed 's/:.*//'` + if [ -z "$FROM_LINE" ]; then + warning "couldn't find line for CTS test $testnum; corrupt log file?" + exit 1 + else + FROM_TIME=`linetime $logf $FROM_LINE` + fi + TO_LINE=`grep -n "Running test.*\[ *$(($testnum+1))\]" $logf | tail -1 | sed 's/:.*//'` + [ "$TO_LINE" -a $FROM_LINE -lt $TO_LINE ] || + TO_LINE=`wc -l < $logf` + TO_TIME=`linetime $logf $TO_LINE` + debug "including segment [$FROM_LINE-$TO_LINE] from $logf" + dumplog $logf $FROM_LINE $TO_LINE +} + +# +# this is how we pass environment to other hosts +# +dumpenv() { + cat<<EOF +DEST=$DEST +FROM_TIME=$FROM_TIME +TO_TIME=$TO_TIME +USER_NODES="$USER_NODES" +NODES="$NODES" +MASTER_NODE="$MASTER_NODE" +HA_LOG=$HA_LOG +MASTER_IS_HOSTLOG=$MASTER_IS_HOSTLOG +UNIQUE_MSG=$UNIQUE_MSG +SANITIZE="$SANITIZE" +DO_SANITIZE="$DO_SANITIZE" +SKIP_LVL="$SKIP_LVL" +EXTRA_LOGS="$EXTRA_LOGS" +USER_CLUSTER_TYPE="$USER_CLUSTER_TYPE" +CONF="$CONF" +B_CONF="$B_CONF" +PACKAGES="$PACKAGES" +CORES_DIRS="$CORES_DIRS" +VERBOSITY="$VERBOSITY" +EOF +} +is_collector() { + test "$SLAVE" +} +is_node() { + test "$THIS_IS_NODE" +} +is_master() { + ! is_collector && test "$WE" = "$MASTER_NODE" +} +start_slave_collector() { + local node=$1 + + dumpenv | + if [ "$node" = "$WE" ]; then + debug "running: $LOCAL_SUDO hb_report __slave" + $LOCAL_SUDO hb_report __slave + else + debug "running: ssh $SSH_OPTS $node \"$SUDO hb_report __slave" + ssh $SSH_OPTS $node \ + "$SUDO hb_report __slave" + fi | (cd $WORKDIR && tar xf -) +} + +# +# does ssh work? +# and how +# test the provided ssh user +# or try to find a ssh user which works without password +# if ssh works without password, we can run the collector in the +# background and save some time +# +testsshconn() { + ssh $SSH_OPTS -T -o Batchmode=yes $1 true 2>/dev/null +} +findsshuser() { + local n u rc + local ssh_s ssh_user="__undef" try_user_list + if [ -z "$SSH_USER" ]; then + try_user_list="__default $TRY_SSH" + else + try_user_list="$SSH_USER" + fi + for n in $NODES; do + rc=1 + [ "$n" = "$WE" ] && continue + for u in $try_user_list; do + if [ "$u" != '__default' ]; then + ssh_s=$u@$n + else + ssh_s=$n + fi + if testsshconn $ssh_s; then + debug "ssh $ssh_s OK" + ssh_user="$u" + try_user_list="$u" # we support just one user + rc=0 + break + else + debug "ssh $ssh_s failed" + fi + done + [ $rc = 1 ] && + SSH_PASSWORD_NODES="$SSH_PASSWORD_NODES $n" + done + if [ -n "$SSH_PASSWORD_NODES" ]; then + warning "passwordless ssh to node(s) $SSH_PASSWORD_NODES does not work" + fi + + if [ "$ssh_user" = "__undef" ]; then + return 1 + fi + if [ "$ssh_user" != "__default" ]; then + SSH_USER=$ssh_user + fi + return 0 +} +node_needs_pwd() { + local n + for n in $SSH_PASSWORD_NODES; do + [ "$n" = "$1" ] && return 0 + done + return 1 +} +say_ssh_user() { + if [ -n "$SSH_USER" ]; then + echo $SSH_USER + else + echo your user + fi +} + +# +# the usual stuff +# +getbacktraces() { + local f bf flist + flist=$( + for f in `find_files "$CORES_DIRS" $1 $2`; do + bf=`basename $f` + test `expr match $bf core` -gt 0 && + echo $f + done) + [ "$flist" ] && { + getbt $flist > $3 + debug "found backtraces: $flist" + } +} +pe2dot() { + local pef=`basename $1` + local dotf=`basename $pef .bz2`.dot + test -z "$PTEST" && return + ( + cd `dirname $1` + $PTEST -D $dotf -x $pef >/dev/null 2>&1 + ) +} +getpeinputs() { + local pe_dir flist + local f + pe_dir=$PE_STATE_DIR + debug "looking for PE files in $pe_dir" + flist=$( + find_files $pe_dir $1 $2 | grep -v '[.]last$' + ) + [ "$flist" ] && { + mkdir $3/`basename $pe_dir` + ( + cd $3/`basename $pe_dir` + for f in $flist; do + ln -s $f + done + ) + debug "found `echo $flist | wc -w` pengine input files in $pe_dir" + } + if [ `echo $flist | wc -w` -le 20 ]; then + for f in $flist; do + skip_lvl 1 || pe2dot $3/`basename $pe_dir`/`basename $f` + done + else + debug "too many PE inputs to create dot files" + fi +} +getratraces() { + local trace_dir flist + local f + trace_dir=$HA_VARLIB/trace_ra + test -d "$trace_dir" || return 0 + debug "looking for RA trace files in $trace_dir" + flist=$(find_files $trace_dir $1 $2 | sed "s,`dirname $trace_dir`/,,g") + [ "$flist" ] && { + tar -cf - -C `dirname $trace_dir` $flist | tar -xf - -C $3 + debug "found `echo $flist | wc -w` RA trace files in $trace_dir" + } +} +touch_DC_if_dc() { + local dc + dc=`crmadmin -D 2>/dev/null | awk '{print $NF}'` + if [ "$WE" = "$dc" ]; then + touch $1/DC + fi +} +corosync_blackbox() { + local from_time=$1 + local to_time=$2 + local outf=$3 + local inpf + inpf=`find_files /var/lib/corosync $from_time $to_time | grep -w fdata` + if [ -f "$inpf" ]; then + corosync-blackbox > $outf + touch -r $inpf $outf + fi +} +getconfigurations() { + local conf + local dest=$1 + for conf in $CONFIGURATIONS; do + if [ -f $conf ]; then + cp -p $conf $dest + elif [ -d $conf ]; then + ( + cd `dirname $conf` && + tar cf - `basename $conf` | (cd $dest && tar xf -) + ) + fi + done +} + + +# +# some basic system info and stats +# +sys_info() { + cluster_info + hb_report -V # our info + echo "resource-agents: `grep 'Build version:' @OCF_ROOT_DIR@/lib/heartbeat/ocf-shellfuncs`" + crm_info + pkg_versions $PACKAGES + skip_lvl 1 || verify_packages $PACKAGES + echo "Platform: `uname`" + echo "Kernel release: `uname -r`" + echo "Architecture: `uname -m`" + [ `uname` = Linux ] && + echo "Distribution: `distro`" +} +sys_stats() { + set -x + uname -n + uptime + ps axf + ps auxw + top -b -n 1 + ifconfig -a + ip addr list + netstat -i + arp -an + test -d /proc && { + cat /proc/cpuinfo + } + lsscsi + lspci + mount + # df can block, run in background, allow for 5 seconds (!) + local maxcnt=5 + df & + while kill -0 $! >/dev/null 2>&1; do + sleep 1 + if [ $maxcnt -le 0 ]; then + warning "df appears to be hanging, continuing without it" + break + fi + maxcnt=$((maxcnt-1)) + done + set +x +} +time_status() { + date + ntpdc -pn +} +dlm_dump() { + if which dlm_tool >/dev/null 2>&1 ; then + echo NOTICE - Lockspace overview: + dlm_tool ls + dlm_tool ls | grep name | + while read X N ; do + echo NOTICE - Lockspace $N: + dlm_tool lockdump $N + done + echo NOTICE - Lockspace history: + dlm_tool dump + fi +} + + +# +# replace sensitive info with '****' +# +sanitize() { + local f rc + for f in $1/$B_CONF; do + [ -f "$f" ] && sanitize_one $f + done + rc=0 + for f in $1/$CIB_F $1/pengine/*; do + if [ -f "$f" ]; then + if [ "$DO_SANITIZE" ]; then + sanitize_one $f + else + test_sensitive_one $f && rc=1 + fi + fi + done + [ $rc -ne 0 ] && { + warning "some PE or CIB files contain possibly sensitive data" + warning "you may not want to send this report to a public mailing list" + } +} + +# +# remove duplicates if files are same, make links instead +# +consolidate() { + for n in $NODES; do + if [ -f $1/$2 ]; then + rm $1/$n/$2 + else + mv $1/$n/$2 $1 + fi + ln -s ../$2 $1/$n + done +} + +# +# some basic analysis of the report +# +checkcrmvfy() { + for n in $NODES; do + if [ -s $1/$n/$CRM_VERIFY_F ]; then + echo "WARN: crm_verify reported warnings at $n:" + cat $1/$n/$CRM_VERIFY_F + fi + done +} +checkbacktraces() { + for n in $NODES; do + [ -s $1/$n/$BT_F ] && { + echo "WARN: coredumps found at $n:" + egrep 'Core was generated|Program terminated' \ + $1/$n/$BT_F | + sed 's/^/ /' + } + done +} +checkpermissions() { + for n in $NODES; do + if [ -s $1/$n/$PERMISSIONS_F ]; then + echo "WARN: problem with permissions/ownership at $n:" + cat $1/$n/$PERMISSIONS_F + fi + done +} +checklogs() { + local logs pattfile l n + logs=$(find $1 -name $HALOG_F; + for l in $EXTRA_LOGS; do find $1/* -name `basename $l`; done) + [ "$logs" ] || return + pattfile=`mktemp` || + fatal "cannot create temporary files" + add_tmpfiles $pattfile + for p in $LOG_PATTERNS; do + echo "$p" + done > $pattfile + echo "" + echo "Log patterns:" + for n in $NODES; do + cat $logs | grep -f $pattfile + done +} + +# +# check if files have same content in the cluster +# +cibdiff() { + local d1 d2 + d1=`dirname $1` + d2=`dirname $2` + if [ -f $d1/RUNNING -a -f $d2/RUNNING ] || + [ -f $d1/STOPPED -a -f $d2/STOPPED ]; then + if which crm_diff > /dev/null 2>&1; then + crm_diff -c -n $1 -o $2 + else + info "crm_diff(8) not found, cannot diff CIBs" + fi + else + echo "can't compare cibs from running and stopped systems" + fi +} +txtdiff() { + diff -bBu $1 $2 +} +diffcheck() { + [ -f "$1" ] || { + echo "$1 does not exist" + return 1 + } + [ -f "$2" ] || { + echo "$2 does not exist" + return 1 + } + case `basename $1` in + $CIB_F) + cibdiff $1 $2;; + $B_CONF) + txtdiff $1 $2;; # confdiff? + *) + txtdiff $1 $2;; + esac +} +analyze_one() { + local rc node0 n + rc=0 + node0="" + for n in $NODES; do + if [ "$node0" ]; then + diffcheck $1/$node0/$2 $1/$n/$2 + rc=$(($rc+$?)) + else + node0=$n + fi + done + return $rc +} +analyze() { + local f flist + flist="$HOSTCACHE $MEMBERSHIP_F $CIB_F $CRM_MON_F $B_CONF logd.cf $SYSINFO_F" + for f in $flist; do + printf "Diff $f... " + ls $1/*/$f >/dev/null 2>&1 || { + echo "no $1/*/$f :/" + continue + } + if analyze_one $1 $f; then + echo "OK" + [ "$f" != $CIB_F ] && + consolidate $1 $f + else + echo "" + fi + done + checkcrmvfy $1 + checkbacktraces $1 + checkpermissions $1 + checklogs $1 +} +events_all() { + local Epatt title p + Epatt=`echo "$EVENT_PATTERNS" | + while read title p; do [ -n "$p" ] && echo -n "|$p"; done | + sed 's/.//' + ` + grep -E "$Epatt" $1 +} +events() { + local destdir n + destdir=$1 + if [ -f $destdir/$HALOG_F ]; then + events_all $destdir/$HALOG_F > $destdir/events.txt + for n in $NODES; do + awk "\$4==\"$n\"" $destdir/events.txt > $destdir/$n/events.txt + done + else + for n in $NODES; do + [ -f $destdir/$n/$HALOG_F ] || + continue + events_all $destdir/$n/$HALOG_F > $destdir/$n/events.txt + done + fi +} + +# +# description template, editing, and other notes +# +mktemplate() { + cat<<EOF +Please edit this template and describe the issue/problem you +encountered. Then, post to + Linux-HA@lists.linux-ha.org +or file a bug at + http://developerbugs.linux-foundation.org/ + +See http://linux-ha.org/wiki/ReportingProblems for detailed +description on how to report problems. + +Thank you. + +Date: `date` +By: $PROG $userargs +Subject: [short problem description] +Severity: [choose one] enhancement minor normal major critical blocking +Component: [choose one] CRM LRM CCM RA fencing $CLUSTER_TYPE comm GUI tools other + +Detailed description: +--- +[...] +--- + +EOF + + if [ -f $WORKDIR/$SYSINFO_F ]; then + echo "Common system info found:" + cat $WORKDIR/$SYSINFO_F + else + for n in $NODES; do + if [ -f $WORKDIR/$n/$SYSINFO_F ]; then + echo "System info $n:" + sed 's/^/ /' $WORKDIR/$n/$SYSINFO_F + fi + done + fi +} +edittemplate() { + local ec + if ec=`pickfirst $EDITOR vim vi emacs nano`; then + $ec $1 + else + warning "could not find a text editor" + fi +} +pickcompress() { + if COMPRESS_PROG=`pickfirst bzip2 gzip xz`; then + if [ "$COMPRESS_PROG" = xz ]; then + COMPRESS_EXT=.xz + elif [ "$COMPRESS_PROG" = bzip2 ]; then + COMPRESS_EXT=.bz2 + else + COMPRESS_EXT=.gz + fi + else + warning "could not find a compression program; the resulting tarball may be huge" + COMPRESS_PROG=cat + COMPRESS_EXT= + fi +} +# get the right part of the log +getlog() { + local cnt + local outf + outf=$WORKDIR/$HALOG_F + + if [ "$HA_LOG" ]; then # log provided by the user? + [ -f "$HA_LOG" ] || { # not present + is_collector || # warning if not on slave + warning "$HA_LOG not found; we will try to find log ourselves" + HA_LOG="" + } + fi + if [ "$HA_LOG" = "" ]; then + HA_LOG=`findlog` + [ "$HA_LOG" ] && + cnt=`fgrep -c $UNIQUE_MSG < $HA_LOG` + fi + if [ "$HA_LOG" = "" -o ! -f "$HA_LOG" ]; then + if [ "$CTS" ]; then + cts_findlogseg $CTS > $outf + else + warning "no log at $WE" + fi + return + fi + if [ "$cnt" ] && [ $cnt -gt 1 -a $cnt -eq $NODECNT ]; then + MASTER_IS_HOSTLOG=1 + info "found the central log!" + fi + + if [ "$NO_str2time" ]; then + warning "a log found; but we cannot slice it" + warning "please install the perl Date::Parse module" + elif [ "$CTS" ]; then + cts_findlogseg $CTS $HA_LOG > $outf + else + getstampproc=`find_getstampproc < $HA_LOG` + if [ "$getstampproc" ]; then + export getstampproc # used by linetime + dumplogset $HA_LOG $FROM_TIME $TO_TIME > $outf && + loginfo $HA_LOG > $outf.info || + fatal "disk full" + else + warning "could not figure out the log format of $HA_LOG" + fi + fi +} +# +# get all other info (config, stats, etc) +# +collect_info() { + local l + sys_info > $WORKDIR/$SYSINFO_F 2>&1 & + sys_stats > $WORKDIR/$SYSSTATS_F 2>&1 & + getconfig $WORKDIR + getpeinputs $FROM_TIME $TO_TIME $WORKDIR & + crmconfig $WORKDIR & + skip_lvl 1 || touch_DC_if_dc $WORKDIR & + getbacktraces $FROM_TIME $TO_TIME $WORKDIR/$BT_F + getconfigurations $WORKDIR + check_perms > $WORKDIR/$PERMISSIONS_F 2>&1 + dlm_dump > $WORKDIR/$DLM_DUMP_F 2>&1 + time_status > $WORKDIR/$TIME_F 2>&1 + corosync_blackbox $FROM_TIME $TO_TIME $WORKDIR/$COROSYNC_RECORDER_F + getratraces $FROM_TIME $TO_TIME $WORKDIR + wait + skip_lvl 1 || sanitize $WORKDIR + + for l in $EXTRA_LOGS; do + [ "$NO_str2time" ] && break + [ ! -f "$l" ] && continue + if [ "$l" = "$HA_LOG" -a "$l" != "$HALOG_F" ]; then + ln -s $HALOG_F $WORKDIR/`basename $l` + continue + fi + getstampproc=`find_getstampproc < $l` + if [ "$getstampproc" ]; then + export getstampproc # used by linetime + dumplogset $l $FROM_TIME $TO_TIME > $WORKDIR/`basename $l` && + loginfo $l > $WORKDIR/`basename $l`.info || + fatal "disk full" + else + warning "could not figure out the log format of $l" + fi + done +} +finalword() { + if [ "$COMPRESS" = "1" ]; then + echo "The report is saved in $DESTDIR/$DEST.tar$COMPRESS_EXT" + else + echo "The report is saved in $DESTDIR/$DEST" + fi + echo " " + echo "Thank you for taking time to create this report." +} + +[ $# -eq 0 ] && usage + +# check for the major prereq for a) parameter parsing and b) +# parsing logs +# +NO_str2time="" +t=`str2time "12:00"` +if [ "$t" = "" ]; then + NO_str2time=1 + is_collector || + fatal "please install the perl Date::Parse module" +fi + +WE=`uname -n` # who am i? +tmpdir=`mktemp -t -d .hb_report.workdir.XXXXXX` || + fatal "disk full" +add_tmpfiles $tmpdir +WORKDIR=$tmpdir + +# +# part 1: get and check options; and the destination +# +if ! is_collector; then + setvarsanddefaults + userargs="$@" + DESTDIR=. + DEST="hb_report-"`date +"%a-%d-%b-%Y"` + while getopts f:t:l:u:X:p:L:e:E:n:MSDCZAVsvhdQ o; do + case "$o" in + h) usage;; + V) version;; + f) + if echo "$OPTARG" | grep -qs '^cts:'; then + FROM_TIME=0 # to be calculated later + CTS=`echo "$OPTARG" | sed 's/cts://'` + DEST="cts-$CTS-"`date +"%a-%d-%b-%Y"` + else + FROM_TIME=`str2time "$OPTARG"` + chktime "$FROM_TIME" "$OPTARG" + fi + ;; + t) TO_TIME=`str2time "$OPTARG"` + chktime "$TO_TIME" "$OPTARG" + ;; + n) NODES_SOURCE=user + USER_NODES="$USER_NODES $OPTARG" + ;; + u) SSH_USER="$OPTARG";; + X) SSH_OPTS="$SSH_OPTS $OPTARG";; + l) HA_LOG="$OPTARG";; + e) EDITOR="$OPTARG";; + p) SANITIZE="$SANITIZE $OPTARG";; + s) DO_SANITIZE="1";; + Q) SKIP_LVL=$((SKIP_LVL + 1));; + L) LOG_PATTERNS="$LOG_PATTERNS $OPTARG";; + S) NO_SSH=1;; + D) NO_DESCRIPTION=1;; + C) : ;; + Z) FORCE_REMOVE_DEST=1;; + M) EXTRA_LOGS="";; + E) EXTRA_LOGS="$EXTRA_LOGS $OPTARG";; + A) USER_CLUSTER_TYPE="openais";; + v) VERBOSITY=$((VERBOSITY + 1));; + d) COMPRESS="";; + [?]) usage short;; + esac + done + shift $(($OPTIND-1)) + [ $# -gt 1 ] && usage short + set_dest $* + [ "$FROM_TIME" ] || usage short + WORKDIR=$tmpdir/$DEST +else + WORKDIR=$tmpdir/$DEST/$WE +fi + +mkdir -p $WORKDIR +[ -d $WORKDIR ] || no_dir + +if is_collector; then + cat > $WORKDIR/.env + . $WORKDIR/.env +fi + +[ $VERBOSITY -gt 1 ] && { + is_collector || { + info "high debug level, please read debug.out" + } + PS4='+ `date +"%T"`: ${FUNCNAME[0]:+${FUNCNAME[0]}:}${LINENO}: ' + if echo "$SHELL" | grep bash > /dev/null && + [ ${BASH_VERSINFO[0]} = "4" ]; then + exec 3>>$WORKDIR/debug.out + BASH_XTRACEFD=3 + else + exec 2>>$WORKDIR/debug.out + fi + set -x +} + +compatibility_pcmk + +# allow user to enforce the cluster type +# if not, then it is found out on _all_ nodes +if [ -z "$USER_CLUSTER_TYPE" ]; then + CLUSTER_TYPE=`get_cluster_type` +else + CLUSTER_TYPE=$USER_CLUSTER_TYPE +fi + +# the very first thing we must figure out is which cluster +# stack is used +CORES_DIRS="`2>/dev/null ls -d $HA_VARLIB/cores $PCMK_LIB/cores | uniq`" +PACKAGES="pacemaker libpacemaker3 +pacemaker-pygui pacemaker-pymgmt pymgmt-client +openais libopenais2 libopenais3 corosync libcorosync4 +resource-agents cluster-glue libglue2 ldirectord +heartbeat heartbeat-common heartbeat-resources libheartbeat2 +ocfs2-tools ocfs2-tools-o2cb ocfs2console +ocfs2-kmp-default ocfs2-kmp-pae ocfs2-kmp-xen ocfs2-kmp-debug ocfs2-kmp-trace +drbd drbd-kmp-xen drbd-kmp-pae drbd-kmp-default drbd-kmp-debug drbd-kmp-trace +drbd-heartbeat drbd-pacemaker drbd-utils drbd-bash-completion drbd-xen +lvm2 lvm2-clvm cmirrord +libdlm libdlm2 libdlm3 +hawk ruby lighttpd +kernel-default kernel-pae kernel-xen +glibc +" +case "$CLUSTER_TYPE" in +openais) + CONF=/etc/corosync/corosync.conf # corosync? + if test -f $CONF; then + CORES_DIRS="$CORES_DIRS /var/lib/corosync" + else + CONF=/etc/ais/openais.conf + CORES_DIRS="$CORES_DIRS /var/lib/openais" + fi + CF_SUPPORT=$HA_NOARCHBIN/openais_conf_support.sh + MEMBERSHIP_TOOL_OPTS="" + unset HOSTCACHE HB_UUID_F + ;; +heartbeat) + CONF=$HA_CF + CF_SUPPORT=$HA_NOARCHBIN/ha_cf_support.sh + MEMBERSHIP_TOOL_OPTS="-H" + ;; +esac +B_CONF=`basename $CONF` + +if test -f "$CF_SUPPORT"; then + . $CF_SUPPORT +else + fatal "no stack specific support: $CF_SUPPORT" +fi + +if [ "x$CTS" = "x" ] || is_collector; then + getlogvars + debug "log settings: facility=$HA_LOGFACILITY logfile=$HA_LOGFILE debugfile=$HA_DEBUGFILE" +elif ! is_collector; then + ctslog=`findmsg "CTS: Stack:" | awk '{print $1}'` + debug "Using CTS control file: $ctslog" + USER_NODES=`grep CTS: $ctslog | grep -v debug: | grep " \* " | sed s:.*\\\*::g | sort -u | tr '\\n' ' '` + HA_LOGFACILITY=`findmsg "CTS:.*Environment.SyslogFacility" | awk '{print $NF}'` + NODES_SOURCE=user +fi + +# the goods +ANALYSIS_F=analysis.txt +DESCRIPTION_F=description.txt +HALOG_F=ha-log.txt +BT_F=backtraces.txt +SYSINFO_F=sysinfo.txt +SYSSTATS_F=sysstats.txt +DLM_DUMP_F=dlm_dump.txt +TIME_F=time.txt +export ANALYSIS_F DESCRIPTION_F HALOG_F BT_F SYSINFO_F SYSSTATS_F DLM_DUMP_F TIME_F +CRM_MON_F=crm_mon.txt +MEMBERSHIP_F=members.txt +HB_UUID_F=hb_uuid.txt +HOSTCACHE=hostcache +CRM_VERIFY_F=crm_verify.txt +PERMISSIONS_F=permissions.txt +CIB_F=cib.xml +CIB_TXT_F=cib.txt +COROSYNC_RECORDER_F=fdata.txt +export CRM_MON_F MEMBERSHIP_F CRM_VERIFY_F CIB_F CIB_TXT_F HB_UUID_F PERMISSIONS_F +export COROSYNC_RECORDER_F +CONFIGURATIONS="/etc/drbd.conf /etc/drbd.d /etc/booth/booth.conf" +export CONFIGURATIONS + +THIS_IS_NODE="" +if ! is_collector; then + MASTER_NODE=$WE + NODES=`getnodes` + debug "nodes: `echo $NODES`" +fi +NODECNT=`echo $NODES | wc -w` +if [ "$NODECNT" = 0 ]; then + fatal "could not figure out a list of nodes; is this a cluster node?" +fi +if echo $NODES | grep -wqs $WE; then # are we a node? + THIS_IS_NODE=1 +fi + +# this only on master +if ! is_collector; then + + # if this is not a node, then some things afterwards might + # make no sense (not work) + if ! is_node && [ "$NODES_SOURCE" != user ]; then + warning "this is not a node and you didn't specify a list of nodes using -n" + fi +# +# part 2: ssh business +# + # find out if ssh works + if [ -z "$NO_SSH" ]; then + # if the ssh user was supplied, consider that it + # works; helps reduce the number of ssh invocations + findsshuser + if [ -n "$SSH_USER" ]; then + SSH_OPTS="$SSH_OPTS -o User=$SSH_USER" + fi + fi + # assume that only root can collect data + SUDO="" + if [ -z "$SSH_USER" -a `id -u` != 0 ] || + [ -n "$SSH_USER" -a "$SSH_USER" != root ]; then + debug "ssh user other than root, use sudo" + SUDO="sudo -u root" + fi + LOCAL_SUDO="" + if [ `id -u` != 0 ]; then + debug "local user other than root, use sudo" + LOCAL_SUDO="sudo -u root" + fi +fi + +if is_collector && [ "$HA_LOGFACILITY" ]; then + logmark $HA_LOGFACILITY.$HA_LOGLEVEL $UNIQUE_MSG + # allow for the log message to get (hopefully) written to the + # log file + sleep 1 +fi + +# +# part 4: find the logs and cut out the segment for the period +# + +# if the master is also a node, getlog is going to be invoked +# from the collector +(is_master && is_node) || + getlog + +if ! is_collector; then + for node in $NODES; do + if node_needs_pwd $node; then + info "Please provide password for `say_ssh_user` at $node" + info "Note that collecting data will take a while." + start_slave_collector $node + else + start_slave_collector $node & + SLAVEPIDS="$SLAVEPIDS $!" + fi + done +fi + +# +# part 5: endgame: +# slaves tar their results to stdout, the master waits +# for them, analyses results, asks the user to edit the +# problem description template, and prints final notes +# +if is_collector; then + collect_info + (cd $WORKDIR/.. && tar -h -cf - $WE) +else + if [ -n "$SLAVEPIDS" ]; then + wait $SLAVEPIDS + fi + analyze $WORKDIR > $WORKDIR/$ANALYSIS_F & + events $WORKDIR & + mktemplate > $WORKDIR/$DESCRIPTION_F + [ "$NO_DESCRIPTION" ] || { + echo press enter to edit the problem description... + read junk + edittemplate $WORKDIR/$DESCRIPTION_F + } + wait + if [ "$COMPRESS" = "1" ]; then + pickcompress + (cd $WORKDIR/.. && tar cf - $DEST) | $COMPRESS_PROG > $DESTDIR/$DEST.tar$COMPRESS_EXT + else + mv $WORKDIR $DESTDIR + fi + finalword +fi diff --git a/hb_report/openais_conf_support.sh b/hb_report/openais_conf_support.sh new file mode 100644 index 0000000..b96d1aa --- /dev/null +++ b/hb_report/openais_conf_support.sh @@ -0,0 +1,97 @@ + # Copyright (C) 2007 Dejan Muhamedagic <dmuhamedagic@suse.de> + # + # 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.1 of the License, or (at your option) any later version. + # + # This software is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + # General Public License for more details. + # + # You should have received a copy of the GNU General Public + # License along with this library; if not, write to the Free Software + # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + # + +# +# Stack specific part (openais) +# openais.conf/logd.cf parsing +# +# cut out a stanza +getstanza() { + awk -v name="$1" ' + !in_stanza && NF==2 && /^[a-z][a-z]*[[:space:]]*{/ { # stanza start + if ($1 == name) + in_stanza = 1 + } + in_stanza { print } + in_stanza && NF==1 && $1 == "}" { exit } + ' +} +# supply stanza in $1 and variable name in $2 +# (stanza is optional) +getcfvar() { + [ -f "$CONF" ] || return + sed 's/#.*//' < $CONF | + if [ $# -eq 2 ]; then + getstanza "$1" + shift 1 + else + cat + fi | + awk -v varname="$1" ' + NF==2 && match($1,varname":$")==1 { print $2; exit; } + ' +} +iscfvarset() { + test "`getcfvar $1`" +} +iscfvartrue() { + getcfvar $1 $2 | + egrep -qsi "^(true|y|yes|on|1)" +} +uselogd() { + iscfvartrue use_logd +} +get_ais_logvars() { + if iscfvartrue to_file; then + HA_LOGFILE=`getcfvar logfile` + HA_LOGFILE=${HA_LOGFILE:-"syslog"} + HA_DEBUGFILE=$HA_LOGFILE + elif iscfvartrue to_syslog; then + HA_LOGFACILITY=`getcfvar syslog_facility` + HA_LOGFACILITY=${HA_LOGFACILITY:-"daemon"} + fi +} +getlogvars() { + HA_LOGFACILITY=${HA_LOGFACILITY:-$DEFAULT_HA_LOGFACILITY} + HA_LOGLEVEL="info" + iscfvartrue debug && # prefer debug level if set + HA_LOGLEVEL="debug" + if uselogd; then + [ -f "$LOGD_CF" ] || { + debug "logd used but logd.cf not found: using defaults" + return # no configuration: use defaults + } + debug "reading log settings from $LOGD_CF" + get_logd_logvars + else + debug "reading log settings from $CONF" + get_ais_logvars + fi +} +cluster_info() { + : echo "openais version: how?" + if [ "$CONF" = /etc/corosync/corosync.conf ]; then + /usr/sbin/corosync -v + fi +} +essential_files() { + cat<<EOF +d $PCMK_LIB 0750 hacluster haclient +d $PE_STATE_DIR 0750 hacluster haclient +d $CIB_DIR 0750 hacluster haclient +EOF +} diff --git a/hb_report/utillib.sh b/hb_report/utillib.sh new file mode 100644 index 0000000..0fcab80 --- /dev/null +++ b/hb_report/utillib.sh @@ -0,0 +1,752 @@ + # Copyright (C) 2007 Dejan Muhamedagic <dmuhamedagic@suse.de> + # + # 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.1 of the License, or (at your option) any later version. + # + # This software is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + # General Public License for more details. + # + # You should have received a copy of the GNU General Public + # License along with this library; if not, write to the Free Software + # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + # + +# +# figure out the cluster type, depending on the process list +# and existence of configuration files +# +get_cluster_type() { + if ps -ef | egrep -qs '[a]isexec|[c]orosync' || + [ -f /etc/ais/openais.conf -a ! -f "$HA_CF" ] || + [ -f /etc/corosync/corosync.conf -a ! -f "$HA_CF" ] + then + debug "this is OpenAIS cluster stack" + echo "openais" + else + debug "this is Heartbeat cluster stack" + echo "heartbeat" + fi +} +# +# find out which membership tool is installed +# +echo_membership_tool() { + local f membership_tools + membership_tools="ccm_tool crm_node" + for f in $membership_tools; do + which $f 2>/dev/null && break + done +} +# find out if ptest or crm_simulate +# +echo_ptest_tool() { + local f ptest_progs + ptest_progs="crm_simulate ptest" + for f in $ptest_progs; do + which $f 2>/dev/null && break + done +} +# +# find nodes for this cluster +# +getnodes() { + # 1. set by user? + if [ "$USER_NODES" ]; then + echo $USER_NODES + # 2. running crm + elif iscrmrunning; then + debug "querying CRM for nodes" + get_crm_nodes + # 3. hostcache + elif [ -f $HA_VARLIB/hostcache ]; then + debug "reading nodes from $HA_VARLIB/hostcache" + awk '{print $1}' $HA_VARLIB/hostcache + # 4. ha.cf + elif [ "$CLUSTER_TYPE" = heartbeat ]; then + debug "reading nodes from ha.cf" + getcfvar node + # 5. if the cluster's stopped, try the CIB + elif [ -f $CIB_DIR/$CIB_F ]; then + debug "reading nodes from the archived $CIB_DIR/$CIB_F" + (CIB_file=$CIB_DIR/$CIB_F get_crm_nodes) + fi +} + +logd_getcfvar() { + sed 's/#.*//' < $LOGD_CF | + grep -w "^$1" | + sed 's/^[^[:space:]]*[[:space:]]*//' +} +get_logd_logvars() { + # unless logfacility is set to none, heartbeat/ha_logd are + # going to log through syslog + HA_LOGFACILITY=`logd_getcfvar logfacility` + [ "" = "$HA_LOGFACILITY" ] && HA_LOGFACILITY=$DEFAULT_HA_LOGFACILITY + [ none = "$HA_LOGFACILITY" ] && HA_LOGFACILITY="" + HA_LOGFILE=`logd_getcfvar logfile` + HA_DEBUGFILE=`logd_getcfvar debugfile` +} +findlogdcf() { + local f + for f in \ + `test -x $HA_BIN/ha_logd && + which strings > /dev/null 2>&1 && + strings $HA_BIN/ha_logd | grep 'logd\.cf'` \ + `for d; do echo $d/logd.cf $d/ha_logd.cf; done` + do + if [ -f "$f" ]; then + echo $f + debug "found logd.cf at $f" + return 0 + fi + done + debug "no logd.cf" + return 1 +} +# +# logging +# +syslogmsg() { + local severity logtag + severity=$1 + shift 1 + logtag="" + [ "$HA_LOGTAG" ] && logtag="-t $HA_LOGTAG" + logger -p ${HA_LOGFACILITY:-$DEFAULT_HA_LOGFACILITY}.$severity $logtag $* +} + +# +# find log destination +# +findmsg() { + local d syslogdirs favourites mark log + # this is tricky, we try a few directories + syslogdirs="/var/log /var/logs /var/syslog /var/adm + /var/log/ha /var/log/cluster /var/log/pacemaker + /var/log/heartbeat /var/log/crm /var/log/corosync /var/log/openais" + favourites="ha-*" + mark=$1 + log="" + for d in $syslogdirs; do + [ -d $d ] || continue + log=`grep -l -e "$mark" $d/$favourites` && break + test "$log" && break + log=`grep -l -e "$mark" $d/*` && break + test "$log" && break + done 2>/dev/null + [ "$log" ] && + ls -t $log | tr '\n' ' ' + [ "$log" ] && + debug "found HA log at `ls -t $log | tr '\n' ' '`" || + debug "no HA log found in $syslogdirs" +} + +# +# print a segment of a log file +# +str2time() { + perl -e "\$time='$*';" -e ' + $unix_tm = 0; + eval "use Date::Parse"; + if (!$@) { + $unix_tm = str2time($time); + } else { + eval "use Date::Manip"; + if (!$@) { + $unit_tm = UnixDate(ParseDateString($time), "%s"); + } + } + if ($unix_tm != "") { + $unix_tm = int($unix_tm); + } + print $unix_tm; + ' +} +getstamp_syslog() { + awk '{print $1,$2,$3}' +} +getstamp_legacy() { + awk '{print $2}' | sed 's/_/ /' +} +getstamp_rfc5424() { + awk '{print $1}' +} +get_ts() { + local l="$1" ts + ts=$(str2time `echo "$l" | $getstampproc`) + if [ -z "$ts" ]; then + local fmt + for fmt in rfc5424 syslog legacy; do + [ "getstamp_$fmt" = "$getstampproc" ] && continue + ts=$(str2time `echo "$l" | getstamp_$fmt`) + [ -n "$ts" ] && break + done + fi + echo $ts +} +linetime() { + get_ts "`tail -n +$2 $1 | head -1`" +} +find_getstampproc() { + local t l func trycnt + t=0 l="" func="" + trycnt=10 + while [ $trycnt -gt 0 ] && read l; do + t=$(str2time `echo $l | getstamp_syslog`) + if [ "$t" ]; then + func="getstamp_syslog" + debug "the log file is in the syslog format" + break + fi + t=$(str2time `echo $l | getstamp_rfc5424`) + if [ "$t" ]; then + func="getstamp_rfc5424" + debug "the log file is in the rfc5424 format" + break + fi + t=$(str2time `echo $l | getstamp_legacy`) + if [ "$t" ]; then + func="getstamp_legacy" + debug "the log file is in the legacy format (please consider switching to syslog format)" + break + fi + trycnt=$(($trycnt-1)) + done + echo $func +} +find_first_ts() { + local l ts + while read l; do + ts=`get_ts "$l"` + [ "$ts" ] && break + warning "cannot extract time: |$l|; will try the next one" + done + echo $ts +} +findln_by_time() { + local logf=$1 + local tm=$2 + local first=1 + local last=`wc -l < $logf` + local tmid mid trycnt + while [ $first -le $last ]; do + mid=$((($last+$first)/2)) + trycnt=10 + while [ $trycnt -gt 0 ]; do + tmid=`linetime $logf $mid` + [ "$tmid" ] && break + warning "cannot extract time: $logf:$mid; will try the next one" + trycnt=$(($trycnt-1)) + # shift the whole first-last segment + first=$(($first-1)) + last=$(($last-1)) + mid=$((($last+$first)/2)) + done + if [ -z "$tmid" ]; then + warning "giving up on log..." + return + fi + if [ $tmid -gt $tm ]; then + last=$(($mid-1)) + elif [ $tmid -lt $tm ]; then + first=$(($mid+1)) + else + break + fi + done + echo $mid +} + +dumplog() { + local logf=$1 + local from_line=$2 + local to_line=$3 + [ "$from_line" ] || + return + tail -n +$from_line $logf | + if [ "$to_line" ]; then + head -$(($to_line-$from_line+1)) + else + cat + fi +} + +# +# find files newer than a and older than b +# +isnumber() { + echo "$*" | grep -qs '^[0-9][0-9]*$' +} +touchfile() { + local t + t=`mktemp` && + perl -e "\$file=\"$t\"; \$tm=$1;" -e 'utime $tm, $tm, $file;' && + echo $t +} +find_files() { + local dirs from_time to_time + local from_stamp to_stamp findexp + dirs=$1 + from_time=$2 + to_time=$3 + isnumber "$from_time" && [ "$from_time" -gt 0 ] || { + warning "sorry, can't find files based on time if you don't supply time" + return + } + if ! from_stamp=`touchfile $from_time`; then + warning "can't create temporary files" + return + fi + add_tmpfiles $from_stamp + findexp="-newer $from_stamp" + if isnumber "$to_time" && [ "$to_time" -gt 0 ]; then + if ! to_stamp=`touchfile $to_time`; then + warning "can't create temporary files" + return + fi + add_tmpfiles $to_stamp + findexp="$findexp ! -newer $to_stamp" + fi + find $dirs -type f $findexp +} + +# +# check permissions of files/dirs +# +pl_checkperms() { +perl -e ' +# check permissions and ownership +# uid and gid are numeric +# everything must match exactly +# no error checking! (file should exist, etc) +($filename, $perms, $in_uid, $in_gid) = @ARGV; +($mode,$uid,$gid) = (stat($filename))[2,4,5]; +$p=sprintf("%04o", $mode & 07777); +$p ne $perms and exit(1); +$uid ne $in_uid and exit(1); +$gid ne $in_gid and exit(1); +' $* +} +num_id() { + getent $1 $2 | awk -F: '{print $3}' +} +chk_id() { + [ "$2" ] && return 0 + echo "$1: id not found" + return 1 +} +check_perms() { + local f p uid gid n_uid n_gid + essential_files | + while read type f p uid gid; do + [ -$type $f ] || { + echo "$f wrong type or doesn't exist" + continue + } + n_uid=`num_id passwd $uid` + chk_id "$uid" "$n_uid" || continue + n_gid=`num_id group $gid` + chk_id "$gid" "$n_gid" || continue + pl_checkperms $f $p $n_uid $n_gid || { + echo "wrong permissions or ownership for $f:" + ls -ld $f + } + done +} + +# +# coredumps +# +pkg_mgr_list() { +# list of: +# regex pkg_mgr +# no spaces allowed in regex + cat<<EOF +zypper.install zypper +EOF +} +listpkg_zypper() { + local bins + local binary=$1 core=$2 + gdb $binary $core </dev/null 2>&1 | + awk ' + # this zypper version dumps all packages on a single line + /Missing separate debuginfos.*zypper.install/ { + sub(".*zypper.install ",""); print + exit} + n>0 && /^Try: zypper install/ {gsub("\"",""); print $NF} + n>0 {n=0} + /Missing separate debuginfo/ {n=1} + ' | sort -u +} +fetchpkg_zypper() { + local pkg + debug "get debuginfo packages using zypper: $@" + zypper -qn ref > /dev/null + for pkg in $@; do + zypper -qn install -C $pkg >/dev/null + done +} +find_pkgmgr() { + local binary=$1 core=$2 + local regex pkg_mgr + pkg_mgr_list | + while read regex pkg_mgr; do + if gdb $binary $core </dev/null 2>&1 | + grep "$regex" > /dev/null; then + echo $pkg_mgr + break + fi + done +} +get_debuginfo() { + local binary=$1 core=$2 + local pkg_mgr pkgs + gdb $binary $core </dev/null 2>/dev/null | + egrep 'Missing.*debuginfo|no debugging symbols found' > /dev/null || + return # no missing debuginfo + pkg_mgr=`find_pkgmgr $binary $core` + if [ -z "$pkg_mgr" ]; then + warning "found core for $binary but there is no debuginfo and we don't know how to get it on this platform" + return + fi + pkgs=`listpkg_$pkg_mgr $binary $core` + [ -n "$pkgs" ] && + fetchpkg_$pkg_mgr $pkgs +} +findbinary() { + local random_binary binary fullpath + random_binary=`which cat 2>/dev/null` # suppose we are lucky + binary=`gdb $random_binary $1 < /dev/null 2>/dev/null | + grep 'Core was generated' | awk '{print $5}' | + sed "s/^.//;s/[.':]*$//"` + if [ x = x"$binary" ]; then + debug "could not detect the program name for core $1 from the gdb output; will try with file(1)" + binary=$(file $1 | awk '/from/{ + for( i=1; i<=NF; i++ ) + if( $i == "from" ) { + print $(i+1) + break + } + }') + binary=`echo $binary | tr -d "'"` + binary=$(echo $binary | tr -d '`') + if [ "$binary" ]; then + binary=`which $binary 2>/dev/null` + fi + fi + if [ x = x"$binary" ]; then + warning "could not find the program path for core $1" + return + fi + fullpath=`which $binary 2>/dev/null` + if [ x = x"$fullpath" ]; then + for d in $HA_BIN $CRM_DAEMON_DIR; do + if [ -x $d/$binary ]; then + echo $d/$binary + debug "found the program at $d/$binary for core $1" + else + warning "could not find the program path for core $1" + fi + done + else + echo $fullpath + debug "found the program at $fullpath for core $1" + fi +} +getbt() { + local corefile absbinpath + which gdb > /dev/null 2>&1 || { + warning "please install gdb to get backtraces" + return + } + for corefile; do + absbinpath=`findbinary $corefile` + [ x = x"$absbinpath" ] && continue + get_debuginfo $absbinpath $corefile + echo "====================== start backtrace ======================" + ls -l $corefile + gdb -batch -n -quiet -ex ${BT_OPTS:-"thread apply all bt full"} -ex quit \ + $absbinpath $corefile 2>/dev/null + echo "======================= end backtrace =======================" + done +} + +# +# heartbeat configuration/status +# +iscrmrunning() { + local pid maxwait + ps -ef | grep -qs [c]rmd || return 1 + crmadmin -D >/dev/null 2>&1 & + pid=$! + maxwait=100 + while kill -0 $pid 2>/dev/null && [ $maxwait -gt 0 ]; do + sleep 0.1 + maxwait=$(($maxwait-1)) + done + if kill -0 $pid 2>/dev/null; then + kill $pid + false + else + wait $pid + fi +} +dumpstate() { + crm_mon -1 | grep -v '^Last upd' > $1/$CRM_MON_F + cibadmin -Ql > $1/$CIB_F + `echo_membership_tool` $MEMBERSHIP_TOOL_OPTS -p > $1/$MEMBERSHIP_F 2>&1 +} +getconfig() { + [ -f "$CONF" ] && + cp -p $CONF $1/ + [ -f "$LOGD_CF" ] && + cp -p $LOGD_CF $1/ + if iscrmrunning; then + dumpstate $1 + touch $1/RUNNING + else + cp -p $CIB_DIR/$CIB_F $1/ 2>/dev/null + touch $1/STOPPED + fi + [ "$HOSTCACHE" ] && + cp -p $HA_VARLIB/hostcache $1/$HOSTCACHE 2>/dev/null + [ "$HB_UUID_F" ] && + crm_uuid -r > $1/$HB_UUID_F 2>&1 + [ -f "$1/$CIB_F" ] && + crm_verify -V -x $1/$CIB_F >$1/$CRM_VERIFY_F 2>&1 +} +crmconfig() { + [ -f "$1/$CIB_F" ] && which crm >/dev/null 2>&1 && + CIB_file=$1/$CIB_F crm configure show >$1/$CIB_TXT_F 2>&1 +} +get_crm_nodes() { + cibadmin -Ql -o nodes | + awk ' + /<node / { + for( i=1; i<=NF; i++ ) + if( $i~/^uname=/ ) { + sub("uname=.","",$i); + sub("\".*","",$i); + print $i; + next; + } + } + ' +} +get_live_nodes() { + if [ `id -u` = 0 ] && which fping >/dev/null 2>&1; then + fping -a $@ 2>/dev/null + else + local h + for h; do ping -c 2 -q $h >/dev/null 2>&1 && echo $h; done + fi +} + +# +# remove values of sensitive attributes +# +# this is not proper xml parsing, but it will work under the +# circumstances +is_sensitive_xml() { + local patt epatt + epatt="" + for patt in $SANITIZE; do + epatt="$epatt|$patt" + done + epatt="`echo $epatt|sed 's/.//'`" + egrep -qs "name=\"$epatt\"" +} +test_sensitive_one() { + local file compress decompress + file=$1 + compress="" + echo $file | grep -qs 'gz$' && compress=gzip + echo $file | grep -qs 'bz2$' && compress=bzip2 + if [ "$compress" ]; then + decompress="$compress -dc" + else + compress=cat + decompress=cat + fi + $decompress < $file | is_sensitive_xml +} +sanitize_xml_attrs() { + local patt + sed $( + for patt in $SANITIZE; do + echo "-e /name=\"$patt\"/s/value=\"[^\"]*\"/value=\"****\"/" + done + ) +} +sanitize_hacf() { + awk ' + $1=="stonith_host"{ for( i=5; i<=NF; i++ ) $i="****"; } + {print} + ' +} +sanitize_one() { + local file compress decompress tmp ref + file=$1 + compress="" + echo $file | grep -qs 'gz$' && compress=gzip + echo $file | grep -qs 'bz2$' && compress=bzip2 + if [ "$compress" ]; then + decompress="$compress -dc" + else + compress=cat + decompress=cat + fi + tmp=`mktemp` + ref=`mktemp` + add_tmpfiles $tmp $ref + if [ -z "$tmp" -o -z "$ref" ]; then + fatal "cannot create temporary files" + fi + touch -r $file $ref # save the mtime + if [ "`basename $file`" = ha.cf ]; then + sanitize_hacf + else + $decompress | sanitize_xml_attrs | $compress + fi < $file > $tmp + mv $tmp $file + touch -r $ref $file +} + +# +# keep the user posted +# +fatal() { + echo "`uname -n`: ERROR: $*" >&2 + exit 1 +} +warning() { + echo "`uname -n`: WARN: $*" >&2 +} +info() { + echo "`uname -n`: INFO: $*" >&2 +} +debug() { + [ "$VERBOSITY" ] && [ $VERBOSITY -gt 0 ] && + echo "`uname -n`: DEBUG: $*" >&2 + return 0 +} +pickfirst() { + for x; do + which $x >/dev/null 2>&1 && { + echo $x + return 0 + } + done + return 1 +} + +# tmp files business +drop_tmpfiles() { + trap 'rm -rf `cat $__TMPFLIST`; rm $__TMPFLIST' EXIT +} +init_tmpfiles() { + if __TMPFLIST=`mktemp`; then + drop_tmpfiles + else + # this is really bad, let's just leave + fatal "eek, mktemp cannot create temporary files" + fi +} +add_tmpfiles() { + test -f "$__TMPFLIST" || return + echo $* >> $__TMPFLIST +} + +# +# get some system info +# +distro() { + local relf f + which lsb_release >/dev/null 2>&1 && { + lsb_release -d + debug "using lsb_release for distribution info" + return + } + relf=`ls /etc/debian_version 2>/dev/null` || + relf=`ls /etc/slackware-version 2>/dev/null` || + relf=`ls -d /etc/*-release 2>/dev/null` && { + for f in $relf; do + test -f $f && { + echo "`ls $f` `cat $f`" + debug "found $relf distribution release file" + return + } + done + } + warning "no lsb_release, no /etc/*-release, no /etc/debian_version: no distro information" +} + +pkg_ver_deb() { + dpkg-query -f '${Name} ${Version}' -W $* 2>/dev/null +} +pkg_ver_rpm() { + rpm -q --qf '%{name} %{version}-%{release} - %{distribution} %{arch}\n' $* 2>&1 | + grep -v 'not installed' +} +pkg_ver_pkg_info() { + for pkg; do + pkg_info | grep $pkg + done +} +pkg_ver_pkginfo() { + for pkg; do + pkginfo $pkg | awk '{print $3}' # format? + done +} +verify_deb() { + debsums -s $* 2>/dev/null +} +verify_rpm() { + rpm --verify $* 2>&1 | grep -v 'not installed' +} +verify_pkg_info() { + : +} +verify_pkginfo() { + : +} + +get_pkg_mgr() { + local pkg_mgr + if which dpkg >/dev/null 2>&1 ; then + pkg_mgr="deb" + elif which rpm >/dev/null 2>&1 ; then + pkg_mgr="rpm" + elif which pkg_info >/dev/null 2>&1 ; then + pkg_mgr="pkg_info" + elif which pkginfo >/dev/null 2>&1 ; then + pkg_mgr="pkginfo" + else + warning "Unknown package manager!" + return + fi + echo $pkg_mgr +} + +pkg_versions() { + local pkg_mgr=`get_pkg_mgr` + [ -z "$pkg_mgr" ] && + return + debug "the package manager is $pkg_mgr" + pkg_ver_$pkg_mgr $* +} +verify_packages() { + local pkg_mgr=`get_pkg_mgr` + [ -z "$pkg_mgr" ] && + return + verify_$pkg_mgr $* +} + +crm_info() { + $CRM_DAEMON_DIR/crmd version 2>&1 +} diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..2e07275 --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,25 @@ +# +# Copyright (C) 2008 Andrew Beekhof +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +MAINTAINERCLEANFILES = Makefile.in +SUBDIRS = clplumbing pils stonith lrm + +idir=$(includedir)/heartbeat +i_HEADERS = compress.h glue_config.h ha_msg.h + +noinst_HEADERS = config.h lha_internal.h replace_uuid.h diff --git a/include/clplumbing/GSource.h b/include/clplumbing/GSource.h new file mode 100644 index 0000000..2acc9eb --- /dev/null +++ b/include/clplumbing/GSource.h @@ -0,0 +1,236 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CLPLUMBING_GSOURCE_H +# define _CLPLUMBING_GSOURCE_H +# include <clplumbing/ipc.h> + +typedef struct GFDSource_s GFDSource; +typedef struct GCHSource_s GCHSource; +typedef struct GWCSource_s GWCSource; +typedef struct GSIGSource_s GSIGSource; +typedef struct GTRIGSource_s GTRIGSource; + + +void G_main_setmaxdispatchdelay(GSource* s, unsigned long delayms); +void G_main_setmaxdispatchtime(GSource* s, unsigned long dispatchms); +void G_main_setdescription(GSource* s, const char * description); + +void G_main_setmaxdispatchdelay_id(guint id, unsigned long delayms); +void G_main_setmaxdispatchtime_id(guint id, unsigned long dispatchms); +void G_main_setdescription_id(guint id, const char * description); +void G_main_setall_id(guint id, const char * description, unsigned long delayms, unsigned long dispatchms); + + +/*********************************************************************** + * Functions for interfacing input to the mainloop + ***********************************************************************/ + +GSource* +G_main_add_input(int priority, + gboolean can_recurse, + GSourceFuncs* funcs); + +/*********************************************************************** + * Functions for interfacing "raw" file descriptors to the mainloop + ***********************************************************************/ +/* +* Add a file descriptor to the gmainloop world... + */ +GFDSource* G_main_add_fd(int priority, int fd, gboolean can_recurse +, gboolean (*dispatch)(int fd, gpointer user_data) +, gpointer userdata +, GDestroyNotify notify); + +/* + * Delete a file descriptor from the gmainloop world... + * Note: destroys the GFDSource object. + */ +gboolean G_main_del_fd(GFDSource* fdp); + +/* + * Notify us that a file descriptor is blocked on output. + * (i.e., we should poll for output events) + */ +void g_main_output_is_blocked(GFDSource* fdp); + + +/************************************************************** + * Functions for interfacing IPC_Channels to the mainloop + **************************************************************/ +/* + * Add an IPC_channel to the gmainloop world... + */ +GCHSource* G_main_add_IPC_Channel(int priority, IPC_Channel* ch +, gboolean can_recurse +, gboolean (*dispatch)(IPC_Channel* source_data +, gpointer user_data) +, gpointer userdata +, GDestroyNotify notify); + +/* + * the events in this source is paused/resumed + */ + +void G_main_IPC_Channel_pause(GCHSource* chp); +void G_main_IPC_Channel_resume(GCHSource* chp); + + +/* + * Delete an IPC_channel from the gmainloop world... + * Note: destroys the GCHSource object, and the IPC_Channel + * object automatically. + */ +gboolean G_main_del_IPC_Channel(GCHSource* chp); + + +/* + * Set the destroy notify function + * + */ +void set_IPC_Channel_dnotify(GCHSource* chp, + GDestroyNotify notify); + + +/********************************************************************* + * Functions for interfacing IPC_WaitConnections to the mainloop + ********************************************************************/ +/* + * Add an IPC_WaitConnection to the gmainloop world... + * Note that the dispatch function is called *after* the + * connection is accepted. + */ +GWCSource* G_main_add_IPC_WaitConnection(int priority, IPC_WaitConnection* ch +, IPC_Auth* auth_info +, gboolean can_recurse +, gboolean (*dispatch)(IPC_Channel* source_data +, gpointer user_data) +, gpointer userdata +, GDestroyNotify notify); + +/* + * Delete an IPC_WaitConnection from the gmainloop world... + * Note: destroys the GWCSource object, and the IPC_WaitConnection + * object automatically. + */ +gboolean G_main_del_IPC_WaitConnection(GWCSource* wcp); + + +/************************************************************** + * Functions for interfacing Signals to the mainloop + **************************************************************/ +/* + * Add an Signal to the gmainloop world... + */ +GSIGSource* G_main_add_SignalHandler( + int priority, int signal, + gboolean (*dispatch)(int nsig, gpointer user_data), + gpointer userdata, GDestroyNotify notify); + +/* + * Delete an signal from the gmainloop world... + * Note: destroys the GSIGSource object, and the removes the + * Signal Handler automatically. + */ +gboolean G_main_del_SignalHandler(GSIGSource* chp); + + +/* + * Set the destroy notify function + * + */ +void set_SignalHandler_dnotify(GSIGSource* chp, GDestroyNotify notify); + + +/* manage child process death using sig source*/ +#define DEFAULT_MAXDISPATCHTIME 30 /* in ms */ +void set_sigchld_proctrack(int priority, unsigned long maxdisptime); + + + +/************************************************************** + * Functions for interfacing Manual triggers to the mainloop + **************************************************************/ +/* + * Add an Trigger to the gmainloop world... + */ +GTRIGSource* G_main_add_TriggerHandler( + int priority, gboolean (*dispatch)(gpointer user_data), + gpointer userdata, GDestroyNotify notify); + +/* + * Delete an signal from the gmainloop world... + * Note: destroys the GTRIGSource object, and the removes the + * Trigger Handler automatically. + */ +gboolean G_main_del_TriggerHandler(GTRIGSource* chp); + + +/* + * Set the destroy notify function + * + */ +void set_TriggerHandler_dnotify(GTRIGSource* chp, GDestroyNotify notify); + + +void G_main_set_trigger(GTRIGSource* man_src); + +/* + * Create a trigger for triggering an action in a short-lived (temporary) + * child process. + * + * The name isn't wonderful, but we couldn't think of a better one. + */ +GTRIGSource* G_main_add_tempproc_trigger(int priority +, int (*fun)(gpointer p) /* What to do? */ + /* Called in child process */ +, const char * procname /* What do we call this process? */ +, gpointer userdata /* Passed to 'triggerfun' */ +, void (*prefork)(gpointer p) /* Called before fork */ +, void (*postfork)(gpointer p) /* Called by parent process + * after fork(2) call. + * Each has 'userdata' + * passed to it. + */ +, void (*complete)(gpointer p, int status, int signo, int exitcode)); /* called after the child process completes */ + +/* + * Special notes: + * - No more than one child process will be active at a time per trigger + * object. + * + * - If you trigger the action while this object has a child active, + * then it will be re-triggered after the currently running child + * completes. There is no necessary correlation between the + * number of times a the action is triggered and how many + * times it is executed. What is guaranteed is that after you + * trigger the action, it will happen (at least) once - as soon + * as the scheduler gets around to it at the priority you've + * assigned it. But if several are triggered while a child + * process is running, only one process will be instantiated to + * take the action requested by all the trigger calls. + * + * - Child processes are forked off at the priority of the trigger, + * not the priority of the SIGCHLD handler. + * + * - This is useful for writing out updates to a file for example. + * While we're writing one copy out, subsequent updates are + * held off until this one completes. When it completes, then + * the file is written again - but not "n" times - just the + * latest version available at the time the trigger is + * activated (run). + */ +#endif diff --git a/include/clplumbing/GSource_internal.h b/include/clplumbing/GSource_internal.h new file mode 100644 index 0000000..c20a9c9 --- /dev/null +++ b/include/clplumbing/GSource_internal.h @@ -0,0 +1,111 @@ +/* + * Author: Alan Robertson <alanr@unix.sh> + * Copyright (C) 2005 International Business Machines Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <clplumbing/longclock.h> +#include <clplumbing/GSource.h> + +#define MAG_GFDSOURCE 0xfeed0001U +#define MAG_GCHSOURCE 0xfeed0002U +#define MAG_GWCSOURCE 0xfeed0003U +#define MAG_GSIGSOURCE 0xfeed0004U +#define MAG_GTRIGSOURCE 0xfeed0005U +#define MAG_GTIMEOUTSRC 0xfeed0006U + +#define IS_FDSOURCE(p) (p && (p)->magno == MAG_GFDSOURCE) +#define IS_CHSOURCE(p) (p && (p)->magno == MAG_GCHSOURCE) +#define IS_WCSOURCE(p) (p && (p)->magno == MAG_GWCSOURCE) +#define IS_SIGSOURCE(p) (p && (p)->magno == MAG_GSIGSOURCE) +#define IS_TRIGSOURCE(p) (p && (p)->magno == MAG_GTRIGSOURCE) +#define IS_TIMEOUTSRC(p) (p && (p)->magno == MAG_GTIMEOUTSRC) + +#define IS_ONEOFOURS(p) (IS_CHSOURCE(p)|IS_FDSOURCE(p)|IS_WCSOURCE(p)|| \ + IS_SIGSOURCE(p)|IS_TRIGSOURCE(p)||IS_TIMEOUTSRC(p)) + + +#define DEFAULT_MAXDISPATCH 0 +#define DEFAULT_MAXDELAY 0 +#define OTHER_MAXDELAY 100 + +#define COMMON_STRUCTSTART \ +GSource source; /* Common glib struct - must be 1st */ \ +unsigned magno; /* Magic number */ \ +long maxdispatchms; /* Time limit for dispatch function */ \ +long maxdispatchdelayms; /* Max delay before processing */ \ +char detecttime[sizeof(longclock_t)]; \ + /* Time last input detected */ \ +void* udata; /* User-defined data */ \ +guint gsourceid; /* Source id of this source */ \ +const char * description; /* Description of this source */ \ +GDestroyNotify dnotify + +struct GFDSource_s { + COMMON_STRUCTSTART; + gboolean (*dispatch)(int fd, gpointer user_data); + GPollFD gpfd; +}; + + +typedef gboolean (*GCHdispatch)(IPC_Channel* ch, gpointer user_data); + +struct GCHSource_s { + COMMON_STRUCTSTART; + IPC_Channel* ch; + gboolean fd_fdx; + GPollFD infd; + GPollFD outfd; + gboolean dontread; /* TRUE when we don't want to read + * more input for a while - we're + * flow controlling the writer off + */ + gboolean (*dispatch)(IPC_Channel* ch, gpointer user_data); +}; + +struct GWCSource_s { + COMMON_STRUCTSTART; + GPollFD gpfd; + IPC_WaitConnection* wch; + IPC_Auth* auth_info; + gboolean (*dispatch)(IPC_Channel* accept_ch, gpointer udata); +}; + +struct GSIGSource_s { + COMMON_STRUCTSTART; + clock_t sh_detecttime; + int signal; + gboolean signal_triggered; + gboolean (*dispatch)(int signal, gpointer user_data); +}; + +struct GTRIGSource_s { + COMMON_STRUCTSTART; + gboolean manual_trigger; + gboolean (*dispatch)(gpointer user_data); +}; + +/************************************************************ + * Functions for IPC_Channels + ***********************************************************/ +gboolean G_CH_prepare_int(GSource* source, gint* timeout); +gboolean G_CH_check_int(GSource* source); +gboolean G_CH_dispatch_int(GSource* source, GSourceFunc callback, + gpointer user_data); +void G_CH_destroy_int(GSource* source); +GCHSource* +G_main_IPC_Channel_constructor(GSource* source, IPC_Channel* ch +, gpointer userdata, GDestroyNotify notify); diff --git a/include/clplumbing/Gmain_timeout.h b/include/clplumbing/Gmain_timeout.h new file mode 100644 index 0000000..c696a9d --- /dev/null +++ b/include/clplumbing/Gmain_timeout.h @@ -0,0 +1,44 @@ +#ifndef _CLPLUMBING_GMAIN_TIMEOUT_H +#define _CLPLUMBING_GMAIN_TIMEOUT_H +#include <glib.h> +/* + * Copyright (C) 2002 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * These functions must work correctly even if someone resets the + * time-of-day clock. The g_main_timeout_add() function does not have + * this property, since it relies on gettimeofday(). + * + * Our functions have the same semantics - except they always work ;-) + * + * This is because we use longclock_t for our time values. + */ +guint Gmain_timeout_add(guint interval +, GSourceFunc function +, gpointer data); + +guint Gmain_timeout_add_full(gint priority +, guint interval +, GSourceFunc function +, gpointer data +, GDestroyNotify notify); + +void Gmain_timeout_remove(guint tag); +#endif diff --git a/include/clplumbing/Makefile.am b/include/clplumbing/Makefile.am new file mode 100644 index 0000000..599b24c --- /dev/null +++ b/include/clplumbing/Makefile.am @@ -0,0 +1,59 @@ +# +# linux-ha: Linux-HA heartbeat code +# +# Copyright (C) 2002 International Business Machines. +# Author: Alan Robertson <alanr@unix.sh> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# +MAINTAINERCLEANFILES = Makefile.in + +idir=$(includedir)/clplumbing + +i_HEADERS = \ + Gmain_timeout.h \ + GSource.h \ + GSource_internal.h \ + apphb_cs.h \ + base64.h \ + cl_log.h \ + cl_poll.h \ + cl_signal.h \ + cl_pidfile.h \ + cl_random.h \ + cl_reboot.h \ + cl_syslog.h \ + cl_uuid.h \ + coredumps.h \ + cpulimits.h \ + ipc.h \ + lsb_exitcodes.h \ + loggingdaemon.h \ + longclock.h \ + mkstemp_mode.h \ + netstring.h \ + proctrack.h \ + realtime.h \ + replytrack.h \ + setproctitle.h \ + timers.h \ + uids.h \ + cl_misc.h \ + md5.h \ + cl_plugin.h \ + cl_tiebreaker.h \ + cl_quorum.h \ + cl_quorumd.h diff --git a/include/clplumbing/apphb_cs.h b/include/clplumbing/apphb_cs.h new file mode 100644 index 0000000..9506db6 --- /dev/null +++ b/include/clplumbing/apphb_cs.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2002 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _CLPLUMBING_APPHB_CS_H +#define _CLPLUMBING_APPHB_CS_H + +/* Internal client-server messages for APP heartbeat service */ + +#ifndef HA_VARRUNDIR +#define HA_VARRUNDIR "/var/run" +#endif +#define APPHBSOCKPATH HA_VARRUNDIR "/heartbeat/apphb.comm" + +#define APPHB_TLEN 8 +#define APPHB_OLEN 256 + +#define REGISTER "reg" +#define UNREGISTER "unreg" +#define HEARTBEAT "hb" +#define SETINTERVAL "setint" +#define SETWARNTIME "setwarn" +#define SETREBOOT "setboot" + +/* + * These messages are really primitive. + * They don't have any form of version control, they're in host byte order, + * and they're all in binary... + * + * Fortunately, this is a very simple local service ;-) + */ + +/* Generic (no parameter) App heartbeat message */ +struct apphb_msg { + char msgtype [APPHB_TLEN]; +}; + +/* App heartbeat Registration message */ +struct apphb_signupmsg { + char msgtype [APPHB_TLEN]; + char appname [APPHB_OLEN]; + char appinstance [APPHB_OLEN]; + char curdir [APPHB_OLEN]; + pid_t pid; + uid_t uid; + gid_t gid; +}; + +/* App heartbeat setinterval / setwarn message */ +struct apphb_msmsg { + char msgtype [APPHB_TLEN]; + unsigned long ms; +}; + +/* App heartbeat server return code (errno) */ +struct apphb_rc { + int rc; +}; +#endif diff --git a/include/clplumbing/base64.h b/include/clplumbing/base64.h new file mode 100644 index 0000000..4ea6810 --- /dev/null +++ b/include/clplumbing/base64.h @@ -0,0 +1,50 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CLPLUMBING_BASE64_H +# define _CLPLUMBING_BASE64_H +/* + * + * Base64 conversion functions. + * They convert from a binary array into a single string + * in base 64. This is almost (but not quite) like section 5.2 of RFC 1341 + * The only difference is that we don't care about line lengths. + * We do use their encoding algorithm. + * + */ + +#define B64inunit 3 +#define B64outunit 4 + +/* How long will the base64 string be for a particular binary object size? */ +/* This is like strlen() and doesn't include the '\0' byte at the end */ +#define B64_stringlen(bytes) \ + ((((bytes)+(B64inunit-1))/B64inunit)*B64outunit) + +/* How many bytes to you need to malloc to store a base64 string? */ +/* (includes space for the '\0' terminator byte) */ +#define B64_stringspace(bytes) (B64_stringlen(bytes)+1) + +/* How many bytes will a base64 string take up back in binary? */ +/* Note: This may be as much as two 2 bytes more than strictly needed */ +#define B64_maxbytelen(slen) (((slen) / B64outunit)*B64inunit) + +/* Returns strlen() of base64 string returned in "output" */ +int binary_to_base64(const void * data, int nbytes, char * output, int outlen); + +/* Returns the size of the binary object we returned in "output" */ +int base64_to_binary(const char * input, int inlen, void * output, int outlen); +#endif diff --git a/include/clplumbing/cl_log.h b/include/clplumbing/cl_log.h new file mode 100644 index 0000000..aa30fcd --- /dev/null +++ b/include/clplumbing/cl_log.h @@ -0,0 +1,99 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CLPLUMBING_CL_LOG_H +# define _CLPLUMBING_CL_LOG_H +# include <glib.h> +# include <syslog.h> + +#define TIME_T unsigned long +#define HA_FAIL 0 +#define HA_OK 1 +#define MAXLINE (512*10) + +/* this is defined by the caller */ +struct logspam { + const char *id; /* identifier */ + int max; /* maximum number of messages ... */ + time_t window; /* ... within this timeframe */ + time_t reset_time; /* log new messages after this time */ + const char *advice; /* what to log in case messages get suppressed */ +}; + +/* this is internal (oblique to the caller) */ +struct msg_ctrl { + struct logspam *lspam; /* */ + time_t *msg_slots; /* msg slot root (space for lspam->max) */ + int last; /* last used msg slot [0..lspam->max-1]; -1 on init */ + int cnt; /* current msg count [0..lspam->max] */ + time_t suppress_t; /* messages blocked since this time */ +}; + +struct IPC_CHANNEL; + +extern int debug_level; +#define ANYDEBUG (debug_level) +#define DEBUGDETAILS (debug_level >= 2) +#define DEBUGAUTH (debug_level >=3) +#define DEBUGMODULE (debug_level >=3) +#define DEBUGPKT (debug_level >= 4) +#define DEBUGPKTCONT (debug_level >= 5) + +void cl_direct_log(int priority, const char* buf, gboolean, const char*, int, TIME_T); +void cl_log(int priority, const char * fmt, ...) G_GNUC_PRINTF(2,3); +void cl_limit_log(struct msg_ctrl *ml, int priority, const char * fmt, ...) G_GNUC_PRINTF(3,4); +struct msg_ctrl *cl_limit_log_new(struct logspam *lspam); +void cl_limit_log_destroy(struct msg_ctrl *ml); +void cl_limit_log_reset(struct msg_ctrl *ml); +void cl_perror(const char * fmt, ...) G_GNUC_PRINTF(1,2); +void cl_log_enable_stderr(int truefalse); +void cl_log_enable_stdout(int truefalse); +gboolean cl_log_test_logd(void); +void cl_log_set_uselogd(int truefalse); +void cl_log_enable_syslog_filefmt(int truefalse); +void cl_log_use_buffered_io(int truefalse); +gboolean cl_log_get_uselogd(void); +void cl_log_set_facility(int facility); +void cl_log_set_entity(const char * entity); +void cl_log_set_syslogprefix(const char *prefix); +void cl_log_set_logfile(const char * path); +void cl_log_set_debugfile(const char * path); +void cl_inherit_logging_environment(int maxqlen); +int cl_log_set_logd_channel_source( void (*create_callback)(struct IPC_CHANNEL* chan), + GDestroyNotify destroy_callback); +int cl_log_get_logdtime(void); +void cl_log_set_logdtime(int logdintval); + +char * ha_timestamp(TIME_T t); +void cl_glib_msg_handler(const gchar *log_domain +, GLogLevelFlags log_level, const gchar *message +, gpointer user_data); + +void cl_flush_logs(void); +void cl_log_args(int argc, char **argv); +int cl_log_is_logd_fd(int fd); +const char * prio2str(int priority); + +/* cl_log_use_buffered_io and cl_log_do_fflush as optimization for logd, + * so it may buffer a few message lines, then fflush them out in one write. + * Set do_fsync != 0, if you even want it to fsync. */ +void cl_log_do_fflush(int do_fsync); +void cl_log_use_buffered_io(int truefalse); +/* We now keep the file handles open for a potentially very long time. + * Sometimes we may need to close them explicitly. */ +void cl_log_close_log_files(void); + +#endif diff --git a/include/clplumbing/cl_misc.h b/include/clplumbing/cl_misc.h new file mode 100644 index 0000000..6f698b5 --- /dev/null +++ b/include/clplumbing/cl_misc.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CLPLUMBING_CL_MISC_H +#define _CLPLUMBING_CL_MISC_H +int cl_str_to_boolean(const char*, int*); + +int cl_file_exists(const char* filename); + +char* cl_get_env(const char* env_name); + +int cl_binary_to_int(const char* data, int len); + +long cl_get_msec(const char * input); /* string to msec */ + +#endif diff --git a/include/clplumbing/cl_pidfile.h b/include/clplumbing/cl_pidfile.h new file mode 100644 index 0000000..3dba50f --- /dev/null +++ b/include/clplumbing/cl_pidfile.h @@ -0,0 +1,25 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LOCKFILE_H_ +#define _LOCKFILE_H_ + +int cl_read_pidfile(const char *filename); +int cl_read_pidfile_no_checking(const char *filename); +int cl_lock_pidfile(const char *filename); +int cl_unlock_pidfile(const char *filename); + +#endif diff --git a/include/clplumbing/cl_plugin.h b/include/clplumbing/cl_plugin.h new file mode 100644 index 0000000..e2431bf --- /dev/null +++ b/include/clplumbing/cl_plugin.h @@ -0,0 +1,29 @@ + + +/* + * cl_manage_plugin.c: This file handle plugin loading and deleting + * + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __CL_PLUGIN__ +#define __CL_PLUGIN__ + +int cl_remove_plugin(const char* type, const char* pluginname); +void* cl_load_plugin(const char* type, const char* pluginname); + +#endif diff --git a/include/clplumbing/cl_poll.h b/include/clplumbing/cl_poll.h new file mode 100644 index 0000000..1b1908f --- /dev/null +++ b/include/clplumbing/cl_poll.h @@ -0,0 +1,46 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef CLPLUMBING_CL_POLL_H +# define CLPLUMBING_CL_POLL_H + +#include <glib.h> +#include <sys/poll.h> + +/* + * Poll the file descriptors described by the NFDS structures starting at + * FDS. If TIMEOUT is nonzero and not -1, allow TIMEOUT milliseconds for + * an event to occur; if TIMEOUT is -1, block until an event occurs. + * Returns the number of file descriptors with events, zero if timed out, + * or -1 for errors. + * + * When available, this function uses POSIX signals, and Linux F_SETSIG() + * calls to provide this capability. When it is not available it + * uses the real poll() call. + * + */ +int cl_poll(struct pollfd *fds, unsigned int nfds, int timeout_ms); + +/* + * Call cl_poll_ignore() when you close a file descriptor you monitored + * via cl_poll() before, or if you don't want it monitored any more. + */ +int cl_poll_ignore(int fd); + +/* Select the signal you want us to use (must be a RT signal) */ +int cl_poll_setsig(int nsig); + +int cl_glibpoll(GPollFD* ufds, guint nfsd, gint timeout); +#endif diff --git a/include/clplumbing/cl_quorum.h b/include/clplumbing/cl_quorum.h new file mode 100644 index 0000000..b7798ba --- /dev/null +++ b/include/clplumbing/cl_quorum.h @@ -0,0 +1,44 @@ +/* + * quorum.h: head file for quorum module + * + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _QUORUM_H_ +#define _QUORUM_H_ + +#define HB_QUORUM_TYPE quorum +#define HB_QUORUM_TYPE_S "quorum" + +#define QUORUM_YES 0 +#define QUORUM_NO 1 +#define QUORUM_TIE 2 +typedef void(*callback_t)(void); +/* + * List of functions provided by implementations of the quorum interface. + */ +struct hb_quorum_fns { + + int (*getquorum) (const char* cluster + , int member_count, int member_quorum_votes + , int total_node_count, int total_quorum_votes); + int (*init) (callback_t notify, const char* cluster, const char* quorum_server); + void (*stop) (void); +}; + + +#endif diff --git a/include/clplumbing/cl_quorumd.h b/include/clplumbing/cl_quorumd.h new file mode 100644 index 0000000..6d282b3 --- /dev/null +++ b/include/clplumbing/cl_quorumd.h @@ -0,0 +1,48 @@ +/* + * quorum.h: head file for quorum module + * + * Author: Huang Zhen <zhenhltc@cn.ibm.com> + * Copyright (C) 2006 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _QUORUMD_H_ +#define _QUORUMD_H_ + +#define HB_QUORUMD_TYPE quorumd +#define HB_QUORUMD_TYPE_S "quorumd" + +#define CONFIGFILE HA_HBCONF_DIR"/quorumd.conf" +#define MAX_DN_LEN 256 +#define quorum_log(priority, fmt...); \ + cl_log(priority, fmt); \ + +#define quorum_debug(priority, fmt...); \ + if ( debug_level > 0 ) { \ + cl_log(priority, fmt); \ + } + +/* List of functions provided by implementations of the quorumd interface. */ +struct hb_quorumd_fns { + int (*test) (void); + int (*init) (void); + int (*load_config_file) (void); + int (*dump_data) (int priority); + int (*on_connect) (int sock, gnutls_session session, const char* CN); +}; + + +#endif diff --git a/include/clplumbing/cl_random.h b/include/clplumbing/cl_random.h new file mode 100644 index 0000000..d1e37ce --- /dev/null +++ b/include/clplumbing/cl_random.h @@ -0,0 +1,81 @@ + +/* + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * Copyright (C) 2005 International Business Machines Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdlib.h> + +/* Intended usage is srand(cl_randseed()). + * This returns on "as good as it gets" random number usually taken from + * /dev/urandom to have a nice seed for future random numbers generated by + * rand(). */ +unsigned int cl_randseed(void); + +/* get_next_random() currently rand() based. + * + * You probably want to use cl_rand_from_interval instead. + * + * You don't need to srand(), it will seed once with cl_randseed internally. + * + * It is called that way, because it was exposed in the header file for a long + * time, and used to be coded in an attempt to pregenerate a queue of random + * numbers from the mainloop, and it would shift the next random number from + * that queue and trigger generation of new random numbers "at idle time" to + * refill that queue. + * Only that functionality never actually worked, is not interessting anymore + * anyways (rand() is cheap enough), and is now ripped out. + * + * So it now does srand(cl_randseed()) once internally, + * and from there on is equivalent to calling rand() directly, + * and could be called cl_rand(). + * + * If you want your own specific rand seed to re-generate a particular + * sequence, call it once, throw away the return code, then call + * srand(yourseed). Or don't use it anywhere in your code. */ +int get_next_random(void); + +/* generate some random number in the range [a;b]; + * typically used to randomly delay messages. */ +#define HAVE_CL_RAND_FROM_INTERVAL 1 +static inline +int cl_rand_from_interval(const int a, const int b) +{ + /* + * Be careful here, you don't know RAND_MAX at coding time, + * only at compile time. If you think + * (int)(a + (rand()*(b-a)+(RAND_MAX/2))/RAND_MAX); + * was correct, think again with RAND_MAX = INT_MAX, + * which is the case for many rand() implementations nowadays. + * + * Don't do modulo, either, as that will skew the distribution, and + * still has possible wraparounds, or an insufficient input set for too + * small RAND_MAX. + * + * Rather do the whole calculation in 64 bit, which should be correct + * as long as r, a, b, and RAND_MAX are all int. + * Of course, if you prefer, you can do it with floating point as well. + */ +#if 1 /* use long long */ + long long r = get_next_random(); + r = a + (r * (b-a) + RAND_MAX/2)/RAND_MAX; +#else /* use floating point */ + int r = get_next_random(); + r = a + (int)(1.0 / RAND_MAX * r * (b-a) + 0.5); +#endif + return r; +} diff --git a/include/clplumbing/cl_reboot.h b/include/clplumbing/cl_reboot.h new file mode 100644 index 0000000..1c759c8 --- /dev/null +++ b/include/clplumbing/cl_reboot.h @@ -0,0 +1,6 @@ +#ifndef CLPLUMBING_CL_REBOOT_H +#define CLPLUMBING_CL_REBOOT_H 1 +#include <glib.h> +void cl_enable_coredump_before_reboot(gboolean yesno); /* not implemented in all OSes */ +void cl_reboot(int msdelaybeforereboot, const char * reason); +#endif diff --git a/include/clplumbing/cl_signal.h b/include/clplumbing/cl_signal.h new file mode 100644 index 0000000..1a13a6b --- /dev/null +++ b/include/clplumbing/cl_signal.h @@ -0,0 +1,91 @@ +/* + * cl_signal.h: signal handling routines to be used by Linux-HA programmes + * + * Copyright (C) 2002 Horms <horms@verge.net.au> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef _CL_SIGNAL_H +#define _CL_SIGNAL_H + +#include <stdio.h> +#include <signal.h> +#include <sys/signal.h> + +typedef struct { + int sig; + void (*handler)(int); + int interrupt; +} cl_signal_mode_t; + +#define CL_SIGNAL(_sig, _handler) \ + cl_signal_set_simple_handler((_sig), (_handler), NULL) +#if HAVE_SIGIGNORE +#define CL_IGNORE_SIG(_sig) sigignore((_sig)) +#else +#define CL_IGNORE_SIG(_sig) CL_SIGNAL((_sig), SIG_IGN) +#endif +#define CL_DEFAULT_SIG(_sig) CL_SIGNAL((_sig), SIG_DFL) + +#define CL_SIGINTERRUPT(_sig, _flag) siginterrupt((_sig), (_flag)) + +#define CL_SIGACTION(_signum, _act, _oldact) \ + sigaction((_signum), (_act), (_oldact)) +#define CL_SIGPROCMASK(_how, _set, _oldset) \ + cl_signal_block_set((_how), (_set), (_oldset)) +#define CL_SIGPENDING(_set) sigpending(_set) +#define CL_SIGSUSPEND(_mask) sigsuspend(_mask) + +#define CL_SIGEMPTYSET(_set) sigemptyset(_set) +#define CL_SIGFILLSET(_set) sigfillset(_set) +#define CL_SIGADDSET(_set, _signum) sigaddset((_set), (_signum)) +#define CL_SIGDELSET(_set, _signum) sigdelset((_set), (_signum)) +#define CL_SIGISMEMBER(_set, _signum) sigmember((_set), (_signum)) + +#define CL_KILL(_pid, _sig) kill((_pid), (_sig)) + +#define CL_PID_EXISTS(_pid) ( CL_KILL((_pid), 0) >= 0 || errno != ESRCH ) + +int +cl_signal_set_handler(int sig, void (*handler)(int), sigset_t *mask +, int flags, struct sigaction *oldact); + +int +cl_signal_set_simple_handler(int sig, void (*handler)(int) +, struct sigaction *oldact); + +int +cl_signal_set_action(int sig, void (*action)(int, siginfo_t *, void *) +, sigset_t *mask, int flags, struct sigaction *oldact); + +int +cl_signal_set_simple_action(int sig, void (*action)(int, siginfo_t *, void *) +, struct sigaction *oldact); + +int +cl_signal_set_interrupt(int sig, int flag); + +int +cl_signal_block(int how, int signal, sigset_t *oldset); + +int +cl_signal_block_set(int how, const sigset_t *set, sigset_t *oldset); + +int +cl_signal_set_handler_mode(const cl_signal_mode_t *mode, sigset_t *set); + + +#endif /* _CL_SIGNAL_H */ diff --git a/include/clplumbing/cl_syslog.h b/include/clplumbing/cl_syslog.h new file mode 100644 index 0000000..a7c1bfa --- /dev/null +++ b/include/clplumbing/cl_syslog.h @@ -0,0 +1,32 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * Functions to support syslog. + * David Lee (c) 2005 + */ + +#ifndef _CLPLUMBING_CL_SYSLOG_H +#define _CLPLUMBING_CL_SYSLOG_H + +/* Convert string "auth" to equivalent number "LOG_AUTH" etc. */ +int cl_syslogfac_str2int(const char *); + +/* Convert number "LOG_AUTH" to equivalent string "auth" etc. */ +/* Returns static string; caller must NOT free. */ +const char *cl_syslogfac_int2str(int); + +#endif /* _CLPLUMBING_CL_SYSLOG_H */ diff --git a/include/clplumbing/cl_tiebreaker.h b/include/clplumbing/cl_tiebreaker.h new file mode 100644 index 0000000..11c10c4 --- /dev/null +++ b/include/clplumbing/cl_tiebreaker.h @@ -0,0 +1,35 @@ +/* + * cl_tiebreaker.h: head file for tiebreaker module + * + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CL_TIEBREAKER_H_ +#define _CL_TIEBREAKER_H_ + +#define HB_TIEBREAKER_TYPE tiebreaker +#define HB_TIEBREAKER_TYPE_S "tiebreaker" + +/* + * List of functions provided by implementations of tiebreaker interface. + */ +struct hb_tiebreaker_fns { + gboolean (*break_tie) (int, int); +}; + + +#endif diff --git a/include/clplumbing/cl_uuid.h b/include/clplumbing/cl_uuid.h new file mode 100644 index 0000000..12542cd --- /dev/null +++ b/include/clplumbing/cl_uuid.h @@ -0,0 +1,40 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CL_UUID_H_ +#define _CL_UUID_H_ +#include <glib.h> + +typedef struct cl_uuid_s{ + unsigned char uuid[16]; +}cl_uuid_t; + +void cl_uuid_copy(cl_uuid_t* dst, cl_uuid_t* src); +void cl_uuid_clear(cl_uuid_t* uu); +int cl_uuid_compare(const cl_uuid_t* uu1, const cl_uuid_t* uu2); +void cl_uuid_generate(cl_uuid_t* out); +int cl_uuid_is_null(cl_uuid_t* uu); +int cl_uuid_parse( char *in, cl_uuid_t* uu); +#define UU_UNPARSE_SIZEOF 37 /* Including NULL byte */ +void cl_uuid_unparse(const cl_uuid_t* uu, char *out); + +/* Suitable for ues as a GHashFunc from glib */ +guint cl_uuid_g_hash(gconstpointer uuid_ptr); +/* Suitable for ues as a GEqualFunc from glib */ +gboolean cl_uuid_g_equal(gconstpointer uuid_ptr_a, gconstpointer uuid_ptr_b); + + +#endif diff --git a/include/clplumbing/coredumps.h b/include/clplumbing/coredumps.h new file mode 100644 index 0000000..4d5ce79 --- /dev/null +++ b/include/clplumbing/coredumps.h @@ -0,0 +1,36 @@ +/* + * Basic Core dump control functions. + * + * Copyright (C) 2004 IBM Corporation + * + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef _CLPLUMBING_COREFILES_H +# define _CLPLUMBING_COREFILES_H 1 + /* Set the root directory of our core directory hierarchy */ +int cl_set_corerootdir(const char * dir); + /* Change directory to the directory our core file needs to go in */ + /* Call after you establish the userid you're running under */ +int cl_cdtocoredir(void); + /* Enable/disable core dumps for ourselves and our child processes */ +int cl_enable_coredumps(int truefalse); +void cl_untaint_coredumps(void); +void cl_set_coredump_signal_handler(int nsig); +void cl_set_all_coredump_signal_handlers(void); + +#endif diff --git a/include/clplumbing/cpulimits.h b/include/clplumbing/cpulimits.h new file mode 100644 index 0000000..f7dd875 --- /dev/null +++ b/include/clplumbing/cpulimits.h @@ -0,0 +1,66 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * Functions to put limits on CPU consumption. + * This allows us to better catch runaway realtime processes that + * might otherwise hang the whole system. + * + * The process is basically this: + * - Set the CPU percentage limit with cl_cpu_limit_setpercent() + * according to what you expect the CPU percentage to top out at + * measured over an interval at >= 10 seconds + * - Call cl_cpu_limit_ms_interval() to figure out how often to update + * the CPU limit (it returns milliseconds) + * - At least as often as indicated above, call cl_cpu_limit_update() + * to update our current CPU limit. + * + * These limits are approximate, so be a little conservative. + * If you've gone into an infinite loop, it'll likely get caught ;-) + * + * Note that exceeding the soft CPU limits we set here will cause a + * SIGXCPU signal to be sent. + * + * The default action for this signal is to cause a core dump. + * This is a good choice ;-) + * + * As of this writing, this code will never set the soft CPU limit less + * than two seconds, or greater than 10 seconds. + * + * It will currrently return a limit update interval between 10000 and + * 400000 milliseconds. + * + */ + +/* + * Set expected CPU percentage upper bound + */ +int cl_cpu_limit_setpercent(int ipercent); + +/* + * Update the current CPU limit + */ +int cl_cpu_limit_update(void); + +/* + * How often should we call cl_cpu_limit_update()? + * + * Note: return result is in milliseconds + */ +int cl_cpu_limit_ms_interval(void); + +/* Disable further CPU limits... */ +int cl_cpu_limit_disable(void); diff --git a/include/clplumbing/ipc.h b/include/clplumbing/ipc.h new file mode 100644 index 0000000..4a5e151 --- /dev/null +++ b/include/clplumbing/ipc.h @@ -0,0 +1,788 @@ +/* + * ipc.h IPC abstraction data structures. + * + * author Xiaoxiang Liu <xiliu@ncsa.uiuc.edu>, + * Alan Robertson <alanr@unix.sh> + * + * + * Copyright (c) 2002 International Business Machines + * Copyright (c) 2002 Xiaoxiang Liu <xiliu@ncsa.uiuc.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _IPC_H_ +#define _IPC_H_ +#include <glib.h> +#undef MIN +#undef MAX +#include <sys/types.h> +#include <sys/poll.h> + +#ifdef IPC_TIME_DEBUG +#include <clplumbing/longclock.h> +#define MAXIPCTIME 3000 + +#endif + +/* constants */ +#define DEFAULT_MAX_QLEN 64 +#define MAX_MSGPAD 128 +/* channel and connection status */ +#define IPC_CONNECT 1 /* Connected: can read, write */ +#define IPC_WAIT 2 /* Waiting for connection */ +#define IPC_DISCONNECT 3 /* Disconnected, can't read or write*/ +#define IPC_DISC_PENDING 4 /* Disconnected, can't write but */ + /* may be more data to read */ + +#define MAXFAILREASON 128 + +#define IPC_SERVER 1 +#define IPC_CLIENT 2 +#define IPC_PEER 3 + +#define IPC_ISRCONN(ch) ((ch)->ch_status == IPC_CONNECT \ + || (ch)->ch_status == IPC_DISC_PENDING) + +#define IPC_ISWCONN(ch) ((ch)->ch_status == IPC_CONNECT) + +/* general return values */ +#define IPC_OK 0 +#define IPC_FAIL 1 +#define IPC_BROKEN 2 +#define IPC_INTR 3 +#define IPC_TIMEOUT 4 + +/* + * IPC: Sockets-like Interprocess Communication Abstraction + * + * We have two fundamental abstractions which we maintain. + * Everything else is in support of these two abstractions. + * + * These two main abstractions are: + * + * IPC_WaitConnection: + * A server-side abstraction for waiting for someone to connect. + * + * IPC_Channel: + * An abstraction for an active communications channel. + * + * All the operations on these two abstractions are carried out + * via function tables (channel->ops). Below we refer to the + * function pointers in these tables as member functions. + * + * On the server side, everything starts up with a call to + * ipc_wait_conn_constructor(), which returns an IPC_WaitConnection. + * + * Once the server has the IPC_WaitConnection object in hand, + * it can give the result of the get_select_fd() member function + * to poll or select to inform you when someone tries to connect. + * + * Once select tells you someone is trying to connect, you then + * use the accept_connection() member function to accept + * the connection. accept_connection() returns an IPC_Channel. + * + * With that, the server can talk to the client, and away they + * go ;-) + * + * On the client side, everything starts up with a call to + * ipc_channel_constructor() which we use to talk to the server. + * The client is much easier ;-) + */ + + +typedef struct IPC_WAIT_CONNECTION IPC_WaitConnection; +typedef struct IPC_CHANNEL IPC_Channel; + +typedef struct IPC_MESSAGE IPC_Message; +typedef struct IPC_QUEUE IPC_Queue; +typedef struct IPC_AUTH IPC_Auth; + +typedef struct IPC_OPS IPC_Ops; +typedef struct IPC_WAIT_OPS IPC_WaitOps; + + + +/* wait connection structure. */ +struct IPC_WAIT_CONNECTION{ + int ch_status; /* wait conn. status.*/ + void * ch_private; /* wait conn. private data. */ + IPC_WaitOps *ops; /* wait conn. function table .*/ +}; + + +typedef void(*flow_callback_t)(IPC_Channel*, void*); + +/* channel structure.*/ +struct IPC_CHANNEL{ + int ch_status; /* identify the status of channel.*/ + int refcount; /* reference count */ + pid_t farside_pid; /* far side pid */ + void* ch_private; /* channel private data. */ + /* (may contain conn. info.) */ + IPC_Ops* ops; /* IPC_Channel function table.*/ + + /* number of bytes needed + * at the begginging of <ipcmessage>->msg_body + * it's for msg head needed to tranmit in wire + */ + unsigned int msgpad; + + /* the number of bytes remainng to send for the first message in send queue + 0 means nothing has been sent thus all bytes needs to be send + n != 0 means there are still n bytes needs to be sent + */ + unsigned int bytes_remaining; + + + /* is the send blocking or nonblocking*/ + gboolean should_send_block; + + /* if send would block, should an error be returned or not */ + gboolean should_block_fail; + +/* There are two queues in channel. One is for sending and the other + * is for receiving. + * Those two queues are channel's internal queues. They should not be + * accessed directly. + */ + /* private: */ + IPC_Queue* send_queue; + IPC_Queue* recv_queue; + + /* buffer pool for receive in this channel*/ + struct ipc_bufpool* pool; + + /* the follwing is for send flow control*/ + int high_flow_mark; + int low_flow_mark; + void* high_flow_userdata; + void* low_flow_userdata; + flow_callback_t high_flow_callback; + flow_callback_t low_flow_callback; + + int conntype; + + char failreason[MAXFAILREASON]; + + /* New members to support Multi-level ACLs for the CIB, + * available since libplumb.so.2.1.0, added at the + * end of the struct to maintain backwards ABI compatibility. + * + * If you don't like to care for library versions, + * create your IPC channels with + * c = ipc_wait_conn_constructor(IPC_UDS_CRED, ...), + * and these members will be available. + */ + uid_t farside_uid; /* far side uid */ + gid_t farside_gid; /* far side gid */ +}; + +struct IPC_QUEUE{ + size_t current_qlen; /* Current qlen */ + size_t max_qlen; /* Max allowed qlen */ + GList* queue; /* List of messages */ + /* keep the time of the last max queue warning */ + time_t last_maxqlen_warn; + /* and the number of messages lost */ + unsigned maxqlen_cnt; +}; + +/* authentication information : set of gids and uids */ +struct IPC_AUTH { + GHashTable * uid; /* hash table for user id */ + GHashTable * gid; /* hash table for group id */ +}; + + +/* Message structure. */ +struct IPC_MESSAGE{ + size_t msg_len; + void* msg_buf; + void* msg_body; +/* + * IPC_MESSAGE::msg_done + * the callback function pointer which can be called after this + * message is sent, received or otherwise processed. + * + * Parameter: + * msg: the back pointer to the message which contains this + * function pointer. + * + */ + void (* msg_done)(IPC_Message * msg); + void* msg_private; /* the message private data. */ + /* Belongs to message creator */ + /* May be used by callback function. */ + IPC_Channel * msg_ch; /* Channel the */ + /* message is from/in */ + +}; + +struct IPC_WAIT_OPS{ +/* + * IPC_WAIT_OPS::destroy + * destroy the wait connection and free the memory space used by + * this wait connection. + * + * Parameters: + * wait_conn (IN): the pointer to the wait connection. + * + */ + void (* destroy)(IPC_WaitConnection *wait_conn); +/* + * IPC_WAIT_OPS::get_select_fd + * provide a fd which user can listen on for a new coming connection. + * + * Parameters: + * wait_conn (IN) : the pointer to the wait connection which + * we're supposed to return the file descriptor for + * (the file descriptor can be used with poll too ;-)) + * + * Return values: + * integer >= 0 : the select_fd. + * -1 : can't get the select fd. + * + */ + int (* get_select_fd)(IPC_WaitConnection *wait_conn); +/* + * IPC_WAIT_OPS::accept_connection + * accept and create a new connection and verify the authentication. + * + * Parameters: + * wait_conn (IN) : the waiting connection which will accept + * create the new connection. + * auth_info (IN) : the authentication information which will be + * verified for the new connection. + * + * Return values: + * the pointer to the new IPC channel; NULL if the creation or + * authentication fails. + * + */ + IPC_Channel * (* accept_connection) + (IPC_WaitConnection * wait_conn, IPC_Auth *auth_info); +}; + +/* Standard IPC channel operations */ + +struct IPC_OPS{ +/* + * IPC_OPS::destroy + * brief destroy the channel object. + * + * Parameters: + * ch (IN) : the pointer to the channel which will be destroyed. + * + */ + void (*destroy) (IPC_Channel * ch); +/* + * IPC_OPS::initiate_connection + * used by service user side to set up a connection. + * + * Parameters: + * ch (IN) : the pointer to channel used to initiate the connection. + * + * Return values: + * IPC_OK : the channel set up the connection successfully. + * IPC_FAIL : the connection initiation fails. + * + */ + int (* initiate_connection) (IPC_Channel * ch); +/* + * IPC_OPS::verify_auth + * used by either side to verify the identity of peer on connection. + * + * Parameters + * ch (IN) : the pointer to the channel. + * + * Return values: + * IPC_OK : the peer is trust. + * IPC_FAIL : verifying authentication fails. + */ + int (* verify_auth) (IPC_Channel * ch, IPC_Auth* info); +/* + * IPC_OPS::assert_auth + * service user asserts to be certain qualified service user. + * + * Parameters: + * ch (IN): the active channel. + * auth (IN): the hash table which contains the asserting information. + * + * Return values: + * IPC_OK : assert the authentication successfully. + * IPC_FAIL : assertion fails. + * + * NOTE: This operation is a bit obscure. It isn't needed with + * UNIX domain sockets at all. The intent is that some kinds + * of IPC (like FIFOs), do not have an intrinsic method to + * authenticate themselves except through file permissions. + * The idea is that you must tell it how to chown/grp your + * FIFO so that the other side and see that if you can write + * this, you can ONLY be the user/group they expect you to be. + * But, I think the parameters may be wrong for this ;-) + */ + int (* assert_auth) (IPC_Channel * ch, GHashTable * auth); +/* + * IPC_OPS::send + * send the message through the sending connection. + * + * Parameters: + * ch (IN) : the channel which contains the connection. + * msg (IN) : pointer to the sending message. User must + * allocate the message space. + * + * Return values: + * IPC_OK : the message was either sent out successfully or + * appended to the send_queue. + * IPC_FAIL : the send operation failed. + * IPC_BROKEN : the channel is broken. + * +*/ + int (* send) (IPC_Channel * ch, IPC_Message* msg); + +/* + * IPC_OPS::recv + * receive the message through receving queue. + * + * Parameters: + * ch (IN) : the channel which contains the connection. + * msg (OUT): the IPC_MESSAGE** pointer which contains the pointer + * to the received message or NULL if there is no + * message available. + * + * Return values: + * IPC_OK : receive operation is completed successfully. + * IPC_FAIL : operation failed. + * IPC_BROKEN : the channel is broken (disconnected) + * + * Note: + * return value IPC_OK doesn't mean the message is already + * sent out to (or received by) the peer. It may be pending + * in the send_queue. In order to make sure the message is no + * longer needed, please specify the msg_done function in the + * message structure and once this function is called, the + * message is no longer needed. + * + * is_sending_blocked() is another way to check if there is a message + * pending in the send_queue. + * + */ + int (* recv) (IPC_Channel * ch, IPC_Message** msg); + +/* + * IPC_OPS::waitin + * Wait for input to become available + * + * Parameters: + * ch (IN) : the channel which contains the connection. + * + * Side effects: + * If output becomes unblocked while waiting, it will automatically + * be resumed without comment. + * + * Return values: + * IPC_OK : a message is pending or output has become unblocked. + * IPC_FAIL : operation failed. + * IPC_BROKEN : the channel is broken (disconnected) + * IPC_INTR : waiting was interrupted by a signal + */ + int (* waitin) (IPC_Channel * ch); +/* + * IPC_OPS::waitout + * Wait for output to finish + * + * Parameters: + * ch (IN) : the channel which contains the connection. + * + * Side effects: + * If input becomes available while waiting, it will automatically + * be read into the channel queue without comment. + * + * Return values: + * IPC_OK : output no longer blocked + * IPC_FAIL : operation failed. + * IPC_BROKEN : the channel is broken (disconnected) + * IPC_INTR : waiting was interrupted by a signal + */ + int (* waitout) (IPC_Channel * ch); + +/* + * IPC_OPS::is_message_pending + * check to see if there is any messages ready to read, or hangup has + * occurred. + * + * Parameters: + * ch (IN) : the pointer to the channel. + * + * Return values: + * TRUE : there are messages ready to read, or hangup. + * FALSE: there are no messages ready to be read. + */ + gboolean (* is_message_pending) (IPC_Channel * ch); + +/* + * IPC_OPS::is_sending_blocked + * check the send_queue to see if there are any messages blocked. + * + * Parameters: + * ch (IN) : the pointer to the channel. + * + * Return values: + * TRUE : there are messages blocked (waiting) in the send_queue. + * FALSE: there are no message blocked (waiting) in the send_queue. + * + * See also: + * get_send_select_fd() + */ + gboolean (* is_sending_blocked) (IPC_Channel *ch); + +/* + * IPC_OPS::resume_io + * Resume all possible IO operations through the IPC transport + * + * Parameters: + * the pointer to the channel. + * + * Return values: + * IPC_OK : resume all the possible I/O operation successfully. + * IPC_FAIL : the operation fails. + * IPC_BROKEN : the channel is broken. + * + */ + int (* resume_io) (IPC_Channel *ch); +/* + * IPC_OPS::get_send_select_fd() + * return a file descriptor which can be given to select/poll. This fd + * is used by the IPC code for sending. It is intended that this be + * ONLY used with select, poll, or similar mechanisms, not for direct I/O. + * Note that due to select(2) and poll(2) semantics, you must check + * is_sending_blocked() to see whether you should include this FD in + * your poll for writability, or you will loop very fast in your + * select/poll loop ;-) + * + * Parameters: + * ch (IN) : the pointer to the channel. + * + * Return values: + * integer >= 0 : the send fd for selection. + * -1 : there is no send fd. + * + * See also: + * is_sending_blocked() + */ + int (* get_send_select_fd) (IPC_Channel * ch); +/* + * IPC_OPS::get_recv_select_fd + * return a file descriptor which can be given to select. This fd + * is for receiving. It is intended that this be ONLY used with select, + * poll, or similar mechanisms, NOT for direct I/O. + * + * Parameters: + * ch (IN) : the pointer to the channel. + * + * Return values: + * integer >= 0 : the recv fd for selection. + * -1 : there is no recv fd. + * + * NOTE: This file descriptor is often the same as the send + * file descriptor. + */ + int (* get_recv_select_fd) (IPC_Channel * ch); +/* + * IPC_OPS::set_send_qlen + * allow user to set the maximum send_queue length. + * + * Parameters + * ch (IN) : the pointer to the channel. + * q_len (IN) : the max length for the send_queue. + * + * Return values: + * IPC_OK : set the send queue length successfully. + * IPC_FAIL : there is no send queue. (This isn't supposed + * to happen). + * It means something bad happened. + * + */ + int (* set_send_qlen) (IPC_Channel * ch, int q_len); +/* + * IPC_OPS::set_recv_qlen + * allow user to set the maximum recv_queue length. + * + * Parameters: + * ch (IN) : the pointer to the channel. + * q_len (IN) : the max length for the recv_queue. + * + * Return values: + * IPC_OK : set the recv queue length successfully. + * IPC_FAIL : there is no recv queue. + * + */ + int (* set_recv_qlen) (IPC_Channel * ch, int q_len); + + +/* + * IPC_OPS: set callback for high/low flow mark + * ch (IN) : the pointer to the channel + * callback (IN) : the callback function + * user_data(IN) : a pointer to user_data + * callback will be called with channel and + * this user_data as parameters + * + * Return values: + * void + * + */ + + + void (* set_high_flow_callback) (IPC_Channel* ch , + flow_callback_t callback, + void* user_data); + + void (* set_low_flow_callback) (IPC_Channel* ch , + flow_callback_t callback, + void* user_data); + +/* + * IPC_OPS::new_ipcmsg + * ch (IN) : the pointer to the channel + * data (IN) : data to be copied to the message body + * len (IN) : data len + * private (IN): the pointer value to set as in the message + * + * Return values: + * the pointer to a new created message will be + * returned if success or NULL if failure + * + */ + + IPC_Message* (*new_ipcmsg)(IPC_Channel* ch, const void* data, + int len, void* private); + + +/* + * IPC_OPS::nget_chan_status + * ch (IN) : the pointer to the channel + * + * Return value: + * channel status. + * + */ + int (*get_chan_status)(IPC_Channel* ch); + + +/* + * These two functions returns true if the corresponding queue + * is full, otherwise it returns false + */ + + gboolean (*is_sendq_full)(struct IPC_CHANNEL * ch); + gboolean (*is_recvq_full)(struct IPC_CHANNEL * ch); + + + /* Get the connection type for the channel + * it can be IPC_SERVER, IPC_CLIENT, IPC_PEER + */ + + int (*get_conntype)(struct IPC_CHANNEL* ch); + + int (*disconnect)(struct IPC_CHANNEL* ch); + +}; + + +/* + * ipc_wait_conn_constructor: + * the common constructor for ipc waiting connection. + * Use ch_type to identify the connection type. Usually it's only + * needed by server side. + * + * Parameters: + * ch_type (IN) : the type of the waiting connection to create. + * ch_attrs (IN) : the hash table which contains the attributes + * needed by this waiting connection in name/value + * pair format. + * + * For example, the only attribute needed by UNIX + * domain sockets is path name. + * + * Return values: + * the pointer to a new waiting connection or NULL if the connection + * can't be created. + * Note: + * current implementation supports + * IPC_ANYTYPE: This is what program code should typically use. + * Internally it is an alias to IPC_UDS_CRED. + * IPC_UDS_CRED: Unix Domain Sockets, + * farside uid + gid credentials is available. + * Available since libplumb.so.2.1.0. + * IPC_DOMAIN_SOCKET: An other alias to Unix Domain Sockets; + * internally it is equivalent to both above. + * Using this explicitly, your code will work + * even with libplumb.so.2.0.0. + * Which also means that you MUST NOT use the + * farside_uid/gid functionality then. + */ +extern IPC_WaitConnection * ipc_wait_conn_constructor(const char * ch_type +, GHashTable* ch_attrs); + +/* + * ipc_channel_constructor: + * brief the common constructor for ipc channel. + * Use ch_type to identify the channel type. + * Usually this function is only called by client side. + * + * Parameters: + * ch_type (IN): the type of the channel you want to create. + * ch_attrs (IN): the hash table which contains the attributes needed + * by this channel. + * For example, the only attribute needed by UNIX domain + * socket is path name. + * + * Return values: + * the pointer to the new channel whose status is IPC_DISCONNECT + * or NULL if the channel can't be created. + * + * Note: + * See comments for ipc_wait_conn_constructor above + * for currently implemented ch_type channel types. + */ +extern IPC_Channel * ipc_channel_constructor(const char * ch_type +, GHashTable* ch_attrs); + +/* + * ipc_channel_pair: + * Construct a pair of connected IPC channels in a fashion analogous + * to pipe(2) or socketpair(2). + * + * Parameters: + * channels: an array of two IPC_Channel pointers for return result + */ +int ipc_channel_pair(IPC_Channel* channels[2]); + +/* + * ipc_set_auth: + * A helper function used to convert array of uid and gid into + * an authentication structure (IPC_Auth) + * + * Parameters: + * a_uid (IN): the array of a set of user ids. + * a_gid (IN): the array of a set of group ids. + * num_uid (IN): the number of user ids. + * num_gid (IN): the number of group ids. + * + * Return values: + * the pointer to the authentication structure which contains the + * set of uid and the set of gid. Or NULL if this structure can't + * be created. + * + */ + + +IPC_Auth* ipc_str_to_auth(const char * uidlist, int, const char * gidlist, int); + +extern IPC_Auth * ipc_set_auth(uid_t * a_uid, gid_t * a_gid +, int num_uid, int num_gid); + +/* Destroys an object constructed by ipc_set_auth or ipc_str_to_auth() */ +extern void ipc_destroy_auth(IPC_Auth * auth); + +extern void ipc_set_pollfunc(int (*)(struct pollfd*, unsigned int, int)); +extern void ipc_bufpool_dump_stats(void); + +#ifdef IPC_TIME_DEBUG + +enum MSGPOS_IN_IPC{ + MSGPOS_ENQUEUE, + MSGPOS_SEND, + MSGPOS_RECV, + MSGPOS_DEQUEUE +}; + +#endif + + +struct SOCKET_MSG_HEAD{ + int msg_len; + unsigned int magic; +#ifdef IPC_TIME_DEBUG + longclock_t enqueue_time; + longclock_t send_time; + longclock_t recv_time; + longclock_t dequeue_time; +#endif + +}; + + +/* MAXMSG is the maximum final message size on the wire. */ +#define MAXMSG (256*1024) +/* MAXUNCOMPRESSED is the maximum, raw data size prior to compression. */ +/* 1:8 compression ratio is to be expected on data such as xml */ +#define MAXUNCOMPRESSED (2048*1024) +#define HEADMAGIC 0xabcd +#define POOL_SIZE (4*1024) +struct ipc_bufpool{ + + int refcount; + char* currpos; + char* consumepos; + char* startpos; + char* endpos; + int size; +}; + +struct ipc_bufpool* ipc_bufpool_new(int); + +void ipc_bufpool_del(struct ipc_bufpool* pool); + +int ipc_bufpool_spaceleft(struct ipc_bufpool* pool); + +int ipc_bufpool_update(struct ipc_bufpool* pool, + struct IPC_CHANNEL * ch, + int msg_len, + IPC_Queue* rqueue); + +gboolean ipc_bufpool_full(struct ipc_bufpool* pool, + struct IPC_CHANNEL* ch, + int*); +int ipc_bufpool_partial_copy(struct ipc_bufpool* dstpool, + struct ipc_bufpool* srcpool); + +void ipc_bufpool_ref(struct ipc_bufpool* pool); + +void ipc_bufpool_unref(struct ipc_bufpool* pool); + +void set_ipc_time_debug_flag(gboolean flag); + +/* pathname attribute */ +#define IPC_PATH_ATTR "path" +/* socket mode attribute */ +#define IPC_MODE_ATTR "sockmode" +/* Unix domain socket, used by old code. + * See also the comment block above ipc_wait_conn_constructor() */ +#define IPC_DOMAIN_SOCKET "uds" +/* Unix domain socket with farside uid + gid credentials. + * Available since libplumb.so.2.1.0 */ +#define IPC_UDS_CRED "uds_c" + +#ifdef IPC_UDS_CRED +# define IPC_ANYTYPE IPC_UDS_CRED +#else +# error "No IPC types defined(!)" +#endif + +#endif diff --git a/include/clplumbing/loggingdaemon.h b/include/clplumbing/loggingdaemon.h new file mode 100644 index 0000000..ba986f5 --- /dev/null +++ b/include/clplumbing/loggingdaemon.h @@ -0,0 +1,32 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* Messages sent to the logging daemon */ +#define LD_LOGIT 2 +#define MAXENTITY 64 + +/* Message contains following header, followed by the text (char[]) itself */ +struct LogDaemonMsgHdr_s { + int msgtype; + int facility; + int priority; + int msglen; + gboolean use_pri_str; + int entity_pid; + char entity[MAXENTITY]; + TIME_T timestamp; +}; +typedef struct LogDaemonMsgHdr_s LogDaemonMsgHdr; diff --git a/include/clplumbing/longclock.h b/include/clplumbing/longclock.h new file mode 100644 index 0000000..ae95b28 --- /dev/null +++ b/include/clplumbing/longclock.h @@ -0,0 +1,143 @@ +/* + * Longclock operations + * + * Copyright (c) 2002 International Business Machines + * Author: Alan Robertson <alanr@unix.sh> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _LONGCLOCK_H +# define _LONGCLOCK_H +/* + * A longclock_t object is a lot like a clock_t object, except that it + * won't wrap in the lifetime of the earth. It is guaranteed to be at + * least 64 bits. This means it should go for around 2 billion years. + * + * It is also supposed to be proof against changes in the local time on + * the computer. This is easy if you have a properly-working times(2) + * for us to use. + * + * longclock_t's are definitely not comparable between computers, and in + * some implementations, not even between processes on the same computer. + * + * + * The functions provided here are: + * + * unsigned long cl_times(void); + * A rational wrapper for the times(2) call + * for those cases where only the return value + * is wanted. + * longclock_t time_longclock(void); + * Returns current time as a longclock_t. + * + * longclock_t msto_longclock(unsigned long); + * Converts quantity in milliseconds to longclock_t + * + * unsigned long longclockto_ms(longclock_t); + * Converts quantity in longclock_t to milliseconds + * NOTE: Can overflow! + * + * unsigned long longclockto_long(longclock_t); + * Converts quantity in longclock_t to clock_t + * NOTE: Can overflow! + * + * longclock_t secsto_longclock(unsigned long); + * Converts quantity in seconds to longclock_t + * + * longclock_t add_longclock(longclock_t l, longclock_t r); + * Adds two longclock_t values + * + * int cmp_longclock(longclock_t l, longclock_t r); + * Returns negative, zero or positive value + * + * longclock_t sub_longclock(longclock_t l, longclock_t r); + * Subtracts two longclock_t values + * NOTE: Undefined if l is < r + * + * longclock_t dsecsto_longclock(double); + * Converts quantity in seconds (as a double) + * to a longclock_t + * + * unsigned hz_longclock(void); + * Returns frequency of longclock_t clock. + * + * We provide this constant: + * + * extern const longclock_t zero_longclock; + */ +extern unsigned long cl_times(void); + +#ifdef CLOCK_T_IS_LONG_ENOUGH +# ifndef HAVE_LONGCLOCK_ARITHMETIC +# define HAVE_LONGCLOCK_ARITHMETIC +# endif + +# include <sys/times.h> + + typedef clock_t longclock_t; + +#else /* clock_t isn't at least 64 bits */ + typedef unsigned long long longclock_t; +#endif + +longclock_t time_longclock(void); + +extern const longclock_t zero_longclock; + +unsigned hz_longclock(void); +longclock_t secsto_longclock(unsigned long); +longclock_t dsecsto_longclock(double); +longclock_t msto_longclock(unsigned long); +unsigned long longclockto_ms(longclock_t); /* Can overflow! */ +long longclockto_long(longclock_t); /* May overflow! */ + + +#ifndef HAVE_LONGCLOCK_ARITHMETIC + +longclock_t add_longclock(longclock_t l, longclock_t r); + + /* Undefined if l is < r according to cmp_longclock() */ +longclock_t sub_longclock(longclock_t l, longclock_t r); + +int cmp_longclock(longclock_t l, longclock_t r); + + +#else /* We HAVE_LONGCLOCK_ARITHMETIC */ + +# define longclockto_long(lc) ((long)(lc)) + +# define add_longclock(l,r) \ + ((longclock_t)(l) + (longclock_t)(r)) + +# define sub_longclock(l,r) \ + ((longclock_t)(l) - (longclock_t)(r)) + +# define cmp_longclock(l,r) \ + (((longclock_t)(l) < (longclock_t)(r)) \ + ? -1 \ + : (((longclock_t)(l) > (longclock_t)(r)) \ + ? +1 : 0)) +#endif + + +/* N.B: Possibly not the best place for this, but it will do for now */ +/* This is consistent with OpenBSD, and is a good choice anyway */ +#define TIME_T unsigned long +#define TIME_F "%lu" +#define TIME_X "%lx" + +#endif diff --git a/include/clplumbing/lsb_exitcodes.h b/include/clplumbing/lsb_exitcodes.h new file mode 100644 index 0000000..e46b5be --- /dev/null +++ b/include/clplumbing/lsb_exitcodes.h @@ -0,0 +1,92 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* LSB status exit codes. + * + * All of these and the supporting text are taken from the LSB. + * + * If the status command is given, the init script will return + * the following exit status codes. + * + * 0 program is running or service is OK + * 1 program is dead and /var/run pid file exists + * 2 program is dead and /var/lock lock file exists + * 3 program is stopped + * 4 program or service status is unknown + * 5-99 reserved for future LSB use + * 100-149 reserved for distribution use + * 150-199 reserved for application use + * 200-254 reserved + */ + +#define LSB_STATUS_OK 0 +#define LSB_STATUS_VAR_PID 1 +#define LSB_STATUS_VAR_LOCK 2 +#define LSB_STATUS_STOPPED 3 +#define LSB_STATUS_UNKNOWN 4 +#define LSB_STATUS_LSBRESERVED 5 +#define LSB_STATUS_DISTRESERVED 100 +#define LSB_STATUS_APPRESERVED 150 +#define LSB_STATUS_RESERVED 200 +/* + * + * In the case of init script commands other than "status" + * (i.e., "start", "stop", "restart", "reload", and "force-reload"), + * the init script must return an exit status of zero if the action + * described by the argument has been successful. Otherwise, the + * exit status shall be non-zero, as defined below. In addition + * to straightforward success, the following situations are also + * to be considered successful: + * + * restarting a service (instead of reloading it) with the + * "force-reload" argument + * running "start" on a service already running + * running "stop" on a service already stopped or not running + * running "restart" on a service already stopped or not running + * In case of an error, while processing any init script action + * except for "status", the init script must print an error + * message and return one of the following non-zero exit + * status codes. + * + * 1 generic or unspecified error (current practice) + * 2 invalid or excess argument(s) + * 3 unimplemented feature (for example, "reload") + * 4 user had insufficient privilege + * 5 program is not installed + * 6 program is not configured + * 7 program is not running + * 8-99 reserved for future LSB use + * 100-149 reserved for distribution use + * 150-199 reserved for application use + * 200-254 reserved + * + * All error messages must be printed on standard error. + * All status messages must be printed on standard output. + * (This does not prevent scripts from calling the logging + * functions such as log_failure_msg). + */ +#define LSB_EXIT_OK 0 +#define LSB_EXIT_GENERIC 1 +#define LSB_EXIT_EINVAL 2 +#define LSB_EXIT_ENOTSUPPORTED 3 +#define LSB_EXIT_EPERM 4 +#define LSB_EXIT_NOTINSTALLED 5 +#define LSB_EXIT_NOTCONFIGED 6 +#define LSB_EXIT_NOTRUNNING 7 +#define LSB_EXIT_LSBRESERVED 8 +#define LSB_EXIT_DISTRESERVED 100 +#define LSB_EXIT_APPRESERVED 150 +#define LSB_EXIT_RESERVED 200 diff --git a/include/clplumbing/md5.h b/include/clplumbing/md5.h new file mode 100644 index 0000000..95b2c33 --- /dev/null +++ b/include/clplumbing/md5.h @@ -0,0 +1,49 @@ +/* + * md5.h: MD5 and keyed-MD5 algorithms + * + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2005 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _MD5_H_ +#define _MD5_H__ + +/* + * MD5: The MD5 Message-Digest Algorithm ( RFC 1321 ) + * return value: 0 - success + * <0 - fail + * Note: The digest buffer should be not less than 16. + * + */ +int MD5( const unsigned char *data + , unsigned long data_len + , unsigned char * digest); + +/* + * HMAC: Keyed-Hashing for Message Authentication + * return value: 0 - success + * <0 - fail + * Note: The digest buffer should be not less than 16. + */ +int HMAC( const unsigned char * key + , unsigned int key_len + , const unsigned char * data + , unsigned long data_len + , unsigned char * digest); + +#endif diff --git a/include/clplumbing/mkstemp_mode.h b/include/clplumbing/mkstemp_mode.h new file mode 100644 index 0000000..ff5f893 --- /dev/null +++ b/include/clplumbing/mkstemp_mode.h @@ -0,0 +1,34 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * A slightly safer version of mkstemp(3) + * + * In this version, the file is initially created mode 0, (using umask) and + * then chmod-ed to the requested permissions after calling mkstemp(3). + * This guarantees that the file is not even momentarily open beyond the + * requested permissions. + * + * Return values: + * + * Like mkstemp, it returns the file descriptor of the open file, or -1 + * on error. + * + * In addition to the errno values documented for mkstemp(3), this functio + * can also fail with any of the errno values documented for chmod(2). + * + */ +int mkstemp_mode(char* template, mode_t requested_filemode); diff --git a/include/clplumbing/netstring.h b/include/clplumbing/netstring.h new file mode 100644 index 0000000..ef24e8f --- /dev/null +++ b/include/clplumbing/netstring.h @@ -0,0 +1,48 @@ +/* + * Intracluster message object (struct ha_msg) + * + * Copyright (C) 1999, 2000 Guochun Shi<gshi@unix.sh> + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef NET_STRING_H +#define NET_STRING_H +#include <stdlib.h> +#include <stdio.h> +#include <ha_msg.h> + +extern gboolean cl_msg_quiet_fmterr; + +/* Convert a message to netstring data */ +char* msg2netstring(const struct ha_msg*, size_t*); +char * msg2netstring_noauth(const struct ha_msg *m, size_t * slen); + +/* Convert netstring data to a message */ +struct ha_msg * netstring2msg(const char*, size_t, int); + +/* Is this netstring authentic? */ +int is_auth_netstring(const char* datap, size_t datalen, + const char* authstring, size_t authlen); + +void cl_set_authentication_computation_method(int (*method)(int authmethod +, const void * data +, size_t datalen +, char * authstr +, size_t authlen)); + +#endif diff --git a/include/clplumbing/proctrack.h b/include/clplumbing/proctrack.h new file mode 100644 index 0000000..975ff1b --- /dev/null +++ b/include/clplumbing/proctrack.h @@ -0,0 +1,120 @@ +/* + * Process tracking object. + * + * Copyright (c) 2002 International Business Machines + * Author: Alan Robertson <alanr@unix.sh> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PROCTRACK_H +# define _PROCTRACK_H +#include <sys/types.h> +#include <sys/times.h> +#include <clplumbing/longclock.h> + +/* + * We track processes, mainly so we can do something appropriate + * when they die, and find processes should we need to kill them... + */ + +typedef struct _ProcTrack ProcTrack; +typedef struct _ProcTrack_ops ProcTrack_ops; +typedef struct _ProcTrackKillInfo ProcTrackKillInfo; + +/* + * The levels of logging possible for our process + */ +enum _ProcTrackLogType { + PT_LOGNONE = 2, /* Exits never automatically logged */ + PT_LOGNORMAL, /* Automatically log abnormal exits */ + PT_LOGVERBOSE /* Automatically log every exit */ +}; +typedef enum _ProcTrackLogType ProcTrackLogType; + +#define proctrack_pid(p) (p)->pid +#define proctrack_data(p) (p)->privatedata +#define reset_proctrack_data(p) (p)->privatedata = NULL +#define proctrack_timedout(p) ((p)->timeoutseq > 0) + +struct _ProcTrack { + pid_t pid; + int isapgrp; + ProcTrackLogType loglevel; + void * privatedata; + ProcTrack_ops* ops; + + longclock_t startticks; + TIME_T starttime; + unsigned timerid; + int timeoutseq; + ProcTrackKillInfo* killinfo; +}; + +/* + * The set of operations to perform on our tracked processes. + */ +struct _ProcTrack_ops { + + /* Called when a process dies */ + void (*procdied) + (ProcTrack* p, int status, int signo, int exitcode + , int waslogged); + + /* Called when a process registers */ + void (*procregistered) + (ProcTrack*p); + + /* Returns a "name" for a process (for messages) */ + /* (may have to be copied, because it may be a static value) */ + const char * + (*proctype) + (ProcTrack* p); +}; + +struct _ProcTrackKillInfo { + long mstimeout; /* Timeout in milliseconds */ + int signalno; /* Signal number to issue @ timeout */ +}; + +/* A function for calling by the process table iterator */ +typedef void (*ProcTrackFun) (ProcTrack* p, void * data); + +/* Call this function to activate the procdied member function */ +/* Returns TRUE if 'pid' was registered */ +int ReportProcHasDied(int pid, int status); + +/* Create/Log a new tracked process */ +void NewTrackedProc(pid_t pid, int isapgrp, ProcTrackLogType loglevel +, void * privatedata , ProcTrack_ops* ops); + +/* "info" is 0-terminated (terminated by a 0 signal) */ +int SetTrackedProcTimeouts(pid_t pid, ProcTrackKillInfo* info); +void RemoveTrackedProcTimeouts(pid_t pid); + +/* Return information associated with the given PID (or NULL) */ +ProcTrack* GetProcInfo(pid_t pid); + +/* + * Iterate over the set of tracked processes. + * If proctype is NULL, then walk through them all, otherwise only those + * of the given type ("f") + */ +void ForEachProc(ProcTrack_ops* proctype, ProcTrackFun f, void * data); + +void DisableProcLogging(void); /* Useful for shutdowns */ +void EnableProcLogging(void); +#endif diff --git a/include/clplumbing/realtime.h b/include/clplumbing/realtime.h new file mode 100644 index 0000000..45eb76c --- /dev/null +++ b/include/clplumbing/realtime.h @@ -0,0 +1,65 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CLPLUMBING_REALTIME_H +# define _CLPLUMBING_REALTIME_H +# include <sched.h> + +#if defined(SCHED_RR) && defined(_POSIX_PRIORITY_SCHEDULING) && !defined(ON_DARWIN) +# define DEFAULT_REALTIME_POLICY SCHED_RR +#endif + +/* + * + * make_realtime() will make the current process a soft realtime process + * and lock it into memory after growing the heap by heapgrowK*1024 bytes + * + * If you set spolicy or priority to <= 0, then defaults will be used. + * Otherwise you need to use a value for spolicy from <sched.h> + * and use an appropriate priority for the given spolicy. + * + * WARNING: badly behaved programs which use the make_realtime() function + * can easily hang the machine. + */ + +void cl_make_realtime +( int spolicy, /* SCHED_RR or SCHED_FIFO (or SCHED_OTHER) */ + int priority, /* typically 1-99 */ + int stackgrowK, /* Amount to grow stack by */ + int heapgrowK /* Amount to grow heap by */ +); + +void cl_make_normaltime(void); + +/* Cause calls to make_realtime() to be ignored */ +void cl_disable_realtime(void); + +/* Cause calls to make_realtime() to be accepted. + * This is the default behaviour */ +void cl_enable_realtime(void); + +/* Sleep a really short (the shortest) time */ +int cl_shortsleep(void); + +/* Print messages if we've done (more) non-realtime mallocs */ +void cl_realtime_malloc_check(void); + +/* Number of times we "go to the well" for memory after becoming realtime */ +int cl_nonrealtime_malloc_count(void); +/* Number of bytes we "got from the well" for memory after becoming realtime */ +unsigned long cl_nonrealtime_malloc_size(void); + +#endif diff --git a/include/clplumbing/replytrack.h b/include/clplumbing/replytrack.h new file mode 100644 index 0000000..f98fe48 --- /dev/null +++ b/include/clplumbing/replytrack.h @@ -0,0 +1,208 @@ +/* + * Process tracking object. + * + * Copyright (c) 2007 Alan Robertson + * Author: Alan Robertson <alanr@unix.sh> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _REPLYTRACK_H +# define _REPLYTRACK_H +#include <sys/types.h> +#include <sys/times.h> +#include <clplumbing/longclock.h> +#include <clplumbing/cl_uuid.h> + +/* + * We track replies - so we can tell when all expected replies were received. + * + * There is a problem in clusters where a message is sent to each node, and a + * reply is expected from each node of knowing when all the replies have been + * received. + * + * If all nodes are up, it's easy to see when all replies are received. + * But, if some nodes are down, we really don't want to wait for a timeout + * before we decide that we've gotten all the replies we're going to get, + * since nodes can be down for potentially very long periods of time, and + * waiting for a long timeout can delay things a great deal again and + * again - causing significant delays and user frustration. + * + * That's where these functions come in! + * Instead, inform these functions what nodes are up and what ones are down, + * and when you receive a reply, and it will tell you when you've gotten + * them all - managing all that tedious bookwork for you. + */ + +typedef enum _replytrack_completion_type replytrack_completion_type_t; +typedef enum _nodetrack_change nodetrack_change_t; +typedef struct _replytrack replytrack_t; +typedef struct _nodetrack nodetrack_t; +typedef struct _nodetrack_intersection nodetrack_intersection_t; + +/* + * The levels of logging possible for our process + */ +enum _replytrack_completion_type { + REPLYT_ALLRCVD = 2, /* All replies received */ + REPLYT_TIMEOUT, /* Timeout occurred with replies missing */ +}; + + +typedef void (*replytrack_callback_t) +( replytrack_t * rl +, gpointer user_data +, replytrack_completion_type_t reason); + +typedef void (*replytrack_iterator_t) +( replytrack_t* rl +, gpointer user_data +, const char* node +, cl_uuid_t uuid); + +typedef void (*nodetrack_iterator_t) +( nodetrack_t* rl +, gpointer user_data +, const char* node +, cl_uuid_t uuid); + + +/* + * Note: + * If you use the timeout feature of this code, it relies on you using glib mainloop + * for your scheduling. timeout_ms should be zero for no timeout. + */ +replytrack_t* replytrack_new(nodetrack_t* membership +, replytrack_callback_t callback +, unsigned long timeout_ms +, gpointer user_data); + +void replytrack_del(replytrack_t *rl); +gboolean replytrack_gotreply(replytrack_t *rl +, const char * node +, cl_uuid_t uuid); + /* Returns TRUE if this was the final expected reply */ +/* + * Iterate over the set of outstanding replies: + * return count of how many items in the iteration + */ +int replytrack_outstanding_iterate(replytrack_t* rl +, replytrack_iterator_t i, gpointer user_data); +int replytrack_outstanding_count(replytrack_t* rl); + +/* + * The functions above operate using a view of membership which is established + * through the functions below. + * + * This can either be through the heartbeat low-level membership API, or any + * other view of membership you wish. Mentioning a node as either up or down + * will automatically add that node to our view of potential membership. + * + * These functions only support one view of membership per process. + * + * The general idea of how to use these functions: + * Initially: + * 1) iterate through init membership and call nodetrack_node(up|down) for + * each node to start things off. + * + * On an ongoing basis: + * 2) call nodetrack_node_up whenever a node comes up + * We expect a reply from nodes that are up. + * 3) call nodetrack_node_down whenever a node goes down + * We don't expect a reply from nodes that are down. + * + * For each set of replies you want tracked: + * 4) Create a replytrack_t for a set of expected replies + * 5) call replytrack_gotreply() each time you get an expected reply + * 6) replist_gotreply() returns TRUE when the final message was received. + * (it does this by comparing against the membership as defined below) + * 7) you will get a callback when timeout occurs or final message is received + * n. b.: + * No callback function => manage timeouts yourself + * 8) call replytrack_del() when you're done with the reply list + * n. b.: + * If you have replies outstanding, and you have a timeout and + * a callback function set, you will get a warning for destroying + * a replytrack_t object 'prematurely'. + * You will also log a warning if you call replytrack_gotreply() after + * all replies were received or a timeout occurred. + * + */ + +/* + * The levels of logging possible for our process + */ +enum _nodetrack_change { + NODET_UP = 2, /* This node came up */ + NODET_DOWN, /* This node went down */ +}; + +typedef void (*nodetrack_callback_t) +( nodetrack_t * mbr +, const char * node +, cl_uuid_t u +, nodetrack_change_t reason +, gpointer user_data); + +nodetrack_t* nodetrack_new(nodetrack_callback_t callback +, gpointer user_data); +void nodetrack_del(nodetrack_t*); +gboolean nodetrack_nodeup(nodetrack_t* mbr, const char * node +, cl_uuid_t u); +gboolean nodetrack_nodedown(nodetrack_t* mbr, const char * node +, cl_uuid_t u); +gboolean nodetrack_ismember(nodetrack_t* mbr, const char * node +, cl_uuid_t u); +int nodetrack_iterate(nodetrack_t* mbr +, nodetrack_iterator_t i, gpointer user_data); + +/* An intesection nodetrack table + * A node is put into the "intersection" nodetrack_t table when it is in all + * the underlying constituent nodetrack_t tables, and removed when it is + * removed from any of them. + * Note that you can set a callback to be informed when these "intersection" + * membership changes occur. + */ +nodetrack_intersection_t* + nodetrack_intersection_new(nodetrack_t** tables, int ntables +, nodetrack_callback_t callback, gpointer user_data); +void nodetrack_intersection_del(nodetrack_intersection_t*); +nodetrack_t* nodetrack_intersection_table(nodetrack_intersection_t*); + +#if 0 +/* + * I don't know if this should be in this library, or just in + * the CCM. Probably only the CCM _should_ be using it (when I write it) + */ +/* + * Use of the nodetrack_hb_* functions implies you're using the heartbeat + * peer-connectivity information as your source of information. This is + * really only suitable if you're using heartbeat's low-level group membership + * for your source of who to expect replies from. + * If you're using nodetrack_hb_init, this replaces step (1) above. + */ +void nodetrack_hb_init(void) +/* + * If you're using nodetrack_hb_statusmsg, just pass it all status messages + * and all peer-connectivity status messages or even all heartbeat messages + * (non-status messages will be ignored). + * This replaces steps (2) and (3) above _if_ you're using heartbeat low + * level membership for your source of who to expect replies from. + */ +void nodetrack_hb_statusmsg(struct ha_msg* statusmsg); +#endif /*0*/ + +#endif diff --git a/include/clplumbing/setproctitle.h b/include/clplumbing/setproctitle.h new file mode 100644 index 0000000..5caeef0 --- /dev/null +++ b/include/clplumbing/setproctitle.h @@ -0,0 +1,64 @@ +/* + * setproctitle.h + * + * The code in this file, setproctitle.h is heavily based on code from + * proftpd, please see the licening information below. + * + * This file added to the heartbeat tree by Horms <horms@vergenet.net> + * + * Code to portably change the title of a programme as displayed + * by ps(1). + * + * heartbeat: Linux-HA heartbeat code + * + * Copyright (C) 1999,2000,2001 Alan Robertson <alanr@unix.sh> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * ProFTPD - FTP server daemon + * Copyright (c) 1997, 1998 Public Flood Software + * Copyright (C) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu + * and other respective copyright holders give permission to link this program + * with OpenSSL, and distribute the resulting executable, without including + * the source code for OpenSSL in the source distribution. + */ + +#ifndef _HA_SETPROCTITLE_H +#define _HA_SETPROCTITLE_H + +#include <glib.h> +int init_set_proc_title(int argc, char *argv[], char *envp[]); + +void set_proc_title(const char *fmt,...) G_GNUC_PRINTF(1,2); + +#endif /* _HA_SETPROCTITLE_H */ diff --git a/include/clplumbing/timers.h b/include/clplumbing/timers.h new file mode 100644 index 0000000..669ac21 --- /dev/null +++ b/include/clplumbing/timers.h @@ -0,0 +1,23 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CLPLUMBING_TIMERS_H +# define _CLPLUMBING_TIMERS_H +int setmsrepeattimer(long ms); +int setmsalarm(long ms); +int cancelmstimer(void); +long mssleep(long ms); +#endif diff --git a/include/clplumbing/uids.h b/include/clplumbing/uids.h new file mode 100644 index 0000000..89ba303 --- /dev/null +++ b/include/clplumbing/uids.h @@ -0,0 +1,32 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CLPLUMBING_UIDS_H +# define CLPLUMBING_UIDS_H +#include <sys/types.h> + +/* Tell us who you want to be - or zero for nobody */ +int drop_privs(uid_t uid, gid_t gid); + +/* Return to original privileged state */ +int return_to_orig_privs(void); + +/* Drop down to (probably nobody) privileges again */ +int return_to_dropped_privs(void); + +/* Return TRUE if we have full privileges at the moment */ +int cl_have_full_privs(void); +#endif diff --git a/include/compress.h b/include/compress.h new file mode 100644 index 0000000..9cd733c --- /dev/null +++ b/include/compress.h @@ -0,0 +1,50 @@ +/* + * compress.h: Compression functions for Linux-HA + * + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _COMPRESS_H_ +#define _COMPRESS_H_ + +#define HB_COMPRESS_TYPE compress +#define HB_COMPRESS_TYPE_S "compress" + +/* + * List of functions provided by implementations of the heartbeat + * compress interface. + */ +struct hb_compress_fns { + int (*compress) (char*, size_t*, const char*, size_t); + int (*decompress) (char*, size_t* , const char*, size_t); + const char* (*getname) (void); +}; + +struct ha_msg; + +/* set the compression method*/ +int cl_compress_remove_plugin(const char* pluginname); +int cl_compress_load_plugin(const char* pluginname); +struct hb_compress_fns* cl_get_compress_fns(void); +int cl_set_compress_fns(const char*); +char* cl_compressmsg(struct ha_msg*m, size_t* len); +struct ha_msg* cl_decompressmsg(struct ha_msg* m); +gboolean is_compressed_msg(struct ha_msg* m); +int cl_compress_field(struct ha_msg* msg, int index, char* buf, size_t* buflen); +int cl_decompress_field(struct ha_msg* msg, int index, char* buf, size_t* buflen); + +#endif diff --git a/include/glue_config.h.in b/include/glue_config.h.in new file mode 100644 index 0000000..0850a63 --- /dev/null +++ b/include/glue_config.h.in @@ -0,0 +1,104 @@ +/* include/config.h.in. Generated from configure.in by autoheader. */ + +/* Location for daemons */ +#undef GLUE_DAEMON_DIR + +/* Group to run daemons as */ +#undef GLUE_DAEMON_GROUP + +/* User to run daemons as */ +#undef GLUE_DAEMON_USER + +/* Where to keep state files and sockets */ +#undef GLUE_STATE_DIR + +/* Location of shared data */ +#undef GLUE_SHARED_DIR + +/* User to run daemons as */ +#undef HA_CCMUSER + +/* Group to run daemons as */ +#undef HA_APIGROUP + +/* Location for daemons */ +#undef HA_LIBHBDIR + +/* top directory of area to drop core files in */ +#undef HA_COREDIR + +/* top directory for LRM related files */ +#undef LRM_VARLIBDIR + +/* CIB secrets */ +#undef LRM_CIBSECRETS + +/* Logging Daemon IPC socket name */ +#undef HA_LOGDAEMON_IPC + +/* Default logging facility */ +#undef HA_LOG_FACILITY + +/* Default plugin search path */ +#undef PILS_BASE_PLUGINDIR + +/* Where to find plugins */ +#undef HA_PLUGIN_DIR + +/* Location of system configuration files */ +#undef HA_SYSCONFDIR + +/* Web site base URL */ +#undef HA_URLBASE + +/* Whatever this used to mean */ +#undef HA_VARLIBHBDIR + +#undef HA_VARLIBDIR + +/* System lock directory */ +#undef HA_VARLOCKDIR + +/* Where Heartbeat keeps state files and sockets - old name */ +#undef HA_VARRUNDIR + +/* Location for v1 Heartbeat RAs */ +#undef HB_RA_DIR + +/* Where to find LRM plugins */ +#undef LRM_PLUGIN_DIR + +/* Location for LSB RAs */ +#undef LSB_RA_DIR + +/* Location for OCF RAs */ +#undef OCF_RA_DIR + +/* OCF root directory - specified by the OCF standard */ +#undef OCF_ROOT_DIR + +/* Compiling for Darwin platform */ +#undef ON_DARWIN + +/* Compiling for Linux platform */ +#undef ON_LINUX + +/* Current glue version */ +#undef GLUE_VERSION + +/* Build version */ +#undef GLUE_BUILD_VERSION + +/* Location of non-pluing stonith scripts */ +#undef STONITH_EXT_PLUGINDIR + +/* Location of RHCS stonith scripts */ +#undef STONITH_RHCS_PLUGINDIR + +/* Location of stonith plugins */ +#undef STONITH_MODULES + +/* Stonith plugin domain */ +#undef ST_TEXTDOMAIN + +#undef HA_HBCONF_DIR diff --git a/include/ha_msg.h b/include/ha_msg.h new file mode 100644 index 0000000..fcb6cf6 --- /dev/null +++ b/include/ha_msg.h @@ -0,0 +1,464 @@ +/* + * Intracluster message object (struct ha_msg) + * + * Copyright (C) 1999, 2000 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _HA_MSG_H +# define _HA_MSG_H 1 +#include <stdio.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/ipc.h> +#include <clplumbing/longclock.h> +#include <clplumbing/cl_uuid.h> +#include <compress.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + + +enum cl_netstring_type{ + FT_STRING = 0, + FT_BINARY, + FT_STRUCT, + FT_LIST, + FT_COMPRESS, + FT_UNCOMPRESS +}; + +enum cl_msgfmt{ + MSGFMT_NVPAIR, + MSGFMT_NETSTRING +}; + + +#define NEEDHEAD 1 +#define NOHEAD 0 +#define HA_MSG_ASSERT(X) do{ if(!(X)){ \ + cl_log(LOG_ERR, "Assertion failed on line %d in file \"%s\"" \ + , __LINE__, __FILE__); \ + abort(); \ + } \ + }while(0) + +typedef struct hb_msg_stats_s { + unsigned long totalmsgs; /* Total # of messages */ + /* ever handled */ + unsigned long allocmsgs; /* # Msgs currently allocated */ + longclock_t lastmsg; +}hb_msg_stats_t; + +struct ha_msg { + int nfields; + int nalloc; + char ** names; + size_t* nlens; + void ** values; + size_t* vlens; + int * types; +}; + +typedef struct ha_msg HA_Message; + +struct fieldtypefuncs_s{ + + /* memfree frees the memory involved*/ + void (*memfree)(void*); + + /* dup makes a complete copy of the field*/ + void* (*dup)(const void*, size_t); + + /* display printout the field*/ + void (*display)(int, int, char* , void*, int); + + /* add the field into a message*/ + int (*addfield) (struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth); + + /* return the string length required to add this field*/ + int (*stringlen) (size_t namlen, size_t vallen, const void* value); + + /* return the netstring length required to add this field*/ + int (*netstringlen) (size_t namlen, size_t vallen, const void* value); + + /* print the field into the provided buffer, convert it first */ + /* if ncecessary*/ + int (*tostring)(char*, char*, void* ,size_t,int); + + /* print the field into the provided buffer*/ + int (*tonetstring)(char*, char*, char*, size_t, + void*, size_t, int, size_t*); + + /* convert the given string to a field + note: this functions involves allocate memory for + for the field + */ + int (*stringtofield)(void*, size_t, int depth, void**, size_t* ); + + /* convert the given netstring to a field + note: this functions involves allocate memory for + for the field + */ + int (*netstringtofield)(const void*, size_t, void**, size_t*); + + /* action before packing*/ + int (*prepackaction)(struct ha_msg* m, int index); + + /* action before a user get the value of a field*/ + int (*pregetaction)(struct ha_msg* m, int index); + +}; + +#define NUM_MSG_TYPES 6 +extern struct fieldtypefuncs_s fieldtypefuncs[NUM_MSG_TYPES]; + +#define MSG_NEEDAUTH 0x01 +#define MSG_ALLOWINTR 0X02 +#define MSG_NEEDCOMPRESS 0x04 +#define MSG_NOSIZECHECK 0x08 + +#define IFACE "!^!\n" +#define MSG_START ">>>\n" +#define MSG_END "<<<\n" +#define MSG_START_NETSTRING "###\n" +#define MSG_END_NETSTRING "%%%\n" +#define EQUAL "=" + +#define MAXDEPTH 16 /* Maximum recursive message depth */ +#define MAXLENGTH 1024 + + /* Common field names for our messages */ +#define F_TYPE "t" /* Message type */ +#define F_SUBTYPE "subt" /* Message type */ +#define F_ORIG "src" /* Real Originator */ +#define F_ORIGUUID "srcuuid" /* Real Originator uuid*/ +#define F_NODE "node" /* Node being described */ +#define F_NODELIST "nodelist" /* Node list being described */ +#define F_DELNODELIST "delnodelist" /* Del node list being described */ +#define F_TO "dest" /* Destination (optional) */ +#define F_TOUUID "destuuid" /* Destination uuid(optional) */ +#define F_STATUS "st" /* New status (type = status) */ +#define F_WEIGHT "weight" /* weight of node */ +#define F_SITE "site" /* site of node */ +#define F_PROTOCOL "protocol" /* Protocol number for communication*/ +#define F_CLIENTNAME "cn" /* Client name */ +#define F_CLIENTSTATUS "cs" /* Client status */ +#define F_TIME "ts" /* Timestamp */ +#define F_SEQ "seq" /* Sequence number */ +#define F_LOAD "ld" /* Load average */ +#define F_COMMENT "info" /* Comment */ +#define F_TTL "ttl" /* Time To Live */ +#define F_AUTH "auth" /* Authentication string */ +#define F_HBGENERATION "hg" /* Heartbeat generation number */ +#define F_CLIENT_GENERATION "client_gen" /* client generation number*/ +#define F_FIRSTSEQ "firstseq" /* Lowest seq # to retransmit */ +#define F_LASTSEQ "lastseq" /* Highest seq # to retransmit */ +#define F_RESOURCES "rsc_hold" /* What resources do we hold? */ +#define F_FROMID "from_id" /* from Client id */ +#define F_TOID "to_id" /* To client id */ +#define F_PID "pid" /* PID of client */ +#define F_UID "uid" /* uid of client */ +#define F_GID "gid" /* gid of client */ +#define F_ISSTABLE "isstable" /* true/false for RESOURCES */ +#define F_APIREQ "reqtype" /* API request type for "hbapi" */ +#define F_APIRESULT "result" /* API request result code */ +#define F_IFNAME "ifname" /* Interface name */ +#define F_PNAME "pname" /* Parameter name */ +#define F_PVALUE "pvalue" /* Parameter name */ +#define F_DEADTIME "deadtime" /* Dead time interval in ms. */ +#define F_KEEPALIVE "keepalive" /* Keep alive time interval in ms. */ +#define F_LOGFACILITY "logfacility" /* Suggested cluster syslog facility */ +#define F_NODETYPE "nodetype" /* Type of node */ +#define F_NUMNODES "numnodes" /* num of total nodes(excluding ping nodes*/ +#define F_RTYPE "rtype" /* Resource type */ +#define F_ORDERSEQ "oseq" /* Order Sequence number */ +#define F_DT "dt" /* Dead time field for heartbeat*/ +#define F_ACKSEQ "ackseq" /* The seq number this msg is acking*/ +#define F_CRM_DATA "crm_xml" +#define F_XML_TAGNAME "__name__" +#define F_STATE "state" /*used in ccm for state info*/ + + + /* Message types */ +#define T_STATUS "status" /* Status (heartbeat) */ +#define T_IFSTATUS "ifstat" /* Interface status */ +#define T_ASKRESOURCES "ask_resources" /* Let other node ask my resources */ +#define T_ASKRELEASE "ip-request" /* Please give up these resources... */ +#define T_ACKRELEASE "ip-request-resp"/* Resources given up... */ +#define T_QCSTATUS "query-cstatus" /* Query client status */ +#define T_RCSTATUS "respond-cstatus"/* Respond client status */ +#define T_STONITH "stonith" /* Stonith return code */ +#define T_SHUTDONE "shutdone" /* External Shutdown complete */ +#define T_CRM "crmd" /* Cluster resource manager message */ +#define T_ATTRD "attrd" /* Cluster resource manager message */ +#define T_ADDNODE "addnode" /* Add node message*/ +#define T_DELNODE "delnode" /* Delete node message*/ +#define T_SETWEIGHT "setweight" /* Set node weight*/ +#define T_SETSITE "setsite" /* Set node site*/ +#define T_REQNODES "reqnodes" /* Request node list */ +#define T_REPNODES "repnodes" /* reply node list rquest*/ + +#define T_APIREQ "hbapi-req" /* Heartbeat API request */ +#define T_APIRESP "hbapi-resp" /* Heartbeat API response */ +#define T_APICLISTAT "hbapi-clstat" /* Client status notification" */ + +#define NOSEQ_PREFIX "NS_" /* PREFIX: Give no sequence number */ + /* Used for messages which can't be retransmitted */ + /* Either they're protocol messages or from dumb (ping) endpoints */ +#define T_REXMIT NOSEQ_PREFIX "rexmit" /* Rexmit request */ +#define T_NAKREXMIT NOSEQ_PREFIX "nak_rexmit" /* NAK Rexmit request */ +#define T_NS_STATUS NOSEQ_PREFIX "st" /* ping status */ +#define T_ACKMSG NOSEQ_PREFIX "ackmsg" /* ACK message*/ + +/* Messages associated with nice_failback */ +#define T_STARTING "starting" /* Starting Heartbeat */ + /* (requesting resource report) */ +#define T_RESOURCES "resource" /* Resources report */ + +/* Messages associated with stonith completion results */ +#define T_STONITH_OK "OK" /* stonith completed successfully */ +#define T_STONITH_BADHOST "badhost" /* stonith failed */ +#define T_STONITH_BAD "bad" /* stonith failed */ +#define T_STONITH_NOTCONFGD "n_stnth" /* no stonith device configured */ +#define T_STONITH_UNNEEDED "unneeded" /* STONITH not required */ + +/* Set up message statistics area */ + +int netstring_extra(int); +int cl_msg_stats_add(longclock_t time, int size); + +void cl_msg_setstats(volatile hb_msg_stats_t* stats); +void cl_dump_msgstats(void); +void cl_set_compression_threshold(size_t threadhold); +void cl_set_traditional_compression(gboolean value); + +/* Allocate new (empty) message */ +struct ha_msg * ha_msg_new(int nfields); + +/* Free message */ +void ha_msg_del(struct ha_msg *msg); + +/* Copy message */ +struct ha_msg* ha_msg_copy(const struct ha_msg *msg); + +int ha_msg_expand(struct ha_msg* msg ); + +/*Add a null-terminated name and binary value to a message*/ +int ha_msg_addbin(struct ha_msg * msg, const char * name, + const void * value, size_t vallen); + +int ha_msg_adduuid(struct ha_msg * msg, const char * name, + const cl_uuid_t* uuid); + +/* Add null-terminated name and a value to the message */ +int ha_msg_add(struct ha_msg * msg + , const char* name, const char* value); + +int cl_msg_remove(struct ha_msg* msg, const char* name); +int cl_msg_remove_value(struct ha_msg* msg, const void* value); +int cl_msg_remove_offset(struct ha_msg* msg, int offset); + +/* Modify null-terminated name and a value to the message */ +int cl_msg_modstring(struct ha_msg * msg, + const char* name, + const char* value); +int cl_msg_modbin(struct ha_msg * msg, + const char* name, + const void* value, + size_t vlen); + +int cl_msg_moduuid(struct ha_msg * msg, const char * name, + const cl_uuid_t* uuid); + +int cl_msg_modstruct(struct ha_msg * msg, + const char* name, + const struct ha_msg* value); +#define ha_msg_mod(msg, name, value) cl_msg_modstring(msg, name, value) +int cl_msg_replace(struct ha_msg* msg, int index, + const void* value, size_t vlen, int type); +int cl_msg_replace_value(struct ha_msg* msg, const void *old_value, + const void* value, size_t vlen, int type); + +/* Add name, value (with known lengths) to the message */ +int ha_msg_nadd(struct ha_msg * msg, const char * name, int namelen + , const char * value, int vallen); + +/* Add a name/value/type to a message (with sizes for name and value) */ +int ha_msg_nadd_type(struct ha_msg * msg, const char * name, int namelen + , const char * value, int vallen, int type); + +/* Add name=value string to a message */ +int ha_msg_add_nv(struct ha_msg* msg, const char * nvline, const char * bufmax); + + +/* Return value associated with particular name */ +#define ha_msg_value(m,name) cl_get_string(m, name) + +/* Call wait(in|out) but only for a finite time */ +int cl_ipc_wait_timeout( + IPC_Channel * chan, int (*waitfun)(IPC_Channel * chan), unsigned int timeout); + +/* Reads an IPC stream -- converts it into a message */ +struct ha_msg * msgfromIPC_timeout(IPC_Channel *ch, int flag, unsigned int timeout, int *rc_out); +struct ha_msg * msgfromIPC(IPC_Channel * f, int flag); + +IPC_Message * ipcmsgfromIPC(IPC_Channel * ch); + +/* Reads a stream -- converts it into a message */ +struct ha_msg * msgfromstream(FILE * f); + +/* Reads a stream with string format--converts it into a message */ +struct ha_msg * msgfromstream_string(FILE * f); + +/* Reads a stream with netstring format--converts it into a message */ +struct ha_msg * msgfromstream_netstring(FILE * f); + +/* Same as above plus copying the iface name to "iface" */ +struct ha_msg * if_msgfromstream(FILE * f, char *iface); + +/* Writes a message into a stream */ +int msg2stream(struct ha_msg* m, FILE * f); + +/* Converts a message into a string and adds the iface name on start */ +char * msg2if_string(const struct ha_msg *m, const char * iface); + +/* Converts a string gotten via UDP into a message */ +struct ha_msg * string2msg(const char * s, size_t length); + +/* Converts a message into a string */ +char * msg2string(const struct ha_msg *m); + +/* Converts a message into a string in the provided buffer with certain +depth and with or without start/end */ +int msg2string_buf(const struct ha_msg *m, char* buf, + size_t len, int depth, int needhead); + +/* Converts a message into wire format */ +char* msg2wirefmt(struct ha_msg *m, size_t* ); +char* msg2wirefmt_noac(struct ha_msg*m, size_t* len); + +/* Converts wire format data into a message */ +struct ha_msg* wirefmt2msg(const char* s, size_t length, int flag); + +/* Convets wire format data into an IPC message */ +IPC_Message* wirefmt2ipcmsg(void* p, size_t len, IPC_Channel* ch); + +/* Converts an ha_msg into an IPC message */ +IPC_Message* hamsg2ipcmsg(struct ha_msg* m, IPC_Channel* ch); + +/* Converts an IPC message into an ha_msg */ +struct ha_msg* ipcmsg2hamsg(IPC_Message*m); + +/* Outputs a message to an IPC channel */ +int msg2ipcchan(struct ha_msg*m, IPC_Channel*ch); + +/* Outpus a message to an IPC channel without authencating +the message */ +struct ha_msg* msgfromIPC_noauth(IPC_Channel * ch); + +/* Reads from control fifo, and creates a new message from it */ +/* This adds the default sequence#, load avg, etc. to the message */ +struct ha_msg * controlfifo2msg(FILE * f); + +/* Check if the message is authenticated */ +gboolean isauthentic(const struct ha_msg * msg); + +/* Get the required string length for the given message */ +int get_stringlen(const struct ha_msg *m); + +/* Get the requried netstring length for the given message*/ +int get_netstringlen(const struct ha_msg *m); + +/* Add a child message to a message as a field */ +int ha_msg_addstruct(struct ha_msg * msg, const char * name, const void* ptr); + +int ha_msg_addstruct_compress(struct ha_msg*, const char*, const void*); + +/* Get binary data from a message */ +const void * cl_get_binary(const struct ha_msg *msg, const char * name, size_t * vallen); + +/* Get uuid data from a message */ +int cl_get_uuid(const struct ha_msg *msg, const char * name, cl_uuid_t* retval); + +/* Get string data from a message */ +const char * cl_get_string(const struct ha_msg *msg, const char *name); + +/* Get the type for a field from a message */ +int cl_get_type(const struct ha_msg *msg, const char *name); + +/* Get a child message from a message*/ +struct ha_msg *cl_get_struct(struct ha_msg *msg, const char* name); + +/* Log the contents of a message */ +void cl_log_message (int log_level, const struct ha_msg *m); + +/* Supply messaging system with old style authentication/authorization method */ +void cl_set_oldmsgauthfunc(gboolean (*authfunc)(const struct ha_msg*)); + +/* Set default messaging format */ +void cl_set_msg_format(enum cl_msgfmt mfmt); + +/* Add a string to a list*/ +int cl_msg_list_add_string(struct ha_msg* msg, const char* name, const char* value); + +/* Return length of a list*/ +int cl_msg_list_length(struct ha_msg* msg, const char* name); + +/* Return nth element of a list*/ +void* cl_msg_list_nth_data(struct ha_msg* msg, const char* name, int n); + +/* Functions to add/mod/get an integer */ +int ha_msg_add_int(struct ha_msg * msg, const char * name, int value); +int ha_msg_mod_int(struct ha_msg * msg, const char * name, int value); +int ha_msg_value_int(const struct ha_msg * msg, const char * name, int* value); + +/* Functions to add/mod/get an unsigned long */ +int ha_msg_add_ul(struct ha_msg * msg, const char * name, unsigned long value); +int ha_msg_mod_ul(struct ha_msg * msg, const char * name, unsigned long value); +int ha_msg_value_ul(const struct ha_msg * msg, const char * name, unsigned long* value); + +/* Functions to add/get a string list*/ +GList* ha_msg_value_str_list(struct ha_msg * msg, const char * name); + +int cl_msg_add_list_int(struct ha_msg* msg, const char* name, + int* buf, size_t n); +int cl_msg_get_list_int(struct ha_msg* msg, const char* name, + int* buf, size_t* n); +GList* cl_msg_get_list(struct ha_msg* msg, const char* name); +int cl_msg_add_list(struct ha_msg* msg, const char* name, GList* list); +int cl_msg_add_list_str(struct ha_msg* msg, const char* name, + char** buf, size_t n); + +/* Function to add/get a string hash table*/ +GHashTable* ha_msg_value_str_table(struct ha_msg * msg, const char * name); +int ha_msg_add_str_table(struct ha_msg * msg, const char * name, + GHashTable* hash_table); +int ha_msg_mod_str_table(struct ha_msg * msg, const char * name, + GHashTable* hash_table); + +/*internal use for list type*/ +size_t string_list_pack_length(const GList* list); +int string_list_pack(GList* list, char* buf, char* maxp); +GList* string_list_unpack(const char* packed_str_list, size_t length); +void list_cleanup(GList* list); + +gboolean must_use_netstring(const struct ha_msg*); + + +#endif /* __HA_MSG_H */ diff --git a/include/lha_internal.h b/include/lha_internal.h new file mode 100644 index 0000000..bae10a0 --- /dev/null +++ b/include/lha_internal.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2001 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + */ + +#ifndef LHA_INTERNAL_H +# define LHA_INTERNAL_H + +#define EOS '\0' +#define DIMOF(a) ((int) (sizeof(a)/sizeof(a[0])) ) +#define STRLEN_CONST(conststr) ((size_t)((sizeof(conststr)/sizeof(char))-1)) +#define STRNCMP_CONST(varstr, conststr) strncmp((varstr), conststr, STRLEN_CONST(conststr)+1) +#define STRLEN(c) STRLEN_CONST(c) +#define MALLOCT(t) ((t *) malloc(sizeof(t))) + +#define HADEBUGVAL "HA_DEBUG" /* current debug value (if nonzero) */ +#define HALOGD "HA_LOGD" /* whether we use logging daemon or not */ + +/* Needs to be defined before any other includes, otherwise some system + * headers do not behave as expected! Major black magic... */ +#undef _GNU_SOURCE /* in case it was defined on the command line */ +#define _GNU_SOURCE + +/* Please leave this as the first #include - Solaris needs it there */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/param.h> +#ifdef BSD +# define SCANSEL_CAST (void *) +#else +# define SCANSEL_CAST /* Nothing */ +#endif + +#if defined(ANSI_ONLY) && !defined(inline) +# define inline /* nothing */ +# undef NETSNMP_ENABLE_INLINE +# define NETSNMP_NO_INLINE 1 +#endif + +#ifndef HAVE_DAEMON + /* We supply a replacement function, but need a prototype */ +int daemon(int nochdir, int noclose); +#endif /* HAVE_DAEMON */ + +#ifndef HAVE_SETENV + /* We supply a replacement function, but need a prototype */ +int setenv(const char *name, const char * value, int why); +#endif /* HAVE_SETENV */ + +#ifndef HAVE_UNSETENV + /* We supply a replacement function, but need a prototype */ +int unsetenv(const char *name); +#endif /* HAVE_UNSETENV */ + +#ifndef HAVE_STRERROR + /* We supply a replacement function, but need a prototype */ +char * strerror(int errnum); +#endif /* HAVE_STRERROR */ + +#ifndef HAVE_SCANDIR + /* We supply a replacement function, but need a prototype */ +# include <dirent.h> +int +scandir (const char *directory_name, + struct dirent ***array_pointer, + int (*select_function) (const struct dirent *), +#ifdef USE_SCANDIR_COMPARE_STRUCT_DIRENT + /* This is what the Linux man page says */ + int (*compare_function) (const struct dirent**, const struct dirent**) +#else + /* This is what the Linux header file says ... */ + int (*compare_function) (const void *, const void *) +#endif + ); +#endif /* HAVE_SCANDIR */ + +#ifndef HAVE_ALPHASORT +# include <dirent.h> +int +alphasort(const void *dirent1, const void *dirent2); +#endif /* HAVE_ALPHASORT */ + +#ifndef HAVE_INET_PTON + /* We supply a replacement function, but need a prototype */ +int +inet_pton(int af, const char *src, void *dst); + +#endif /* HAVE_INET_PTON */ + +#ifndef HAVE_STRNLEN + size_t strnlen(const char *s, size_t maxlen); +#else +# define USE_GNU +#endif + +#ifndef HAVE_STRNDUP + char *strndup(const char *str, size_t len); +#else +# define USE_GNU +#endif +#ifndef HAVE_STRLCPY + size_t strlcpy(char * dest, const char *source, size_t len); +#endif +#ifndef HAVE_STRLCAT + size_t strlcat(char * dest, const char *source, size_t len); +#endif + +#ifndef HAVE_NFDS_T + typedef unsigned int nfds_t; +#endif + +#ifdef HAVE_STRUCT_UCRED_DARWIN +# include <sys/utsname.h> +# ifndef SYS_NMLN +# define SYS_NMLN _SYS_NAMELEN +# endif /* SYS_NMLN */ +#endif + +#define POINTER_TO_SIZE_T(p) ((size_t)(p)) /*pointer cast as size_t*/ +#define POINTER_TO_SSIZE_T(p) ((ssize_t)(p)) /*pointer cast as ssize_t*/ +#define POINTER_TO_ULONG(p) ((unsigned long)(p)) /*pointer cast as unsigned long*/ + /* Sometimes we get a const g_something *, but need to pass it internally + * to other functions taking a non-const g_something *, which results + * with gcc and -Wcast-qual in a compile time warning, and with -Werror + * even to a compile time error. + * Workarounds have been to e.g. memcpy(&list, _list); or similar, + * the reason of which is non-obvious to the casual reader. + * This macro achieves the same, and annotates why it is done. + */ +#define UNCONST_CAST_POINTER(t, p) ((t)(unsigned long)(p)) + +#define HAURL(url) HA_URLBASE url + +/* + * Some compilers may not have defined __FUNCTION__. + */ +#ifndef __FUNCTION__ + +/* Sun studio compiler */ +# ifdef __SUNPRO_C +# define __FUNCTION__ __func__ +# endif + +/* Similarly add your compiler here ... */ + +#endif + +/* You may need to change this for your compiler */ +#ifdef HAVE_STRINGIZE +# define ASSERT(X) {if(!(X)) ha_assert(#X, __LINE__, __FILE__);} +#else +# define ASSERT(X) {if(!(X)) ha_assert("X", __LINE__, __FILE__);} +#endif + +/* shamelessly stolen from linux kernel */ +/* Force a compilation error if condition is true */ +#define BUILD_BUG_ON(condition) ((void)BUILD_BUG_ON_ZERO(condition)) +/* Force a compilation error if condition is true, but also produce a + * result (of value 0 and type size_t), so the expression can be used + * e.g. in a structure initializer (or where-ever else comma expressions + * aren't permitted). */ +#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); })) +#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); })) + +#endif /* LHA_INTERNAL_H */ diff --git a/include/lrm/Makefile.am b/include/lrm/Makefile.am new file mode 100644 index 0000000..ec4f5a5 --- /dev/null +++ b/include/lrm/Makefile.am @@ -0,0 +1,22 @@ +# +# Author: Sun Jiang Dong <sunjd@cn.ibm.com> +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +idir=$(includedir)/heartbeat/lrm +i_HEADERS = lrm_api.h lrm_msg.h racommon.h raexec.h diff --git a/include/lrm/lrm_api.h b/include/lrm/lrm_api.h new file mode 100644 index 0000000..cebff1b --- /dev/null +++ b/include/lrm/lrm_api.h @@ -0,0 +1,455 @@ +/* + * Client-side Local Resource Manager API. + * + * Author: Huang Zhen <zhenh@cn.ibm.com> + * Copyright (C) 2004 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * + * By Huang Zhen <zhenhltc@cn.ibm.com> 2004/2/23 + * + * It is based on the works of Alan Robertson, Lars Marowsky Bree, + * Andrew Beekhof. + * + * The Local Resource Manager needs to provide the following functionalities: + * 1. Provide the information of the resources holding by the node to its + * clients, including listing the resources and their status. + * 2. Its clients can add new resources to lrm or remove from it. + * 3. Its clients can ask lrm to operate the resources, including start, + * restart, stop and so on. + * 4. Provide the information of the lrm itself, including what types of + * resource are supporting by lrm. + * + * The typical clients of lrm are crm and lrmadmin. + */ + + /* + * Notice: + * "status" indicates the exit status code of "status" operation + * its value is defined in LSB, OCF... + * + * "state" indicates the state of resource, maybe LRM_RSC_BUSY, LRM_RSC_IDLE + * + * "op_status" indicates how the op exit. like LRM_OP_DONE,LRM_OP_CANCELLED, + * LRM_OP_TIMEOUT,LRM_OP_NOTSUPPORTED. + * + * "rc" is the return code of an opertioan. it's value is in following enum + * which is defined in "raexec.h" + * enum UNIFORM_RET_EXECRA { + * EXECRA_EXEC_UNKNOWN_ERROR = -2, + * EXECRA_NO_RA = -1, + * EXECRA_OK = 0, + * EXECRA_UNKNOWN_ERROR = 1, + * EXECRA_INVALID_PARAM = 2, + * EXECRA_UNIMPLEMENT_FEATURE = 3, + * EXECRA_INSUFFICIENT_PRIV = 4, + * EXECRA_NOT_INSTALLED = 5, + * EXECRA_NOT_CONFIGURED = 6, + * EXECRA_NOT_RUNNING = 7, + * + * EXECRA_RA_DEAMON_DEAD1 = 11, + * EXECRA_RA_DEAMON_DEAD2 = 12, + * EXECRA_RA_DEAMON_STOPPED = 13, + * EXECRA_STATUS_UNKNOWN = 14 + * }; + */ + +#ifndef __LRM_API_H +#define __LRM_API_H 1 + +#include <glib.h> +#include <lrm/raexec.h> +#include <clplumbing/GSource.h> + +#define LRM_PROTOCOL_MAJOR 0 +#define LRM_PROTOCOL_MINOR 1 +#define LRM_PROTOCOL_VERSION ((LRM_PROTCOL_MAJOR << 16) | LRM_PROTOCOL_MINOR) + +#define RID_LEN 128 + +/*lrm's client uses this structure to access the resource*/ +typedef struct +{ + char* id; + char* type; + char* class; + char* provider; + GHashTable* params; + struct rsc_ops* ops; +}lrm_rsc_t; + + +/*used in struct lrm_op_t to show how an operation exits*/ +typedef enum { + LRM_OP_PENDING = -1, + LRM_OP_DONE, + LRM_OP_CANCELLED, + LRM_OP_TIMEOUT, + LRM_OP_NOTSUPPORTED, + LRM_OP_ERROR +}op_status_t; + +/*for all timeouts: in milliseconds. 0 for no timeout*/ + +/*this structure is the information of the operation.*/ + +#define EVERYTIME -1 +#define CHANGED -2 + +/* Notice the interval and target_rc + * + * when interval==0, the operation will be executed only once + * when interval>0, the operation will be executed repeatly with the interval + * + * when target_rc==EVERYTIME, the client will be notified every time the + * operation executed. + * when target_rc==CHANGED, the client will be notified when the return code + * is different with the return code of last execute of the operation + * when target_rc is other value, only when the return code is the same of + * target_rc, the client will be notified. + */ + +typedef struct{ + /*input fields*/ + char* op_type; + GHashTable* params; + int timeout; + char* user_data; + int user_data_len; + int interval; + int start_delay; + int copyparams; /* copy parameters to the rsc */ + int target_rc; + + /*output fields*/ + op_status_t op_status; + int rc; + int call_id; + char* output; + char* rsc_id; + char* app_name; + char* fail_reason; + unsigned long t_run; /* when did the op run (as age) */ + unsigned long t_rcchange; /* last rc change (as age) */ + unsigned long exec_time; /* time it took the op to run */ + unsigned long queue_time; /* time spent in queue */ + int rsc_deleted; /* resource just deleted? */ +}lrm_op_t; + +extern const lrm_op_t lrm_zero_op; /* an all-zeroes lrm_op_t value */ + +lrm_op_t* lrm_op_new(void); +void lrm_free_op(lrm_op_t* op); +void lrm_free_rsc(lrm_rsc_t* rsc); +void lrm_free_str_list(GList* list); +void lrm_free_op_list(GList* list); +void lrm_free_str_table(GHashTable* table); + + +/*this enum is used in get_cur_state*/ +typedef enum { + LRM_RSC_IDLE, + LRM_RSC_BUSY +}state_flag_t; + +/* defaults for the asynchronous resource failures */ +enum { DEFAULT_FAIL_RC = EXECRA_UNKNOWN_ERROR }; +#define DEFAULT_FAIL_REASON "asynchronous monitor error" +#define ASYNC_OP_NAME "asyncmon" + +/* in addition to HA_OK and HA_FAIL */ +#define HA_RSCBUSY 2 + +struct rsc_ops +{ +/* + *perform_op: Performs the operation on the resource. + *Notice: op is the operation which need to pass to RA and done asyn + * + *op: the structure of the operation. Caller can create the op by + * lrm_op_new() and release the op using lrm_free_op() + * + *return: All operations will be asynchronous. + * The call will return the call id or failed code immediately. + * The call id will be passed to the callback function + * when the operation finished later. + */ + int (*perform_op) (lrm_rsc_t*, lrm_op_t* op); + + +/* + *cancel_op: cancel the operation on the resource. + * + *callid: the call id returned by perform_op() + * + *return: HA_OK for success, HA_FAIL for failure op not found + * or other failure + * NB: the client always gets a notification over callback + * even for operations which were idle (of course, if + * the request has been accepted for processing) + */ + int (*cancel_op) (lrm_rsc_t*, int call_id); + +/* + *flush_ops: throw away all operations queued for this resource, + * and return them as cancelled. + * + *return: HA_OK for success, HA_FAIL for failure + * NB: op is not flushed unless it is idle; + * in that case this call will block + */ + int (*flush_ops) (lrm_rsc_t*); + +/* + *get_cur_state: + * return the current state of the resource + * + *cur_state: current state of the resource + * + *return: cur_state should be in LRM_RSC_IDLE or LRM_RSC_BUSY. + * and the function returns a list of ops. + * the list includes: + * 1. last ops for each type (start/stop/etc) from current client + * 2. current pending ops + * 3. all recurring ops waiting to execute + * the list is sorted by the call_id of ops. + * client can release the list using lrm_free_op_list() + */ + GList* (*get_cur_state) (lrm_rsc_t*, state_flag_t* cur_state); + +/* + *get_last_result: + * return the last op of given type from current client + * + *op_type: the given type + * + *return: the last op. if there is no such op, return NULL. + * client can release the op using lrm_free_op() + */ + lrm_op_t* (*get_last_result)(lrm_rsc_t*, const char *op_type); +}; + + +/* + *lrm_op_done_callback_t: + * this type of callback functions are called when some + * asynchronous operation is done. + * client can release op by lrm_free_op() + */ +typedef void (*lrm_op_done_callback_t) (lrm_op_t* op); + + +typedef struct ll_lrm +{ + struct lrm_ops* lrm_ops; +}ll_lrm_t; + +struct lrm_ops +{ + int (*signon) (ll_lrm_t*, const char * app_name); + + int (*signoff) (ll_lrm_t*); + + int (*delete) (ll_lrm_t*); + + int (*set_lrm_callback) (ll_lrm_t*, + lrm_op_done_callback_t op_done_callback_func); + +/* + *set_lrmd_param: set lrmd parameter + *get_lrmd_param: get lrmd parameter + * + *return: HA_OK for success, HA_FAIL for failure + * NB: currently used only for max_child_count + */ + int (*set_lrmd_param)(ll_lrm_t*, const char *name, const char *value); + char* (*get_lrmd_param)(ll_lrm_t*, const char *name); + +/* + int (*set_parameters)(ll_lrm_t*, const GHashTable* option); + + GHashTable* (*get_all_parameters)(ll_lrm_t*); + + char * (*get_parameter)(ll_lrm_t *, const char * paramname); + + char * (*get_parameter_description)(ll_lrm_t*); +*/ + +/* + *get_rsc_class_supported: + * Returns the resource classes supported. + * e.g. ocf, heartbeat,lsb... + * + *return: a list of the names of supported resource classes. + * caller can release the list by lrm_free_str_list(). + */ + GList* (*get_rsc_class_supported)(ll_lrm_t*); + +/* + *get_rsc_type_supported: + * Returns the resource types supported of class rsc_class. + * e.g. drdb, apache,IPaddr... + * + *return: a list of the names of supported resource types. + * caller can release the list by lrm_free_str_list(). + */ + GList* (*get_rsc_type_supported)(ll_lrm_t*, const char* rsc_class); + +/* + *get_rsc_provider_supported: + * Returns the provider list of the given resource types + * e.g. heartbeat, failsafe... + * + *rsc_provider: if it is null, the default one will used. + * + *return: a list of the names of supported resource provider. + * caller can release the list by lrm_free_str_list(). + */ + GList* (*get_rsc_provider_supported)(ll_lrm_t*, + const char* rsc_class, const char* rsc_type); + +/* + *get_rsc_type_metadata: + * Returns the metadata of the resource type + * + *rsc_provider: if it is null, the default one will used. + * + *return: the metadata. use g_free() to free. + * + */ + char* (*get_rsc_type_metadata)(ll_lrm_t*, const char* rsc_class, + const char* rsc_type, const char* rsc_provider); + +/* + *get_all_type_metadatas: + * Returns all the metadata of the resource type of the class + * + *return: A GHashtable, the key is the RA type, + * the value is the metadata. + * Now only default RA's metadata will be returned. + * please use lrm_free_str_table() to free the return value. + */ + GHashTable* (*get_all_type_metadata)(ll_lrm_t*, const char* rsc_class); + +/* + *get_all_rscs: + * Returns all resources. + * + *return: a list of id of resources. + * caller can release the list by lrm_free_str_list(). + */ + GList* (*get_all_rscs)(ll_lrm_t*); + + +/* + *get_rsc: Gets one resource pointer by the id + * + *return: the lrm_rsc_t type pointer, NULL for failure + * caller can release the pointer by lrm_free_rsc(). + */ + lrm_rsc_t* (*get_rsc)(ll_lrm_t*, const char* rsc_id); + +/* + *add_rsc: Adds a new resource to lrm. + * lrmd holds nothing when it starts. + * crm or lrmadmin should add resources to lrm using + * this function. + * + *rsc_id: An id which sould be generated by client, + * 128byte(include '\0') UTF8 string + * + *class: the class of the resource + * + *type: the type of the resource. + * + *rsc_provider: if it is null, the default provider will used. + * + *params: the parameters for the resource. + * + *return: HA_OK for success, HA_FAIL for failure + */ + int (*add_rsc)(ll_lrm_t*, const char* rsc_id, const char* class, + const char* type, const char* provider, GHashTable* params); + +/* + *delete_rsc: delete the resource by the rsc_id + * + *return: HA_OK for success, HA_FAIL for failure + * NB: resource removal is delayed until all operations are + * removed; the client, however, gets the reply immediately + */ + int (*delete_rsc)(ll_lrm_t*, const char* rsc_id); + +/* + *fail_rsc: fail a resource + * Allow asynchronous monitor failures. Notifies all clients + * which have operations defined for the resource. + * The fail_rc parameter should be set to one of the OCF + * return codes (if non-positive it defaults to + * OCF_ERR_GENERIC). The fail_reason parameter should + * contain the description of the failure (i.e. "daemon + * panicked" or similar). If NULL is passed or empty string, + * it defaults to "asynchronous monitor failure". + * + *return: HA_OK for success, HA_FAIL for failure + */ + int (*fail_rsc)(ll_lrm_t* lrm, const char* rsc_id, + const int fail_rc, const char* fail_reason); +/* + *ipcchan: Return the IPC channel which can be used for determining + * when messages are ready to be read. + *return: the IPC Channel + */ + + IPC_Channel* (*ipcchan)(ll_lrm_t*); + +/* + *msgready: Returns TRUE (1) when a message is ready to be read. + */ + gboolean (*msgready)(ll_lrm_t*); + +/* + *rcvmsg: Cause the next message to be read - activating callbacks for + * processing the message. If no callback processes the message + * it will be ignored. The message is automatically disposed of. + * + *return: the count of message was received. + */ + int (*rcvmsg)(ll_lrm_t*, int blocking); + +}; + +/* + *ll_lrm_new: + * initializes the lrm client library. + * + *llctype: "lrm" + * + */ +ll_lrm_t* ll_lrm_new(const char * llctype); + +/* + *execra_code2string: + * Translate the return code of the operation to string + * + *code: the rc field in lrm_op_t structure + */ +const char *execra_code2string(uniform_ret_execra_t code); +#endif /* __LRM_API_H */ + diff --git a/include/lrm/lrm_msg.h b/include/lrm/lrm_msg.h new file mode 100644 index 0000000..6f671e1 --- /dev/null +++ b/include/lrm/lrm_msg.h @@ -0,0 +1,160 @@ +/* + * Message Define For Local Resource Manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * By Huang Zhen <zhenh@cn.ibm.com> 2004/2/23 + * + */ +/* + * Notice: + *"status" indicates the exit status code of "status" operation + * its value is defined in LSB + *"state" indicates the state of resource, maybe LRM_RSC_BUSY, LRM_RSC_IDLE + *"opstate" indicates how the op exit.like LRM_OP_DONE,LRM_OP_CANCELLED, + * LRM_OP_TIMEOUT,LRM_OP_NOTSUPPORTED. + */ +#ifndef __LRM_MSG_H +#define __LRM_MSG_H 1 + +#include <lrm/lrm_api.h> + +#define LRM_CMDPATH HA_VARRUNDIR"/heartbeat/lrm_cmd_sock" +#define LRM_CALLBACKPATH HA_VARRUNDIR"/heartbeat/lrm_callback_sock" + +/*define the field type used by lrm*/ +#define F_LRM_TYPE "lrm_t" +#define F_LRM_APP "lrm_app" +#define F_LRM_PID "lrm_pid" +#define F_LRM_UID "lrm_uid" +#define F_LRM_GID "lrm_gid" +#define F_LRM_RID "lrm_rid" +#define F_LRM_RTYPE "lrm_rtype" +#define F_LRM_RTYPES "lrm_rtypes" +#define F_LRM_RCLASS "lrm_rclass" +#define F_LRM_RPROVIDER "lrm_rprovider" +#define F_LRM_RPROVIDERS "lrm_rproviders" +#define F_LRM_PARAM "lrm_param" +#define F_LRM_COPYPARAMS "lrm_copyparams" +#define F_LRM_TIMEOUT "lrm_timeout" +#define F_LRM_OP "lrm_op" +#define F_LRM_OPCNT "lrm_opcount" +#define F_LRM_OPSTATUS "lrm_opstatus" +#define F_LRM_RC "lrm_rc" +#define F_LRM_RET "lrm_ret" +#define F_LRM_CALLID "lrm_callid" +#define F_LRM_RCOUNT "lrm_rcount" +#define F_LRM_RIDS "lrm_rids" +#define F_LRM_DATALEN "lrm_datalen" +#define F_LRM_DATA "lrm_data" +#define F_LRM_STATE "lrm_state" +#define F_LRM_INTERVAL "lrm_interval" +#define F_LRM_TARGETRC "lrm_targetrc" +#define F_LRM_LASTRC "lrm_lastrc" +#define F_LRM_STATUS "lrm_status" +#define F_LRM_RSCDELETED "lrm_rscdeleted" +#define F_LRM_METADATA "lrm_metadata" +#define F_LRM_USERDATA "lrm_userdata" +#define F_LRM_DELAY "lrm_delay" +#define F_LRM_T_RUN "lrm_t_run" +#define F_LRM_T_RCCHANGE "lrm_t_rcchange" +#define F_LRM_EXEC_TIME "lrm_exec_time" +#define F_LRM_QUEUE_TIME "lrm_queue_time" +#define F_LRM_FAIL_REASON "lrm_fail_reason" +#define F_LRM_ASYNCMON_RC "lrm_asyncmon_rc" +#define F_LRM_LRMD_PARAM_NAME "lrm_lrmd_param_name" +#define F_LRM_LRMD_PARAM_VAL "lrm_lrmd_param_val" + +#define PRINT printf("file:%s,line:%d\n",__FILE__,__LINE__); + + +/*define the message typs between lrmd and client lib*/ +#define REGISTER "reg" +#define GETRSCCLASSES "rclasses" +#define GETRSCTYPES "rtypes" +#define GETPROVIDERS "rproviders" +#define GETRSCMETA "rmetadata" +#define GETALLRCSES "getall" +#define GETRSC "getrsc" +#define GETLASTOP "getlastop" +#define GETRSCSTATE "getstate" +#define SETMONITOR "setmon" +#define GETMONITORS "getmons" +#define FLUSHRSC "flush" +#define ADDRSC "addrsc" +#define DELRSC "delrsc" +#define FAILRSC "failrsc" +#define PERFORMOP "op" +#define ISOPSUPPORT "opspt" +#define OPDONE "opdone" +#define MONITOR "monitor" +#define RETURN "return" +#define FLUSHOPS "flushops" +#define CANCELOP "cancelop" +#define SETLRMDPARAM "setparam" +#define GETLRMDPARAM "getparam" + +#define MAX_INT_LEN 64 +#define MAX_NAME_LEN 255 +#define MAX_VALUE_LEN 255 +#define MAX_PARAM_LEN 1024 + + +GHashTable* copy_str_table(GHashTable* hash_table); +GHashTable* merge_str_tables(GHashTable* old, GHashTable* new); +void free_str_table(GHashTable* hash_table); + + /* + * message for no parameter, like unreg,types,getall + * they do not include any paramters + */ +struct ha_msg* create_lrm_msg(const char* msg); + +/* + * message for only one parameter - resource id, + * like getrsc,delrsc,flush,getstate,getmons + */ +struct ha_msg* create_lrm_rsc_msg(const char* rid, const char* msg); + +/* register client message */ +struct ha_msg* create_lrm_reg_msg(const char* app_name); + +/* + * add new resource + * according to the opinion of Lars, it is awkward that we combine all + * parameters in to one string. I think so too. So this call may changed soon + */ +struct ha_msg* create_lrm_addrsc_msg(const char* rid, const char* class, + const char* type, const char* provider, GHashTable* parameter); + +/* + * + *the return message from lrmd for reg,unreg,addrsc,delrsc,isopsupport. + *these return messages only include return code. + * + */ +struct ha_msg* create_lrm_ret(int rc, int fields); + + +/* + * the return message for a status change monitoring. + */ + +struct ha_msg* create_rsc_perform_op_msg (const char* rid, lrm_op_t* op); + +#endif /* __LRM_MSG_H */ diff --git a/include/lrm/racommon.h b/include/lrm/racommon.h new file mode 100644 index 0000000..b16aa24 --- /dev/null +++ b/include/lrm/racommon.h @@ -0,0 +1,29 @@ +/* + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef RACOMMON_H +#define RACOMMON_H + +void get_ra_pathname(const char* class_path, const char* type, const char* provider, char pathname[]); +gboolean filtered(char * file_name); +int get_runnable_list(const char* class_path, GList ** rsc_info); +int get_failed_exec_rc(void); +void closefiles(void); + +#endif /* RACOMMON_H */ diff --git a/include/lrm/raexec.h b/include/lrm/raexec.h new file mode 100644 index 0000000..0b69831 --- /dev/null +++ b/include/lrm/raexec.h @@ -0,0 +1,157 @@ +/* + * raexec.h: The universal interface of RA Execution Plugin + * + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef RAEXEC_H +#define RAEXEC_H +#include <glib.h> +#include <lrm/racommon.h> + +/* Uniform return value of executing RA */ +enum UNIFORM_RET_EXECRA { + EXECRA_EXEC_UNKNOWN_ERROR = -2, + EXECRA_NO_RA = -1, + EXECRA_OK = 0, + EXECRA_UNKNOWN_ERROR = 1, + EXECRA_INVALID_PARAM = 2, + EXECRA_UNIMPLEMENT_FEATURE = 3, + EXECRA_INSUFFICIENT_PRIV = 4, + EXECRA_NOT_INSTALLED = 5, + EXECRA_NOT_CONFIGURED = 6, + EXECRA_NOT_RUNNING = 7, + EXECRA_RUNNING_MASTER = 8, + EXECRA_FAILED_MASTER = 9, + + /* For status command only */ + EXECRA_RA_DEAMON_DEAD1 = 11, + EXECRA_RA_DEAMON_DEAD2 = 12, + EXECRA_RA_DEAMON_STOPPED = 13, + EXECRA_STATUS_UNKNOWN = 14 +}; +typedef enum UNIFORM_RET_EXECRA uniform_ret_execra_t; + +#define RA_MAX_NAME_LENGTH 240 +#define RA_MAX_DIRNAME_LENGTH 200 +#define RA_MAX_BASENAME_LENGTH 40 + +/* + * RA Execution Interfaces + * The plugin usage is divided into two step. First to send out a command to + * execute a resource agent via calling function execra. Execra is a unblock + * function, always return at once. Then to call function post_query_result to + * get the RA exection result. +*/ +struct RAExecOps { + /* + * Description: + * Launch a exection of a resource agent -- normally is a script + * + * Parameters: + * rsc_id: The resource instance id. + * rsc_type: The basename of a RA. + * op_type: The operation that hope RA to do, such as "start", + * "stop" and so on. + * cmd_params: The command line parameters need to be passed to + * the RA for a execution. + * env_params: The enviroment parameters need to be set for + * affecting the action of a RA execution. As for + * OCF style RA, it's the only way to pass + * parameter to the RA. + * + * Return Value: + * 0: RA execution is ok, while the exec_key is a valid value. + * -1: The RA don't exist. + * -2: There are invalid command line parameters. + * -3: Other unkown error when launching the execution. + */ + int (*execra)( + const char * rsc_id, + const char * rsc_type, + const char * provider, + const char * op_type, + const int timeout, + GHashTable * params); + + /* + * Description: + * Map the specific ret value to a uniform value. + * + * Parameters: + * ret_execra: the RA type specific ret value. + * op_type: the operation type + * std_output: the output which the RA write to stdout. + * + * Return Value: + * A uniform value without regarding RA type. + */ + uniform_ret_execra_t (*map_ra_retvalue)( + int ret_execra + , const char * op_type + , const char * std_output); + + /* + * Description: + * List all resource info of this class + * + * Parameters: + * rsc_info: a GList which item data type is rsc_info_t as + * defined above, containing all resource info of + * this class in the local machine. + * + * Return Value: + * >=0 : succeed. the RA type number of this RA class + * -1: failed due to invalid RA directory such as not existing. + * -2: failed due to other factors + */ + int (*get_resource_list)(GList ** rsc_info); + + /* + * Description: + * List all providers of this type + * + * Parameters: + * providers: a GList which item data type is string. + * the name of providers of the resource agent + * + * Return Value: + * >=0 : succeed. the provider number of this RA + * -1: failed due to invalid RA directory such as not existing. + * -2: failed due to other factors + */ + int (*get_provider_list)(const char* ra_type, GList ** providers); + + /* + * Description: + * List the metadata of the resource agent this class + * + * Parameters: + * rsc_type: the type of the ra + * + * Return Value: + * !NULL : succeed. the RA metadata. + * NULL: failed + */ + char* (*get_resource_meta)(const char* rsc_type, const char* provider); +}; + +#define RA_EXEC_TYPE RAExec +#define RA_EXEC_TYPE_S "RAExec" + +#endif /* RAEXEC_H */ diff --git a/include/pils/Makefile.am b/include/pils/Makefile.am new file mode 100644 index 0000000..4b6c6a0 --- /dev/null +++ b/include/pils/Makefile.am @@ -0,0 +1,25 @@ +# +# linux-ha: Linux-HA heartbeat code +# +# Copyright (C) 2001 Michael Moerz +# This instance created by Horms +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +idir=$(includedir)/pils + +i_HEADERS = generic.h interface.h plugin.h diff --git a/include/pils/generic.h b/include/pils/generic.h new file mode 100644 index 0000000..83bf3e3 --- /dev/null +++ b/include/pils/generic.h @@ -0,0 +1,118 @@ +#ifndef PILS_GENERIC_H +#define PILS_GENERIC_H +/* + * Copyright (C) 2000 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Generic interface (implementation) manager + * + * This manager will manage any number of types of interfaces. + * + * This means that when any implementations of our client interfaces register + * or unregister, it is us that makes their interfaces show up in the outside + * world. + * + * And, of course, we have to do this in a very generic way, since we have + * no idea about the client programs or interface types, or anything else. + * + * We do that by getting a parameter passed to us which tell us the names + * of the interface types we want to manage, and the address of a GHashTable + * for each type that we put the implementation in when they register + * themselves. + * + * So, each type of interface that we manage gets its own private + * GHashTable of the implementations of that type that are currently + * registered. + * + * For example, if we manage communication modules, their exported + * interfaces will be registered in a hash table. If we manage + * authentication modules, they'll have their (separate) hash table that + * their exported interfaces are registered in. + * + */ +#include <pils/interface.h> + +/* + * Header defintions for using the generic interface/implementation + * manager plugin. + */ + +/* + * Notification types for the callback function. + */ +typedef enum { + PIL_REGISTER, /* Someone has registered an implementation */ + PIL_UNREGISTER /* Someone has unregistered an implementation */ +}GenericPILCallbackType; + +/* A user callback for the generic interface manager */ +typedef int (*GenericPILCallback) +( GenericPILCallbackType type /* Event type */ +, PILPluginUniv* univ /* pointer to plugin universe */ +, const char * iftype /* Interface type */ +, const char * ifname /* Implementation (interface) name */ +, void * userptr /* Whatever you want it to be ;-) */ +); + +/* + * Structures to declare the set of interface types we're managing. + */ +typedef struct { + const char * iftype; /* What type of interface is this? */ + GHashTable** ifmap; /* Table with implementation info */ + void* importfuns; /* Functions for interface to import */ + GenericPILCallback callback; /* Function2call when events occur */ + void* userptr; /* Passed to Callback function */ +}PILGenericIfMgmtRqst; +/* + * What does this look like in practice? + * + * GHashTable* authmodules = NULL; + * GHashTable* commmodules = NULL; + * PILGenericIfMgmtRqst RegisterRequests[] = + * { + * {"auth", &authmodules, &authimports, NULL, NULL}, + * {"comm", &commmodules, &commimports, NULL, NULL}, + * {NULL, NULL, NULL, NULL, NULL} + // NULL entry must be here + * }; + * + * PILPlugin* PluginUniverse; + * + * PluginUniverse = NewPILPlugin("/usr/lib/whatever/plugins"); + * + * PILLoadPlugin(PluginUniverse, "InterfaceMgr", "generic", &RegisterRequests); + * // N. B.: Passing RegisterRequests as an argument is essential + * + * Then, when you load an auth module, its exported interface gets added + * to "authmodules". When you unload an auth module, it gets removed + * from authmodules. + * + * Then, when you load a comm module, its exported interfaces gets added + * to "commodules". When you unload a comm module, its exported + * interfaces get removed from "commodules" + * + * If there are simple changes that would be useful for this generic + * plugin manager, then "patches are being accepted" :-) + * + * On the other hand, If you don't like the way this plugin manager works + * in a broader way, you're free to write your own - it's just another + * plugin ;-) + */ +#endif diff --git a/include/pils/interface.h b/include/pils/interface.h new file mode 100644 index 0000000..5a5114e --- /dev/null +++ b/include/pils/interface.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2000 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef PILS_INTERFACE_H +# define PILS_INTERFACE_H +# ifndef PILS_PLUGIN_H +# include <pils/plugin.h> +# endif + +/***************************************************************************** + * + * The most basic interface type is the "IFManager" interface. + * Each interface manager registers and deals with interfaces of a given type. + * + * Such an interface must be loaded before any plugins of it's type can + * be loaded. + * + * In order to register any plugin of type "foo", we must load a interface of + * type "Interface" named "foo". This interface then manages the + * registration of all interfaces of type foo. + * + * To bootstrap, we load a interface of type "Interface" named "Interface" + * during the initialization of the plugin system. + * + * IFManagers will be autoloaded if certain conditions are met... + * + * If a IFManager is to be autoloaded, there must be one interface manager + * per file, and the file must be named according to the type of the + * interface it implements, and loaded in the directory named PI_IFMANAGER + * ("Interface"). + * + */ + + +/* + * I'm unsure exactly which of the following structures + * are needed to write a interface, or a interface manager. + * We'll get that figured out and scope the defintions accordingly... + */ + +/* + * PILInterface (AKA struct PILInterface_s) holds the information + * we use to track a single interface manager. + */ + + +struct PILInterface_s { + unsigned long MagicNum; + PILInterfaceType* interfacetype; /* Parent pointer */ + char * interfacename; /* malloced interface name */ + PILInterface* ifmanager; /* plugin managing us */ + void* exports; /* Exported Functions */ + /* for this interface */ + PILInterfaceFun if_close; /* Interface close operation*/ + void* ud_interface; /* per-interface user data */ + int refcnt; /* Ref count for plugin */ + PILPlugin* loadingpi; /* Plugin that loaded us */ +}; +/* + * PILInterfaceType (AKA struct PILInterfaceType_s) holds the info + * we use to track the set of all interfaces of a single kind. + */ +struct PILInterfaceType_s { + unsigned long MagicNum; + char* typename; /* Our interface type name */ + GHashTable* interfaces; /* The set of interfaces + * of our type. The + * "values" are all + * PILInterface * objects + */ + void* ud_if_type; /* per-interface-type user + data*/ + PILInterfaceUniv* universe; /* Pointer to parent (up) */ + PILInterface* ifmgr_ref; /* Pointer to our interface + manager */ +}; + +/* + * PILInterfaceUniv (AKA struct PILInterfaceUniv_s) holds the information + * for all interfaces of all types. From our point of view this is + * our universe ;-) + */ + +struct PILInterfaceUniv_s{ + unsigned long MagicNum; + GHashTable* iftypes; /* + * Set of Interface Types + * The values are all + * PILInterfaceType objects + */ + struct PILPluginUniv_s* piuniv; /* parallel universe of + * plugins + */ +}; + +#ifdef ENABLE_PLUGIN_MANAGER_PRIVATE +/* + * From here to the end is specific to interface managers. + * This data is only needed by interface managers, and the interface + * management system itself. + * + */ +typedef struct PILInterfaceOps_s PILInterfaceOps; + + +/* Interfaces imported by a IFManager interface */ +struct PILInterfaceImports_s { + + /* Return current reference count */ + int (*RefCount)(PILInterface * eifinfo); + + /* Incr/Decr reference count */ + int (*ModRefCount)(PILInterface*eifinfo, int plusminus); + + /* Unregister us as a interface */ + void (*ForceUnRegister)(PILInterface *eifinfo); + + /* For each client */ + void (*ForEachClientDel)(PILInterface* manangerif + , gboolean(*f)(PILInterface* clientif, void * other) + , void* other); + +}; + +/* Interfaces exported by an InterfaceManager interface */ +struct PILInterfaceOps_s{ +/* + * These are the interfaces exported by an InterfaceManager to the + * interface management infrastructure. These are not imported + * by interfaces - only the interface management infrastructure. + */ + + /* RegisterInterface - register this interface */ + PIL_rc (*RegisterInterface)(PILInterface* newif + , void** imports); + + PIL_rc (*UnRegisterInterface)(PILInterface*ifinfo); /* Unregister IF*/ + /* And destroy PILInterface object */ +}; + +#endif /* ENABLE_PLUGIN_MANAGER_PRIVATE */ +#endif /* PILS_INTERFACE_H */ diff --git a/include/pils/plugin.h.in b/include/pils/plugin.h.in new file mode 100644 index 0000000..cb67d91 --- /dev/null +++ b/include/pils/plugin.h.in @@ -0,0 +1,736 @@ +/* + * Copyright (C) 2000 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PILS_PLUGIN_H +# define PILS_PLUGIN_H +# include <ltdl.h> +# include <glue_config.h> + +/* Glib headers generate warnings - so we make them go away */ + +#define time FOOtime +#define index FOOindex +#include <glib.h> +#undef index +#undef time + +/***************************************************************************** + * PILS - Universal Plugin and Interface loading system + ***************************************************************************** + * + * An Overview of PILS... + * + * PILS is fairly general and reasonably interesting plugin loading system. + * We manage both plugins and their interfaces + * + * This plugin / interface management system is quite general, and should be + * directly usable by basically any project on any platform on which it runs + * - which should be many, since everything is build with automake + * and libtool. + * + * Some terminology... + * + * There are two basic kinds of objects we deal with here: + * + * Plugins: dynamically loaded chunks of code which implement one or more + * interfaces. The system treats all plugins as the same. + * In UNIX, these are dynamically loaded ".so" files. + * + * Interface: A set of functions which implement a particular capability + * (or interface) + * Generally interfaces are registered as part of a plugin. + * The system treats all interfaces of the same type the same. + * It is common to have exactly one interface inside of each plugin. + * In this case, the interface name should match the plugin name. + * + * Each interface implementation exports certain functions for its clients + * to use. We refer to these those "Ops". Every interface of the same type + * "imports" the same interfaces from its interface manager, + * and exports the same "Ops". + * + * Each interface implementation is provided certain interfaces which it + * imports when it from its interface manager when it is registered. + * We refer to these as "Imports". Every interface of a given type + * imports the same interfaces. + * + * The story with plugins is a little different... + * + * Every plugin exports a certain set of interfaces, regardless of what type + * of interfaces is implemented by it. These are described in the + * PILPluginOps structure. + * + * Every plugin imports a certain set of interfaces, regardless of what type + * of interfaces it may implement. These are described by the + * PILPluginImports structure. + * + * In the function parameters below, the following notation will + * sometimes appear: + * + * (OP) == Output Parameter - a parameter which is modified by the + * function being called + * + * + ***************************************************************************** + * + * The basic structures we maintain about plugins are as follows: + * + * PILPlugin The data which represents a plugin. + * PILPluginType The data common to all plugins of a given type + * PILPluginUniv The set of all plugin types in the Universe + * (well... at least *this* universe) + * + * The basic structures we maintain about interfaces are as follows: + * PILInterface The data which represents a interface + * PILInterfaceType The data which is common to all + * interfaces of a given type + * PILPluginUniv The set of all interface types in the Universe + * (well... at least *this* universe) + * + * Regarding "Universe"s. It is our intent that a given program can deal + * with plugins in more than one universe. This might occur if you have two + * independent libraries each of which uses the plugin loading environment + * to manage their own independent interface components. There should be + * no restriction in creating a program which uses both of these libraries. + * At least that's what we hope ;-) + * + * + *************************************************************************** + * SOME MORE DETAILS ABOUT PLUGINS... + *************************************************************************** + * + * Going back to more detailed data structures about plugins... + * + * PILPluginImports The set of standard functions all plugins + * import. + * This includes: + * register_plugin() + * unregister_plugin() + * register_interface() + * unregister_interface() + * load_plugin() + * log() Preferred logging function + * + * PILPluginOps The set of standard operations all plugins + * export. + * This includes: + * pluginversion() + * pluginname() + * getdebuglevel() + * setdebuglevel() + * close() Prepare for unloading... + * + * Although we treat plugins pretty much the same, they are still + * categorized into "types" - one type per directory. These types + * generally correspond to interface types. + * + * One can only cause a plugin to be loaded - not a interface. But it is + * common to assume that loading a plugin named foo of type bar will + * cause a interface named foo of type bar to be registered. If one + * wants to implement automatic plugin loading in a given interface type, + * this assumption is necessary. + * + * The general way this works is... + * + * - A request is made to load a particular plugin of a particular type. + * + * - The plugin is loaded from the appropriate directory for plugins + * of that type. + * + * - The ml_plugin_init() function is called once when the plugin is + * loaded. + * + * The ml_plugin_init() function is passed a vector of functions which + * point to functions it can call to register itself, etc. + * (it's of type PILPluginImports) + * + * The ml_plugin_init function then uses this set of imported functions + * to register itself and its interfaces. + * + * The mechanism of registering a interface is largely the same for + * every interface. However, the semantics of registering a interfaces + * is determined by the interface manager for the particular type of + * interface being discussed. + * + *************************************************************************** + * SOME MORE DETAILS ABOUT PLUGINS... + *************************************************************************** + * + * There is only one built in type of interface. That's the Interface + * manager interface. + * The interface manager for the interface of type "InterfaceMgr", + * named "InterfaceMgr" inserts itself into the system in order + * to bootstrap things... + * + * When an attempt is made to register a interface of an unknown type, + * then the appropriate Interface manager is loaded automatically. + * + * The name of an interface manager determines the type of + * interface it manages. + * + * It handles requests for interfaces whose type is the same + * as its interface name. If the interface manager's interface name + * is foo, then it is the interface manager for all interfaces whose + * type is foo. + * + * Types associated with interfaces of type Interface + * + * PILInterfaceOps The set of interfaces that every interface + * manager exports + * PILInterfaceImports The set of interfaces which are supplied to + * (imported by) every interface of type + * Interface. (that is, every interface + * manager). + * + ***************************************************************************** + * + * Each plugin has only one entry point which is exported directly, regardless + * of what kind of interface(s) it may implement... + * + * This entrypoint is named ml_plugin_init() {more or less - see below} + * + * The ml_plugin_init() function is called once when the plugin is loaded. + * + * + * All other function pointers are registered (exported) through parameters + * passed to ml_plugin_init() + * + * It is the purpose of the Ml_plugin_init() to register the plugin, + * and all the interfaces which this plugin implements. A pointer to + * the registration function is in the parameters which are passed + * to ml_plugin_init(). + * + ***************************************************************************** + * + * THINGS IN THIS DESIGN WHICH ARE PROBABLY BROKEN... + * + * It may also be the case that the plugin loading environment needs + * to be able to have some kind of user_data passed to it which it can + * also pass along to any interface ... + * + * Maybe this should be handled by a sort of global user_data registration + * structure, so globals can be passed to interfaces when they're registered. + * + * A sort of "user_data" registry. One for each interface type and one + * for each interface... Or maybe it could be even more flexible... + * + * This is all so that these nice pristene, beautiful concepts can come out + * and work well in the real world where interfaces need to interact with + * some kind of global system view, and with each other... + * + * Probably need some better way of managing interface versions, etc. + * + **************************************************************************** + */ + +/* + * If you want to use this funky export stuff, then you need to #define + * PIL_PLUGINTYPE and PIL_PLUGIN *before* including this file. + * + * The way to use this stuff is to declare your primary entry point this way: + * + * This example is for an plugin of type "auth" named "sha1" + * + * #define PIL_PLUGINTYPE auth + * #define PIL_PLUGIN sha1 + * #include <upmls/PILPlugin.h> + * + * static const char* Ourpluginversion (void); + * static const char* Ourpluginname (void); + * static int Ourgetdebuglevel(void); + * static void Oursetdebuglevel(int); + * static void Ourclose (PILPlugin*); + * + * static struct PILPluginOps our_exported_plugin_operations = + * { Ourpluginversion, + * , Ourpluginname + * , Ourgetdebuglevel + * , Oursetdebuglevel + * , Ourclose + * }; + * + * static const PILPluginImports* PluginOps; + * static PILPlugin* OurPlugin; + * + * // Our plugin initialization and registration function + * // It gets called when the plugin gets loaded. + * PIL_rc + * PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) + * { + * PluginOps = imports; + * OurPlugin = us; + * + * // Register ourself as a plugin * / + * imports->register_plugin(us, &our_exported_plugin_operations); + * + * // Register our interfaces + * imports->register_interface(us, "interfacetype", "interfacename" + * // Be sure and define "OurExports" and OurImports + * // above... + * , &OurExports + * , &OurImports); + * // Repeat for all interfaces in this plugin... + * + * } + * + * Except for the PIL_PLUGINTYPE and the PIL_PLUGIN definitions, and changing + * the names of various static variables and functions, every single plugin is + * set up pretty much the same way + * + */ + +/* + * No doubt there is a fancy preprocessor trick for avoiding these + * duplications but I don't have time to figure it out. Patches are + * being accepted... + */ +#define mlINIT_FUNC _pil_plugin_init +#define mlINIT_FUNC_STR "_pil_plugin_init" +#define PIL_INSERT _LTX_ +#define PIL_INSERT_STR "_LTX_" + +/* + * snprintf-style format string for initialization entry point name: + * arguments are: (plugintype, pluginname) + */ +#define PIL_FUNC_FMT "%s" PIL_INSERT_STR "%s" mlINIT_FUNC_STR + +#ifdef __STDC__ +# define EXPORTHELPER1(plugintype, insert, pluginname, function) \ + plugintype##insert##pluginname##function +#else +# define EXPORTHELPER1(plugintype, insert, pluginname, function) \ + plugintype/**/insert/**/pluginname/**/function +#endif + +#define EXPORTHELPER2(a, b, c, d) EXPORTHELPER1(a, b, c, d) +#define PIL_PLUGIN_INIT \ + EXPORTHELPER2(PIL_PLUGINTYPE,PIL_INSERT,PIL_PLUGIN,mlINIT_FUNC) + +/* + * Plugin loading return codes. OK will always be zero. + * + * There are many ways to fail, but only one kind of success ;-) + */ + +typedef enum { + PIL_OK=0, /* Success */ + PIL_INVAL=1, /* Invalid Parameters */ + PIL_BADTYPE=2, /* Bad plugin/interface type */ + PIL_EXIST=3, /* Duplicate Plugin/Interface name */ + PIL_OOPS=4, /* Internal Error */ + PIL_NOPLUGIN=5 /* No such plugin or Interface */ +}PIL_rc; /* Return code from Plugin fns*/ + +const char * PIL_strerror(PIL_rc rc); + +typedef struct PILPluginImports_s PILPluginImports; +typedef struct PILPluginOps_s PILPluginOps; +typedef struct PILPlugin_s PILPlugin; +typedef struct PILPluginUniv_s PILPluginUniv; +typedef struct PILPluginType_s PILPluginType; + +typedef struct PILInterface_s PILInterface; +typedef struct PILInterfaceImports_s PILInterfaceImports; +typedef struct PILInterfaceUniv_s PILInterfaceUniv; +typedef struct PILInterfaceType_s PILInterfaceType; + +typedef PIL_rc(*PILInterfaceFun)(PILInterface*, void* ud_interface); + +#define PIL_MAGIC_PLUGIN 0xFEEDBEEFUL +#define PIL_MAGIC_PLUGINTYPE 0xFEEDCEEFUL +#define PIL_MAGIC_PLUGINUNIV 0xFEEDDEEFUL +#define PIL_MAGIC_INTERFACE 0xFEEDEEEFUL +#define PIL_MAGIC_INTERFACETYPE 0xFEEDFEEFUL +#define PIL_MAGIC_INTERFACEUNIV 0xFEED0EEFUL + +#define IS_PILPLUGIN(s) ((s)->MagicNum == PIL_MAGIC_PLUGIN) +#define IS_PILPLUGINTYPE(s) ((s)->MagicNum == PIL_MAGIC_PLUGINTYPE) +#define IS_PILPLUGINUNIV(s) ((s)->MagicNum == PIL_MAGIC_PLUGINUNIV) +#define IS_PILINTERFACE(s) ((s)->MagicNum == PIL_MAGIC_INTERFACE) +#define IS_PILINTERFACETYPE(s) ((s)->MagicNum == PIL_MAGIC_INTERFACETYPE) +#define IS_PILINTERFACEUNIV(s) ((s)->MagicNum == PIL_MAGIC_INTERFACEUNIV) + +/* The type of a Plugin Initialization Function */ +typedef PIL_rc (*PILPluginInitFun) (PILPlugin*us +, PILPluginImports* imports +, void* plugin_user_data); + +/* + * struct PILPluginOps_s (typedef PILPluginOps) defines the set of functions + * exported by all plugins... + */ +struct PILPluginOps_s { + const char* (*pluginversion) (void); + int (*getdebuglevel) (void); + void (*setdebuglevel) (int); + const char* (*license) (void); + const char* (*licenseurl) (void); + void (*close) (PILPlugin*); +}; + +/* + * Logging levels for the "standard" log interface. + */ + +typedef enum { + PIL_FATAL= 1, /* BOOM! Causes program to stop */ + PIL_CRIT = 2, /* Critical -- serious error */ + PIL_WARN = 3, /* Warning */ + PIL_INFO = 4, /* Informative message */ + PIL_DEBUG= 5 /* Debug message */ +}PILLogLevel; +typedef void (*PILLogFun)(PILLogLevel priority, const char * fmt, ...) + G_GNUC_PRINTF(2,3); + +/* + * The size glib2 type du jour? + * (once, this used to be size_t, so this change could break + * distributions with older glib2 versions; if so, just add an + * #ifelse below) + */ +#if GLIB_MINOR_VERSION <= 14 + typedef gulong glib_size_t; +#else + typedef gsize glib_size_t; +#endif + +/* + * struct PILPluginImports_s (typedef PILPluginImports) defines + * the functions and capabilities that every plugin imports when it is loaded. + */ + + +struct PILPluginImports_s { + PIL_rc (*register_plugin)(PILPlugin* piinfo + , const PILPluginOps* commonops); + PIL_rc (*unregister_plugin)(PILPlugin* piinfo); +/* + * A little explanation of the close_func parameter to register_interface + * is in order. + * + * It is an exported operation function, just like the Ops structure. + * However, the Ops vector is exported to applications that + * are using the interface. Unlike the Ops structure, close_func is + * exported only to the interface system, since applications shouldn't + * call it directly, but should manage the reference counts for the + * interfaces instead. + * The generic interface system doesn't have any idea how to call + * any functions in the operations vector. So, it's a separate + * parameter for two good reasons. + */ + PIL_rc (*register_interface)(PILPlugin* piinfo + , const char * interfacetype /* Type of interface */ + , const char * interfacename /* Name of interface */ + , void* Ops /* Info (functions) exported + by this interface */ + /* Function to call to shut down this interface */ + , PILInterfaceFun close_func + + , PILInterface** interfaceid /* Interface id (OP) */ + , void** Imports + , void* ud_interface); /* interface user data */ + + PIL_rc (*unregister_interface)(PILInterface* interfaceid); + PIL_rc (*load_plugin)(PILPluginUniv* universe + , const char * plugintype, const char * pluginname + , void* plugin_private); + + void (*log) (PILLogLevel priority, const char * fmt, ...) + G_GNUC_PRINTF(2,3); + gpointer (*alloc)(glib_size_t size); + gpointer (*mrealloc)(gpointer space, glib_size_t size); + void (*mfree)(gpointer space); + char* (*mstrdup)(const char *s); +}; + +/* + * Function for logging with the given logging function + * The reason why it's here is so we can get printf arg checking + * You can't get that when you call a function pointer directly. + */ +void PILCallLog(PILLogFun logfun, PILLogLevel priority, const char * fmt, ...) + G_GNUC_PRINTF(3,4); + +/* + * EXPORTED INTERFACES... + */ + +/* Create a new plugin universe - start the plugin loading system up */ +PILPluginUniv* NewPILPluginUniv(const char * baseplugindirectory); + +/* Change memory allocation functions right after creating universe */ +void PilPluginUnivSetMemalloc(PILPluginUniv* +, gpointer (*alloc)(glib_size_t size) +, gpointer (*mrealloc)(gpointer, glib_size_t size) +, void (*mfree)(void* space) +, char* (*mstrdup)(const char *s)); + + +void PilPluginUnivSetLog(PILPluginUniv* +, void (*log) (PILLogLevel priority, const char * fmt, ...) + G_GNUC_PRINTF(2,3)); + + +/* Delete a plugin universe - shut the plugin loading system down */ +/* Best if used carefully ;-) */ +void DelPILPluginUniv(PILPluginUniv*); + +/* Set the debug level for the plugin system itself */ +void PILpisysSetDebugLevel (int level); + +/* Return a list of plugins of the given type */ +char ** PILListPlugins(PILPluginUniv* u, const char *plugintype +, int* plugincount /*can be NULL*/); + +/* Free the plugin list returned by PILFreeListPlugins */ +void PILFreePluginList(char ** pluginlist); + +/* Load the requested plugin */ +PIL_rc PILLoadPlugin(PILPluginUniv* piuniv +, const char * plugintype +, const char * pluginname +, void * pi_private); + +/* Return PIL_OK if the given plugin exists */ +PIL_rc PILPluginExists(PILPluginUniv* piuniv +, const char * plugintype +, const char * pluginname); + +/* Either or both of pitype and piname may be NULL */ +void PILSetDebugLevel(PILPluginUniv*u, const char * pitype +, const char * piname +, int level); + +/* Neither pitype nor piname may be NULL */ +int PILGetDebugLevel(PILPluginUniv* u, const char * pitype +, const char * piname); + +PIL_rc PILIncrIFRefCount(PILPluginUniv* piuniv +, const char * interfacetype +, const char * interfacename +, int plusminus); + +int PILGetIFRefCount(PILPluginUniv* piuniv +, const char * interfacetype +, const char * interfacename); + +void PILLogMemStats(void); +/* The plugin/interface type of a interface manager */ + +#define PI_IFMANAGER "InterfaceMgr" +#define PI_IFMANAGER_TYPE InterfaceMgr + +/* + * These functions are standard exported functions for all plugins. + */ + +#define PIL_PLUGIN_BOILERPLATE_PROTOTYPES_GENERIC(PluginVersion, DebugName) \ +/* \ + * Prototypes for boilerplate functions \ + */ \ +static const char* Ourpluginversion(void); \ +static int GetOurDebugLevel(void); \ +static void SetOurDebugLevel(int); \ +static const char * ReturnOurLicense(void); \ +static const char * ReturnOurLicenseURL(void); + +#define PIL_PLUGIN_BOILERPLATE_FUNCS(PluginVersion, DebugName) \ +/* \ + * Definitions of boilerplate functions \ + */ \ +static const char* \ +Ourpluginversion(void) \ +{ return PluginVersion; } \ + \ +static int DebugName = 0; \ + \ +static int \ +GetOurDebugLevel(void) \ +{ return DebugName; } \ + \ +static void \ +SetOurDebugLevel(int level) \ +{ DebugName = level; } \ + \ +static const char * \ +ReturnOurLicense(void) \ +{ return PIL_PLUGINLICENSE; } \ + \ +static const char * \ +ReturnOurLicenseURL(void) \ +{ return PIL_PLUGINLICENSEURL; } + +#define PIL_PLUGIN_BOILERPLATE(PluginVersion, DebugName, CloseName) \ +PIL_PLUGIN_BOILERPLATE_PROTOTYPES_GENERIC(PluginVersion, DebugName) \ +static void CloseName(PILPlugin*); \ +/* \ + * Initialize Plugin Exports structure \ + */ \ +static PILPluginOps OurPIExports = \ +{ Ourpluginversion \ +, GetOurDebugLevel \ +, SetOurDebugLevel \ +, ReturnOurLicense \ +, ReturnOurLicenseURL \ +, CloseName \ +}; \ +PIL_PLUGIN_BOILERPLATE_FUNCS(PluginVersion, DebugName) + +#define PIL_PLUGIN_BOILERPLATE2(PluginVersion, DebugName) \ +PIL_PLUGIN_BOILERPLATE_PROTOTYPES_GENERIC(PluginVersion, DebugName) \ +/* \ + * Initialize Plugin Exports structure \ + */ \ +static PILPluginOps OurPIExports = \ +{ Ourpluginversion \ +, GetOurDebugLevel \ +, SetOurDebugLevel \ +, ReturnOurLicense \ +, ReturnOurLicenseURL \ +, NULL \ +}; \ +PIL_PLUGIN_BOILERPLATE_FUNCS(PluginVersion, DebugName) + + +/* A few sample licenses and URLs. We can easily add to this */ + +#define LICENSE_GPL "gpl" +#define URL_GPL "http://www.fsf.org/licenses/gpl.html" + +#define LICENSE_LGPL "lgpl" +#define URL_LGPL "http://www.fsf.org/licenses/lgpl.html" + +#define LICENSE_X11 "x11" +#define URL_X11 "http://www.x.org/terms.htm" + +#define LICENSE_PUBDOM "publicdomain" +#define URL_PUBDOM "file:///dev/null" + +#define LICENSE_MODBSD "modbsd" +#define URL_MODBSD "http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5" + +#define LICENSE_OLDBSD "origbsd" +#define URL_OLDBSD "http://www.xfree86.org/3.3.6/COPYRIGHT2.html#6" + +#define LICENSE_EXPAT "expat" +#define URL_EXPAT "http://www.jclark.com/xml/copying.txt" + +#define LICENSE_ZLIB "zlib" +#define URL_ZLIB "http://www.gzip.org/zlib/zlib_license.html" + +#define LICENSE_APACHE_10 "apache1_0" +#define URL_APACHE_10 "http://www.apache.org/LICENSE-1.0" + +#define LICENSE_APACHE_11 "apache1_1" +#define URL_APACHE_11 "http://www.apache.org/LICENSE-1.1" + +#define LICENSE_MPL "mpl" +#define URL_MPL "http://www.mozilla.org/MPL/MPL-1.1.html" + +#define LICENSE_PROP "proprietary" +#define URL_PROP "" + +#define LICENSE_IBMPL "ibmpl" +#define URL_IBMPL "http://oss.software.ibm.com/developerworks/opensource/license10.html" + +#ifdef ENABLE_PIL_DEFS_PRIVATE +/* Perhaps these should be moved to a different header file */ + +/* + * PILPluginType is the "class" for the basic plugin loading mechanism. + * + * To enable loading of plugins from a particular plugin type + * one calls NewPILPluginType with the plugin type name, the plugin + * base directory, and the set of functions to be imported to the plugin. + * + * + * The general idea of these structures is as follows: + * + * The PILPluginUniv object contains information about all plugins of + * all types. + * + * The PILPluginType object contains information about all the plugins of a + * specific type. + * + * Note: for plugins which implement a single interface, the plugin type name + * should be the same as the interface type name. + * + * For other plugins that implement more than one interface, one of + * the interface names should normally match the plugin name. + */ + + +/* + * struct PILPlugin_s (typedef PILPlugin) is the structure which + * represents/defines a plugin, and is used to identify which plugin is + * being referred to in various function calls. + * + * NOTE: It may be the case that this definition should be moved to + * another header file - since no one ought to be messing with them anyway ;-) + * + * I'm not sure that we're putting the right stuff in here, either... + */ + +struct PILPlugin_s { + unsigned long MagicNum; + char* plugin_name; + PILPluginType* plugintype; /* Parent structure */ + int refcnt; /* Reference count for this plugin */ + lt_dlhandle dlhandle; /* Reference to D.L. object */ + PILPluginInitFun dlinitfun; /* Initialization function */ + const PILPluginOps* pluginops; /* Exported plugin operations */ + + void* ud_plugin; /* Plugin-Private data */ + /* Other stuff goes here ... (?) */ +}; + +/* + * PILPluginType Information about all plugins of a given type. + * (i.e., in a given directory) + * (AKA struct PILPluginType_s) + */ + +struct PILPluginType_s { + unsigned long MagicNum; + char * plugintype; + PILPluginUniv* piuniv; /* The universe to which we belong */ + GHashTable* Plugins; + /* Key is plugin type, value is PILPlugin */ + + char** (*listplugins)(PILPluginType*, int* listlen); +}; + +/* + * PILPluginUniv (aka struct PILPluginUniv_s) is the structure which + * represents the universe of all PILPluginType objects. + * There is one PILPluginType object for each Plugin type. + */ + +struct PILPluginUniv_s { + unsigned long MagicNum; + char ** rootdirlist; + /* key is plugin type, data is PILPluginType* */ + GHashTable* PluginTypes; + struct PILInterfaceUniv_s*ifuniv; /* Universe of interfaces */ + PILPluginImports* imports; +}; + +# endif /* ENABLE_PIL_DEFS_PRIVATE */ +#endif /*PILS_PLUGIN_H */ diff --git a/include/replace_uuid.h b/include/replace_uuid.h new file mode 100644 index 0000000..d6bca89 --- /dev/null +++ b/include/replace_uuid.h @@ -0,0 +1,50 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * uuid: wrapper declarations. + * + * heartbeat originally used "uuid" functionality by calling directly, + * and only, onto the "e2fsprogs" implementation. + * + * The run-time usages in the code have since been abstracted, funnelled + * through a thin, common interface layer: a Good Thing. + * + * Similarly, the compile-time usages of "include <uuid/uuid.h>" are + * replaced, being funnelled through a reference to this header file. + * + * This header file interfaces onto the actual underlying implementation. + * In the case of the "e2fsprogs" implementation, it is simply a stepping + * stone onto "<uuid/uuid.h>". As other implementations are accommodated, + * so their header requirements can be accommodated here. + * + * Copyright (C) 2004 David Lee <t.d.lee@durham.ac.uk> + */ + +#ifndef REPLACE_UUID_H +#define REPLACE_UUID_H + +typedef unsigned char uuid_t[16]; +void uuid_clear(uuid_t uu); +int uuid_compare(const uuid_t uu1, const uuid_t uu2); +void uuid_copy(uuid_t dst, const uuid_t src); +void uuid_generate(uuid_t out); +void uuid_generate_random(uuid_t out); +int uuid_is_null(const uuid_t uu); +int uuid_parse(const char *in, uuid_t uu); +void uuid_unparse(const uuid_t uu, char *out); + +#endif /* REPLACE_UUID_H */ diff --git a/include/stonith/Makefile.am b/include/stonith/Makefile.am new file mode 100644 index 0000000..9e67a2a --- /dev/null +++ b/include/stonith/Makefile.am @@ -0,0 +1,25 @@ +# +# linux-ha: Linux-HA heartbeat code +# +# Copyright (C) 2001 Michael Moerz +# This instance created by Horms +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +idir=$(includedir)/stonith + +i_HEADERS = expect.h stonith.h stonith_plugin.h st_ttylock.h diff --git a/include/stonith/expect.h b/include/stonith/expect.h new file mode 100644 index 0000000..6084ef1 --- /dev/null +++ b/include/stonith/expect.h @@ -0,0 +1,61 @@ +/* + * Expect simple tokens. Simple expect infrastructure for STONITH API + * + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __EXPECT_H +# define __EXPECT_H +/* + * If we find any of the given tokens in the input stream, + * we return it's "toktype", so we can tell which one was + * found. + * + */ + +struct Etoken { + const char * string; /* The token to look for */ + int toktype; /* The type to return on match */ + int matchto; /* Modified during matches */ +}; + +int ExpectToken(int fd +, struct Etoken * toklist /* List of tokens to match against */ + /* Final token has NULL string */ +, int to_secs /* Timeout value in seconds */ +, char * buf /* If non-NULL, then all the text + * matched/skipped over by this match */ +, int maxline, +, int debug); /* debug level */ + + +/* + * A handy little routine. It runs the given process + * with it's standard output redirected into our *readfd, and + * its standard input redirected from our *writefd + * + * Doing this with all the pipes, etc. required for doing this + * is harder than it sounds :-) + */ + +int StartProcess(const char * cmd, int* readfd, int* writefd); + +#ifndef EOS +# define EOS '\0' +#endif +#endif /*__EXPECT_H*/ diff --git a/include/stonith/st_ttylock.h b/include/stonith/st_ttylock.h new file mode 100644 index 0000000..5b5c7fd --- /dev/null +++ b/include/stonith/st_ttylock.h @@ -0,0 +1,21 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __STONITH_ST_TTYLOCK_H +# define __STONITH_ST_TTYLOCK_H +int st_ttylock(const char *serial_device); +int st_ttyunlock(const char *serial_device); +#endif /*__STONITH_ST_TTYLOCK_H*/ diff --git a/include/stonith/stonith.h b/include/stonith/stonith.h new file mode 100644 index 0000000..93fbaac --- /dev/null +++ b/include/stonith/stonith.h @@ -0,0 +1,187 @@ +/* + * S hoot + * T he + * O ther + * N ode + * I n + * T he + * H ead + * + * Cause the other machine to reboot or die - now. + * + * We guarantee that when we report that the machine has been + * rebooted, then it has been (barring misconfiguration or hardware + * errors) + * + * A machine which we have STONITHed won't do anything more to its + * peripherials etc. until it goes through the reboot cycle. + */ + +/* + * + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * Copyright (c) 2004 International Business Machines, Inc. + * + * Author: Alan Robertson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __STONITH_H +# define __STONITH_H +#include <glib.h> +#include <ctype.h> + +#include <pils/plugin.h> +#define STONITH_VERS 2 + +/* + * Return codes from "Stonith" operations + */ + +#define S_OK 0 /* Machine correctly reset */ +#define S_BADCONFIG 1 /* Bad config info given */ +#define S_ACCESS 2 /* Can't access STONITH device */ + /* (login/passwd problem?) */ +#define S_INVAL 3 /* Bad/illegal argument */ +#define S_BADHOST 4 /* Bad/illegal host/node name */ +#define S_RESETFAIL 5 /* Reset failed */ +#define S_TIMEOUT 6 /* Timed out in the dialogues */ +#define S_ISOFF 7 /* Can't reboot: Outlet is off */ +#define S_OOPS 8 /* Something strange happened */ + +typedef struct stonith { + char * stype; +}Stonith; + +/* An array of StonithNVpairs is terminated by a NULL s_name */ +typedef struct { + char * s_name; + char * s_value; +}StonithNVpair; + +/* + * Operation requested by reset_req() + */ +#define ST_GENERIC_RESET 1 /* Reset the machine any way you can */ +#define ST_POWERON 2 /* Power the node on */ +#define ST_POWEROFF 3 /* Power the node off */ +/* + * Type of information requested by the get_info() call + */ +#define ST_CONF_XML 1 /* XML config info */ +#define ST_DEVICEID 2 /* Device Type Identification */ +#define ST_DEVICENAME 3 /* Unique Individual Device Identification */ + /* (only after stonith_set_config() call) */ +#define ST_DEVICEDESCR 4 /* Device Description text */ +#define ST_DEVICEURL 5 /* Manufacturer/Device URL */ + +extern PILPluginUniv *StonithPIsys; + +char ** stonith_types(void); /* NULL-terminated list */ + /* valid until next call of stonith_types() */ +Stonith*stonith_new(const char * type); +void stonith_delete(Stonith *); + +const char * const * stonith_get_confignames (Stonith* s); + /* static/global return */ + /* Return number and list of valid s_names */ + +const char* /* static/global return - lots of things! */ + stonith_get_info (Stonith* s, int infotype); + +void stonith_set_debug (Stonith* s, int debuglevel); +void stonith_set_log (Stonith* s + , PILLogFun); + +int stonith_set_config (Stonith* s, StonithNVpair* list); +int stonith_set_config_file(Stonith* s, const char * configname); + /* uses get_confignames to determine which + * names to look for in file configname, which + * is passed in by the -F option */ +int stonith_set_config_info(Stonith* s, const char * info); + /* uses get_confignames to determine which + * names to look for in string info, which + * is passed in by the -p option */ + /* + * Must call stonith_set_config() before calling functions below... + */ +char** stonith_get_hostlist (Stonith* s); +void stonith_free_hostlist (char** hostlist); +int stonith_get_status (Stonith* s); +int stonith_req_reset (Stonith* s, int operation, const char* node); + +StonithNVpair* stonith_env_to_NVpair(Stonith* s); + +/* Stonith 1 compatibility: Convert string to an NVpair set */ +StonithNVpair* + stonith1_compat_string_to_NVpair(Stonith* s, const char * str); +StonithNVpair* + stonith_ghash_to_NVpair(GHashTable* stringtable); +void free_NVpair(StonithNVpair*); /* Free result from above 2 functions */ +void strdown(char *str); /* simple replacement for g_strdown */ + +/* + * The ST_DEVICEID info call is intended to return the type of the Stonith + * device. Note that it may return a different result once it has attempted + * to talk to the device (like after a status() call). This is because + * a given STONITH module may be able to talk to more than one kind of + * model of STONITH device, and can't tell which type is out there + * to until it talks to it. For example, Baytech 3, Baytech 5 and + * Baytech 5a are all supported by one module, and this module actually + * captures the particular model number after it talks to it. + * + * The ST_DEVICEDESCR info call is intended to return information identifying + * the type of STONITH device supported by this STONITH object. This is so + * users can tell if they have this kind of device or not. + * + * SHOULD THIS BE IN THE XML SO IT CAN BE SUPPLIED IN SEVERAL LANGUAGES?? + * But, this would mean the STONITH command would have to parse XML. + * Sigh... I'd rather not... Or maybe it can be supplied duplicately + * in the XML if that is thought to be desirable... + * + * The ST_DEVICEURL info call is intended to return the URL of a web site + * related to the device in question. This might be the manufacturer, + * a pointer to the product line, or the individual product itself. + * + * A good way for a GUI to work which configures STONITH devices would be to + * use the result of the stonith_types() call in a pulldown menu. + * + * Once the type is selected, create a Stonith object of the selected type. + * One can then create a dialog box to create the configuration info for the + * device using return from the ST_CONF_XML info call to direct the + * GUI in what information to ask for to fill up the StonithNVpair + * argument to the stonith_set_config() call. This information would then + * be prompted for according to the XML information, and then put into + * a NULL-terminated array of StonithNVpair objects. + * + * Once this has been done, it can be tested for syntactic + * validity with stonith_set_config(). + * + * If it passes set_config(), it can be further validated using status() + * which will then actually try and talk to the STONITH device. If status() + * returns S_OK, then communication with the device was successfully + * established. + * + * Normally that would mean that logins, passwords, device names, and IP + * addresses, etc. have been validated as required by the particular device. + * + * At this point, you can ask the device which machines it knows how to reset + * using the stonith_get_hostlist() function. + * + */ + +#endif /*__STONITH_H*/ diff --git a/include/stonith/stonith_plugin.h b/include/stonith/stonith_plugin.h new file mode 100644 index 0000000..9091a6e --- /dev/null +++ b/include/stonith/stonith_plugin.h @@ -0,0 +1,125 @@ +/* + * S hoot + * T he + * O ther + * N ode + * I n + * T he + * H ead + * + * Cause the other machine to reboot or die - now. + * + * We guarantee that when we report that the machine has been + * rebooted, then it has been (barring misconfiguration or hardware errors) + * + * A machine which we have STONITHed won't do anything more to its + * peripherials etc. until it goes through the reboot cycle. + */ + +/* + * + * Copyright (c) 2004 International Business Machines, Inc. + * + * Author: Alan Robertson <alanr@unix.sh> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __STONITH_PLUGIN_H +# define __STONITH_PLUGIN_H + +#include <stonith/stonith.h> +#include <glib.h> + +typedef struct stonith_plugin StonithPlugin; + +#define NUM_STONITH_FNS 7 + +struct stonith_ops { + StonithPlugin * (*new) (const char*); /* mini-Constructor */ + void (*destroy) (StonithPlugin*); /*(full) Destructor */ + + const char* (*get_info) (StonithPlugin*, int infotype); + const char * const * (*get_confignames) (StonithPlugin*); + int (*set_config) (StonithPlugin*, StonithNVpair* list); + /* Finishes construction */ + /* + * Must call set_config before calling any of + * the member functions below... + */ + + int (*get_status) (StonithPlugin*s); + int (*req_reset) (StonithPlugin*, int op, const char* node); + + + char** (*get_hostlist) (StonithPlugin*); + /* Returns list of hosts it supports */ +}; + +struct stonith_plugin { + Stonith s; + struct stonith_ops* s_ops; + gboolean isconfigured; +}; + +#define STONITH_TYPE stonith2 +#define STONITH_TYPE_S "stonith2" +typedef struct StonithImports_s StonithImports; + +struct Etoken { + const char * string; /* The token to look for */ + int toktype; /* The type to return on match */ + int matchto; /* Modified during matches */ +}; + +/* An array of StonithNamesToGet is terminated by a NULL s_name */ +typedef struct { + const char * s_name; + char * s_value; +}StonithNamesToGet; + +#define TELNET_PORT 23 +#define TELNET_SERVICE "telnet" + +struct StonithImports_s { + int (*ExpectToken)(int fd, struct Etoken * toklist, int to_secs + , char * buf, int maxline, int debug); + int (*StartProcess)(const char * cmd, int * readfd, int * writefd); + int (*OpenStreamSocket) (const char * host, int port + , const char * service); + /* Service can be NULL, port can be <= 0, but not both... */ + const char* (*GetValue)(StonithNVpair*, const char * name); + int (*CopyAllValues) (StonithNamesToGet* out, StonithNVpair* in); + char **(*StringToHostList)(const char * hlstring); + char **(*CopyHostList)(const char * const * hlstring); + void (*FreeHostList)(char** hostlist); + int (*TtyLock)(const char* tty); + int (*TtyUnlock)(const char* tty); +}; + + +/* + * A few standardized parameter names + */ + +#define ST_HOSTLIST "hostlist" +#define ST_IPADDR "ipaddr" +#define ST_LOGIN "login" +#define ST_PASSWD "password" +#define ST_COMMUNITY "community" /* SNMP community */ +#define ST_TTYDEV "ttydev" /* TTY device name */ + +#endif /*__STONITH__PLUGIN_H*/ diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..5047e1d --- /dev/null +++ b/lib/Makefile.am @@ -0,0 +1,20 @@ +# +# Copyright (C) 2008 Andrew Beekhof +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +MAINTAINERCLEANFILES = Makefile.in +SUBDIRS = pils clplumbing lrm stonith plugins diff --git a/lib/clplumbing/GSource.c b/lib/clplumbing/GSource.c new file mode 100644 index 0000000..48bb198 --- /dev/null +++ b/lib/clplumbing/GSource.c @@ -0,0 +1,1864 @@ +/* + * Copyright (c) 2002 Alan Robertson <alanr@unix.sh> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <string.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> + +#include <clplumbing/cl_log.h> +#include <clplumbing/cl_signal.h> +#include <clplumbing/GSource_internal.h> +#include <clplumbing/proctrack.h> +#include <clplumbing/Gmain_timeout.h> +#include <clplumbing/timers.h> + +#ifdef events +# undef events +#endif +#ifdef revents +# undef revents +#endif + +#define DEFAULT_MAXDISPATCH 0 +#define DEFAULT_MAXDELAY 0 +#define OTHER_MAXDELAY 100 + +/* + * On architectures with alignment constraints, our casting between + * "(GSource*)" and "(GFDSource_s*)" etc. causes trouble, because of + * the massive alignment requirements of "longclock_t". + * + * Use the following to store and fetch. + */ +static +void +lc_store(char *destptr, longclock_t value) { + longclock_t _ltt; + _ltt = value; + memcpy((destptr), &_ltt, sizeof(longclock_t)); +} + +static +longclock_t +lc_fetch(char *ptr) { + longclock_t _ltt; + memcpy(&_ltt, (ptr), sizeof(longclock_t)); + return _ltt; +} + +#define ERR_EVENTS (G_IO_ERR|G_IO_NVAL) +#define INPUT_EVENTS (G_IO_IN|G_IO_PRI|G_IO_HUP) +#define OUTPUT_EVENTS (G_IO_OUT) +#define DEF_EVENTS (INPUT_EVENTS|ERR_EVENTS) + +#define WARN_DELAY(ms, mx, input) cl_log(LOG_WARNING \ + , "%s: Dispatch function for %s was delayed" \ + " %lu ms (> %lu ms) before being called (GSource: 0x%lx)" \ + , __FUNCTION__, (input)->description, ms, mx \ + , POINTER_TO_ULONG(input)) + +#define EXPLAINDELAY(started, detected) cl_log(LOG_INFO \ + , "%s: started at %llu should have started at %llu" \ + , __FUNCTION__, (unsigned long long)started \ + , (unsigned long long)detected) + + +#define WARN_TOOLONG(ms, mx, input) cl_log(LOG_WARNING \ + , "%s: Dispatch function for %s took too long to execute" \ + ": %lu ms (> %lu ms) (GSource: 0x%lx)" \ + , __FUNCTION__, (input)->description, ms, mx \ + , POINTER_TO_ULONG(input)) + +#define CHECK_DISPATCH_DELAY(i) { \ + unsigned long ms; \ + longclock_t dettime; \ + dispstart = time_longclock(); \ + dettime = lc_fetch((i)->detecttime); \ + ms = longclockto_ms(sub_longclock(dispstart,dettime)); \ + if ((i)->maxdispatchdelayms > 0 \ + && ms > (i)->maxdispatchdelayms) { \ + WARN_DELAY(ms, (i)->maxdispatchdelayms, (i)); \ + EXPLAINDELAY(dispstart, dettime); \ + } \ +} + +#define CHECK_DISPATCH_TIME(i) { \ + unsigned long ms; \ + longclock_t dispend = time_longclock(); \ + ms = longclockto_ms(sub_longclock(dispend, dispstart)); \ + if ((i)->maxdispatchms > 0 && ms > (i)->maxdispatchms) { \ + WARN_TOOLONG(ms, (i)->maxdispatchms, (i)); \ + } \ + lc_store(((i)->detecttime), zero_longclock); \ +} + +#define WARN_TOOMUCH(ms, mx, input) cl_log(LOG_WARNING \ + , "%s: working on %s took %ld ms (> %ld ms)" \ + , __FUNCTION__, (input)->description, ms, mx); + +#define SAVESTART {funstart = time_longclock();} + +#define CHECKEND(input) { \ + longclock_t funend = time_longclock(); \ + long ms; \ + ms = longclockto_ms(sub_longclock(funend, funstart)); \ + if (ms > OTHER_MAXDELAY){ \ + WARN_TOOMUCH(ms, ((long) OTHER_MAXDELAY), input); \ + } \ +} \ + + +#ifndef _NSIG +# define _NSIG 2*NSIG +#endif + +static gboolean G_fd_prepare(GSource* source, + gint* timeout); +static gboolean G_fd_check(GSource* source); +static gboolean G_fd_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data); +static void G_fd_destroy(GSource* source); + +static GSourceFuncs G_fd_SourceFuncs = { + G_fd_prepare, + G_fd_check, + G_fd_dispatch, + G_fd_destroy, +}; + +GSource* +G_main_add_input(int priority, + gboolean can_recurse, + GSourceFuncs* funcs) +{ + GSource * input_source = g_source_new(funcs, sizeof(GSource)); + if (input_source == NULL){ + cl_log(LOG_ERR, "create glib source for input failed!"); + }else { + g_source_set_priority(input_source, priority); + g_source_set_can_recurse(input_source, can_recurse); + if(g_source_attach(input_source, NULL) == 0){ + cl_log(LOG_ERR, "attaching input_source to main context" + " failed!! "); + } + } + + return input_source; +} + + +/* + * Add the given file descriptor to the gmainloop world. + */ + + +GFDSource* +G_main_add_fd(int priority, int fd, gboolean can_recurse +, gboolean (*dispatch)(int fd, gpointer user_data) +, gpointer userdata +, GDestroyNotify notify) +{ + + GSource* source = g_source_new(&G_fd_SourceFuncs, + sizeof(GFDSource)); + GFDSource* ret = (GFDSource*)source; + + ret->magno = MAG_GFDSOURCE; + ret->maxdispatchdelayms = DEFAULT_MAXDELAY; + ret->maxdispatchms = DEFAULT_MAXDISPATCH; + ret->udata = userdata; + ret->dispatch = dispatch; + ret->gpfd.fd = fd; + ret->gpfd.events = DEF_EVENTS; + ret->gpfd.revents = 0; + ret->dnotify = notify; + lc_store((ret->detecttime), zero_longclock); + + g_source_add_poll(source, &ret->gpfd); + + + g_source_set_priority(source, priority); + + g_source_set_can_recurse(source, can_recurse); + + ret->gsourceid = g_source_attach(source, NULL); + ret->description = "file descriptor"; + + if (ret->gsourceid == 0) { + g_source_remove_poll(source, &ret->gpfd); + memset(ret, 0, sizeof(GFDSource)); + g_source_unref(source); + source = NULL; + ret = NULL; + } + return ret; +} + +gboolean +G_main_del_fd(GFDSource* fdp) +{ + GSource * source = (GSource*) fdp; + + + if (fdp->gsourceid <= 0) { + return FALSE; + } + + g_source_remove_poll(source, &fdp->gpfd); + g_source_remove(fdp->gsourceid); + fdp->gsourceid = 0; + g_source_unref(source); + + return TRUE; + +} + +void +g_main_output_is_blocked(GFDSource* fdp) +{ + fdp->gpfd.events |= OUTPUT_EVENTS; +} + + +/* + * For pure file descriptor events, return FALSE because we + * have to poll to get events. + * + * Note that we don't modify 'timeout' either. + */ +static gboolean +G_fd_prepare(GSource* source, + gint* timeout) +{ + GFDSource* fdp = (GFDSource*)source; + g_assert(IS_FDSOURCE(fdp)); + return FALSE; +} + +/* + * Did we notice any I/O events? + */ + +static gboolean +G_fd_check(GSource* source) + +{ + GFDSource* fdp = (GFDSource*)source; + g_assert(IS_FDSOURCE(fdp)); + if (fdp->gpfd.revents) { + lc_store((fdp->detecttime), time_longclock()); + return TRUE; + } + return FALSE; +} + +/* + * Some kind of event occurred - notify the user. + */ +static gboolean +G_fd_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data) +{ + + GFDSource* fdp = (GFDSource*)source; + longclock_t dispstart; + g_assert(IS_FDSOURCE(fdp)); + CHECK_DISPATCH_DELAY(fdp); + + + /* + * Is output now unblocked? + * + * If so, turn off OUTPUT_EVENTS to avoid going into + * a tight poll(2) loop. + */ + if (fdp->gpfd.revents & OUTPUT_EVENTS) { + fdp->gpfd.events &= ~OUTPUT_EVENTS; + } + + if(fdp->dispatch) { + if(!(fdp->dispatch(fdp->gpfd.fd, fdp->udata))){ + g_source_remove_poll(source,&fdp->gpfd); + g_source_unref(source); + CHECK_DISPATCH_TIME(fdp); + return FALSE; + } + CHECK_DISPATCH_TIME(fdp); + } + + return TRUE; +} + +/* + * Free up our data, and notify the user process... + */ +static void +G_fd_destroy(GSource* source) +{ + GFDSource* fdp = (GFDSource*)source; + g_assert(IS_FDSOURCE(fdp)); + fdp->gsourceid = 0; + if (fdp->dnotify) { + fdp->dnotify(fdp->udata); + } +} + + +/************************************************************ + * Functions for IPC_Channels + ***********************************************************/ +gboolean G_CH_prepare_int(GSource* source, + gint* timeout); +gboolean G_CH_check_int(GSource* source); + +gboolean G_CH_dispatch_int(GSource* source, + GSourceFunc callback, + gpointer user_data); +void G_CH_destroy_int(GSource* source); + + +static GSourceFuncs G_CH_SourceFuncs = { + G_CH_prepare_int, + G_CH_check_int, + G_CH_dispatch_int, + G_CH_destroy_int, +}; + + + + +void +set_IPC_Channel_dnotify(GCHSource* chp, + GDestroyNotify notify){ + chp->dnotify = notify; +} + +/* + * Add an IPC_channel to the gmainloop world... + */ +GCHSource* +G_main_IPC_Channel_constructor(GSource* source, IPC_Channel* ch + , gpointer userdata + , GDestroyNotify notify) +{ + int rfd, wfd; + GCHSource* chp; + + if( !source ) { + cl_log(LOG_WARNING, "%s:%d: got null source", __FUNCTION__,__LINE__); + return NULL; + } + if( !ch ) { + cl_log(LOG_WARNING, "%s:%d: got null channel", __FUNCTION__,__LINE__); + return NULL; + } + chp = (GCHSource*)source; + + chp->magno = MAG_GCHSOURCE; + chp->maxdispatchdelayms = DEFAULT_MAXDELAY; + chp->maxdispatchms = DEFAULT_MAXDISPATCH; + lc_store((chp->detecttime), zero_longclock); + ch->refcount++; + chp->ch = ch; + chp->udata=userdata; + chp->dnotify = notify; + chp->dontread = FALSE; + + rfd = ch->ops->get_recv_select_fd(ch); + wfd = ch->ops->get_send_select_fd(ch); + + chp->fd_fdx = (rfd == wfd); + + if (debug_level > 1) { + cl_log(LOG_DEBUG, "%s(sock=%d,%d)",__FUNCTION__, rfd,wfd); + } + chp->infd.fd = rfd; + chp->infd.events = DEF_EVENTS; + g_source_add_poll(source, &chp->infd); + if (!chp->fd_fdx) { + chp->outfd.fd = wfd; + chp->outfd.events = DEF_EVENTS; + g_source_add_poll(source, &chp->outfd); + } + chp->dispatch = NULL; + chp->description = "IPC channel(base)"; + chp->gsourceid = 0; + return chp; +} + +GCHSource* +G_main_add_IPC_Channel(int priority, IPC_Channel* ch + , gboolean can_recurse + , gboolean (*dispatch)(IPC_Channel* source_data, + gpointer user_data) + , gpointer userdata + , GDestroyNotify notify) +{ + GCHSource *chp; + GSource *source; + + if( !ch ) { + cl_log(LOG_WARNING, "%s:%d: got null channel", __FUNCTION__,__LINE__); + return NULL; + } + source = g_source_new(&G_CH_SourceFuncs, + sizeof(GCHSource)); + G_main_IPC_Channel_constructor(source,ch,userdata,notify); + + chp = (GCHSource*)source; + chp->dispatch = dispatch; + + g_source_set_priority(source, priority); + g_source_set_can_recurse(source, can_recurse); + + chp->gsourceid = g_source_attach(source, NULL); + chp->description = "IPC channel"; + + + if (chp->gsourceid == 0) { + g_source_remove_poll(source, &chp->infd); + if (!chp->fd_fdx) { + g_source_remove_poll(source, &chp->outfd); + } + g_source_unref(source); + source = NULL; + chp = NULL; + } + return chp; +} + + +void /* Suspend reading from far end writer (flow control) */ +G_main_IPC_Channel_pause(GCHSource* chp) +{ + if (chp == NULL){ + cl_log(LOG_ERR, "%s: invalid input", __FUNCTION__); + return; + } + + chp->dontread = TRUE; + return; +} + + +void /* Resume reading from far end writer (un-flow-control) */ +G_main_IPC_Channel_resume(GCHSource* chp) +{ + if (chp == NULL){ + cl_log(LOG_ERR, "%s: invalid input", __FUNCTION__); + return; + } + + chp->dontread = FALSE; + return; + +} + +/* + * Delete an IPC_channel from the gmainloop world... + */ +gboolean +G_main_del_IPC_Channel(GCHSource* chp) +{ + GSource* source = (GSource*) chp; + + if (chp == NULL || chp->gsourceid <= 0) { + return FALSE; + } + + if (debug_level > 1) { + cl_log(LOG_DEBUG, "%s(sock=%d)",__FUNCTION__, chp->infd.fd); + } + g_source_remove(chp->gsourceid); + chp->gsourceid = 0; + /* chp should (may) now be undefined */ + g_source_unref(source); + + return TRUE; +} + +/* + * For IPC_CHANNEL events, enable output checking when needed + * and note when unread input is already queued. + * + * Note that we don't modify 'timeout' either. + */ +gboolean +G_CH_prepare_int(GSource* source, + gint* timeout) +{ + GCHSource* chp = (GCHSource*)source; + longclock_t funstart; + gboolean ret; + + g_assert(IS_CHSOURCE(chp)); + SAVESTART; + + + if (chp->ch->ops->is_sending_blocked(chp->ch)) { + if (chp->fd_fdx) { + chp->infd.events |= OUTPUT_EVENTS; + }else{ + chp->outfd.events |= OUTPUT_EVENTS; + } + } + + if (chp->ch->recv_queue->current_qlen < chp->ch->recv_queue->max_qlen) { + chp->infd.events |= INPUT_EVENTS; + }else{ + /* + * This also disables EOF events - until we + * read some of the packets we've already gotten + * This prevents a tight loop in poll(2). + */ + chp->infd.events &= ~INPUT_EVENTS; + } + + if (chp->dontread){ + return FALSE; + } + ret = chp->ch->ops->is_message_pending(chp->ch); + if (ret) { + lc_store((chp->detecttime), time_longclock()); + } + CHECKEND(chp); + return ret; +} + +/* + * Did we notice any I/O events? + */ + +gboolean +G_CH_check_int(GSource* source) +{ + + GCHSource* chp = (GCHSource*)source; + gboolean ret; + longclock_t funstart; + + g_assert(IS_CHSOURCE(chp)); + SAVESTART; + + + if (chp->dontread){ + /* Make sure output gets unblocked */ + chp->ch->ops->resume_io(chp->ch); + return FALSE; + } + + ret = (chp->infd.revents != 0 + || (!chp->fd_fdx && chp->outfd.revents != 0) + || chp->ch->ops->is_message_pending(chp->ch)); + if (ret) { + lc_store((chp->detecttime), time_longclock()); + } + CHECKEND(chp); + return ret; +} + +/* + * Some kind of event occurred - notify the user. + */ +gboolean +G_CH_dispatch_int(GSource * source, + GSourceFunc callback, + gpointer user_data) +{ + GCHSource* chp = (GCHSource*)source; + longclock_t dispstart; + longclock_t resume_start = zero_longclock; + + g_assert(IS_CHSOURCE(chp)); + CHECK_DISPATCH_DELAY(chp); + + + if (chp->dontread){ + return TRUE; + } + + /* Is output now unblocked? + * + * If so, turn off OUTPUT_EVENTS to avoid going into + * a tight poll(2) loop. + */ + if (chp->fd_fdx) { + if (chp->infd.revents & OUTPUT_EVENTS) { + chp->infd.events &= ~OUTPUT_EVENTS; + } + }else if (chp->outfd.revents & OUTPUT_EVENTS) { + chp->outfd.events &= ~OUTPUT_EVENTS; + } + + if (ANYDEBUG) { + resume_start = time_longclock(); + } + + chp->ch->ops->resume_io(chp->ch); + + if (ANYDEBUG) { + longclock_t resume_end = time_longclock(); + unsigned long ms; + ms = longclockto_ms(sub_longclock(resume_end + , resume_start)); + if (ms > 10) { + cl_log(LOG_WARNING + , "%s: resume_io() for %s took %lu ms" + , __FUNCTION__ + , chp->description, ms); + } + } + + + if(chp->dispatch && chp->ch->ops->is_message_pending(chp->ch)) { + if(!(chp->dispatch(chp->ch, chp->udata))){ + g_source_remove_poll(source, &chp->infd); + if (!chp->fd_fdx) { + g_source_remove_poll(source, &chp->outfd); + } + CHECK_DISPATCH_TIME(chp); + g_source_unref(source); + return FALSE; + } + } + CHECK_DISPATCH_TIME(chp); + + if (chp->ch->ch_status == IPC_DISCONNECT){ + return FALSE; + } + return TRUE; +} + +/* + * Free up our data, and notify the user process... + */ +void +G_CH_destroy_int(GSource* source) +{ + GCHSource* chp = (GCHSource*)source; + + g_assert(IS_CHSOURCE(chp)); + if (debug_level > 1) { + cl_log(LOG_DEBUG, "%s(chp=0x%lx, sock=%d) {", __FUNCTION__ + , (unsigned long)chp, chp->infd.fd); + } + + if (chp->dnotify) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: Calling dnotify(sock=%d, arg=0x%lx) function" + , __FUNCTION__, chp->infd.fd, (unsigned long)chp->udata); + } + chp->dnotify(chp->udata); + }else{ + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: NOT calling dnotify(sock=%d) function" + , __FUNCTION__, chp->infd.fd); + } + } + if (chp->ch) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: calling IPC destroy (chp->ch=0x%lx, sock=%d)" + , __FUNCTION__ , (unsigned long)chp->ch, chp->infd.fd); + } + chp->ch->ops->destroy(chp->ch); + chp->ch = NULL; + } + /*chp->gsourceid = 0; ?*/ + if (debug_level > 1) { + cl_log(LOG_DEBUG, "}/*%s(sock=%d)*/", __FUNCTION__, chp->infd.fd); + } +} + + +/************************************************************ + * Functions for IPC_WaitConnections + ***********************************************************/ +static gboolean G_WC_prepare(GSource * source, + gint* timeout); +static gboolean G_WC_check(GSource* source); +static gboolean G_WC_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data); +static void G_WC_destroy(GSource* source); + +static GSourceFuncs G_WC_SourceFuncs = { + G_WC_prepare, + G_WC_check, + G_WC_dispatch, + G_WC_destroy, +}; + + +/* + * Add an IPC_WaitConnection to the gmainloop world... + */ +GWCSource* +G_main_add_IPC_WaitConnection(int priority +, IPC_WaitConnection* wch +, IPC_Auth* auth_info +, gboolean can_recurse +, gboolean (*dispatch)(IPC_Channel* wch +, gpointer user_data) +, gpointer userdata +, GDestroyNotify notify) +{ + + GWCSource* wcp; + GSource * source = g_source_new(&G_WC_SourceFuncs, + sizeof(GWCSource)); + + wcp = (GWCSource*)source; + + wcp->magno = MAG_GWCSOURCE; + wcp->maxdispatchdelayms = DEFAULT_MAXDELAY; + wcp->maxdispatchms = DEFAULT_MAXDISPATCH; + lc_store((wcp->detecttime), zero_longclock); + wcp->udata = userdata; + wcp->gpfd.fd = wch->ops->get_select_fd(wch); + wcp->gpfd.events = DEF_EVENTS; + wcp->gpfd.revents = 0; + wcp->wch = wch; + wcp->dnotify = notify; + wcp->auth_info = auth_info; + wcp->dispatch = dispatch; + + g_source_add_poll(source, &wcp->gpfd); + + g_source_set_priority(source, priority); + + g_source_set_can_recurse(source, can_recurse); + + wcp->gsourceid = g_source_attach(source, NULL); + wcp->description = "IPC wait for connection"; + + if (wcp->gsourceid == 0) { + g_source_remove_poll(source, &wcp->gpfd); + g_source_unref(source); + source = NULL; + wcp = NULL; + } + return wcp; +} + + +/* Delete the given IPC_WaitConnection from the gmainloop world */ +gboolean +G_main_del_IPC_WaitConnection(GWCSource* wcp) +{ + + GSource* source = (GSource*) wcp; + + + if (wcp->gsourceid <= 0) { + return FALSE; + } + + g_source_remove(wcp->gsourceid); + wcp->gsourceid = 0; + g_source_unref(source); + + return TRUE; +} + + + +/* + * For IPC_WaitConnection events, return FALSE because we + * have to poll to get events. + * + * We don't modify 'timeout' either. + */ +static gboolean +G_WC_prepare(GSource* source, + gint* timeout) +{ + GWCSource* wcp = (GWCSource*)source; + g_assert(IS_WCSOURCE(wcp)); + return FALSE; +} + +/* + * Did we notice any I/O (connection pending) events? + */ + +static gboolean +G_WC_check(GSource * source) +{ + GWCSource* wcp = (GWCSource*)source; + g_assert(IS_WCSOURCE(wcp)); + + if (wcp->gpfd.revents != 0) { + lc_store((wcp->detecttime), time_longclock()); + return TRUE; + } + return FALSE; +} + +/* + * Someone is trying to connect. + * Try to accept the connection and notify the user. + */ +static gboolean +G_WC_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data) +{ + GWCSource* wcp = (GWCSource*)source; + IPC_Channel* ch; + gboolean rc = TRUE; + int count = 0; + longclock_t dispstart; + + g_assert(IS_WCSOURCE(wcp)); + CHECK_DISPATCH_DELAY(wcp); + + while(1) { + ch = wcp->wch->ops->accept_connection(wcp->wch, wcp->auth_info); + if (ch == NULL) { + if (errno == EBADF) { + cl_perror("%s: Stopping accepting connections(socket=%d)!!" + , __FUNCTION__, wcp->gpfd.fd); + rc = FALSE; + } + break; + } + ++count; + + if(!wcp->dispatch) { + continue; + } + + rc = wcp->dispatch(ch, wcp->udata); + if(!rc) { + g_source_remove_poll(source, &wcp->gpfd); + g_source_unref(source); + break; + } + } + CHECK_DISPATCH_TIME(wcp); + return rc; +} + +/* + * Free up our data, and notify the user process... + */ +static void +G_WC_destroy(GSource* source) +{ + + GWCSource* wcp = (GWCSource*)source; + wcp->gsourceid = 0; + g_assert(IS_WCSOURCE(wcp)); + wcp->wch->ops->destroy(wcp->wch); + if (wcp->dnotify) { + wcp->dnotify(wcp->udata); + } +} + + +/************************************************************ + * Functions for Signals + ***********************************************************/ +static gboolean G_SIG_prepare(GSource* source, + gint* timeout); +static gboolean G_SIG_check(GSource* source); + +static gboolean G_SIG_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data); +static void G_SIG_destroy(GSource* source); + +static void G_main_signal_handler(int nsig); + +static GSourceFuncs G_SIG_SourceFuncs = { + G_SIG_prepare, + G_SIG_check, + G_SIG_dispatch, + G_SIG_destroy, +}; + +static GSIGSource *G_main_signal_list[_NSIG]; + +void +set_SignalHandler_dnotify(GSIGSource* sig_src, GDestroyNotify notify) +{ + sig_src->dnotify = notify; +} + +/* + * Add an Signal to the gmainloop world... + */ +GSIGSource* +G_main_add_SignalHandler(int priority, int signal, + gboolean (*dispatch)(int nsig, gpointer user_data), + gpointer userdata, GDestroyNotify notify) +{ + GSIGSource* sig_src; + GSource * source = g_source_new(&G_SIG_SourceFuncs, sizeof(GSIGSource)); + gboolean failed = FALSE; + + sig_src = (GSIGSource*)source; + + sig_src->magno = MAG_GSIGSOURCE; + sig_src->maxdispatchdelayms = DEFAULT_MAXDELAY; + sig_src->maxdispatchms = DEFAULT_MAXDISPATCH; + sig_src->signal = signal; + sig_src->dispatch = dispatch; + sig_src->udata = userdata; + sig_src->dnotify = notify; + + sig_src->signal_triggered = FALSE; + + g_source_set_priority(source, priority); + g_source_set_can_recurse(source, FALSE); + + if(G_main_signal_list[signal] != NULL) { + cl_log(LOG_ERR + , "%s: Handler already present for signal %d" + , __FUNCTION__, signal); + failed = TRUE; + } + if(!failed) { + sig_src->gsourceid = g_source_attach(source, NULL); + sig_src->description = "signal"; + if (sig_src->gsourceid < 1) { + cl_log(LOG_ERR + , "%s: Could not attach source for signal %d (%d)" + , __FUNCTION__ + , signal, sig_src->gsourceid); + failed = TRUE; + } + } + + if(failed) { + cl_log(LOG_ERR + , "%s: Signal handler for signal %d NOT added" + , __FUNCTION__, signal); + g_source_remove(sig_src->gsourceid); + g_source_unref(source); + source = NULL; + sig_src = NULL; + } else { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: Added signal handler for signal %d" + , __FUNCTION__, signal); + } + G_main_signal_list[signal] = sig_src; + CL_SIGNAL(signal, G_main_signal_handler); + /* + * If we don't set this on, then the mainloop poll(2) call + * will never be interrupted by this signal - which sort of + * defeats the whole purpose of a signal handler in a + * mainloop program + */ + cl_signal_set_interrupt(signal, TRUE); + } + return sig_src; +} + + +/* + * Delete a Signal from the gmainloop world... + */ +gboolean +G_main_del_SignalHandler(GSIGSource* sig_src) +{ + GSource* source = (GSource*) sig_src; + + if (sig_src->gsourceid <= 0) { + return FALSE; + } + if(_NSIG <= sig_src->signal) { + g_assert(_NSIG > sig_src->signal); + return FALSE; + } + + CL_SIGNAL(sig_src->signal, NULL); + + sig_src->signal_triggered = FALSE; + g_source_remove(sig_src->gsourceid); + G_main_signal_list[sig_src->signal] = NULL; + sig_src->gsourceid = 0; + g_source_unref(source); + + return TRUE; +} + +static gboolean +G_SIG_prepare(GSource* source, gint* timeoutms) +{ + GSIGSource* sig_src = (GSIGSource*)source; + + g_assert(IS_SIGSOURCE(sig_src)); + + /* Don't let a timing window keep us in poll() forever + * + * The timing window in question looks like this: + * No signal has occurred up to the point of prepare being called. + * Signal comes in _after_ prepare was called, but _before_ poll. + * signal_detected gets set, but no one checks it before going into poll + * We wait in poll forever... It's not a pretty sight :-(. + */ + *timeoutms = 1000; /* Sigh... */ + + if (sig_src->signal_triggered) { + clock_t now; + clock_t diff; + + /* detecttime is reset in the dispatch function */ + if (cmp_longclock(lc_fetch(sig_src->detecttime), zero_longclock) != 0) { + cl_log(LOG_ERR, "%s: detecttime already set?", __FUNCTION__); + return TRUE; + } + /* Otherwise, this is when it was first detected */ + now = cl_times(); + diff = now - sig_src->sh_detecttime; /* How long since signal occurred? */ + lc_store( + sig_src->detecttime, + sub_longclock(time_longclock(), (longclock_t)diff) + ); + return TRUE; + } + return FALSE; +} + +/* + * Did we notice any I/O events? + */ + +static gboolean +G_SIG_check(GSource* source) +{ + + GSIGSource* sig_src = (GSIGSource*)source; + + g_assert(IS_SIGSOURCE(sig_src)); + + if (sig_src->signal_triggered) { + clock_t now; + clock_t diff; + if (cmp_longclock(lc_fetch(sig_src->detecttime), zero_longclock) != 0){ + return TRUE; + } + /* Otherwise, this is when it was first detected */ + now = cl_times(); + diff = now - sig_src->sh_detecttime; + lc_store( + sig_src->detecttime, + sub_longclock(time_longclock(), (longclock_t)diff) + ); + return TRUE; + } + return FALSE; +} + +/* + * Some kind of event occurred - notify the user. + */ +static gboolean +G_SIG_dispatch(GSource * source, + GSourceFunc callback, + gpointer user_data) +{ + GSIGSource* sig_src = (GSIGSource*)source; + longclock_t dispstart; + + g_assert(IS_SIGSOURCE(sig_src)); + CHECK_DISPATCH_DELAY(sig_src); + + sig_src->sh_detecttime = 0UL; + sig_src->signal_triggered = FALSE; + + if(sig_src->dispatch) { + if(!(sig_src->dispatch(sig_src->signal, sig_src->udata))){ + G_main_del_SignalHandler(sig_src); + CHECK_DISPATCH_TIME(sig_src); + return FALSE; + } + } + CHECK_DISPATCH_TIME(sig_src); + + return TRUE; +} + +/* + * Free up our data, and notify the user process... + */ +static void +G_SIG_destroy(GSource* source) +{ + GSIGSource* sig_src = (GSIGSource*)source; + + g_assert(IS_SIGSOURCE(sig_src)); + sig_src->gsourceid = 0; + + if (sig_src->dnotify) { + sig_src->dnotify(sig_src->udata); + } +} + +/* Find and set the correct mainloop input */ + +static void +G_main_signal_handler(int nsig) +{ + GSIGSource* sig_src = NULL; + + if(G_main_signal_list == NULL) { + g_assert(G_main_signal_list != NULL); + return; + } + if(_NSIG <= nsig) { + g_assert(_NSIG > nsig); + return; + } + + sig_src = G_main_signal_list[nsig]; + + if(sig_src == NULL) { + /* cl_log(LOG_CRIT, "No handler for signal -%d", nsig); */ + return; + } + + g_assert(IS_SIGSOURCE(sig_src)); + /* Time from first occurance of signal */ + if (!sig_src->signal_triggered) { + /* Avoid calling longclock_time() on a signal */ + sig_src->sh_detecttime=cl_times(); + } + sig_src->signal_triggered = TRUE; +} + +/* + * Functions to handle child process + */ + +#define WAITALARM 5000L /* milliseconds */ + +static int alarm_count = 0; +static void +G_main_alarm_helper(int nsig) +{ + ++alarm_count; +} + +static gboolean +child_death_dispatch(int sig, gpointer notused) +{ + int status; + pid_t pid; + const int waitflags = WNOHANG; + struct sigaction saveaction; + int childcount = 0; + + /* + * wait3(WNOHANG) isn't _supposed_ to hang + * Unfortunately, it seems to do just that on some OSes. + * + * The workaround is to set an alarm. I don't think for this purpose + * that it matters if siginterrupt(SIGALRM) is set TRUE or FALSE since + * the tiniest little excuse seems to cause the wait3() to finish. + */ + + memset(&saveaction, 0, sizeof(saveaction)); + cl_signal_set_simple_handler(SIGALRM, G_main_alarm_helper, &saveaction); + + alarm_count = 0; + cl_signal_set_interrupt(SIGALRM, TRUE); + setmsrepeattimer(WAITALARM); /* Might as well be persistent ;-) */ + while((pid=wait3(&status, waitflags, NULL)) > 0 + || (pid < 0 && errno == EINTR)) { + cancelmstimer(); + if (pid > 0) { + ++childcount; + ReportProcHasDied(pid, status); + } + setmsrepeattimer(WAITALARM); /* Let's be persistent ;-) */ + } + cancelmstimer(); + cl_signal_set_simple_handler(SIGALRM, saveaction.sa_handler, &saveaction); + + if (pid < 0 && errno != ECHILD) { + cl_perror("%s: wait3() failed" + , __FUNCTION__); + } +#if defined(DEBUG) + if (childcount < 1) { + /* + * This happens when we receive a SIGCHLD after we clear + * 'sig_src->signal_triggered' in G_SIG_dispatch() but + * before the last wait3() call returns no child above. + */ + cl_log(LOG_DEBUG, "NOTE: %s called without children to wait on" + , __FUNCTION__); + } +#endif + if (alarm_count) { + cl_log(LOG_ERR + , "%s: wait3() call hung %d times. childcount = %d" + , __FUNCTION__, alarm_count, childcount); + alarm_count = 0; + } + return TRUE; +} + +void +set_sigchld_proctrack(int priority, unsigned long maxdisptime) +{ + GSIGSource* src = G_main_add_SignalHandler(priority, SIGCHLD + , child_death_dispatch, NULL, NULL); + + G_main_setmaxdispatchdelay((GSource*) src, 100); + G_main_setmaxdispatchtime((GSource*) src, maxdisptime); + G_main_setdescription((GSource*)src, "SIGCHLD"); + return; +} + + +/************************************************************ + * Functions for Trigger inputs + ***********************************************************/ +static gboolean G_TRIG_prepare(GSource* source, + gint* timeout); +static gboolean G_TRIG_check(GSource* source); + +static gboolean G_TRIG_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data); +static void G_TRIG_destroy(GSource* source); + +static GSourceFuncs G_TRIG_SourceFuncs = { + G_TRIG_prepare, + G_TRIG_check, + G_TRIG_dispatch, + G_TRIG_destroy +}; + +void +set_TriggerHandler_dnotify(GTRIGSource* trig_src, GDestroyNotify notify) +{ + trig_src->dnotify = notify; +} + +/* + * Add an Trigger to the gmainloop world... + */ +GTRIGSource* +G_main_add_TriggerHandler(int priority, + gboolean (*dispatch)(gpointer user_data), + gpointer userdata, GDestroyNotify notify) +{ + GTRIGSource* trig_src = NULL; + GSource * source = g_source_new(&G_TRIG_SourceFuncs, sizeof(GTRIGSource)); + gboolean failed = FALSE; + + trig_src = (GTRIGSource*)source; + + trig_src->magno = MAG_GTRIGSOURCE; + trig_src->maxdispatchdelayms = DEFAULT_MAXDELAY; + trig_src->maxdispatchms = DEFAULT_MAXDISPATCH; + trig_src->dispatch = dispatch; + trig_src->udata = userdata; + trig_src->dnotify = notify; + lc_store((trig_src->detecttime), zero_longclock); + + trig_src->manual_trigger = FALSE; + + g_source_set_priority(source, priority); + g_source_set_can_recurse(source, FALSE); + + if(!failed) { + trig_src->gsourceid = g_source_attach(source, NULL); + trig_src->description = "trigger"; + if (trig_src->gsourceid < 1) { + cl_log(LOG_ERR, "G_main_add_TriggerHandler: Could not attach new source (%d)", + trig_src->gsourceid); + failed = TRUE; + } + } + + if(failed) { + cl_log(LOG_ERR, "G_main_add_TriggerHandler: Trigger handler NOT added"); + g_source_remove(trig_src->gsourceid); + g_source_unref(source); + source = NULL; + trig_src = NULL; + } else { + if (debug_level > 1) { + cl_log(LOG_DEBUG, "G_main_add_TriggerHandler: Added signal manual handler"); + } + } + + return trig_src; +} + +void +G_main_set_trigger(GTRIGSource* source) +{ + GTRIGSource* trig_src = (GTRIGSource*)source; + + g_assert(IS_TRIGSOURCE(trig_src)); + + trig_src->manual_trigger = TRUE; + lc_store((trig_src->detecttime), time_longclock()); +} + + +/* + * Delete a Trigger from the gmainloop world... + */ +gboolean +G_main_del_TriggerHandler(GTRIGSource* trig_src) +{ + GSource* source = (GSource*) trig_src; + + if (trig_src->gsourceid <= 0) { + return FALSE; + } + trig_src->gsourceid = 0; + trig_src->manual_trigger = FALSE; + g_source_remove(trig_src->gsourceid); + g_source_unref(source); + + return TRUE; +} + +static gboolean +G_TRIG_prepare(GSource* source, gint* timeout) +{ + GTRIGSource* trig_src = (GTRIGSource*)source; + + g_assert(IS_TRIGSOURCE(trig_src)); + + + if (trig_src->manual_trigger + && cmp_longclock(lc_fetch(trig_src->detecttime), zero_longclock) == 0) { + lc_store((trig_src->detecttime), time_longclock()); + } + return trig_src->manual_trigger; +} + +/* + * Did we notice any I/O events? + */ + +static gboolean +G_TRIG_check(GSource* source) +{ + + GTRIGSource* trig_src = (GTRIGSource*)source; + + g_assert(IS_TRIGSOURCE(trig_src)); + if (trig_src->manual_trigger + && cmp_longclock(lc_fetch(trig_src->detecttime), zero_longclock) == 0) { + lc_store((trig_src->detecttime), time_longclock()); + } + return trig_src->manual_trigger; +} + +/* + * Some kind of event occurred - notify the user. + */ +static gboolean +G_TRIG_dispatch(GSource * source, + GSourceFunc callback, + gpointer user_data) +{ + GTRIGSource* trig_src = (GTRIGSource*)source; + longclock_t dispstart; + + g_assert(IS_TRIGSOURCE(trig_src)); + CHECK_DISPATCH_DELAY(trig_src); + + trig_src->manual_trigger = FALSE; + + if(trig_src->dispatch) { + if(!(trig_src->dispatch(trig_src->udata))){ + G_main_del_TriggerHandler(trig_src); + CHECK_DISPATCH_TIME(trig_src); + return FALSE; + } + CHECK_DISPATCH_TIME(trig_src); + } + lc_store((trig_src->detecttime), zero_longclock); + + return TRUE; +} + +/* + * Free up our data, and notify the user process... + */ +static void +G_TRIG_destroy(GSource* source) +{ + GTRIGSource* trig_src = (GTRIGSource*)source; + + g_assert(IS_TRIGSOURCE(trig_src)); + trig_src->gsourceid = 0; + + if (trig_src->dnotify) { + trig_src->dnotify(trig_src->udata); + } +} +/* + * Glib mainloop timeout handling code. + * + * These functions work correctly even if someone resets the + * time-of-day clock. The g_main_timeout_add() function does not have + * this property, since it relies on gettimeofday(). + * + * Our functions have the same semantics - except they always work ;-) + * + * This is because we use longclock_t for our time values. + * + */ + + +static gboolean +Gmain_timeout_prepare(GSource* src, gint* timeout); + +static gboolean +Gmain_timeout_check(GSource* src); + +static gboolean +Gmain_timeout_dispatch(GSource* src, GSourceFunc func, gpointer user_data); + +static GSourceFuncs Gmain_timeout_funcs = { + Gmain_timeout_prepare, + Gmain_timeout_check, + Gmain_timeout_dispatch, +}; + + +struct GTimeoutAppend { + COMMON_STRUCTSTART; + longclock_t nexttime; + guint interval; +}; + +#define GTIMEOUT(GS) ((struct GTimeoutAppend*)((void*)(GS))) + +guint +Gmain_timeout_add(guint interval +, GSourceFunc function +, gpointer data) +{ + return Gmain_timeout_add_full(G_PRIORITY_DEFAULT + , interval, function, data, NULL); +} + +guint +Gmain_timeout_add_full(gint priority +, guint interval +, GSourceFunc function +, gpointer data +, GDestroyNotify notify) +{ + + struct GTimeoutAppend* append; + + GSource* source = g_source_new( &Gmain_timeout_funcs, + sizeof(struct GTimeoutAppend)); + + append = GTIMEOUT(source); + append->magno = MAG_GTIMEOUTSRC; + append->maxdispatchms = DEFAULT_MAXDISPATCH; + append->maxdispatchdelayms = DEFAULT_MAXDELAY; + append->description = "(timeout)"; + lc_store((append->detecttime), zero_longclock); + append->udata = NULL; + + append->nexttime = add_longclock(time_longclock() + , msto_longclock(interval)); + append->interval = interval; + + g_source_set_priority(source, priority); + + g_source_set_can_recurse(source, FALSE); + + g_source_set_callback(source, function, data, notify); + + append->gsourceid = g_source_attach(source, NULL); + g_source_unref(source); + return append->gsourceid; + +} + +void +Gmain_timeout_remove(guint tag) +{ + GSource* source = g_main_context_find_source_by_id(NULL,tag); + struct GTimeoutAppend* append = GTIMEOUT(source); + + if (source == NULL){ + cl_log(LOG_ERR, "Attempt to remove timeout (%u)" + " with NULL source", tag); + }else{ + g_assert(IS_TIMEOUTSRC(append)); + g_source_remove(tag); + } + + return; +} + +/* g_main_loop-style prepare function */ +static gboolean +Gmain_timeout_prepare(GSource* src, gint* timeout) +{ + + struct GTimeoutAppend* append = GTIMEOUT(src); + longclock_t lnow = time_longclock(); + longclock_t remain; + + g_assert(IS_TIMEOUTSRC(append)); + if (cmp_longclock(lnow, append->nexttime) >= 0) { + *timeout = 0L; + return TRUE; + } + /* This is safe - we will always have a positive result */ + remain = sub_longclock(append->nexttime, lnow); + /* This is also safe - we started out in 'ms' */ + *timeout = longclockto_ms(remain); + return ((*timeout) == 0); +} + +/* g_main_loop-style check function */ +static gboolean +Gmain_timeout_check (GSource* src) +{ + struct GTimeoutAppend* append = GTIMEOUT(src); + longclock_t lnow = time_longclock(); + + g_assert(IS_TIMEOUTSRC(append)); + if (cmp_longclock(lnow, append->nexttime) >= 0) { + return TRUE; + } + return FALSE; +} + +/* g_main_loop-style dispatch function */ +static gboolean +Gmain_timeout_dispatch(GSource* src, GSourceFunc func, gpointer user_data) +{ + struct GTimeoutAppend* append = GTIMEOUT(src); + longclock_t dispstart; + gboolean ret; + + g_assert(IS_TIMEOUTSRC(append)); + lc_store(append->detecttime, append->nexttime); + CHECK_DISPATCH_DELAY(append); + + + /* Schedule our next dispatch */ + append->nexttime = add_longclock(time_longclock() + , msto_longclock(append->interval)); + + /* Then call the user function */ + ret = func(user_data); + + CHECK_DISPATCH_TIME(append); + return ret; +} +void +G_main_setmaxdispatchdelay(GSource* s, unsigned long delayms) +{ + GFDSource* fdp = (GFDSource*)s; + if (!IS_ONEOFOURS(fdp)) { + cl_log(LOG_ERR + , "Attempt to set max dispatch delay on wrong object"); + return; + } + fdp->maxdispatchdelayms = delayms; +} +void +G_main_setmaxdispatchtime(GSource* s, unsigned long dispatchms) +{ + GFDSource* fdp = (GFDSource*)s; + if (!IS_ONEOFOURS(fdp)) { + cl_log(LOG_ERR + , "Attempt to set max dispatch time on wrong object"); + return; + } + fdp->maxdispatchms = dispatchms; +} +void +G_main_setdescription(GSource* s, const char * description) +{ + GFDSource* fdp = (GFDSource*)s; + if (!IS_ONEOFOURS(fdp)) { + cl_log(LOG_ERR + , "Attempt to set max dispatch time on wrong object"); + return; + } + fdp->description = description; +} +void +G_main_setmaxdispatchdelay_id(guint id, unsigned long delayms) +{ + GSource* source = g_main_context_find_source_by_id(NULL,id); + + if (source) { + G_main_setmaxdispatchdelay(source, delayms); + } +} +void +G_main_setmaxdispatchtime_id(guint id, unsigned long dispatchms) +{ + GSource* source = g_main_context_find_source_by_id(NULL,id); + + if (source) { + G_main_setmaxdispatchtime(source, dispatchms); + } +} +void +G_main_setdescription_id(guint id, const char * description) +{ + GSource* source = g_main_context_find_source_by_id(NULL,id); + + if (source) { + G_main_setdescription(source, description); + } +} +void +G_main_setall_id(guint id, const char * description, unsigned long delay +, unsigned long elapsed) +{ + G_main_setdescription_id(id, description); + G_main_setmaxdispatchdelay_id(id, delay); + G_main_setmaxdispatchtime_id(id, elapsed); +} + +static void TempProcessRegistered(ProcTrack* p); +static void TempProcessDied(ProcTrack* p, int status, int signo +, int exitcode, int waslogged); +static const char* TempProcessName(ProcTrack* p); + +/*********************************************************************** + * Track our temporary child processes... + * + * We run no more than one of each type at once. + * If we need to run some and one is still running we run another one + * when this one exits. + * + * Requests to run a child process don't add up. So, 3 requests to run + * a child while one is running only cause it to be run once more, not + * three times. + * + * The only guarantee is that a new child process will run after a request + * was made. + * + * To create the possibility of running a particular type of child process + * call G_main_add_tempproc_trigger(). + * + * To cause it to be run, call G_main_set_trigger(). + * + ***********************************************************************/ + +static ProcTrack_ops TempProcessTrackOps = { + TempProcessDied, + TempProcessRegistered, + TempProcessName +}; + +/* + * Information for tracking our generic temporary child processes. + */ +struct tempproc_track { + const char * procname; /* name of the process*/ + GTRIGSource* trigger; /* Trigger for this event */ + int (*fun)(gpointer userdata); /* Function to call + * in child process */ + void (*prefork)(gpointer userdata);/* Call before fork */ + void (*postfork)(gpointer userdata);/* Call after fork */ + void (*complete)(gpointer userdata, int status, int signo, int exitcode);/* Call after complete */ + gpointer userdata; /* Info to pass 'fun' */ + gboolean isrunning; /* TRUE if child is running */ + gboolean runagain; /* TRUE if we need to run + * again after child process + * finishes. + */ +}; +static void +TempProcessRegistered(ProcTrack* p) +{ + return; /* Don't need to do much here... */ +} + +static void +TempProcessDied(ProcTrack* p, int status, int signo, int exitcode +, int waslogged) +{ + struct tempproc_track * pt = p->privatedata; + + if (pt->complete) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: Calling 'complete' for temp process %s" + , __FUNCTION__, pt->procname); + } + pt->complete(pt->userdata, status, signo, exitcode); + } + + pt->isrunning=FALSE; + if (pt->runagain) { + pt->runagain=FALSE; + + /* Do it again, Sam! */ + G_main_set_trigger(pt->trigger); + + /* Note that we set the trigger for this, we don't + * fork or call the function now. + * + * This allows the mainloop scheduler to decide + * when the fork should happen according to the priority + * of this trigger event - NOT according to the priority + * of general SIGCHLD handling. + */ + } + p->privatedata = NULL; /* Don't free until trigger is destroyed */ + return; +} + +static const char * +TempProcessName(ProcTrack* p) +{ + struct tempproc_track * pt = p->privatedata; + return pt->procname; +} +/* + * Make sure only one copy is running at a time... + */ +static gboolean +TempProcessTrigger(gpointer ginfo) +{ + struct tempproc_track* info = ginfo; + int pid; + + /* Make sure only one copy is running at a time. */ + /* This avoids concurrency problems. */ + if (info->isrunning) { + info->runagain = TRUE; + return TRUE; + } + info->isrunning = TRUE; + + if (info->prefork) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: Calling prefork for temp process %s" + , __FUNCTION__, info->procname); + } + info->prefork(info->userdata); + } + if (ANYDEBUG) { + cl_log(LOG_DEBUG, "Forking temp process %s", info->procname); + } + switch ((pid=fork())) { + int rc; + case -1: cl_perror("%s: Can't fork temporary child" + " process [%s]!", __FUNCTION__ + , info->procname); + info->isrunning = FALSE; + break; + + case 0: /* Child */ + if ((rc=info->fun(info->userdata)) == HA_OK) { + exit(0); + } + cl_log(LOG_WARNING + , "%s: %s returns %d", __FUNCTION__ + , info->procname, rc); + exit(1); + break; + default: + /* Fall out */; + + } + if (pid > 0) { + NewTrackedProc(pid, 0, (ANYDEBUG? PT_LOGVERBOSE : PT_LOGNORMAL) + , ginfo, &TempProcessTrackOps); + if (info->postfork) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: Calling postfork for temp process %s" + , __FUNCTION__, info->procname); + } + info->postfork(info->userdata); + } + } + return TRUE; +} + +static void +tempproc_destroy_notify(gpointer userdata) +{ + if (userdata != NULL) { + free(userdata); + userdata = NULL; + } +} + +GTRIGSource* +G_main_add_tempproc_trigger(int priority +, int (*triggerfun) (gpointer p) +, const char * procname +, gpointer userdata +, void (*prefork)(gpointer p) +, void (*postfork)(gpointer p) +, void (*complete)(gpointer userdata, int status, int signo, int exitcode)) +{ + + struct tempproc_track* p; + GTRIGSource* ret; + + p = (struct tempproc_track *) malloc(sizeof(struct tempproc_track)); + if (p == NULL) { + return NULL; + } + + memset(p, 0, sizeof(*p)); + p->procname = procname; + p->fun = triggerfun; + p->userdata = userdata; + p->prefork = prefork; + p->postfork = postfork; + p->complete = complete; + + ret = G_main_add_TriggerHandler(priority + , TempProcessTrigger, p, tempproc_destroy_notify); + + if (ret == NULL) { + free(p); + p = NULL; + }else{ + p->trigger = ret; + } + return ret; +} diff --git a/lib/clplumbing/Makefile.am b/lib/clplumbing/Makefile.am new file mode 100644 index 0000000..1b504fc --- /dev/null +++ b/lib/clplumbing/Makefile.am @@ -0,0 +1,99 @@ +# +# plumbing: OCF general plumbing libraries +# +# Copyright (C) 2002 Alan Robertson, International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + + +halibdir = $(libdir)/@HB_PKG@ + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + +## libraries + +lib_LTLIBRARIES = libplumb.la libplumbgpl.la + +libplumb_la_SOURCES = \ + base64.c \ + cl_compress.c \ + cl_log.c \ + cl_misc.c \ + cl_msg.c \ + cl_msg_types.c \ + cl_netstring.c \ + cl_pidfile.c \ + cl_poll.c \ + cl_random.c \ + cl_signal.c \ + cl_syslog.c \ + cl_uuid.c \ + cl_plugin.c \ + cl_reboot.c \ + coredumps.c \ + cpulimits.c \ + GSource.c \ + ipcsocket.c \ + longclock.c \ + md5.c \ + mkstemp_mode.c \ + ocf_ipc.c \ + proctrack.c \ + realtime.c \ + replytrack.c \ + timers.c \ + uids.c + +libplumb_la_LIBADD = $(top_builddir)/replace/libreplace.la \ + $(top_builddir)/lib/pils/libpils.la +libplumb_la_LDFLAGS = -version-info 3:0:1 + +libplumbgpl_la_SOURCES = setproctitle.c +libplumbgpl_la_LIBADD = $(top_builddir)/replace/libreplace.la \ + $(top_builddir)/lib/pils/libpils.la +libplumbgpl_la_LDFLAGS = -version-info 2:0:0 + +testdir = $(libdir)/@HB_PKG@ +test_PROGRAMS = ipctest ipctransientclient ipctransientserver base64_md5_test +test_SCRIPTS = transient-test.sh + +ipctest_SOURCES = ipctest.c +ipctest_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) \ + $(top_builddir)/lib/pils/libpils.la + +noinst_HEADERS = ipctransient.h + +#ipctransient_SOURCES = ipctransient.c +#ipctransient_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la $(top_builddir)/heartbeat/libhbclient.la $(GLIBLIB) + +ipctransientclient_SOURCES = ipctransientclient.c ipctransientlib.c +ipctransientclient_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) \ + $(top_builddir)/lib/pils/libpils.la + +ipctransientserver_SOURCES = ipctransientserver.c ipctransientlib.c +ipctransientserver_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) \ + $(top_builddir)/lib/pils/libpils.la + +#netstring_test_SOURCES = netstring_test.c +#netstring_test_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la libhbclient.la $(gliblib) + +base64_md5_test_SOURCES = base64_md5_test.c +base64_md5_test_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) + +EXTRA_DIST = $(test_SCRIPTS) diff --git a/lib/clplumbing/base64.c b/lib/clplumbing/base64.c new file mode 100644 index 0000000..c8ad325 --- /dev/null +++ b/lib/clplumbing/base64.c @@ -0,0 +1,422 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <syslog.h> +#include <string.h> +#include <stdlib.h> +#include "clplumbing/base64.h" + +/* + * + * Base64 conversion functions. + * They convert from a binary array into a single string + * in base 64. This is almost (but not quite) like section 5.2 of RFC 1341 + * The only difference is that we don't care about line lengths. + * We do use their encoding algorithm. + * + */ + + +static char b64chars[] += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +#define EQUALS '=' +#define MASK6 (077) +#define MASK24 (077777777) + + +/* Convert from binary to a base64 string (~ according to RFC1341) */ +int +binary_to_base64(const void * data, int nbytes, char * output, int outlen) +{ + int requiredlen = B64_stringlen(nbytes)+1; /* EOS */ + char * outptr; + const unsigned char * inmax; + const unsigned char * inlast; + const unsigned char * inptr; + int bytesleft; + + if (outlen < requiredlen) { + syslog(LOG_ERR, "binary_to_base64: output area too small."); + return -1; + } + + inptr = data; + /* Location of last whole 3-byte chunk */ + inmax = inptr + ((nbytes / B64inunit)*B64inunit); + inlast = inptr + nbytes; + outptr = output; + + + /* Convert whole 3-byte chunks */ + for (;inptr < inmax; inptr += B64inunit) { + unsigned long chunk; + unsigned int sixbits; + + chunk = ((*inptr) << 16 + | ((*(inptr+1)) << 8) + | (*(inptr+2))) & MASK24; + + sixbits = (chunk >> 18) & MASK6; + *outptr = b64chars[sixbits]; ++outptr; + + sixbits = (chunk >> 12) & MASK6; + *outptr = b64chars[sixbits]; ++outptr; + + sixbits = (chunk >> 6) & MASK6; + *outptr = b64chars[sixbits]; ++outptr; + + sixbits = (chunk & MASK6); + *outptr = b64chars[sixbits]; ++outptr; + } + + /* Do we have anything left over? */ + + bytesleft = inlast - inptr; + if (bytesleft > 0) { + /* bytesleft can only be 1 or 2 */ + + unsigned long chunk; + unsigned int sixbits; + + + /* Grab first byte */ + chunk = (*inptr) << 16; + + if (bytesleft == 2) { + /* Grab second byte */ + chunk |= ((*(inptr+1)) << 8); + } + chunk &= MASK24; + + /* OK, now we have our chunk... */ + sixbits = (chunk >> 18) & MASK6; + *outptr = b64chars[sixbits]; ++outptr; + sixbits = (chunk >> 12) & MASK6; + *outptr = b64chars[sixbits]; ++outptr; + + if (bytesleft == 2) { + sixbits = (chunk >> 6) & MASK6; + *outptr = b64chars[sixbits]; + }else{ + *outptr = EQUALS; + } + ++outptr; + + *outptr = EQUALS; ++outptr; + } + *outptr = EOS; /* Don't increment */ + return (outptr - output); +} + + +/* This macro is only usable by the base64_to_binary() function. + * + * There are faster ways of doing this, but I didn't spend the time + * to implement one of them. If we have an array of six bit values, + * sized by 256 or so, then we could look it up. + * FIXME: This is how it really ought to be done... + */ + +static unsigned char b64values [256]; +#define BADVALUE 0xff + +static void +init_b64_values(void) +{ + int j; + memset(b64values, BADVALUE, sizeof(b64values)); + + for (j=0; b64chars[j] != EOS; ++j) { + b64values[(int)b64chars[j]] = (unsigned char)j; + } +} + + +#define Char2SixBits(in, out) { \ + unsigned char ch; \ + ch = b64values[(unsigned int)in]; \ + if (ch == BADVALUE) { \ + syslog(LOG_ERR \ + , "base64_to_binary: invalid input [%c]!" \ + , in); \ + return -1; \ + } \ + out = ch; \ + } \ + + +/* Convert from a base64 string (~ according to RFC1341) to binary */ +int +base64_to_binary(const char * in, int inlen, void * output, int outlen) +{ + int maxbinlen = B64_maxbytelen(inlen); /* Worst case size */ + const char * input = in; + const char * lastinput = in + inlen - B64outunit; + int equalcount = 0; + unsigned char * startout; + unsigned char * out; + unsigned sixbits1; + unsigned sixbits2; + unsigned sixbits3; + unsigned sixbits4; + unsigned long chunk; + static int inityet = 0; + + if (!inityet) { + inityet=1; + init_b64_values(); + } + + /* Make sure we have enough room */ + if (outlen < maxbinlen) { + int residue = maxbinlen - outlen; + + if (residue > 2 + || input[inlen-1] != EQUALS + || (residue == 2 && input[inlen-2] != EQUALS)) { + syslog(LOG_ERR + , "base64_to_binary: output area too small."); + return -1; + } + } + if ((inlen % 4) != 0) { + syslog(LOG_ERR + , "base64_to_binary: input length invalid."); + return -1; + } + + if (inlen == 0) { + return 0; + } + + /* We have enough space. We are happy :-) */ + + startout = out = (unsigned char *)output; + + while (input < lastinput) { + Char2SixBits(*input, sixbits1); ++input; + Char2SixBits(*input, sixbits2); ++input; + Char2SixBits(*input, sixbits3); ++input; + Char2SixBits(*input, sixbits4); ++input; + + chunk = (sixbits1 << 18) + | (sixbits2 << 12) | (sixbits3 << 6) | sixbits4; + + *out = ((chunk >> 16) & 0xff); ++out; + *out = ((chunk >> 8) & 0xff); ++out; + *out = (chunk & 0xff); ++out; + } + + /* Process last 4 chars of input (1 to 3 bytes of output) */ + + /* The first two input chars must be normal chars */ + Char2SixBits(*input, sixbits1); ++input; + Char2SixBits(*input, sixbits2); ++input; + + /* We should find one of: (char,char) (char,=) or (=,=) */ + /* We then output: (3 bytes) (2 bytes) (1 byte) */ + + if (*input == EQUALS) { + /* The (=,=): 1 byte case */ + equalcount=2; + sixbits3 = 0; + sixbits4 = 0; + /* We assume the 2nd char is an = sign :-) */ + }else{ + /* We have either the (char,char) or (char,=) case */ + Char2SixBits(*input, sixbits3); ++input; + if (*input == EQUALS) { + /* The (char, =): 2 bytes case */ + equalcount=1; + sixbits4 = 0; + }else{ + /* The (char, char): 3 bytes case */ + Char2SixBits(*input, sixbits4); ++input; + equalcount=0; + } + } + + chunk = (sixbits1 << 18) + | (sixbits2 << 12) | (sixbits3 << 6) | sixbits4; + + /* We always have one more char to output... */ + *out = ((chunk >> 16) & 0xff); ++out; + + if (equalcount < 2) { + /* Zero or one equal signs: total of 2 or 3 bytes output */ + *out = ((chunk >> 8) & 0xff); ++out; + + if (equalcount < 1) { + /* No equal signs: total of 3 bytes output */ + *out = (chunk & 0xff); ++out; + } + } + + return out - startout; +} + +#if 0 +#define RAND(upb) (rand()%(upb)) + +void dumpbin(void * Bin, int length); +void randbin(void * Bin, int length); + +void +dumpbin(void * Bin, int length) +{ + unsigned char * bin = Bin; + + int j; + + for (j=0; j < length; ++j) { + fprintf(stderr, "%02x ", bin[j]); + if ((j % 32) == 31) { + fprintf(stderr, "\n"); + } + } + fprintf(stderr, "\n"); +} + +void +randbin(void * Bin, int length) +{ + unsigned char * bin = Bin; + int j; + + for (j=0; j < length; ++j) { + bin[j] = (unsigned char)RAND(256); + } + +} + +#define MAXLEN 320 +#define MAXSTRING B64_stringlen(MAXLEN)+1 +#define MAXITER 300000 +int +main(int argc, char ** argv) +{ + int errcount = 0; + char origbin[MAXLEN+1]; + char sourcebin[MAXLEN+1]; + char destbin[MAXLEN+1]; + char deststr[MAXSTRING]; + int maxiter = MAXITER; + int j; + + for (j=0; j < maxiter; ++j) { + int iterlen = RAND(MAXLEN+1); + int slen; + int blen; + + if ((j%100) == 99) { + fprintf(stderr, "+"); + } + + memset(origbin, 0, MAXLEN+1); + memset(sourcebin, 0, MAXLEN+1); + memset(destbin, 0, MAXLEN+1); + randbin(origbin, iterlen); + origbin[iterlen] = 0x1; + memcpy(sourcebin, origbin, iterlen); + sourcebin[iterlen] = 0x2; + slen = binary_to_base64(sourcebin, iterlen, deststr, MAXSTRING); + if (slen < 0) { + fprintf(stderr + , "binary_to_base64 failure: length %d\n" + , iterlen); + ++errcount; + continue; + } + if (strlen(deststr) != slen) { + fprintf(stderr + , "binary_to_base64 failure: length was %d (strlen) vs %d (ret value)\n" + , strlen(deststr), slen); + fprintf(stderr, "binlen: %d, deststr: [%s]\n" + , iterlen, deststr); + continue; + ++errcount; + } + destbin[iterlen] = 0x3; + blen = base64_to_binary(deststr, slen, destbin, iterlen); + + if (blen != iterlen) { + fprintf(stderr + , "base64_to_binary failure: length was %d vs %d\n" + , blen, iterlen); + dumpbin(origbin, iterlen); + fprintf(stderr + , "Base64 intermediate: [%s]\n", deststr); + ++errcount; + continue; + } + if (memcmp(destbin, origbin, iterlen) != 0) { + fprintf(stderr + , "base64_to_binary mismatch. Orig:\n"); + dumpbin(origbin, iterlen); + fprintf(stderr, "Dest:\n"); + dumpbin(destbin, iterlen); + fprintf(stderr + , "Base64 intermediate: [%s]\n", deststr); + ++errcount; + } + if (destbin[iterlen] != 0x3) { + fprintf(stderr + , "base64_to_binary corruption. dest byte: 0x%02x\n" + , destbin[iterlen]); + ++errcount; + } + + if (sourcebin[iterlen] != 0x2) { + fprintf(stderr + , "base64_to_binary corruption. source byte: 0x%02x\n" + , sourcebin[iterlen]); + ++errcount; + } + sourcebin[iterlen] = 0x0; + origbin[iterlen] = 0x0; + if (memcmp(sourcebin, origbin, MAXLEN+1) != 0) { + fprintf(stderr + , "base64_to_binary corruption. origbin:\n"); + dumpbin(origbin, MAXLEN+1); + fprintf(stderr, "sourcebin:\n"); + dumpbin(sourcebin, MAXLEN+1); + ++errcount; + } + + } + + fprintf(stderr, "\n%d iterations, %d errors.\n" + , maxiter, errcount); + + return errcount; +} +/* HA-logging function */ +void +ha_log(int priority, const char * fmt, ...) +{ + va_list ap; + char buf[MAXLINE]; + + va_start(ap, fmt); + vsnprintf(buf, MAXLINE, fmt, ap); + va_end(ap); + + fprintf(stderr, "%s\n", buf); + +} +#endif diff --git a/lib/clplumbing/base64_md5_test.c b/lib/clplumbing/base64_md5_test.c new file mode 100644 index 0000000..d536776 --- /dev/null +++ b/lib/clplumbing/base64_md5_test.c @@ -0,0 +1,113 @@ +/* File: base64_md5_test.c + * Description: base64 and md5 algorithm tests + * + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2005 International Business Machines + * + * 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.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <lha_internal.h> +#include <stdio.h> +#include <string.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/base64.h> +#include <clplumbing/md5.h> + +#define MD5LEN 16 /* md5 buffer */ +#define BASE64_BUF_LEN 32 + +/* gcc -o base64_md5_test base64_md5_test.c -lplumb */ +int main(void) +{ + int error_count = 0; + + const char base64_encode[] = "YWJjZGVmZ2g="; + const char raw_data[] = "abcdefgh"; + + /* A test case from + * RFC 1321 - The MD5 Message-Digest Algorithm + */ + const char * data1 = "message digest"; + const char digest_rfc1321[(MD5LEN+1)*2+1] + = "f96b697d7cb7938d525a2f31aaf161d0"; + + /* A test case from + * RFC 2104 - HMAC: Keyed-Hashing for Message Authentication + */ + const char *key = "Jefe"; + const char *data2 = "what do ya want for nothing?"; + const char digest_rfc2104[(MD5LEN+1)*2+1] + = "750c783e6ab0b503eaa86e310a5db738"; + + char buffer_tmp[BASE64_BUF_LEN]; + + char md[(MD5LEN+1)*2+1]; + unsigned char digest[MD5LEN]; + char * md_tmp; + int rc,i; + + /* base64 encode test */ + binary_to_base64(raw_data, strlen(raw_data), buffer_tmp + , BASE64_BUF_LEN); + /* printf("base64_encode = %s\n", buffer_tmp); */ + if (0 != strncmp(buffer_tmp, base64_encode, strlen(buffer_tmp)) ) { + cl_log(LOG_ERR, "binary_to_base64 works bad."); + error_count++; + } + + /* base64 decode test */ + memset(buffer_tmp, 0, BASE64_BUF_LEN); + base64_to_binary(base64_encode, strlen(base64_encode) + , buffer_tmp, BASE64_BUF_LEN); + /* printf("decoded data= %s\n", buffer_tmp); */ + if (0 != strncmp(buffer_tmp, raw_data, strlen(buffer_tmp)) ) { + cl_log(LOG_ERR, "base64_to_binary works bad."); + error_count++; + } + + rc = MD5((const unsigned char *)data1, strlen(data1), digest); + + md_tmp = md; + for (i = 0; i < MD5LEN; i++) { + snprintf(md_tmp, sizeof(md), "%02x", digest[i]); + md_tmp += 2; + } + *md_tmp = '\0'; + /* printf("rc=%d MD5=%s\n", rc, md); */ + + if (0 != strncmp(md, digest_rfc1321, MD5LEN*2) ) { + cl_log(LOG_ERR, "The md5-rfc1321 algorithm works bad."); + error_count++; + } + + rc = HMAC((const unsigned char *)key, strlen(key) + , (const unsigned char *)data2, strlen(data2), digest); + md_tmp = md; + for (i = 0; i < MD5LEN; i++) { + sprintf(md_tmp,"%02x", digest[i]); + md_tmp += 2; + } + *md_tmp = '\0'; + /* printf("rc=%d HMAC=%s\n", rc, md); */ + + if (0 != strncmp(md, digest_rfc2104, MD5LEN*2) ) { + cl_log(LOG_ERR, "The md5-rfc2104 algorithm works bad."); + error_count++; + } + + (void) rc; /* Suppress -Werror=unused-but-set-variable */ + return error_count; +} diff --git a/lib/clplumbing/cl_compress.c b/lib/clplumbing/cl_compress.c new file mode 100644 index 0000000..6b56ad6 --- /dev/null +++ b/lib/clplumbing/cl_compress.c @@ -0,0 +1,500 @@ + +/* + * compress.c: Compression functions for Linux-HA + * + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * Compression is designed to handle big messages, right now with 4 nodes + * cib message can go up to 64 KB or more. I expect much larger messages + * when the number of node increase. This makes message compression necessary. + * + * + * Compression is handled in field level. One can add a struct field using + * ha_msg_addstruct() -- the field will not get compressed, or using + * ha_msg_addstruct_compress(), and the field will get compressed when + * the message is converted to wire format, i.e. when msg2wirefmt() is called. + * The compressed field will stay compressed until it reached the desination. + * It will finally decompressed when the user start to get the field value. + * It is designed this way so that the compression/decompression only happens + * in end users so that heartbeat itself can save cpu cycle and memory. + * (more info about compression can be found in cl_msg_types.c about FT_COMPRESS + * FT_UNCOMPRESS types) + * + * compression has another legacy mode, which is there so it can be compatible + * to old ways of compression. In the old way, no field is compressed individually + * and the messages is compressed before it is sent out, and it will be decompressed + * in the receiver side immediately. So in each IPC channel, the message is compressed + * and decompressed once. This way will cost a lot of cpu time and memory and it is + * discouraged. + * + * If use_traditional_compression is true, then it is using the legacy mode, otherwise + * it is using the new compression. For back compatibility, the default is legacy mode. + * + * The real compression work is done by compression plugins. There are two plugins right + * now: zlib and bz2, they are in lib/plugins/compress + * + */ + +#include <lha_internal.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <unistd.h> +#include <assert.h> +#include <glib.h> +#include <compress.h> +#include <ha_msg.h> +#include <clplumbing/netstring.h> +#include <pils/plugin.h> +#include <pils/generic.h> +#include <stonith/stonith.h> +#include <stonith/stonith_plugin.h> + +#define COMPRESSED_FIELD "_compressed_payload" +#define COMPRESS_NAME "_compression_algorithm" +#define HACOMPRESSNAME "HA_COMPRESSION" +#define DFLT_COMPRESS_PLUGIN "bz2" + +static struct hb_compress_fns* msg_compress_fns = NULL; +static char* compress_name = NULL; +GHashTable* CompressFuncs = NULL; + +static PILGenericIfMgmtRqst Reqs[] = + { + {"compress", &CompressFuncs, NULL, NULL, NULL}, + {NULL, NULL, NULL, NULL, NULL} + }; + +static PILPluginUniv* CompressPIsys = NULL; + +static int +init_pluginsys(void){ + + if (CompressPIsys) { + return TRUE; + } + + CompressPIsys = NewPILPluginUniv(HA_PLUGIN_DIR); + + if (CompressPIsys) { + if (PILLoadPlugin(CompressPIsys, PI_IFMANAGER, "generic", Reqs) + != PIL_OK){ + cl_log(LOG_ERR, "generic plugin load failed\n"); + DelPILPluginUniv(CompressPIsys); + CompressPIsys = NULL; + } + }else{ + cl_log(LOG_ERR, "pi univ creation failed\n"); + } + return CompressPIsys != NULL; + +} + +int +cl_compress_remove_plugin(const char* pluginname) +{ + return HA_OK; +} + +int +cl_compress_load_plugin(const char* pluginname) +{ + struct hb_compress_fns* funcs = NULL; + + if (!init_pluginsys()){ + return HA_FAIL; + } + + if ((funcs = g_hash_table_lookup(CompressFuncs, pluginname)) + == NULL){ + if (PILPluginExists(CompressPIsys, HB_COMPRESS_TYPE_S, + pluginname) == PIL_OK){ + PIL_rc rc; + if ((rc = PILLoadPlugin(CompressPIsys, + HB_COMPRESS_TYPE_S, + pluginname, + NULL))!= PIL_OK){ + cl_log(LOG_ERR, + "Cannot load compress plugin %s[%s]", + pluginname, + PIL_strerror(rc)); + return HA_FAIL; + } + funcs = g_hash_table_lookup(CompressFuncs, + pluginname); + } + + } + if (funcs == NULL){ + cl_log(LOG_ERR, "Compression module(%s) not found", pluginname); + return HA_FAIL; + } + + /* set the environment variable so that later programs can + * load the appropriate plugin + */ + setenv(HACOMPRESSNAME,pluginname,1); + msg_compress_fns = funcs; + + return HA_OK; +} + +int +cl_set_compress_fns(const char* pluginname) +{ + /* this function was unnecessary duplication of the + * code in cl_compress_load_plugin + */ + return cl_compress_load_plugin(pluginname); +} + +struct hb_compress_fns* +cl_get_compress_fns(void) +{ + static int try_dflt = 1; + + if (try_dflt && !msg_compress_fns) { + try_dflt = 0; + cl_log(LOG_INFO, "%s: user didn't set compression type, " + "loading %s plugin", + __FUNCTION__, DFLT_COMPRESS_PLUGIN); + cl_compress_load_plugin(DFLT_COMPRESS_PLUGIN); + } + return msg_compress_fns; +} + +static struct hb_compress_fns* +get_compress_fns(const char* pluginname) +{ + struct hb_compress_fns* funcs = NULL; + + if (cl_compress_load_plugin(pluginname) != HA_OK){ + cl_log(LOG_ERR, "%s: loading compression module" + "(%s) failed", + __FUNCTION__, pluginname); + return NULL; + } + + funcs = g_hash_table_lookup(CompressFuncs, pluginname); + return funcs; +} + +void cl_realtime_malloc_check(void); + +char* +cl_compressmsg(struct ha_msg* m, size_t* len) +{ + char* src; + char* dest; + size_t destlen; + int rc; + char* ret = NULL; + struct ha_msg* tmpmsg; + size_t datalen; + + destlen = MAXMSG; + + dest = malloc(destlen); + if (!dest) { + cl_log(LOG_ERR, "%s: failed to allocate destination buffer", + __FUNCTION__); + return NULL; + } + + if (msg_compress_fns == NULL){ + cl_log(LOG_ERR, "%s: msg_compress_fns is NULL!", + __FUNCTION__); + goto out; + } + if ( get_netstringlen(m) > MAXUNCOMPRESSED + || get_stringlen(m) > MAXUNCOMPRESSED){ + cl_log(LOG_ERR, "%s: msg too big(stringlen=%d," + "netstringlen=%d)", + __FUNCTION__, + get_stringlen(m), + get_netstringlen(m)); + goto out; + } + + + if ((src = msg2wirefmt_noac(m, &datalen)) == NULL){ + cl_log(LOG_ERR,"%s: converting msg" + " to wirefmt failed", __FUNCTION__); + goto out; + } + + rc = msg_compress_fns->compress(dest, &destlen, + src, datalen); + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: compression failed", + __FUNCTION__); + goto out; + } + + free(src); + + tmpmsg =ha_msg_new(0); + rc = ha_msg_addbin(tmpmsg, COMPRESSED_FIELD, dest, destlen)/*discouraged function*/; + + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: adding binary to msg failed", + __FUNCTION__); + goto out; + } + + rc = ha_msg_add(tmpmsg, COMPRESS_NAME, + msg_compress_fns->getname()); + + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: adding compress name to msg failed", + __FUNCTION__); + goto out; + } + + + ret = msg2netstring(tmpmsg, len); + ha_msg_del(tmpmsg); + +#if 0 + cl_log(LOG_INFO, "------original stringlen=%d, netstringlen=%d," + "compressed_datalen=%d,current len=%d", + get_stringlen(m), get_netstringlen(m),(int)destlen, (int)*len); + +#endif + +out: + if (dest) { + free(dest); + } + + return ret; +} + + +gboolean +is_compressed_msg(struct ha_msg* m) +{ + if( cl_get_binary(m, COMPRESSED_FIELD, NULL) /*discouraged function*/ + != NULL){ + return TRUE; + } + + return FALSE; + +} + +/* the decompressmsg function is not exactly the reverse + * operation of compressmsg, it starts when the prorgram + * detects there is compressed_field in a msg + */ + +struct ha_msg* +cl_decompressmsg(struct ha_msg* m) +{ + const char* src; + size_t srclen; + char *dest = NULL; + size_t destlen = MAXUNCOMPRESSED; + int rc; + struct ha_msg* ret = NULL; + const char* decompress_name; + struct hb_compress_fns* funcs = NULL; + + dest = malloc(destlen); + + if (!dest) { + cl_log(LOG_ERR, "%s: Failed to allocate buffer.", __FUNCTION__); + return NULL; + } + + if (m == NULL){ + cl_log(LOG_ERR, "%s: NULL message", __FUNCTION__); + goto out; + } + src = cl_get_binary(m, COMPRESSED_FIELD, &srclen)/*discouraged function*/; + if (src == NULL){ + cl_log(LOG_ERR, "%s: compressed-field is NULL", + __FUNCTION__); + goto out; + } + + if (srclen > MAXMSG){ + cl_log(LOG_ERR, "%s: field too long(%d)", + __FUNCTION__, (int)srclen); + goto out; + } + + decompress_name = ha_msg_value(m, COMPRESS_NAME); + if (decompress_name == NULL){ + cl_log(LOG_ERR, "compress name not found"); + goto out; + } + + + funcs = get_compress_fns(decompress_name); + + if (funcs == NULL){ + cl_log(LOG_ERR, "%s: compress method(%s) is not" + " supported in this machine", + __FUNCTION__, decompress_name); + goto out; + } + + rc = funcs->decompress(dest, &destlen, src, srclen); + + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: decompression failed", + __FUNCTION__); + goto out; + } + + ret = wirefmt2msg(dest, destlen, 0); + +#if 0 + cl_log(LOG_INFO, "%s: srclen =%d, destlen=%d", + __FUNCTION__, + srclen, destlen); +#endif + +out: + if (dest) { + free(dest); + } + + return ret; +} + + +int +cl_decompress_field(struct ha_msg* msg, int index, char* buf, size_t* buflen) +{ + char* value; + int vallen; + int rc; + const char* decompress_name; + struct hb_compress_fns* funcs; + + if ( msg == NULL|| index >= msg->nfields){ + cl_log(LOG_ERR, "%s: wrong argument", + __FUNCTION__); + return HA_FAIL; + } + + value = msg->values[index]; + vallen = msg->vlens[index]; + + decompress_name = ha_msg_value(msg, COMPRESS_NAME); + if (decompress_name == NULL){ + cl_log(LOG_ERR, "compress name not found"); + return HA_FAIL; + } + + + funcs = get_compress_fns(decompress_name); + + if (funcs == NULL){ + cl_log(LOG_ERR, "%s: compress method(%s) is not" + " supported in this machine", + __FUNCTION__, decompress_name); + return HA_FAIL; + } + + rc = funcs->decompress(buf, buflen, value, vallen); + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: decompression failed", + __FUNCTION__); + return HA_FAIL; + } + + return HA_OK; +} + + +int +cl_compress_field(struct ha_msg* msg, int index, char* buf, size_t* buflen) +{ + char* src; + size_t srclen; + int rc; + + if ( msg == NULL|| index >= msg->nfields + || msg->types[index] != FT_UNCOMPRESS){ + cl_log(LOG_ERR, "%s: wrong argument", + __FUNCTION__); + return HA_FAIL; + } + + if (msg_compress_fns == NULL){ + if (compress_name == NULL){ + compress_name = getenv(HACOMPRESSNAME); + } + + if (compress_name == NULL){ + cl_log(LOG_ERR, "%s: no compression module name found", + __FUNCTION__); + return HA_FAIL; + } + + if(cl_set_compress_fns(compress_name) != HA_OK){ + cl_log(LOG_ERR, "%s: loading compression module failed", + __FUNCTION__); + return HA_FAIL; + } + } + + if (msg_compress_fns == NULL){ + cl_log(LOG_ERR, "%s: msg_compress_fns is NULL!", + __FUNCTION__); + return HA_FAIL; + } + + src = msg2wirefmt_noac(msg->values[index], &srclen); + if (src == NULL){ + cl_log(LOG_ERR,"%s: converting msg" + " to wirefmt failed", __FUNCTION__); + return HA_FAIL; + } + + rc = msg_compress_fns->compress(buf, buflen, + src, srclen); + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: compression failed", + __FUNCTION__); + return HA_FAIL; + } + + + rc = ha_msg_mod(msg, COMPRESS_NAME, + msg_compress_fns->getname()); + + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: adding compress name to msg failed", + __FUNCTION__); + return HA_FAIL;; + } + + free(src); + src = NULL; + + return HA_OK; + +} diff --git a/lib/clplumbing/cl_log.c b/lib/clplumbing/cl_log.c new file mode 100644 index 0000000..213e760 --- /dev/null +++ b/lib/clplumbing/cl_log.c @@ -0,0 +1,1261 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <syslog.h> +#include <time.h> +#include <sys/utsname.h> +#include <clplumbing/ipc.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/loggingdaemon.h> +#include <clplumbing/longclock.h> +#include <clplumbing/uids.h> +#include <glib.h> +#include <netinet/in.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <clplumbing/GSource.h> +#include <clplumbing/cl_misc.h> +#include <clplumbing/cl_syslog.h> +#include <ha_msg.h> + +#ifndef MAXLINE +# define MAXLINE 512 +#endif +/* + * <syslog.h> might not contain LOG_PRI... + * So, we define it ourselves, or error out if we can't... + */ + +#ifndef LOG_PRI +# ifdef LOG_PRIMASK + /* David Lee <T.D.Lee@durham.ac.uk> reports this works on Solaris */ +# define LOG_PRI(p) ((p) & LOG_PRIMASK) +# else +# error "Syslog.h does not define either LOG_PRI or LOG_PRIMASK." +# endif +#endif + +#define DFLT_ENTITY "cluster" +#define DFLT_PREFIX "" +#define NULLTIME 0 +#define QUEUE_SATURATION_FUZZ 10 + +static IPC_Channel* logging_daemon_chan = NULL; +static gboolean syslogformatfile = TRUE; +/* + * If true, then output messages more or less like this... + * Jul 14 21:45:18 beam logd: [1056]: info: setting log file to /dev/null + */ + +int LogToDaemon(int priority, const char * buf, int bstrlen, gboolean use_pri_str); + +static int LogToLoggingDaemon(int priority, const char * buf, int bstrlen, gboolean use_pri_str); +static IPC_Message* ChildLogIPCMessage(int priority, const char *buf, int bstrlen, + gboolean use_priority_str, IPC_Channel* ch); +static void FreeChildLogIPCMessage(IPC_Message* msg); +static gboolean send_dropped_message(gboolean use_pri_str, IPC_Channel *chan); +static int cl_set_logging_wqueue_maxlen(int qlen); + +static int use_logging_daemon = FALSE; +static int conn_logd_time = 0; +static char cl_log_entity[MAXENTITY]= DFLT_ENTITY; +static char cl_log_syslogprefix[MAXENTITY] = DFLT_PREFIX; +static char common_log_entity[MAXENTITY]= DFLT_ENTITY; +static int cl_log_facility = LOG_USER; +static int use_buffered_io = 0; + +static void cl_opensyslog(void); +static int syslog_enabled = 0; +static int stderr_enabled = 0; +static int stdout_enabled = 0; +static const char* logfile_name = NULL; +static const char* debugfile_name = NULL; +static int cl_process_pid = -1; +int debug_level = 0; +static GDestroyNotify destroy_logging_channel_callback; +static void (*create_logging_channel_callback)(IPC_Channel* chan); +static gboolean logging_chan_in_main_loop = FALSE; + +/*********************** + *debug use only, do not use this function in your program + */ +IPC_Channel * get_log_chan(void); + +IPC_Channel* get_log_chan(void){ + return logging_daemon_chan; +} +/*************************/ + +/************************** + * check if the fd is in use for logging + **************************/ +int +cl_log_is_logd_fd(int fd) +{ + return logging_daemon_chan && ( + fd == logging_daemon_chan->ops->get_send_select_fd(logging_daemon_chan) + || + fd == logging_daemon_chan->ops->get_recv_select_fd(logging_daemon_chan) + ); +} + +void +cl_log_enable_stderr(int truefalse) +{ + stderr_enabled = truefalse; +} + +void +cl_log_enable_stdout(int truefalse) +{ + stdout_enabled = truefalse; +} + +void +cl_log_set_uselogd(int truefalse) +{ + use_logging_daemon = truefalse; +} +void +cl_log_enable_syslog_filefmt(int truefalse) +{ + syslogformatfile = (gboolean)truefalse; +} + +gboolean +cl_log_get_uselogd(void) +{ + return use_logging_daemon; +} + + +int +cl_log_get_logdtime(void) +{ + return conn_logd_time; + +} + +void +cl_log_set_logdtime(int logdtime) +{ + conn_logd_time = logdtime; + return; +} + +void +cl_log_use_buffered_io(int truefalse) +{ + use_buffered_io = truefalse; + cl_log_close_log_files(); +} + +#define ENVPRE "HA_" + +#define ENV_HADEBUGVAL "HA_debug" +#define ENV_LOGFENV "HA_logfile" /* well-formed log file :-) */ +#define ENV_DEBUGFENV "HA_debugfile" /* Debug log file */ +#define ENV_LOGFACILITY "HA_logfacility"/* Facility to use for logger */ +#define ENV_SYSLOGFMT "HA_syslogmsgfmt"/* TRUE if we should use syslog message formatting */ +#define ENV_LOGDAEMON "HA_use_logd" +#define ENV_CONNINTVAL "HA_conn_logd_time" +#define TRADITIONAL_COMPRESSION "HA_traditional_compression" +#define COMPRESSION "HA_compression" + +static void +inherit_compress(void) +{ + char* inherit_env = NULL; + + inherit_env = getenv(TRADITIONAL_COMPRESSION); + if (inherit_env != NULL && *inherit_env != EOS) { + gboolean value; + + if (cl_str_to_boolean(inherit_env, &value)!= HA_OK){ + cl_log(LOG_ERR, "inherit traditional_compression failed"); + }else{ + cl_set_traditional_compression(value); + } + } + +} + +void +cl_inherit_logging_environment(int logqueuemax) +{ + char * inherit_env = NULL; + + /* Donnot need to free the return pointer from getenv */ + inherit_env = getenv(ENV_HADEBUGVAL); + if (inherit_env != NULL && atoi(inherit_env) != 0 ) { + debug_level = atoi(inherit_env); + inherit_env = NULL; + } + + inherit_env = getenv(ENV_LOGFENV); + if (inherit_env != NULL && *inherit_env != EOS) { + cl_log_set_logfile(inherit_env); + inherit_env = NULL; + } + + inherit_env = getenv(ENV_DEBUGFENV); + if (inherit_env != NULL && *inherit_env != EOS) { + cl_log_set_debugfile(inherit_env); + inherit_env = NULL; + } + + inherit_env = getenv(ENV_LOGFACILITY); + if (inherit_env != NULL && *inherit_env != EOS) { + int facility = -1; + facility = cl_syslogfac_str2int(inherit_env); + if ( facility >= 0 ) { + cl_log_set_facility(facility); + } + inherit_env = NULL; + } + + inherit_env = getenv(ENV_SYSLOGFMT); + if (inherit_env != NULL && *inherit_env != EOS) { + gboolean truefalse; + if (cl_str_to_boolean(inherit_env, &truefalse) == HA_OK) { + cl_log_enable_syslog_filefmt(truefalse); + } + } + + inherit_env = getenv(ENV_LOGDAEMON); + if (inherit_env != NULL && *inherit_env != EOS) { + gboolean uselogd; + cl_str_to_boolean(inherit_env, &uselogd); + cl_log_set_uselogd(uselogd); + if (uselogd) { + if (logqueuemax > 0) { + cl_set_logging_wqueue_maxlen(logqueuemax); + } + } + } + + inherit_env = getenv(ENV_CONNINTVAL); + if (inherit_env != NULL && *inherit_env != EOS) { + int logdtime; + logdtime = cl_get_msec(inherit_env); + cl_log_set_logdtime(logdtime); + } + + inherit_compress(); + return; +} + + +static void +add_logging_channel_mainloop(IPC_Channel* chan) +{ + GCHSource* chp= + G_main_add_IPC_Channel( G_PRIORITY_DEFAULT, + chan, + FALSE, + NULL, + NULL, + destroy_logging_channel_callback); + + if (chp == NULL){ + cl_log(LOG_INFO, "adding logging channel to mainloop failed"); + } + + logging_chan_in_main_loop = TRUE; + + + return; +} + +static void +remove_logging_channel_mainloop(gpointer userdata) +{ + logging_chan_in_main_loop = FALSE; + + return; +} + + +static IPC_Channel* +create_logging_channel(void) +{ + GHashTable* attrs; + char path[] = IPC_PATH_ATTR; + char sockpath[] = HA_LOGDAEMON_IPC; + IPC_Channel* chan; + static gboolean complained_yet = FALSE; + + attrs = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(attrs, path, sockpath); + + chan =ipc_channel_constructor(IPC_ANYTYPE, attrs); + + g_hash_table_destroy(attrs); + + if (chan == NULL) { + cl_log(LOG_ERR, "create_logging_channel:" + "contructing ipc channel failed"); + return NULL; + } + + if (chan->ops->initiate_connection(chan) != IPC_OK) { + if (!complained_yet) { + complained_yet = TRUE; + cl_log(LOG_WARNING, "Initializing connection" + " to logging daemon failed." + " Logging daemon may not be running"); + } + if (!logging_chan_in_main_loop){ + chan->ops->destroy(chan); + } + + return NULL; + } + complained_yet = FALSE; + + if (create_logging_channel_callback){ + create_logging_channel_callback(chan); + } + + + return chan; + +} + +gboolean +cl_log_test_logd(void) +{ + IPC_Channel* chan = logging_daemon_chan; + + if (chan && chan->ops->get_chan_status(chan) == IPC_CONNECT){ + return TRUE; + } + if (chan ){ + if (!logging_chan_in_main_loop){ + chan->ops->destroy(chan); + } + logging_daemon_chan = chan = NULL; + } + + logging_daemon_chan = chan = create_logging_channel(); + + if (chan == NULL){ + return FALSE; + } + + if(chan->ops->get_chan_status(chan) != IPC_CONNECT){ + if (!logging_chan_in_main_loop){ + chan->ops->destroy(chan); + } + logging_daemon_chan = chan = NULL; + return FALSE; + } + + return TRUE; + +} + +/* FIXME: This is way too ugly to bear */ + +void +cl_log_set_facility(int facility) +{ + if (syslog_enabled && facility == cl_log_facility) { + return; + } + cl_log_facility = facility; + closelog(); + syslog_enabled = 0; + if (facility > 0) { + cl_opensyslog(); + } +} + +void +cl_log_set_entity(const char * entity) +{ + if (entity == NULL) { + entity = DFLT_ENTITY; + } + strncpy(cl_log_entity, entity, MAXENTITY); + cl_log_entity[MAXENTITY-1] = '\0'; + if (syslog_enabled) { + syslog_enabled = 0; + cl_opensyslog(); + } +} + +void +cl_log_set_syslogprefix(const char *prefix) +{ + if (prefix == NULL) { + prefix = DFLT_PREFIX; + } + strncpy(cl_log_syslogprefix, prefix, MAXENTITY); + cl_log_syslogprefix[MAXENTITY-1] = '\0'; + if (syslog_enabled) { + syslog_enabled = 0; + cl_opensyslog(); + } +} + +void +cl_log_set_logfile(const char * path) +{ + if(path != NULL && strcasecmp("/dev/null", path) == 0) { + path = NULL; + } + logfile_name = path; + cl_log_close_log_files(); +} +void +cl_log_set_debugfile(const char * path) +{ + if(path != NULL && strcasecmp("/dev/null", path) == 0) { + path = NULL; + } + debugfile_name = path; + cl_log_close_log_files(); +} + + +/* + * This function sets two callback functions. + * One for creating a channel and + * the other for destroying a channel* + */ +int +cl_log_set_logd_channel_source( void (*create_callback)(IPC_Channel* chan), + GDestroyNotify destroy_callback) +{ + IPC_Channel* chan = logging_daemon_chan ; + + if (destroy_callback == NULL){ + destroy_logging_channel_callback = remove_logging_channel_mainloop; + }else{ + destroy_logging_channel_callback = destroy_callback; + } + + if (create_callback == NULL){ + create_logging_channel_callback = add_logging_channel_mainloop; + }else{ + create_logging_channel_callback = create_callback; + } + + if (chan != NULL + && chan->ops->get_chan_status(chan) == IPC_CONNECT){ + add_logging_channel_mainloop(chan); + } + + return 0; +} + +const char * +prio2str(int priority) +{ + static const char *log_prio[8] = { + "EMERG", + "ALERT", + "CRIT", + "ERROR", + "WARN", + "notice", + "info", + "debug" + }; + int logpri; + + logpri = LOG_PRI(priority); + + return (logpri < 0 || logpri >= DIMOF(log_prio)) ? + "(undef)" : log_prio[logpri]; +} + +/* print log line to a FILE *f */ +#define print_logline(fp,entity,entity_pid,ts,pristr,buf) { \ + fprintf(fp, "%s[%d]: %s ",entity,entity_pid,ha_timestamp(ts)); \ + if (pristr) \ + fprintf(fp,"%s: %s\n",pristr,buf); \ + else \ + fprintf(fp,"%s\n",buf); \ + } + +static char * syslog_timestamp(TIME_T t); +static void cl_limit_log_update(struct msg_ctrl *ml, time_t ts); + +static void +append_log(FILE * fp, const char * entity, int entity_pid +, TIME_T timestamp, const char * pristr, const char * msg) +{ + static int got_uname = FALSE; + static struct utsname un; + + if (!syslogformatfile) { + print_logline(fp, entity, entity_pid, timestamp, pristr, msg); + return; + } + if (!got_uname) { + uname(&un); + } + /* + * Jul 14 21:45:18 beam logd: [1056]: info: setting log file to /dev/null + */ + fprintf(fp, "%s %s %s: [%d]: %s%s%s\n" + , syslog_timestamp(timestamp) + , un.nodename, entity, entity_pid + , (pristr ? pristr : "") + , (pristr ? ": " : "") + , msg); +} + +/* As performance optimization we try to keep the file descriptor + * open all the time, but as logrotation needs to work, the calling + * program actually needs a signal handler. + * + * To be able to keep files open even without signal handler, + * we remember the stat info, and close/reopen if the inode changed. + * We keep the number of stat() calls to one per file per minute. + * logrotate should be configured for delayed compression, if any. + */ + +struct log_file_context { + FILE *fp; + struct stat stat_buf; +}; + +static struct log_file_context log_file, debug_file; + +static void close_log_file(struct log_file_context *lfc) +{ + /* ignore errors, we cannot do anything about them anyways */ + fflush(lfc->fp); + fsync(fileno(lfc->fp)); + fclose(lfc->fp); + lfc->fp = NULL; +} + +void cl_log_close_log_files(void) +{ + if (log_file.fp) + close_log_file(&log_file); + if (debug_file.fp) + close_log_file(&debug_file); +} + +static void maybe_close_log_file(const char *fname, struct log_file_context *lfc) +{ + struct stat buf; + if (!lfc->fp) + return; + if (stat(fname, &buf) || buf.st_ino != lfc->stat_buf.st_ino) { + close_log_file(lfc); + cl_log(LOG_INFO, "log-rotate detected on logfile %s", fname); + } +} + +/* Default to unbuffered IO. logd or others can use cl_log_use_buffered_io(1) + * to enable fully buffered mode, and then use fflush appropriately. + */ +static void open_log_file(const char *fname, struct log_file_context *lfc) +{ + lfc->fp = fopen(fname ,"a"); + if (!lfc->fp) { + syslog(LOG_ERR, "Failed to open log file %s: %s\n" , + fname, strerror(errno)); + } else { + setvbuf(lfc->fp, NULL, + use_buffered_io ? _IOFBF : _IONBF, + BUFSIZ); + fstat(fileno(lfc->fp), &lfc->stat_buf); + } +} + +static void maybe_reopen_log_files(const char *log_fname, const char *debug_fname) +{ + static TIME_T last_stat_time; + + if (log_file.fp || debug_file.fp) { + TIME_T now = time(NULL); + if (now - last_stat_time > 59) { + /* Don't use an exact minute, have it jitter around a + * bit against cron or others. Note that, if there + * is no new log message, it can take much longer + * than this to notice logrotation and actually close + * our file handle on the possibly already rotated, + * or even deleted. + * + * As long as at least one minute pases between + * renaming the log file, and further processing, + * no message will be lost, so this should do fine: + * (mv ha-log ha-log.1; sleep 60; gzip ha-log.1) + */ + maybe_close_log_file(log_fname, &log_file); + maybe_close_log_file(debug_fname, &debug_file); + last_stat_time = now; + } + } + + if (log_fname && !log_file.fp) + open_log_file(log_fname, &log_file); + + if (debug_fname && !debug_file.fp) + open_log_file(debug_fname, &debug_file); +} + +/* + * This function can cost us realtime unless use_logging_daemon + * is enabled. Then we log everything through a child process using + * non-blocking IPC. + */ + +/* Cluster logging function */ +void +cl_direct_log(int priority, const char* buf, gboolean use_priority_str, + const char* entity, int entity_pid, TIME_T ts) +{ + const char * pristr; + int needprivs = !cl_have_full_privs(); + + pristr = use_priority_str ? prio2str(priority) : NULL; + + if (!entity) + entity = *cl_log_entity ? cl_log_entity : DFLT_ENTITY; + + if (needprivs) { + return_to_orig_privs(); + } + + if (syslog_enabled) { + snprintf(common_log_entity, MAXENTITY, "%s", + *cl_log_syslogprefix ? cl_log_syslogprefix : entity); + + /* The extra trailing '\0' is supposed to work around some + * "known syslog bug that ends up concatinating entries". + * Knowledge about which syslog package, version, platform and + * what exactly the bug was has been lost, but leaving it in + * won't do any harm either. */ + syslog(priority, "%s[%d]: %s%s%s%c", + *cl_log_syslogprefix ? entity : "", + entity_pid, + pristr ?: "", pristr ? ": " : "", + buf, 0); + } + + maybe_reopen_log_files(logfile_name, debugfile_name); + + if (debug_file.fp) + append_log(debug_file.fp, entity, entity_pid, ts, pristr, buf); + + if (priority != LOG_DEBUG && log_file.fp) + append_log(log_file.fp, entity, entity_pid, ts, pristr, buf); + + if (needprivs) { + return_to_dropped_privs(); + } + return; +} + +void cl_log_do_fflush(int do_fsync) +{ + if (log_file.fp) { + fflush(log_file.fp); + if (do_fsync) + fsync(fileno(log_file.fp)); + } + if (debug_file.fp) { + fflush(debug_file.fp); + if (do_fsync) + fsync(fileno(debug_file.fp)); + } +} + +/* + * This function can cost us realtime unless use_logging_daemon + * is enabled. Then we log everything through a child process using + * non-blocking IPC. + */ + +static int cl_log_depth = 0; + +/* Cluster logging function */ +void +cl_log(int priority, const char * fmt, ...) +{ + va_list ap; + char buf[MAXLINE]; + ssize_t nbytes; + + cl_process_pid = (int)getpid(); + + cl_log_depth++; + + buf[MAXLINE-1] = EOS; + va_start(ap, fmt); + nbytes=vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + if (nbytes >= (ssize_t)sizeof(buf)){ + nbytes = sizeof(buf) -1 ; + } + + if (stderr_enabled) { + append_log(stderr, cl_log_entity,cl_process_pid, + NULLTIME, prio2str(priority), buf); + } + + if (stdout_enabled) { + append_log(stdout, cl_log_entity,cl_process_pid, + NULLTIME, prio2str(priority), buf); + } + + if (use_logging_daemon && cl_log_depth <= 1) { + LogToLoggingDaemon(priority, buf, nbytes, TRUE); + }else{ + /* this may cause blocking... maybe should make it optional? */ + cl_direct_log(priority, buf, TRUE, NULL, cl_process_pid, NULLTIME); + } + + cl_log_depth--; + return; +} + +/* + * Log a message only if there were not too many messages of this + * kind recently. This is too prevent log spamming in case a + * condition persists over a long period of time. The maximum + * number of messages for the timeframe and other details are + * provided in struct logspam (see cl_log.h). + * + * Implementation details: + * - max number of time_t slots is allocated; slots keep time + * stamps of previous max number of messages + * - we check if the difference between now (i.e. new message just + * arrived) and the oldest message is _less_ than the window + * timeframe + * - it's up to the user to do cl_limit_log_new and afterwards + * cl_limit_log_destroy, though the latter is usually not + * necessary; the memory allocated with cl_limit_log_new stays + * constant during the lifetime of the process + * + * NB on Thu Aug 4 15:26:49 CEST 2011: + * This interface is very new, use with caution and report bugs. + */ + +struct msg_ctrl * +cl_limit_log_new(struct logspam *lspam) +{ + struct msg_ctrl *ml; + + ml = (struct msg_ctrl *)malloc(sizeof(struct msg_ctrl)); + if (!ml) { + cl_log(LOG_ERR, "%s:%d: out of memory" + , __FUNCTION__, __LINE__); + return NULL; + } + ml->msg_slots = (time_t *)calloc(lspam->max, sizeof(time_t)); + if (!ml->msg_slots) { + cl_log(LOG_ERR, "%s:%d: out of memory" + , __FUNCTION__, __LINE__); + return NULL; + } + ml->lspam = lspam; + cl_limit_log_reset(ml); + return ml; /* to be passed later to cl_limit_log() */ +} + +void +cl_limit_log_destroy(struct msg_ctrl *ml) +{ + if (!ml) + return; + g_free(ml->msg_slots); + g_free(ml); +} + +void +cl_limit_log_reset(struct msg_ctrl *ml) +{ + ml->last = -1; + ml->cnt = 0; + ml->suppress_t = (time_t)0; + memset(ml->msg_slots, 0, ml->lspam->max * sizeof(time_t)); +} + +static void +cl_limit_log_update(struct msg_ctrl *ml, time_t ts) +{ + ml->last = (ml->last + 1) % ml->lspam->max; + *(ml->msg_slots + ml->last) = ts; + if (ml->cnt < ml->lspam->max) + ml->cnt++; +} + +void +cl_limit_log(struct msg_ctrl *ml, int priority, const char * fmt, ...) +{ + va_list ap; + char buf[MAXLINE]; + time_t last_ts, now = time(NULL); + + if (!ml) + goto log_msg; + if (ml->suppress_t) { + if ((now - ml->suppress_t) < ml->lspam->reset_time) + return; + /* message blocking expired */ + cl_limit_log_reset(ml); + } + last_ts = ml->last != -1 ? *(ml->msg_slots + ml->last) : (time_t)0; + if ( + ml->cnt < ml->lspam->max || /* not so many messages logged */ + (now - last_ts) > ml->lspam->window /* messages far apart */ + ) { + cl_limit_log_update(ml, now); + goto log_msg; + } else { + cl_log(LOG_INFO + , "'%s' messages logged too often, " + "suppressing messages of this kind for %ld seconds" + , ml->lspam->id, ml->lspam->reset_time); + cl_log(priority, "%s", ml->lspam->advice); + ml->suppress_t = now; + return; + } + +log_msg: + va_start(ap, fmt); + vsnprintf(buf, MAXLINE, fmt, ap); + va_end(ap); + cl_log(priority, "%s", buf); +} + +void +cl_perror(const char * fmt, ...) +{ + const char * err; + + va_list ap; + char buf[MAXLINE]; + + err = strerror(errno); + va_start(ap, fmt); + vsnprintf(buf, MAXLINE, fmt, ap); + va_end(ap); + + cl_log(LOG_ERR, "%s: %s", buf, err); + +} +void +cl_glib_msg_handler(const gchar *log_domain, GLogLevelFlags log_level +, const gchar *message, gpointer user_data) +{ + GLogLevelFlags level = (log_level & G_LOG_LEVEL_MASK); + int ha_level; + + switch(level) { + case G_LOG_LEVEL_ERROR: ha_level = LOG_ERR; break; + case G_LOG_LEVEL_CRITICAL: ha_level = LOG_ERR; break; + case G_LOG_LEVEL_WARNING: ha_level = LOG_WARNING; break; + case G_LOG_LEVEL_MESSAGE: ha_level = LOG_NOTICE; break; + case G_LOG_LEVEL_INFO: ha_level = LOG_INFO; break; + case G_LOG_LEVEL_DEBUG: ha_level = LOG_DEBUG; break; + + default: ha_level = LOG_WARNING; break; + } + + + cl_log(ha_level, "glib: %s", message); +} +static char * +syslog_timestamp(TIME_T t) +{ + static char ts[64]; + struct tm* ttm; + TIME_T now; + time_t nowtt; + static const char* monthstr [12] = { + "Jan", "Feb", "Mar", + "Apr", "May", "Jun", + "Jul", "Aug", "Sep", + "Oct", "Nov", "Dec" + }; + + /* Work around various weridnesses in different OSes and time_t definitions */ + if (t == 0){ + now = time(NULL); + }else{ + now = t; + } + + nowtt = (time_t)now; + ttm = localtime(&nowtt); + + snprintf(ts, sizeof(ts), "%3s %02d %02d:%02d:%02d" + , monthstr[ttm->tm_mon], ttm->tm_mday + , ttm->tm_hour, ttm->tm_min, ttm->tm_sec); + return(ts); +} + + + +char * +ha_timestamp(TIME_T t) +{ + static char ts[64]; + struct tm* ttm; + TIME_T now; + time_t nowtt; + + /* Work around various weridnesses in different OSes and time_t definitions */ + if (t == 0){ + now = time(NULL); + }else{ + now = t; + } + + nowtt = (time_t)now; + ttm = localtime(&nowtt); + + snprintf(ts, sizeof(ts), "%04d/%02d/%02d_%02d:%02d:%02d" + , ttm->tm_year+1900, ttm->tm_mon+1, ttm->tm_mday + , ttm->tm_hour, ttm->tm_min, ttm->tm_sec); + return(ts); +} + + +static int +cl_set_logging_wqueue_maxlen(int qlen) +{ + int sendrc; + IPC_Channel* chan = logging_daemon_chan; + + if (chan == NULL){ + chan = logging_daemon_chan = create_logging_channel(); + } + + if (chan == NULL){ + return HA_FAIL; + } + + if (chan->ch_status != IPC_CONNECT){ + cl_log(LOG_ERR, "cl_set_logging_wqueue_maxle:" + "channel is not connected"); + if (!logging_chan_in_main_loop){ + chan->ops->destroy(chan); + } + logging_daemon_chan = NULL; + return HA_FAIL; + } + + sendrc = chan->ops->set_send_qlen(logging_daemon_chan, qlen); + + if (sendrc == IPC_OK) { + return HA_OK; + }else { + return HA_FAIL; + } +} + + + +int +LogToDaemon(int priority, const char * buf, + int bufstrlen, gboolean use_pri_str) +{ + int rc; + + cl_log_depth++; + + rc= LogToLoggingDaemon(priority, buf, bufstrlen, use_pri_str); + + cl_log_depth--; + + return rc; +} + +static int drop_msg_num = 0; + +void +cl_flush_logs(void) +{ + if(logging_daemon_chan == NULL) { + return; + } + logging_daemon_chan->ops->waitout(logging_daemon_chan); +} + +static int +LogToLoggingDaemon(int priority, const char * buf, + int bufstrlen, gboolean use_pri_str) +{ + IPC_Channel* chan = logging_daemon_chan; + static longclock_t nexttime = 0; + IPC_Message* msg; + int sendrc = IPC_FAIL; + int intval = conn_logd_time; + + /* make sure we don't hold file descriptors open + * we don't intend to use again */ + cl_log_close_log_files(); + + if (chan == NULL) { + longclock_t lnow = time_longclock(); + + if (cmp_longclock(lnow, nexttime) >= 0){ + nexttime = add_longclock( + lnow, msto_longclock(intval)); + + logging_daemon_chan = chan = create_logging_channel(); + } + } + + if (chan == NULL){ + cl_direct_log( + priority, buf, TRUE, NULL, cl_process_pid, NULLTIME); + return HA_FAIL; + } + + msg = ChildLogIPCMessage(priority, buf, bufstrlen, use_pri_str, chan); + if (msg == NULL) { + drop_msg_num++; + return HA_FAIL; + } + + if (chan->ch_status == IPC_CONNECT){ + + if (chan->ops->is_sending_blocked(chan)) { + chan->ops->resume_io(chan); + } + /* Make sure there is room for the drop message _and_ the + * one we wish to log. Otherwise there is no point. + * + * Try to avoid bouncing on the limit by additionally + * waiting until there is room for QUEUE_SATURATION_FUZZ + * messages. + */ + if (drop_msg_num > 0 + && chan->send_queue->current_qlen + < (chan->send_queue->max_qlen -1 -QUEUE_SATURATION_FUZZ)) + { + /* have to send it this way so the order is correct */ + send_dropped_message(use_pri_str, chan); + } + + /* Don't log a debug message if we're + * approaching the queue limit and already + * dropped a message + */ + if (drop_msg_num == 0 + || chan->send_queue->current_qlen < + (chan->send_queue->max_qlen -1 -QUEUE_SATURATION_FUZZ) + || priority != LOG_DEBUG ) + { + sendrc = chan->ops->send(chan, msg); + } + } + + if (sendrc == IPC_OK) { + return HA_OK; + + } else { + + if (chan->ops->get_chan_status(chan) != IPC_CONNECT) { + if (!logging_chan_in_main_loop){ + chan->ops->destroy(chan); + } + logging_daemon_chan = NULL; + cl_direct_log(priority, buf, TRUE, NULL, cl_process_pid, NULLTIME); + + if (drop_msg_num > 0){ + /* Direct logging here is ok since we're + * switching to that for everything + * "for a while" + */ + cl_log(LOG_ERR, + "cl_log: %d messages were dropped" + " : channel destroyed", drop_msg_num); + } + + drop_msg_num=0; + FreeChildLogIPCMessage(msg); + return HA_FAIL; + } + + drop_msg_num++; + + } + + FreeChildLogIPCMessage(msg); + return HA_FAIL; +} + + +static gboolean +send_dropped_message(gboolean use_pri_str, IPC_Channel *chan) +{ + int sendrc; + char buf[64]; + int buf_len = 0; + IPC_Message *drop_msg = NULL; + + memset(buf, 0, 64); + snprintf(buf, 64, "cl_log: %d messages were dropped", drop_msg_num); + buf_len = strlen(buf)+1; + drop_msg = ChildLogIPCMessage(LOG_ERR, buf, buf_len, use_pri_str, chan); + + if(drop_msg == NULL || drop_msg->msg_len == 0) { + return FALSE; + } + + sendrc = chan->ops->send(chan, drop_msg); + + if(sendrc == IPC_OK) { + drop_msg_num = 0; + }else{ + FreeChildLogIPCMessage(drop_msg); + } + return sendrc == IPC_OK; +} + + +static IPC_Message* +ChildLogIPCMessage(int priority, const char *buf, int bufstrlen, + gboolean use_prio_str, IPC_Channel* ch) +{ + IPC_Message* ret; + LogDaemonMsgHdr logbuf; + int msglen; + char* bodybuf; + + + if (ch->msgpad > MAX_MSGPAD){ + cl_log(LOG_ERR, "ChildLogIPCMessage: invalid msgpad(%d)", + ch->msgpad); + return NULL; + } + + + ret = (IPC_Message*)malloc(sizeof(IPC_Message)); + + if (ret == NULL) { + return ret; + } + + memset(ret, 0, sizeof(IPC_Message)); + + /* Compute msg len: including room for the EOS byte */ + msglen = sizeof(LogDaemonMsgHdr)+bufstrlen + 1; + bodybuf = malloc(msglen + ch->msgpad); + if (bodybuf == NULL) { + free(ret); + return NULL; + } + + memset(bodybuf, 0, msglen + ch->msgpad); + memset(&logbuf, 0, sizeof(logbuf)); + logbuf.msgtype = LD_LOGIT; + logbuf.facility = cl_log_facility; + logbuf.priority = priority; + logbuf.use_pri_str = use_prio_str; + logbuf.entity_pid = getpid(); + logbuf.timestamp = time(NULL); + if (*cl_log_entity){ + strncpy(logbuf.entity,cl_log_entity,MAXENTITY); + }else { + strncpy(logbuf.entity,DFLT_ENTITY,MAXENTITY); + } + + logbuf.msglen = bufstrlen + 1; + memcpy(bodybuf + ch->msgpad, &logbuf, sizeof(logbuf)); + memcpy(bodybuf + ch->msgpad + sizeof(logbuf), + buf, + bufstrlen); + + ret->msg_len = msglen; + ret->msg_buf = bodybuf; + ret->msg_body = bodybuf + ch->msgpad; + ret->msg_done = FreeChildLogIPCMessage; + ret->msg_ch = ch; + + return ret; +} + + +static void +FreeChildLogIPCMessage(IPC_Message* msg) +{ + if (msg == NULL) { + return; + } + memset(msg->msg_body, 0, msg->msg_len); + free(msg->msg_buf); + + memset(msg, 0, sizeof (*msg)); + free(msg); + + return; + +} + + + +static void +cl_opensyslog(void) +{ + if (*cl_log_entity == '\0' || cl_log_facility < 0) { + return; + } + syslog_enabled = 1; + strncpy(common_log_entity, cl_log_entity, MAXENTITY); + openlog(common_log_entity, LOG_CONS, cl_log_facility); +} + + +void +cl_log_args(int argc, char **argv) +{ + int lpc = 0; + int len = 0; + int existing_len = 0; + char *arg_string = NULL; + + if(argc == 0 || argv == NULL) { + return; + } + + for(;lpc < argc; lpc++) { + if(argv[lpc] == NULL) { + break; + } + + len = 2 + strlen(argv[lpc]); /* +1 space, +1 EOS */ + if(arg_string) { + existing_len = strlen(arg_string); + } + + arg_string = realloc(arg_string, len + existing_len); + sprintf(arg_string + existing_len, "%s ", argv[lpc]); + } + cl_log(LOG_INFO, "Invoked: %s", arg_string); + free(arg_string); +} diff --git a/lib/clplumbing/cl_malloc.c b/lib/clplumbing/cl_malloc.c new file mode 100644 index 0000000..ca6dc0b --- /dev/null +++ b/lib/clplumbing/cl_malloc.c @@ -0,0 +1,1044 @@ +/* + * Copyright (C) 2000 Alan Robertson <alanr@unix.sh> + * + * This software licensed under the GNU LGPL. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define HA_MALLOC_ORIGINAL +#include <lha_internal.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_STDINT_H +#include <stdint.h> +#endif /* HAVE_STDINT_H */ +#include <string.h> +#include <errno.h> +#ifndef BSD +#ifdef HAVE_MALLOC_H +# include <malloc.h> +#endif +#endif +#include <clplumbing/cl_malloc.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/longclock.h> + +#include <ltdl.h> + +#ifndef _CLPLUMBING_CLMALLOC_NATIVE_H +static cl_mem_stats_t default_memstats; +static volatile cl_mem_stats_t * memstats = &default_memstats; + +/* + * Compile time malloc debugging switches: + * + * MARK_PRISTINE - puts known byte pattern in freed memory + * Good at finding "use after free" cases + * Cheap in memory, but expensive in CPU + * + * MAKE_GUARD - puts a known pattern *after* allocated memory + * Good at finding overrun problems after the fact + * Cheap in CPU, adds a few bytes to each malloc item + * + */ + +#define MARK_PRISTINE 1 /* Expensive in CPU time */ +#undef MARK_PRISTINE +#define MAKE_GUARD 1 /* Adds 'n' bytes memory - cheap in CPU*/ +#define USE_ASSERTS 1 +#define DUMPONERR 1 +#define RETURN_TO_MALLOC 1 +#undef RETURN_TO_MALLOC + +#ifndef DUMPONERR +# define DUMPIFASKED() /* nothing */ +#else +# define DUMPIFASKED() {abort();} +#endif + + +/* + * + * Malloc wrapper functions + * + * I wrote these so we can better track memory leaks, etc. and verify + * that the system is stable in terms of memory usage. + * + * For our purposes, these functions are a somewhat faster than using + * malloc directly (although they use a bit more memory) + * + * The general strategy is loosely related to the buddy system, + * except very simple, well-suited to our continuous running + * nature, and the constancy of the requests and messages. + * + * We keep an array of linked lists, each for a different size + * buffer. If we need a buffer larger than the largest one provided + * by the list, we go directly to malloc. + * + * Otherwise, we keep return them to the appropriate linked list + * when we're done with them, and reuse them from the list. + * + * We never coalesce buffers on our lists, and we never free them. + * + * It's very simple. We get usage stats. It makes me happy. + */ + +#define HA_MALLOC_MAGIC 0xFEEDBEEFUL +#define HA_FREE_MAGIC 0xDEADBEEFUL + + +/* + * We put a struct cl_mhdr in front of every malloc item. + * This means each malloc item is at least 12 bytes bigger than it theoretically + * needs to be. But, it allows this code to be fast and recognize + * multiple free attempts, and memory corruption *before* the object + * + * It's probably possible to combine these fields a bit, + * since bucket and reqsize are only needed for allocated items, + * both are bounded in value, and fairly strong integrity checks apply + * to them. But then we wouldn't be able to tell *quite* as reliably + * if someone gave us an item to free that we didn't allocate... + * + * Could even make the bucket and reqsize objects into 16-bit ints... + * + * The idea of getting it all down into 32-bits of overhead is + * an interesting thought... + * + * But some architectures have alignment constraints. For instance, sparc + * requires that double-word accesses be aligned on double-word boundaries. + * Thus if the requested space is bigger than a double-word, then cl_mhdr + * should, for safety, be a double-word multiple (minimum 8bytes, 64bits). + +*/ + +#ifdef HA_MALLOC_TRACK +# define HA_MALLOC_OWNER 64 +struct cl_bucket; +#endif + +struct cl_mhdr { +# ifdef HA_MALLOC_MAGIC + unsigned long magic; /* Must match HA_*_MAGIC */ +#endif +# ifdef HA_MALLOC_TRACK + char owner[HA_MALLOC_OWNER]; + struct cl_bucket * left; + struct cl_bucket * right; + int dumped; + longclock_t mtime; +#endif + size_t reqsize; + int bucket; +}; + +struct cl_bucket { + struct cl_mhdr hdr; + struct cl_bucket * next; +}; + +#define NUMBUCKS 12 +#define NOBUCKET (NUMBUCKS) + +static struct cl_bucket* cl_malloc_buckets[NUMBUCKS]; +static size_t cl_bucket_sizes[NUMBUCKS]; +static size_t buckminpow2 = 0L; + +static int cl_malloc_inityet = 0; +static size_t cl_malloc_hdr_offset = sizeof(struct cl_mhdr); + +static void* cl_new_mem(size_t size, int numbuck); +static void cl_malloc_init(void); +static void cl_dump_item(const struct cl_bucket*b); + +#ifdef MARK_PRISTINE +# define PRISTVALUE 0xff + static int cl_check_is_pristine(const void* v, unsigned size); + static void cl_mark_pristine(void* v, unsigned size); + static int pristoff; +#endif + +#define BHDR(p) ((struct cl_bucket*)(void*)(((char*)p)-cl_malloc_hdr_offset)) +#define CBHDR(p) ((const struct cl_bucket*)(const void*)(((const char*)p)-cl_malloc_hdr_offset)) +#define MEMORYSIZE(p)(CBHDR(p)->hdr.reqsize) + +#define MALLOCSIZE(allocsize) ((allocsize) + cl_malloc_hdr_offset + GUARDSIZE) +#define MAXMALLOC (SIZE_MAX-(MALLOCSIZE(0)+1)) + +#ifdef MAKE_GUARD +# define GUARDLEN 4 + static const unsigned char cl_malloc_guard[] = +#if GUARDLEN == 1 + {0xA5}; +#endif +#if GUARDLEN == 2 + {0x5A, 0xA5}; +#endif +#if GUARDLEN == 4 + {0x5A, 0xA5, 0x5A, 0xA5}; +#endif +# define GUARDSIZE sizeof(cl_malloc_guard) +# define ADD_GUARD(cp) (memcpy((((char*)cp)+MEMORYSIZE(cp)), cl_malloc_guard, sizeof(cl_malloc_guard))) +# define GUARD_IS_OK(cp) (memcmp((((const char*)cp)+MEMORYSIZE(cp)), \ + cl_malloc_guard, sizeof(cl_malloc_guard)) == 0) +# define CHECK_GUARD_BYTES(cp, msg) { \ + if (!GUARD_IS_OK(cp)) { \ + cl_log(LOG_ERR, "%s: guard corrupted at 0x%lx", msg \ + , (unsigned long)cp); \ + cl_dump_item(CBHDR(cp)); \ + DUMPIFASKED(); \ + } \ + } +#else +# define GUARDSIZE 0 +# define ADD_GUARD(cp) /* */ +# define GUARD_IS_OK(cp) (1) +# define CHECK_GUARD_BYTES(cp, msg) /* */ +#endif + +#define MALLOCROUND 4096 /* Round big mallocs up to a multiple of this size */ + + +#ifdef HA_MALLOC_TRACK + +static struct cl_bucket * cl_malloc_track_root = NULL; + +static void +cl_ptr_tag(void *ptr, const char *file, const char *function, const int line) +{ + struct cl_bucket* bhdr = BHDR(ptr); + snprintf(bhdr->hdr.owner, HA_MALLOC_OWNER, "%s:%s:%d", + file, function, line); +} + +static void +cl_ptr_track(void *ptr) +{ + struct cl_bucket* bhdr = BHDR(ptr); + +#if defined(USE_ASSERTS) + g_assert(bhdr->hdr.left == NULL); + g_assert(bhdr->hdr.right == NULL); + g_assert((cl_malloc_track_root == NULL) || (cl_malloc_track_root->hdr.left == NULL)); +#endif + + bhdr->hdr.dumped = 0; + bhdr->hdr.mtime = time_longclock(); + + if (cl_malloc_track_root == NULL) { + cl_malloc_track_root = bhdr; + } else { + bhdr->hdr.right = cl_malloc_track_root; + cl_malloc_track_root->hdr.left = bhdr; + cl_malloc_track_root = bhdr; + } +} + +static void +cl_ptr_release(void *ptr) +{ + struct cl_bucket* bhdr = BHDR(ptr); + +/* cl_log(LOG_DEBUG, "cl_free: Freeing memory belonging to %s" + , bhdr->hdr.owner); */ + +#if defined(USE_ASSERTS) + g_assert(cl_malloc_track_root != NULL); + g_assert(cl_malloc_track_root->hdr.left == NULL); +#endif + + if (bhdr->hdr.left != NULL) { + bhdr->hdr.left->hdr.right=bhdr->hdr.right; + } + + if (bhdr->hdr.right != NULL) { + bhdr->hdr.right->hdr.left=bhdr->hdr.left; + } + + if (cl_malloc_track_root == bhdr) { + cl_malloc_track_root=bhdr->hdr.right; + } + + bhdr->hdr.left = NULL; + bhdr->hdr.right = NULL; +} + +static void +cl_ptr_init(void) +{ + cl_malloc_track_root = NULL; +} + +int +cl_malloc_dump_allocated(int log_level, gboolean filter) +{ + int lpc = 0; + struct cl_bucket* cursor = cl_malloc_track_root; + longclock_t time_diff; + + cl_log(LOG_INFO, "Dumping allocated memory buffers:"); + + while (cursor != NULL) { + if(filter && cursor->hdr.dumped) { + + } else if(log_level > LOG_DEBUG) { + } else if(filter) { + lpc++; + cl_log(log_level, "cl_malloc_dump: %p owner %s, size %d" + , cursor+cl_malloc_hdr_offset + , cursor->hdr.owner + , (int)cursor->hdr.reqsize); + } else { + lpc++; + time_diff = sub_longclock(time_longclock(), cursor->hdr.mtime); + cl_log(log_level, "cl_malloc_dump: %p owner %s, size %d, dumped %d, age %lu ms" + , cursor+cl_malloc_hdr_offset + , cursor->hdr.owner + , (int)cursor->hdr.reqsize + , cursor->hdr.dumped + , longclockto_long(time_diff)); + } + cursor->hdr.dumped = 1; + cursor = cursor->hdr.right; + } + + cl_log(LOG_INFO, "End dump."); + return lpc; +} +#endif +static const int LogTable256[] = +{ + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 +}; +#define POW2BYTE(b) (LogTable256[b]) +#define BYTE3(i) (((i)&0xFF000000)>>24) +#define BYTE2(i) (((i)&0x00FF0000)>>16) +#define BYTE1(i) (((i)&0x0000FF00)>>8) +#define BYTE0(i) ((i)&0x000000FF) + +/* Works for malloc bucket sizes up to 2^8 */ +#define POW21BYTE(i) (POW2BYTE(BYTE0(i))) + +/* Works for malloc bucket sizes up to 2^16 */ +#define POW22BYTE(i) ((BYTE1(i) != 0x00)? (POW2BYTE(BYTE1(i))+8) \ + : (POW21BYTE(i))) + +/* Works for malloc bucket sizes up to 2^24 */ +#define POW23BYTE(i) ((BYTE2(i) != 0x00)? (POW2BYTE(BYTE2(i))+16) \ + : POW22BYTE(i)) + +/* Works for malloc bucket sizes up to 2^32 */ +#define POW24BYTE(i) ((BYTE3(i) != 0x00)? (POW2BYTE(BYTE3(i))+24) \ + : POW23BYTE(i)) + +/* #define INT2POW2(i) POW24BYTE(i) / * This would allow 2G in our largest malloc chain */ + /* which I don't think we need */ +#define INT2POW2(i) POW23BYTE(i) /* This allows up to about 16 Mbytes in our largest malloc chain */ + /* and it's a little faster than the one above */ + + +/* + * cl_malloc: malloc clone + */ + +void * +cl_malloc(size_t size) +{ +#if 0 + int j; +#endif + int numbuck = NOBUCKET; + struct cl_bucket* buckptr = NULL; + void* ret; + + if(!size) { + cl_log(LOG_ERR + , "%s: refusing to allocate zero sized block" + , __FUNCTION__ + ); + return NULL; + } + if (size > MAXMALLOC) { + return NULL; + } + if (!cl_malloc_inityet) { + cl_malloc_init(); + } + +#if 1 + /* + * NOTE: This restricts bucket sizes to be powers of two + * - which is OK with me - and how the code has always worked :-D + */ + numbuck = INT2POW2(size-1)-buckminpow2; + numbuck = MAX(0, numbuck); + if (numbuck < NUMBUCKS) { + if (size <= cl_bucket_sizes[numbuck] + || (numbuck > 0 && size <= (cl_bucket_sizes[numbuck]/2))) { + buckptr = cl_malloc_buckets[numbuck]; + }else{ + cl_log(LOG_ERR + , "%s: bucket size bug: %lu bytes in %lu byte bucket #%d" + , __FUNCTION__ + , (unsigned long)size + , (unsigned long)cl_bucket_sizes[numbuck] + , numbuck); + + } + } +#else + /* + * Find which bucket would have buffers of the requested size + */ + for (j=0; j < NUMBUCKS; ++j) { + if (size <= cl_bucket_sizes[j]) { + numbuck = j; + buckptr = cl_malloc_buckets[numbuck]; + break; + } + } +#endif + + /* + * Pull it out of the linked list of free buffers if we can... + */ + + if (buckptr == NULL) { + ret = cl_new_mem(size, numbuck); + }else{ + cl_malloc_buckets[numbuck] = buckptr->next; + buckptr->hdr.reqsize = size; + ret = (((char*)buckptr)+cl_malloc_hdr_offset); + +#ifdef MARK_PRISTINE + { + int bucksize = cl_bucket_sizes[numbuck]; + if (!cl_check_is_pristine(ret, bucksize)) { + cl_log(LOG_ERR + , "attempt to allocate memory" + " which is not pristine."); + cl_dump_item(buckptr); + DUMPIFASKED(); + } + } +#endif + +#ifdef HA_MALLOC_MAGIC + switch (buckptr->hdr.magic) { + + case HA_FREE_MAGIC: + break; + + case HA_MALLOC_MAGIC: + cl_log(LOG_ERR + , "attempt to allocate memory" + " already allocated at 0x%lx" + , (unsigned long)ret); + cl_dump_item(buckptr); + DUMPIFASKED(); + ret=NULL; + break; + + default: + cl_log(LOG_ERR + , "corrupt malloc buffer at 0x%lx" + , (unsigned long)ret); + cl_dump_item(buckptr); + DUMPIFASKED(); + ret=NULL; + break; + } + buckptr->hdr.magic = HA_MALLOC_MAGIC; +#endif /* HA_MALLOC_MAGIC */ + if (memstats) { + memstats->nbytes_req += size; + memstats->nbytes_alloc + += MALLOCSIZE(cl_bucket_sizes[numbuck]); + } + + } + + if (ret && memstats) { +#if 0 && defined(HAVE_MALLINFO) + /* mallinfo is too expensive to use :-( */ + struct mallinfo i = mallinfo(); + memstats->arena = i.arena; +#endif + memstats->numalloc++; + } + if (ret) { +#ifdef HA_MALLOC_TRACK + /* If we were _always_ called via the wrapper functions, + * this wouldn't be necessary, but we aren't, some use + * function pointers directly to cl_malloc() */ + cl_ptr_track(ret); + cl_ptr_tag(ret, "cl_malloc.c", "cl_malloc", 0); +#endif + ADD_GUARD(ret); + } + return(ret); +} + +int +cl_is_allocated(const void *ptr) +{ +#ifdef HA_MALLOC_MAGIC + if (NULL == ptr || CBHDR(ptr)->hdr.magic != HA_MALLOC_MAGIC) { + return FALSE; + }else if (GUARD_IS_OK(ptr)) { + return TRUE; + } + cl_log(LOG_ERR + , "cl_is_allocated: supplied storage is guard-corrupted at 0x%lx" + , (unsigned long)ptr); + cl_dump_item(CBHDR(ptr)); + DUMPIFASKED(); + return FALSE; +#else + return (ptr != NULL); +#endif +} + +/* + * cl_free: "free" clone + */ + +void +cl_free(void *ptr) +{ + int bucket; + struct cl_bucket* bhdr; + + if (!cl_malloc_inityet) { + cl_malloc_init(); + } + + if (ptr == NULL) { + cl_log(LOG_ERR, "attempt to free NULL pointer in cl_free()"); + DUMPIFASKED(); + return; + } + + /* Find the beginning of our "hidden" structure */ + + bhdr = BHDR(ptr); + +#ifdef HA_MALLOC_MAGIC + switch (bhdr->hdr.magic) { + case HA_MALLOC_MAGIC: + break; + + case HA_FREE_MAGIC: + cl_log(LOG_ERR + , "cl_free: attempt to free already-freed" + " object at 0x%lx" + , (unsigned long)ptr); + cl_dump_item(bhdr); + DUMPIFASKED(); + return; + break; + default: + cl_log(LOG_ERR, "cl_free: Bad magic number" + " in object at 0x%lx" + , (unsigned long)ptr); + cl_dump_item(bhdr); + DUMPIFASKED(); + return; + break; + } +#endif + if (!GUARD_IS_OK(ptr)) { + cl_log(LOG_ERR + , "cl_free: attempt to free guard-corrupted" + " object at 0x%lx", (unsigned long)ptr); + cl_dump_item(bhdr); + DUMPIFASKED(); + return; + } +#ifdef HA_MALLOC_TRACK + cl_ptr_release(ptr); +#endif + bucket = bhdr->hdr.bucket; +#ifdef HA_MALLOC_MAGIC + bhdr->hdr.magic = HA_FREE_MAGIC; +#endif + + /* + * Return it to the appropriate bucket (linked list), or just free + * it if it didn't come from one of our lists... + */ + +#ifndef RETURN_TO_MALLOC + if (bucket >= NUMBUCKS) { +#endif +#ifdef MARK_PRISTINE + /* Is this size right? */ + cl_mark_pristine(ptr, bhdr->hdr.reqsize); +#endif + if (memstats) { + memstats->nbytes_req -= bhdr->hdr.reqsize; + memstats->nbytes_alloc -= MALLOCSIZE(bhdr->hdr.reqsize); + memstats->mallocbytes -= MALLOCSIZE(bhdr->hdr.reqsize); + } + free(bhdr); +#ifndef RETURN_TO_MALLOC + }else{ + int bucksize = cl_bucket_sizes[bucket]; +#if defined(USE_ASSERTS) + g_assert(bhdr->hdr.reqsize <= cl_bucket_sizes[bucket]); +# endif + if (memstats) { + memstats->nbytes_req -= bhdr->hdr.reqsize; + memstats->nbytes_alloc -= MALLOCSIZE(bucksize); + } + bhdr->next = cl_malloc_buckets[bucket]; + cl_malloc_buckets[bucket] = bhdr; +#ifdef MARK_PRISTINE + cl_mark_pristine(ptr, bucksize); +# endif + } +#endif /* RETURN_TO_MALLOC */ + if (memstats) { + memstats->numfree++; + } +} + +void* +cl_realloc(void *ptr, size_t newsize) +{ + struct cl_bucket* bhdr; + int bucket; + size_t bucksize; + + if (!cl_malloc_inityet) { + cl_malloc_init(); + } + + if (memstats) { + memstats->numrealloc++; + } + if (ptr == NULL) { + /* NULL is a legal 'ptr' value for realloc... */ + return cl_malloc(newsize); + } + if (newsize == 0) { + /* realloc() is the most redundant interface ever */ + cl_free(ptr); + return NULL; + } + + /* Find the beginning of our "hidden" structure */ + + bhdr = BHDR(ptr); + +#ifdef HA_MALLOC_MAGIC + switch (bhdr->hdr.magic) { + case HA_MALLOC_MAGIC: + break; + + case HA_FREE_MAGIC: + cl_log(LOG_ERR + , "cl_realloc: attempt to realloc already-freed" + " object at 0x%lx" + , (unsigned long)ptr); + cl_dump_item(bhdr); + DUMPIFASKED(); + return NULL; + break; + default: + cl_log(LOG_ERR, "cl_realloc: Bad magic number" + " in object at 0x%lx" + , (unsigned long)ptr); + cl_dump_item(bhdr); + DUMPIFASKED(); + return NULL; + break; + } +#endif + CHECK_GUARD_BYTES(ptr, "cl_realloc"); + + bucket = bhdr->hdr.bucket; + + /* + * Figure out which bucket it came from... If any... + */ + + if (bucket >= NUMBUCKS) { + /* Not from our bucket-area... Call realloc... */ + if (memstats) { + memstats->nbytes_req -= bhdr->hdr.reqsize; + memstats->nbytes_alloc -= MALLOCSIZE(bhdr->hdr.reqsize); + memstats->mallocbytes -= MALLOCSIZE(bhdr->hdr.reqsize); + memstats->nbytes_req += newsize; + memstats->nbytes_alloc += MALLOCSIZE(newsize); + memstats->mallocbytes += MALLOCSIZE(newsize); + } +#ifdef HA_MALLOC_TRACK + cl_ptr_release(ptr); +#endif + bhdr = realloc(bhdr, newsize + cl_malloc_hdr_offset + GUARDSIZE); + if (!bhdr) { + return NULL; + } +#ifdef HA_MALLOC_TRACK + cl_ptr_track(ptr); + cl_ptr_tag(ptr, "cl_malloc.c", "realloc", 0); +#endif + bhdr->hdr.reqsize = newsize; + ptr = (((char*)bhdr)+cl_malloc_hdr_offset); + ADD_GUARD(ptr); + CHECK_GUARD_BYTES(ptr, "cl_realloc - real realloc return value"); + /* Not really a memory leak... BEAM thinks so though... */ + return ptr; /*memory leak*/ + } + bucksize = cl_bucket_sizes[bucket]; +#if defined(USE_ASSERTS) + g_assert(bhdr->hdr.reqsize <= bucksize); +#endif + if (newsize > bucksize) { + /* Need to allocate new space for it */ + void* newret = cl_malloc(newsize); + if (newret != NULL) { + memcpy(newret, ptr, bhdr->hdr.reqsize); + CHECK_GUARD_BYTES(newret, "cl_realloc - cl_malloc case"); + } + cl_free(ptr); + return newret; + } + + /* Amazing! It fits into the space previously allocated for it! */ + bhdr->hdr.reqsize = newsize; + if (memstats) { + memstats->nbytes_req -= bhdr->hdr.reqsize; + memstats->nbytes_req += newsize; + } + ADD_GUARD(ptr); + CHECK_GUARD_BYTES(ptr, "cl_realloc - fits in existing space"); + return ptr; +} + +/* + * cl_new_mem: use the real malloc to allocate some new memory + */ + +static void* +cl_new_mem(size_t size, int numbuck) +{ + struct cl_bucket* hdrret; + size_t allocsize; + size_t mallocsize; + + if (numbuck < NUMBUCKS) { + allocsize = cl_bucket_sizes[numbuck]; + }else{ + allocsize = size; + } + + mallocsize = MALLOCSIZE(allocsize); + if (numbuck == NOBUCKET) { + mallocsize = (((mallocsize + (MALLOCROUND-1))/MALLOCROUND)*MALLOCROUND); + } + + if ((hdrret = malloc(mallocsize)) == NULL) { + return NULL; + } + + hdrret->hdr.reqsize = size; + hdrret->hdr.bucket = numbuck; +#ifdef HA_MALLOC_MAGIC + hdrret->hdr.magic = HA_MALLOC_MAGIC; +#endif +#ifdef HA_MALLOC_TRACK + hdrret->hdr.left = NULL; + hdrret->hdr.right = NULL; + hdrret->hdr.owner[0] = '\0'; + hdrret->hdr.dumped = 0; +#endif + + if (memstats) { + memstats->nbytes_alloc += mallocsize; + memstats->nbytes_req += size; + memstats->mallocbytes += mallocsize; + } + /* BEAM BUG -- this is NOT a leak */ + return(((char*)hdrret)+cl_malloc_hdr_offset); /*memory leak*/ +} + + +/* + * cl_calloc: calloc clone + */ + +void * +cl_calloc(size_t nmemb, size_t size) +{ + void * ret = cl_malloc(nmemb*size); + + if (ret != NULL) { + memset(ret, 0, nmemb*size); +#ifdef HA_MALLOC_TRACK + cl_ptr_tag(ret, "cl_malloc.c", "cl_calloc", 0); +#endif + } + + return(ret); +} + +#ifdef HA_MALLOC_TRACK +void * +cl_calloc_track(size_t nmemb, size_t size, + const char *file, const char *function, const int line) +{ + void* ret; + + ret = cl_calloc(nmemb, size); + + if (ret) { + cl_ptr_tag(ret, file, function, line); + } + + return ret; +} + +void* +cl_realloc_track(void *ptr, size_t newsize, + const char *file, const char *function, const int line) +{ + void* ret; + + ret = cl_realloc(ptr, newsize); + + if (ret) { + cl_ptr_tag(ret, file, function, line); + } + + return ret; +} + +void * +cl_malloc_track(size_t size, + const char *file, const char *function, const int line) +{ + void* ret; + + ret = cl_malloc(size); + if (ret) { + /* Retag with the proper owner. */ + cl_ptr_tag(ret, file, function, line); + } + + return ret; +} + +#endif + +/* + * cl_strdup: strdup clone + */ + +char * +cl_strdup(const char *s) +{ + void * ret; + + if (!s) { + cl_log(LOG_ERR, "cl_strdup(NULL)"); + return(NULL); + } + ret = cl_malloc((strlen(s) + 1) * sizeof(char)); + + if (ret) { + strcpy(ret, s); + } + + return(ret); +} + + +/* + * cl_malloc_init(): initialize our malloc wrapper things + */ + +static void +cl_malloc_init() +{ + int j; + size_t cursize = 32; + int llcount = 1; + + cl_malloc_inityet = 1; + + /* cl_malloc_hdr_offset should be a double-word multiple */ + while (cl_malloc_hdr_offset > (llcount * sizeof(long long))) { + llcount++; + } + cl_malloc_hdr_offset = llcount * sizeof(long long); + + + for (j=0; j < NUMBUCKS; ++j) { + cl_malloc_buckets[j] = NULL; + + cl_bucket_sizes[j] = cursize; + cursize <<= 1; + } + buckminpow2 = INT2POW2(cl_bucket_sizes[0]-1); +#ifdef MARK_PRISTINE + { + struct cl_bucket b; + pristoff = (unsigned char*)&(b.next)-(unsigned char*)&b; + pristoff += sizeof(b.next); + } +#endif +#ifdef HA_MALLOC_TRACK + cl_ptr_init(); +#endif +} + +void +cl_malloc_setstats(volatile cl_mem_stats_t *stats) +{ + if (memstats && stats) { + *stats = *memstats; + } + memstats = stats; +} + +volatile cl_mem_stats_t * +cl_malloc_getstats(void) +{ + return memstats; +} + +static void +cl_dump_item(const struct cl_bucket*b) +{ + const unsigned char * cbeg; + const unsigned char * cend; + const unsigned char * cp; + cl_log(LOG_INFO, "Dumping cl_malloc item @ 0x%lx, bucket address: 0x%lx" + , ((unsigned long)b)+cl_malloc_hdr_offset, (unsigned long)b); +#ifdef HA_MALLOC_TRACK + cl_log(LOG_INFO, "Owner: %s" + , b->hdr.owner); +#endif +#ifdef HA_MALLOC_MAGIC + cl_log(LOG_INFO, "Magic number: 0x%lx reqsize=%ld" + ", bucket=%d, bucksize=%ld" + , b->hdr.magic + , (long)b->hdr.reqsize, b->hdr.bucket + , (long)(b->hdr.bucket >= NUMBUCKS ? 0 + : cl_bucket_sizes[b->hdr.bucket])); +#else + cl_log(LOG_INFO, "reqsize=%ld" + ", bucket=%d, bucksize=%ld" + , (long)b->hdr.reqsize, b->hdr.bucket + , (long)(b->hdr.bucket >= NUMBUCKS ? 0 + : cl_bucket_sizes[b->hdr.bucket])); +#endif + cbeg = ((const unsigned char *)b)+cl_malloc_hdr_offset; + cend = cbeg+b->hdr.reqsize+GUARDSIZE; + + for (cp=cbeg; cp < cend; cp+= sizeof(unsigned)) { + cl_log(LOG_INFO, "%02x %02x %02x %02x \"%c%c%c%c\"" + , (unsigned)cp[0], (unsigned)cp[1] + , (unsigned)cp[2], (unsigned)cp[3] + , cp[0], cp[1], cp[2], cp[3]); + } +} + +/* The only reason these functions exist is because glib uses non-standard + * types (gsize)in place of size_t. Since size_t is 64-bits on some + * machines where gsize (unsigned int) is 32-bits, this is annoying. + */ + +static gpointer +cl_malloc_glib(gsize n_bytes) +{ + return (gpointer)cl_malloc((size_t)n_bytes); +} + +static void +cl_free_glib(gpointer mem) +{ + cl_free((void*)mem); +} + +static void * +cl_realloc_glib(gpointer mem, gsize n_bytes) +{ + return cl_realloc((void*)mem, (size_t)n_bytes); +} + + +/* Call before using any glib functions(!) */ +/* See also: g_mem_set_vtable() */ +void +cl_malloc_forced_for_glib(void) +{ + static GMemVTable vt = { + cl_malloc_glib, + cl_realloc_glib, + cl_free_glib, + NULL, + NULL, + NULL, + }; + if (!cl_malloc_inityet) { + cl_malloc_init(); + } + g_mem_set_vtable(&vt); +} + +#ifdef MARK_PRISTINE +static int +cl_check_is_pristine(const void* v, unsigned size) +{ + const unsigned char * cp; + const unsigned char * last; + cp = v; + last = cp + size; + cp += pristoff; + + for (;cp < last; ++cp) { + if (*cp != PRISTVALUE) { + return FALSE; + } + } + return TRUE; +} +static void +cl_mark_pristine(void* v, unsigned size) +{ + unsigned char * cp = v; + memset(cp+pristoff, PRISTVALUE, size-pristoff); +} +#endif + +#endif /* _CLPLUMBING_CLMALLOC_NATIVE_H */ diff --git a/lib/clplumbing/cl_misc.c b/lib/clplumbing/cl_misc.c new file mode 100644 index 0000000..be6441d --- /dev/null +++ b/lib/clplumbing/cl_misc.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include <lha_internal.h> + +#include <strings.h> +#include <clplumbing/cl_misc.h> +#include <clplumbing/cl_log.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#ifdef HAVE_TIME_H +#include <time.h> +#endif + +#include <sys/time.h> + +int +cl_str_to_boolean(const char * s, int * ret) +{ + if(s == NULL) { + return HA_FAIL; + } + + if ( strcasecmp(s, "true") == 0 + || strcasecmp(s, "on") == 0 + || strcasecmp(s, "yes") == 0 + || strcasecmp(s, "y") == 0 + || strcasecmp(s, "1") == 0){ + *ret = TRUE; + return HA_OK; + } + if ( strcasecmp(s, "false") == 0 + || strcasecmp(s, "off") == 0 + || strcasecmp(s, "no") == 0 + || strcasecmp(s, "n") == 0 + || strcasecmp(s, "0") == 0){ + *ret = FALSE; + return HA_OK; + } + return HA_FAIL; +} + +int +cl_file_exists(const char* filename) +{ + struct stat st; + + if (filename == NULL){ + cl_log(LOG_ERR, "%s: NULL filename", + __FUNCTION__); + return FALSE; + } + + if (lstat(filename, &st) == 0){ + return S_ISREG(st.st_mode); + } + + return FALSE; +} + +char* +cl_get_env(const char* env_name) +{ + if (env_name == NULL){ + cl_log(LOG_ERR, "%s: null name", + __FUNCTION__); + return NULL; + } + + return getenv(env_name); +} + + +int +cl_binary_to_int(const char* data, int len) +{ + const char *p = data; + const char *pmax = p + len; + guint h = *p; + + if (h){ + for (p += 1; p < pmax; p++){ + h = (h << 5) - h + *p; + } + } + + return h; +} + +/* + * Convert a string into a positive, rounded number of milliseconds. + * + * Returns -1 on error. + * + * Permissible forms: + * [0-9]+ units are seconds + * [0-9]*.[0-9]+ units are seconds + * [0-9]+ *[Mm][Ss] units are milliseconds + * [0-9]*.[0-9]+ *[Mm][Ss] units are milliseconds + * [0-9]+ *[Uu][Ss] units are microseconds + * [0-9]*.[0-9]+ *[Uu][Ss] units are microseconds + * + * Examples: + * + * 1 = 1000 milliseconds + * 1000ms = 1000 milliseconds + * 1000000us = 1000 milliseconds + * 0.1 = 100 milliseconds + * 100ms = 100 milliseconds + * 100000us = 100 milliseconds + * 0.001 = 1 millisecond + * 1ms = 1 millisecond + * 1000us = 1 millisecond + * 499us = 0 milliseconds + * 501us = 1 millisecond + */ + +#define NUMCHARS "0123456789." +#define WHITESPACE " \t\n\r\f" +#define EOS '\0' + +long +cl_get_msec(const char * input) +{ + const char * cp = input; + const char * units; + long multiplier = 1000; + long divisor = 1; + long ret = -1; + double dret; + + cp += strspn(cp, WHITESPACE); + units = cp + strspn(cp, NUMCHARS); + units += strspn(units, WHITESPACE); + + if (strchr(NUMCHARS, *cp) == NULL) { + return ret; + } + + if (strncasecmp(units, "ms", 2) == 0 + || strncasecmp(units, "cl_get_msec", 4) == 0) { + multiplier = 1; + divisor = 1; + }else if (strncasecmp(units, "us", 2) == 0 + || strncasecmp(units, "usec", 4) == 0) { + multiplier = 1; + divisor = 1000; + }else if (*units != EOS && *units != '\n' + && *units != '\r') { + return ret; + } + dret = atof(cp); + dret *= (double)multiplier; + dret /= (double)divisor; + dret += 0.5; + ret = (long)dret; + return(ret); +} diff --git a/lib/clplumbing/cl_msg.c b/lib/clplumbing/cl_msg.c new file mode 100644 index 0000000..22f00e3 --- /dev/null +++ b/lib/clplumbing/cl_msg.c @@ -0,0 +1,2537 @@ +/* + * Heartbeat messaging object. + * + * Copyright (C) 2000 Alan Robertson <alanr@unix.sh> + * + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <sys/utsname.h> +#include <ha_msg.h> +#include <unistd.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/ipc.h> +#include <clplumbing/base64.h> +#include <clplumbing/netstring.h> +#include <glib.h> +#include <clplumbing/cl_uuid.h> +#include <compress.h> +#include <clplumbing/timers.h> +#include <clplumbing/cl_signal.h> + +#define MAXMSGLINE 512 +#define MINFIELDS 30 +#define NEWLINE "\n" + + +#define NEEDAUTH 1 +#define NOAUTH 0 +#define MAX_INT_LEN 64 +#define MAX_NAME_LEN 255 +#define UUID_SLEN 64 +#define MAXCHILDMSGLEN 512 + +static int compression_threshold = (128*1024); + +static enum cl_msgfmt msgfmt = MSGFMT_NVPAIR; +static gboolean use_traditional_compression = FALSE; + +const char* +FT_strings[]={ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" +}; + +#undef DOAUDITS +#define DOAUDITS + +#undef DOPARANOIDAUDITS +/* #define DOPARANOIDAUDITS */ + +#ifdef DOAUDITS +void ha_msg_audit(const struct ha_msg* msg); +# define AUDITMSG(msg) ha_msg_audit(msg) +# ifdef DOPARANOIDAUDITS +# define PARANOIDAUDITMSG(msg) ha_msg_audit(msg) +# else +# define PARANOIDAUDITMSG(msg) /*nothing*/ +# endif +#else +# define AUDITMSG(msg) /*nothing*/ +# define PARANOIDAUDITMSG(msg) /*nothing*/ +#endif + + +static volatile hb_msg_stats_t* msgstats = NULL; + +gboolean cl_msg_quiet_fmterr = FALSE; + +extern int netstring_format; + +static struct ha_msg* wirefmt2msg_ll(const char* s, size_t length, int need_auth); + +struct ha_msg* string2msg_ll(const char * s, size_t length, int need_auth, int depth); + +extern int struct_stringlen(size_t namlen, size_t vallen, const void* value); +extern int struct_netstringlen(size_t namlen, size_t vallen, const void* value); +extern int process_netstring_nvpair(struct ha_msg* m, const char* nvpair, int nvlen); +static char* msg2wirefmt_ll(struct ha_msg*m, size_t* len, gboolean need_compress); +extern GHashTable* CompressFuncs; + + +void +cl_set_traditional_compression(gboolean value) +{ + use_traditional_compression = value; + if (use_traditional_compression && CompressFuncs) { + cl_log(LOG_WARNING + , "Traditional compression selected" + ". Realtime behavior will likely be impacted(!)"); + cl_log(LOG_INFO + , "See %s for more information." + , HAURL("Ha.cf#traditional_compression_-_controls_compression_mode")); + } +} + +void +cl_set_compression_threshold(size_t threadhold) +{ + compression_threshold = threadhold; + +} + +void +cl_msg_setstats(volatile hb_msg_stats_t* stats) +{ + msgstats = stats; +} + +static int msg_stats_fd = -1; + +static int +cl_msg_stats_open(const char* filename) +{ + if (filename == NULL){ + cl_log(LOG_ERR, "%s: filename is NULL", __FUNCTION__); + return -1; + } + + return open(filename, O_WRONLY|O_CREAT|O_APPEND, 0644); + +} + +static int +cl_msg_stats_close(void) +{ + if (msg_stats_fd > 0){ + close(msg_stats_fd); + } + + msg_stats_fd = -1; + + return HA_OK; +} + +#define STATSFILE "/var/log/ha_msg_stats" +int +cl_msg_stats_add(longclock_t time, int size) +{ + char buf[MAXLINE]; + int len; + + if (msg_stats_fd < 0){ + msg_stats_fd = cl_msg_stats_open(STATSFILE); + if (msg_stats_fd < 0){ + cl_log(LOG_ERR, "%s:opening file failed", + __FUNCTION__); + return HA_FAIL; + } + } + + + sprintf(buf, "%lld %d\n", (long long)time, size); + len = strnlen(buf, MAXLINE); + if (write(msg_stats_fd, buf, len) == len){ + cl_msg_stats_close(); + return HA_OK; + } + + cl_msg_stats_close(); + + return HA_FAIL;; + +} + + +/* Set default messaging format */ +void +cl_set_msg_format(enum cl_msgfmt mfmt) +{ + msgfmt = mfmt; +} + +void +cl_dump_msgstats(void) +{ + if (msgstats){ + cl_log(LOG_INFO, "dumping msg stats: " + "allocmsgs=%lu", + msgstats->allocmsgs); + } + return; +} +void +list_cleanup(GList* list) +{ + size_t i; + for (i = 0; i < g_list_length(list); i++){ + char* element = g_list_nth_data(list, i); + if (element == NULL){ + cl_log(LOG_WARNING, "list_cleanup:" + "element is NULL"); + continue; + } + free(element); + } + g_list_free(list); +} + + + +/* Create a new (empty) message */ +struct ha_msg * +ha_msg_new(int nfields) +{ + struct ha_msg * ret; + int nalloc; + + ret = MALLOCT(struct ha_msg); + if (ret) { + ret->nfields = 0; + + if (nfields > MINFIELDS) { + nalloc = nfields; + } else { + nalloc = MINFIELDS; + } + + ret->nalloc = nalloc; + ret->names = (char **)calloc(sizeof(char *), nalloc); + ret->nlens = (size_t *)calloc(sizeof(size_t), nalloc); + ret->values = (void **)calloc(sizeof(void *), nalloc); + ret->vlens = (size_t *)calloc(sizeof(size_t), nalloc); + ret->types = (int*)calloc(sizeof(int), nalloc); + + if (ret->names == NULL || ret->values == NULL + || ret->nlens == NULL || ret->vlens == NULL + || ret->types == NULL) { + + cl_log(LOG_ERR, "%s" + , "ha_msg_new: out of memory for ha_msg"); + /* It is safe to give this to ha_msg_del() */ + /* at this point. It's well-enough-formed */ + ha_msg_del(ret); /*violated property*/ + ret = NULL; + }else if (msgstats) { + msgstats->allocmsgs++; + msgstats->totalmsgs++; + msgstats->lastmsg = time_longclock(); + } + } + return(ret); +} + +/* Delete (destroy) a message */ +void +ha_msg_del(struct ha_msg *msg) +{ + if (msg) { + int j; + PARANOIDAUDITMSG(msg); + if (msgstats) { + msgstats->allocmsgs--; + } + if (msg->names) { + for (j=0; j < msg->nfields; ++j) { + if (msg->names[j]) { + free(msg->names[j]); + msg->names[j] = NULL; + } + } + free(msg->names); + msg->names = NULL; + } + if (msg->values) { + for (j=0; j < msg->nfields; ++j) { + + if (msg->values[j] == NULL){ + continue; + } + + if(msg->types[j] < DIMOF(fieldtypefuncs)){ + fieldtypefuncs[msg->types[j]].memfree(msg->values[j]); + } + } + free(msg->values); + msg->values = NULL; + } + if (msg->nlens) { + free(msg->nlens); + msg->nlens = NULL; + } + if (msg->vlens) { + free(msg->vlens); + msg->vlens = NULL; + } + if (msg->types){ + free(msg->types); + msg->types = NULL; + } + msg->nfields = -1; + msg->nalloc = -1; + free(msg); + } +} +struct ha_msg* +ha_msg_copy(const struct ha_msg *msg) +{ + struct ha_msg* ret; + int j; + + + PARANOIDAUDITMSG(msg); + if (msg == NULL || (ret = ha_msg_new(msg->nalloc)) == NULL) { + return NULL; + } + + ret->nfields = msg->nfields; + + memcpy(ret->nlens, msg->nlens, sizeof(msg->nlens[0])*msg->nfields); + memcpy(ret->vlens, msg->vlens, sizeof(msg->vlens[0])*msg->nfields); + memcpy(ret->types, msg->types, sizeof(msg->types[0])*msg->nfields); + + for (j=0; j < msg->nfields; ++j) { + + if ((ret->names[j] = malloc(msg->nlens[j]+1)) == NULL) { + goto freeandleave; + } + memcpy(ret->names[j], msg->names[j], msg->nlens[j]+1); + + + if(msg->types[j] < DIMOF(fieldtypefuncs)){ + ret->values[j] = fieldtypefuncs[msg->types[j]].dup(msg->values[j], + msg->vlens[j]); + if (!ret->values[j]){ + cl_log(LOG_ERR,"duplicating the message field failed"); + goto freeandleave; + } + } + } + return ret; + +freeandleave: + /* + * ha_msg_del nicely handles partially constructed ha_msgs + * so, there's not really a memory leak here at all, but BEAM + * thinks there is. + */ + ha_msg_del(ret);/* memory leak */ ret=NULL; + return ret; +} + +#ifdef DOAUDITS +void +ha_msg_audit(const struct ha_msg* msg) +{ + int doabort = FALSE; + int j; + + if (!msg) { + return; + } + if (!msg) { + cl_log(LOG_CRIT, "Message @ %p is not allocated" + , msg); + abort(); + } + if (msg->nfields < 0) { + cl_log(LOG_CRIT, "Message @ %p has negative fields (%d)" + , msg, msg->nfields); + doabort = TRUE; + } + if (msg->nalloc < 0) { + cl_log(LOG_CRIT, "Message @ %p has negative nalloc (%d)" + , msg, msg->nalloc); + doabort = TRUE; + } + + if (!msg->names) { + cl_log(LOG_CRIT + , "Message names @ %p is not allocated" + , msg->names); + doabort = TRUE; + } + if (!msg->values) { + cl_log(LOG_CRIT + , "Message values @ %p is not allocated" + , msg->values); + doabort = TRUE; + } + if (!msg->nlens) { + cl_log(LOG_CRIT + , "Message nlens @ %p is not allocated" + , msg->nlens); + doabort = TRUE; + } + if (!msg->vlens) { + cl_log(LOG_CRIT + , "Message vlens @ %p is not allocated" + , msg->vlens); + doabort = TRUE; + } + if (doabort) { + cl_log_message(LOG_INFO,msg); + abort(); + } + for (j=0; j < msg->nfields; ++j) { + + if (msg->nlens[j] == 0){ + cl_log(LOG_ERR, "zero namelen found in msg"); + abort(); + } + + if (msg->types[j] == FT_STRING){ + if (msg->vlens[j] != strlen(msg->values[j])){ + cl_log(LOG_ERR, "stringlen does not match"); + cl_log_message(LOG_INFO,msg); + abort(); + } + } + + if (!msg->names[j]) { + cl_log(LOG_CRIT, "Message name[%d] @ 0x%p" + " is not allocated." , + j, msg->names[j]); + abort(); + } + if (msg->types[j] != FT_LIST && !msg->values[j]) { + cl_log(LOG_CRIT, "Message value [%d] @ 0x%p" + " is not allocated.", j, msg->values[j]); + cl_log_message(LOG_INFO, msg); + abort(); + } + } +} +#endif + + + +int +ha_msg_expand(struct ha_msg* msg ) +{ + char ** names ; + size_t *nlens ; + void ** values ; + size_t* vlens ; + int * types ; + int nalloc; + + if(!msg){ + cl_log(LOG_ERR, "ha_msg_expand:" + "input msg is null"); + return HA_FAIL; + } + + names = msg->names; + nlens = msg->nlens; + values = msg->values; + vlens = msg->vlens; + types = msg->types; + + nalloc = msg->nalloc + MINFIELDS; + msg->names = (char **)calloc(sizeof(char *), nalloc); + msg->nlens = (size_t *)calloc(sizeof(size_t), nalloc); + msg->values = (void **)calloc(sizeof(void *), nalloc); + msg->vlens = (size_t *)calloc(sizeof(size_t), nalloc); + msg->types= (int*)calloc(sizeof(int), nalloc); + + if (msg->names == NULL || msg->values == NULL + || msg->nlens == NULL || msg->vlens == NULL + || msg->types == NULL) { + + cl_log(LOG_ERR, "%s" + , " out of memory for ha_msg"); + return(HA_FAIL); + } + + memcpy(msg->names, names, msg->nalloc*sizeof(char *)); + memcpy(msg->nlens, nlens, msg->nalloc*sizeof(size_t)); + memcpy(msg->values, values, msg->nalloc*sizeof(void *)); + memcpy(msg->vlens, vlens, msg->nalloc*sizeof(size_t)); + memcpy(msg->types, types, msg->nalloc*sizeof(int)); + + free(names); + free(nlens); + free(values); + free(vlens); + free(types); + + msg->nalloc = nalloc; + + return HA_OK; +} + +int +cl_msg_remove_value(struct ha_msg* msg, const void* value) +{ + int j; + + if (msg == NULL || value == NULL){ + cl_log(LOG_ERR, "cl_msg_remove: invalid argument"); + return HA_FAIL; + } + + for (j = 0; j < msg->nfields; ++j){ + if (value == msg->values[j]){ + break; + } + } + if (j == msg->nfields){ + cl_log(LOG_ERR, "cl_msg_remove: field %p not found", value); + return HA_FAIL; + } + return cl_msg_remove_offset(msg, j); + +} + + +int +cl_msg_remove(struct ha_msg* msg, const char* name) +{ + int j; + + if (msg == NULL || name == NULL){ + cl_log(LOG_ERR, "cl_msg_remove: invalid argument"); + return HA_FAIL; + } + + for (j = 0; j < msg->nfields; ++j){ + if (strcmp(name, msg->names[j]) == 0){ + break; + } + } + + if (j == msg->nfields){ + cl_log(LOG_ERR, "cl_msg_remove: field %s not found", name); + return HA_FAIL; + } + return cl_msg_remove_offset(msg, j); +} + +int +cl_msg_remove_offset(struct ha_msg* msg, int offset) +{ + int j = offset; + int i; + + if (j == msg->nfields){ + cl_log(LOG_ERR, "cl_msg_remove: field %d not found", j); + return HA_FAIL; + } + + free(msg->names[j]); + fieldtypefuncs[msg->types[j]].memfree(msg->values[j]); + + for (i= j + 1; i < msg->nfields ; i++){ + msg->names[i -1] = msg->names[i]; + msg->nlens[i -1] = msg->nlens[i]; + msg->values[i -1] = msg->values[i]; + msg->vlens[i-1] = msg->vlens[i]; + msg->types[i-1] = msg->types[i]; + } + msg->nfields--; + + + return HA_OK; +} + + + +/* low level implementation for ha_msg_add + the caller is responsible to allocate/free memories + for @name and @value. + +*/ + +static int +ha_msg_addraw_ll(struct ha_msg * msg, char * name, size_t namelen, + void * value, size_t vallen, int type, int depth) +{ + + size_t startlen = sizeof(MSG_START)-1; + + + int (*addfield) (struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth); + + if (!msg || msg->names == NULL || (msg->values == NULL) ) { + cl_log(LOG_ERR, "ha_msg_addraw_ll: cannot add field to ha_msg"); + return(HA_FAIL); + } + + if (msg->nfields >= msg->nalloc) { + if( ha_msg_expand(msg) != HA_OK){ + cl_log(LOG_ERR, "message expanding failed"); + return(HA_FAIL); + } + + } + + if (namelen >= startlen + && name[0] == '>' + && strncmp(name, MSG_START, startlen) == 0) { + if(!cl_msg_quiet_fmterr) { + cl_log(LOG_ERR, "ha_msg_addraw_ll: illegal field"); + } + return(HA_FAIL); + } + + if (name == NULL || (value == NULL) + || namelen <= 0 || vallen < 0) { + cl_log(LOG_ERR, "ha_msg_addraw_ll: " + "cannot add name/value to ha_msg"); + return(HA_FAIL); + } + + HA_MSG_ASSERT(type < DIMOF(fieldtypefuncs)); + + addfield = fieldtypefuncs[type].addfield; + if (!addfield || + addfield(msg, name, namelen, value, vallen,depth) != HA_OK){ + cl_log(LOG_ERR, "ha_msg_addraw_ll: addfield failed"); + return(HA_FAIL); + } + + PARANOIDAUDITMSG(msg); + + return(HA_OK); + + +} + +static int +ha_msg_addraw(struct ha_msg * msg, const char * name, size_t namelen, + const void * value, size_t vallen, int type, int depth) +{ + + char *cpvalue = NULL; + char *cpname = NULL; + int ret; + + + if (namelen == 0){ + cl_log(LOG_ERR, "%s: Adding a field with 0 name length", __FUNCTION__); + return HA_FAIL; + } + + if ((cpname = malloc(namelen+1)) == NULL) { + cl_log(LOG_ERR, "ha_msg_addraw: no memory for string (name)"); + return(HA_FAIL); + } + strncpy(cpname, name, namelen); + cpname[namelen] = EOS; + + HA_MSG_ASSERT(type < DIMOF(fieldtypefuncs)); + + if (fieldtypefuncs[type].dup){ + cpvalue = fieldtypefuncs[type].dup(value, vallen); + } + if (cpvalue == NULL){ + cl_log(LOG_ERR, "ha_msg_addraw: copying message failed"); + free(cpname); + return(HA_FAIL); + } + + ret = ha_msg_addraw_ll(msg, cpname, namelen, cpvalue, vallen + , type, depth); + + if (ret != HA_OK){ + cl_log(LOG_ERR, "ha_msg_addraw(): ha_msg_addraw_ll failed"); + free(cpname); + fieldtypefuncs[type].memfree(cpvalue); + } + + return(ret); + +} + +/*Add a null-terminated name and binary value to a message*/ +int +ha_msg_addbin(struct ha_msg * msg, const char * name, + const void * value, size_t vallen) +{ + + return(ha_msg_addraw(msg, name, strlen(name), + value, vallen, FT_BINARY, 0)); + +} + +int +ha_msg_adduuid(struct ha_msg* msg, const char *name, const cl_uuid_t* u) +{ + return(ha_msg_addraw(msg, name, strlen(name), + u, sizeof(cl_uuid_t), FT_BINARY, 0)); +} + +/*Add a null-terminated name and struct value to a message*/ +int +ha_msg_addstruct(struct ha_msg * msg, const char * name, const void * value) +{ + const struct ha_msg* childmsg = (const struct ha_msg*) value; + + if (get_netstringlen(childmsg) > MAXCHILDMSGLEN + || get_stringlen(childmsg) > MAXCHILDMSGLEN) { + /*cl_log(LOG_WARNING, + "%s: childmsg too big (name=%s, nslen=%d, len=%d)." + " Use ha_msg_addstruct_compress() instead.", + __FUNCTION__, name, get_netstringlen(childmsg), + get_stringlen(childmsg)); + */ + } + + return ha_msg_addraw(msg, name, strlen(name), value, + sizeof(struct ha_msg), FT_STRUCT, 0); +} + +int +ha_msg_addstruct_compress(struct ha_msg * msg, const char * name, const void * value) +{ + + if (use_traditional_compression){ + return ha_msg_addraw(msg, name, strlen(name), value, + sizeof(struct ha_msg), FT_STRUCT, 0); + }else{ + return ha_msg_addraw(msg, name, strlen(name), value, + sizeof(struct ha_msg), FT_UNCOMPRESS, 0); + } +} + +int +ha_msg_add_int(struct ha_msg * msg, const char * name, int value) +{ + char buf[MAX_INT_LEN]; + snprintf(buf, MAX_INT_LEN, "%d", value); + return (ha_msg_add(msg, name, buf)); +} + +int +ha_msg_mod_int(struct ha_msg * msg, const char * name, int value) +{ + char buf[MAX_INT_LEN]; + snprintf(buf, MAX_INT_LEN, "%d", value); + return (cl_msg_modstring(msg, name, buf)); +} + +int +ha_msg_value_int(const struct ha_msg * msg, const char * name, int* value) +{ + const char* svalue = ha_msg_value(msg, name); + if(NULL == svalue) { + return HA_FAIL; + } + *value = atoi(svalue); + return HA_OK; +} + +int +ha_msg_add_ul(struct ha_msg * msg, const char * name, unsigned long value) +{ + char buf[MAX_INT_LEN]; + snprintf(buf, MAX_INT_LEN, "%lu", value); + return (ha_msg_add(msg, name, buf)); +} + +int +ha_msg_mod_ul(struct ha_msg * msg, const char * name, unsigned long value) +{ + char buf[MAX_INT_LEN]; + snprintf(buf, MAX_INT_LEN, "%lu", value); + return (cl_msg_modstring(msg, name, buf)); +} + +int +ha_msg_value_ul(const struct ha_msg * msg, const char * name, unsigned long* value) +{ + const char* svalue = ha_msg_value(msg, name); + if(NULL == svalue) { + return HA_FAIL; + } + *value = strtoul(svalue, NULL, 10); + return HA_OK; +} + +/* + * ha_msg_value_str_list()/ha_msg_add_str_list(): + * transform a string list suitable for putting into an ha_msg is by a convention + * of naming the fields into the following format: + * listname1=foo + * listname2=bar + * listname3=stuff + * etc. + */ + +GList* +ha_msg_value_str_list(struct ha_msg * msg, const char * name) +{ + + int i = 1; + int len = 0; + const char* value; + char* element; + GList* list = NULL; + + + if( NULL==msg||NULL==name||strnlen(name, MAX_NAME_LEN)>=MAX_NAME_LEN ){ + return NULL; + } + len = cl_msg_list_length(msg,name); + for(i=0; i<len; i++) { + value = cl_msg_list_nth_data(msg,name,i); + if (NULL == value) { + break; + } + element = g_strdup(value); + list = g_list_append(list, element); + } + return list; +} + + + +static void +pair_to_msg(gpointer key, gpointer value, gpointer user_data) +{ + struct ha_msg* msg = (struct ha_msg*)user_data; + if( HA_OK != ha_msg_add(msg, key, value)) { + cl_log(LOG_ERR, "ha_msg_add in pair_to_msg failed"); + } +} + + +static struct ha_msg* +str_table_to_msg(GHashTable* hash_table) +{ + struct ha_msg* hash_msg; + + if ( NULL == hash_table) { + return NULL; + } + + hash_msg = ha_msg_new(5); + g_hash_table_foreach(hash_table, pair_to_msg, hash_msg); + return hash_msg; +} + + +static GHashTable* +msg_to_str_table(struct ha_msg * msg) +{ + int i; + GHashTable* hash_table; + + if ( NULL == msg) { + return NULL; + } + + hash_table = g_hash_table_new(g_str_hash, g_str_equal); + + for (i = 0; i < msg->nfields; i++) { + if( FT_STRING != msg->types[i] ) { + continue; + } + g_hash_table_insert(hash_table, + g_strndup(msg->names[i],msg->nlens[i]), + g_strndup(msg->values[i],msg->vlens[i])); + } + return hash_table; +} + +GHashTable* +ha_msg_value_str_table(struct ha_msg * msg, const char * name) +{ + struct ha_msg* hash_msg; + GHashTable * hash_table = NULL; + + if (NULL == msg || NULL == name) { + return NULL; + } + + hash_msg = cl_get_struct(msg, name); + if (NULL == hash_msg) { + return NULL; + } + hash_table = msg_to_str_table(hash_msg); + return hash_table; +} + +int +ha_msg_add_str_table(struct ha_msg * msg, const char * name, + GHashTable* hash_table) +{ + struct ha_msg* hash_msg; + if (NULL == msg || NULL == name || NULL == hash_table) { + return HA_FAIL; + } + + hash_msg = str_table_to_msg(hash_table); + if( HA_OK != ha_msg_addstruct(msg, name, hash_msg)) { + ha_msg_del(hash_msg); + cl_log(LOG_ERR + , "ha_msg_addstruct in ha_msg_add_str_table failed"); + return HA_FAIL; + } + ha_msg_del(hash_msg); + return HA_OK; +} + +int +ha_msg_mod_str_table(struct ha_msg * msg, const char * name, + GHashTable* hash_table) +{ + struct ha_msg* hash_msg; + if (NULL == msg || NULL == name || NULL == hash_table) { + return HA_FAIL; + } + + hash_msg = str_table_to_msg(hash_table); + if( HA_OK != cl_msg_modstruct(msg, name, hash_msg)) { + ha_msg_del(hash_msg); + cl_log(LOG_ERR + , "ha_msg_modstruct in ha_msg_mod_str_table failed"); + return HA_FAIL; + } + ha_msg_del(hash_msg); + return HA_OK; +} + +int +cl_msg_list_add_string(struct ha_msg* msg, const char* name, const char* value) +{ + GList* list = NULL; + int ret; + + if(!msg || !name || !value){ + cl_log(LOG_ERR, "cl_msg_list_add_string: input invalid"); + return HA_FAIL; + } + + + list = g_list_append(list, UNCONST_CAST_POINTER(gpointer, value)); + if (!list){ + cl_log(LOG_ERR, "cl_msg_list_add_string: append element to" + "a glist failed"); + return HA_FAIL; + } + + ret = ha_msg_addraw(msg, name, strlen(name), list, + string_list_pack_length(list), + FT_LIST, 0); + + g_list_free(list); + + return ret; + +} + +/* Add a null-terminated name and value to a message */ +int +ha_msg_add(struct ha_msg * msg, const char * name, const char * value) +{ + if(name == NULL || value == NULL) { + return HA_FAIL; + } + return(ha_msg_nadd(msg, name, strlen(name), value, strlen(value))); +} + +/* Add a name/value pair to a message (with sizes for name and value) */ +int +ha_msg_nadd(struct ha_msg * msg, const char * name, int namelen + , const char * value, int vallen) +{ + return(ha_msg_addraw(msg, name, namelen, value, vallen, FT_STRING, 0)); + +} + +/* Add a name/value/type to a message (with sizes for name and value) */ +int +ha_msg_nadd_type(struct ha_msg * msg, const char * name, int namelen + , const char * value, int vallen, int type) +{ + return(ha_msg_addraw(msg, name, namelen, value, vallen, type, 0)); + +} + + + +/* Add a "name=value" line to the name, value pairs in a message */ +static int +ha_msg_add_nv_depth(struct ha_msg* msg, const char * nvline, + const char * bufmax, int depth) +{ + int namelen; + const char * valp; + int vallen; + + if (!nvline) { + cl_log(LOG_ERR, "ha_msg_add_nv: NULL nvline"); + return(HA_FAIL); + } + /* How many characters before the '='? */ + if ((namelen = strcspn(nvline, EQUAL)) <= 0 + || nvline[namelen] != '=') { + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING + , "ha_msg_add_nv_depth: line doesn't contain '='"); + cl_log(LOG_INFO, "%s", nvline); + } + return(HA_FAIL); + } + valp = nvline + namelen +1; /* Point just *past* the '=' */ + if (valp >= bufmax){ + return HA_FAIL; + } + vallen = strcspn(valp, NEWLINE); + if ((valp + vallen) >= bufmax){ + return HA_FAIL; + } + + if (vallen == 0){ + valp = NULL; + } + /* Call ha_msg_nadd to actually add the name/value pair */ + return(ha_msg_addraw(msg, nvline, namelen, valp, vallen + , FT_STRING, depth)); + +} + +int +ha_msg_add_nv(struct ha_msg* msg, const char * nvline, + const char * bufmax) +{ + + return(ha_msg_add_nv_depth(msg, nvline, bufmax, 0)); + +} + + +static void * +cl_get_value(const struct ha_msg * msg, const char * name, + size_t * vallen, int *type) +{ + + int j; + if (!msg || !msg->names || !msg->values) { + cl_log(LOG_ERR, "%s: wrong argument (%s)", + __FUNCTION__, name); + return(NULL); + } + + PARANOIDAUDITMSG(msg); + for (j=0; j < msg->nfields; ++j) { + const char *local_name = msg->names[j]; + if (name[0] == local_name[0] + && strcmp(name, local_name) == 0) { + if (vallen){ + *vallen = msg->vlens[j]; + } + if (type){ + *type = msg->types[j]; + } + return(msg->values[j]); + } + } + return(NULL); +} + +static void * +cl_get_value_mutate(struct ha_msg * msg, const char * name, + size_t * vallen, int *type) +{ + + int j; + if (!msg || !msg->names || !msg->values) { + cl_log(LOG_ERR, "%s: wrong argument", + __FUNCTION__); + return(NULL); + } + + AUDITMSG(msg); + for (j=0; j < msg->nfields; ++j) { + if (strcmp(name, msg->names[j]) == 0) { + int tp = msg->types[j]; + if (fieldtypefuncs[tp].pregetaction){ + fieldtypefuncs[tp].pregetaction(msg, j); + } + + if (vallen){ + *vallen = msg->vlens[j]; + } + if (type){ + *type = msg->types[j]; + } + return(msg->values[j]); + } + } + return(NULL); +} + + +const void * +cl_get_binary(const struct ha_msg *msg, + const char * name, size_t * vallen) +{ + + const void *ret; + int type; + + ret = cl_get_value( msg, name, vallen, &type); + + if (ret == NULL){ + /* + cl_log(LOG_WARNING, "field %s not found", name); + cl_log_message(msg); + */ + return(NULL); + } + if ( type != FT_BINARY){ + cl_log(LOG_WARNING, "field %s is not binary", name); + cl_log_message(LOG_WARNING, msg); + return(NULL); + } + + return(ret); +} + +/* UUIDs are stored with a machine-independent byte ordering (even though it's binary) */ +int +cl_get_uuid(const struct ha_msg *msg, const char * name, cl_uuid_t* retval) +{ + const void * vret; + size_t vretsize; + + cl_uuid_clear(retval); + + if ((vret = cl_get_binary(msg, name, &vretsize)/*discouraged function*/) == NULL) { + /* But perfectly portable in this case */ + return HA_FAIL; + } + if (vretsize != sizeof(cl_uuid_t)) { + cl_log(LOG_WARNING, "Binary field %s is not a uuid.", name); + cl_log(LOG_INFO, "expecting %d bytes, got %d bytes", + (int)sizeof(cl_uuid_t), (int)vretsize); + cl_log_message(LOG_INFO, msg); + return HA_FAIL; + } + memcpy(retval, vret, sizeof(cl_uuid_t)); + return HA_OK; +} + +const char * +cl_get_string(const struct ha_msg *msg, const char *name) +{ + + const void *ret; + int type; + ret = cl_get_value( msg, name, NULL, &type); + + if (ret == NULL || type != FT_STRING){ + return(NULL); + } + + return(ret); + +} + +int +cl_get_type(const struct ha_msg *msg, const char *name) +{ + + const void *ret; + int type; + + ret = cl_get_value( msg, name, NULL, &type); + + if (ret == NULL) { + return -1; + } + if (type < 0){ + cl_log(LOG_WARNING, "field %s not a valid type" + , name); + return(-1); + } + + return(type); + +} + +/* +struct ha_msg * +cl_get_struct(const struct ha_msg *msg, const char* name) +{ + struct ha_msg* ret; + int type; + size_t vallen; + + ret = cl_get_value(msg, name, &vallen, &type); + + if (ret == NULL ){ + return(NULL); + } + + switch(type){ + + case FT_STRUCT: + break; + + default: + cl_log(LOG_ERR, "%s: field %s is not a struct (%d)", + __FUNCTION__, name, type); + return NULL; + } + + return ret; +} +*/ + + +struct ha_msg * +cl_get_struct(struct ha_msg *msg, const char* name) +{ + struct ha_msg* ret; + int type = -1; + size_t vallen; + + ret = cl_get_value_mutate(msg, name, &vallen, &type); + + if (ret == NULL ){ + return(NULL); + } + + switch(type){ + + case FT_UNCOMPRESS: + case FT_STRUCT: + break; + + default: + cl_log(LOG_ERR, "%s: field %s is not a struct (%d)", + __FUNCTION__, name, type); + return NULL; + } + + return ret; +} + + +int +cl_msg_list_length(struct ha_msg* msg, const char* name) +{ + GList* ret; + int type; + + ret = cl_get_value( msg, name, NULL, &type); + + if ( ret == NULL || type != FT_LIST){ + return -1; + } + + return g_list_length(ret); + +} + + +void* +cl_msg_list_nth_data(struct ha_msg* msg, const char* name, int n) +{ + GList* ret; + int type; + + ret = cl_get_value( msg, name, NULL, &type); + + if ( ret == NULL || type != FT_LIST){ + cl_log(LOG_WARNING, "field %s not found " + " or type mismatch", name); + return NULL; + } + + return g_list_nth_data(ret, n); + +} + +int +cl_msg_add_list(struct ha_msg* msg, const char* name, GList* list) +{ + int ret; + + if(msg == NULL|| name ==NULL || list == NULL){ + cl_log(LOG_ERR, "cl_msg_add_list:" + "invalid arguments"); + return HA_FAIL; + } + + ret = ha_msg_addraw(msg, name, strlen(name), list, + string_list_pack_length(list), + FT_LIST, 0); + + return ret; +} + +GList* +cl_msg_get_list(struct ha_msg* msg, const char* name) +{ + GList* ret; + int type; + + ret = cl_get_value( msg, name, NULL, &type); + + if ( ret == NULL || type != FT_LIST){ + cl_log(LOG_WARNING, "field %s not found " + " or type mismatch", name); + return NULL; + } + + return ret; +} + + +int +cl_msg_add_list_str(struct ha_msg* msg, const char* name, + char** buf, size_t n) +{ + GList* list = NULL; + int i; + int ret = HA_FAIL; + + if (n <= 0 || buf == NULL|| name ==NULL ||msg == NULL){ + cl_log(LOG_ERR, "%s:" + "invalid parameter(%s)", + !n <= 0?"n is negative or zero": + !buf?"buf is NULL": + !name?"name is NULL": + "msg is NULL",__FUNCTION__); + return HA_FAIL; + } + + for ( i = 0; i < n; i++){ + if (buf[i] == NULL){ + cl_log(LOG_ERR, "%s: %dth element in buf is null", + __FUNCTION__, i); + goto free_and_out; + } + list = g_list_append(list, buf[i]); + if (list == NULL){ + cl_log(LOG_ERR, "%s:adding integer to list failed", + __FUNCTION__); + goto free_and_out; + } + } + + ret = ha_msg_addraw(msg, name, strlen(name), list, + string_list_pack_length(list), + FT_LIST, 0); + + free_and_out: + if (list){ + g_list_free(list); + list = NULL; + } + return ret; +} + +static void +list_element_free(gpointer data, gpointer userdata) +{ + if (data){ + g_free(data); + } +} + +int +cl_msg_add_list_int(struct ha_msg* msg, const char* name, + int* buf, size_t n) +{ + + GList* list = NULL; + size_t i; + int ret = HA_FAIL; + + if (n <= 0 || buf == NULL|| name ==NULL ||msg == NULL){ + cl_log(LOG_ERR, "cl_msg_add_list_int:" + "invalid parameter(%s)", + !n <= 0?"n is negative or zero": + !buf?"buf is NULL": + !name?"name is NULL": + "msg is NULL"); + goto free_and_out; + } + + for ( i = 0; i < n; i++){ + char intstr[MAX_INT_LEN]; + sprintf(intstr,"%d", buf[i]); + list = g_list_append(list, g_strdup(intstr)); + if (list == NULL){ + cl_log(LOG_ERR, "cl_msg_add_list_int:" + "adding integer to list failed"); + goto free_and_out; + } + } + + ret = ha_msg_addraw(msg, name, strlen(name), list, + string_list_pack_length(list), + FT_LIST, 0); + free_and_out: + if (list){ + g_list_foreach(list,list_element_free , NULL); + g_list_free(list); + list = NULL; + } + + return ret; +} +int +cl_msg_get_list_int(struct ha_msg* msg, const char* name, + int* buf, size_t* n) +{ + GList* list; + size_t len; + int i; + GList* list_element; + + + if (n == NULL || buf == NULL|| name ==NULL ||msg == NULL){ + cl_log(LOG_ERR, "cl_msg_get_list_int:" + "invalid parameter(%s)", + !n?"n is NULL": + !buf?"buf is NULL": + !name?"name is NULL": + "msg is NULL"); + return HA_FAIL; + } + + list = cl_msg_get_list(msg, name); + if (list == NULL){ + cl_log(LOG_ERR, "cl_msg_get_list_int:" + "list of integers %s not found", name); + return HA_FAIL; + } + + len = g_list_length(list); + if (len > *n){ + cl_log(LOG_ERR, "cl_msg_get_list_int:" + "buffer too small: *n=%ld, required len=%ld", + (long)*n, (long)len); + *n = len; + return HA_FAIL; + } + + *n = len; + i = 0; + list_element = g_list_first(list); + while( list_element != NULL){ + char* intstr = list_element->data; + if (intstr == NULL){ + cl_log(LOG_ERR, "cl_msg_get_list_int:" + "element data is NULL"); + return HA_FAIL; + } + + if (sscanf(intstr,"%d", &buf[i]) != 1){ + cl_log(LOG_ERR, "cl_msg_get_list_int:" + "element data is NULL"); + return HA_FAIL; + } + + i++; + list_element = g_list_next(list_element); + } + + return HA_OK; +} + +int +cl_msg_replace_value(struct ha_msg* msg, const void *old_value, + const void* value, size_t vlen, int type) +{ + int j; + + if (msg == NULL || old_value == NULL) { + cl_log(LOG_ERR, "cl_msg_replace: invalid argument"); + return HA_FAIL; + } + + for (j = 0; j < msg->nfields; ++j){ + if (old_value == msg->values[j]){ + break; + } + } + if (j == msg->nfields){ + cl_log(LOG_ERR, "cl_msg_replace: field %p not found", old_value); + return HA_FAIL; + } + return cl_msg_replace(msg, j, value, vlen, type); +} + +/*this function is for internal use only*/ +int +cl_msg_replace(struct ha_msg* msg, int index, + const void* value, size_t vlen, int type) +{ + void * newv ; + int newlen = vlen; + int oldtype; + + PARANOIDAUDITMSG(msg); + if (msg == NULL || value == NULL) { + cl_log(LOG_ERR, "%s: NULL input.", __FUNCTION__); + return HA_FAIL; + } + + if(type >= DIMOF(fieldtypefuncs)){ + cl_log(LOG_ERR, "%s:" + "invalid type(%d)",__FUNCTION__, type); + return HA_FAIL; + } + + if (index >= msg->nfields){ + cl_log(LOG_ERR, "%s: index(%d) out of range(%d)", + __FUNCTION__,index, msg->nfields); + return HA_FAIL; + } + + oldtype = msg->types[index]; + + newv = fieldtypefuncs[type].dup(value,vlen); + if (!newv){ + cl_log(LOG_ERR, "%s: duplicating message fields failed" + "value=%p, vlen=%d, msg->names[i]=%s", + __FUNCTION__,value, (int)vlen, msg->names[index]); + return HA_FAIL; + } + + fieldtypefuncs[oldtype].memfree(msg->values[index]); + + msg->values[index] = newv; + msg->vlens[index] = newlen; + msg->types[index] = type; + PARANOIDAUDITMSG(msg); + return(HA_OK); + +} + + +static int +cl_msg_mod(struct ha_msg * msg, const char * name, + const void* value, size_t vlen, int type) +{ + int j; + int rc; + + PARANOIDAUDITMSG(msg); + if (msg == NULL || name == NULL || value == NULL) { + cl_log(LOG_ERR, "cl_msg_mod: NULL input."); + return HA_FAIL; + } + + if(type >= DIMOF(fieldtypefuncs)){ + cl_log(LOG_ERR, "cl_msg_mod:" + "invalid type(%d)", type); + return HA_FAIL; + } + + for (j=0; j < msg->nfields; ++j) { + if (strcmp(name, msg->names[j]) == 0) { + + char * newv ; + int newlen = vlen; + + if (type != msg->types[j]){ + cl_log(LOG_ERR, "%s: type mismatch(%d %d)", + __FUNCTION__, type, msg->types[j]); + return HA_FAIL; + } + + newv = fieldtypefuncs[type].dup(value,vlen); + if (!newv){ + cl_log(LOG_ERR, "duplicating message fields failed" + "value=%p, vlen=%d, msg->names[j]=%s", + value, (int)vlen, msg->names[j]); + return HA_FAIL; + } + + fieldtypefuncs[type].memfree(msg->values[j]); + msg->values[j] = newv; + msg->vlens[j] = newlen; + PARANOIDAUDITMSG(msg); + return(HA_OK); + } + } + + rc = ha_msg_nadd_type(msg, name,strlen(name), value, vlen, type); + + PARANOIDAUDITMSG(msg); + return rc; +} + +int +cl_msg_modstruct(struct ha_msg * msg, const char* name, + const struct ha_msg* value) +{ + return cl_msg_mod(msg, name, value, 0, FT_STRUCT); +} + +int +cl_msg_modbin(struct ha_msg * msg, const char* name, + const void* value, size_t vlen) +{ + return cl_msg_mod(msg, name, value, vlen, FT_BINARY); + +} +int +cl_msg_moduuid(struct ha_msg * msg, const char* name, + const cl_uuid_t* uuid) +{ + return cl_msg_mod(msg, name, uuid, sizeof(cl_uuid_t), FT_BINARY); +} + + + +/* Modify the value associated with a particular name */ +int +cl_msg_modstring(struct ha_msg * msg, const char * name, const char * value) +{ + return cl_msg_mod(msg, name, value, strlen(value), FT_STRING); +} + + + +/* Return the next message found in the stream */ +struct ha_msg * +msgfromstream(FILE * f) +{ + char buf[MAXMSGLINE]; + char * getsret; + clearerr(f); + /* Skip until we find a MSG_START (hopefully we skip nothing) */ + while(1) { + getsret = fgets(buf, sizeof(buf), f); + if (!getsret) { + break; + } + if (strcmp(buf, MSG_START) == 0) { + return msgfromstream_string(f); + + } + if (strcmp(buf, MSG_START_NETSTRING) == 0){ + return msgfromstream_netstring(f); + } + + } + + return NULL; +} + +/* Return the next message found in the stream with string format */ +struct ha_msg * +msgfromstream_string(FILE * f) +{ + char buf[MAXMSGLINE]; + const char * bufmax = buf + sizeof(buf); + struct ha_msg* ret; + char * getsret; + + + if ((ret = ha_msg_new(0)) == NULL) { + /* Getting an error with EINTR is pretty normal */ + /* (so is EOF) */ + if ( (!ferror(f) || (errno != EINTR && errno != EAGAIN)) + && !feof(f)) { + cl_log(LOG_ERR, "msgfromstream: cannot get message"); + } + return(NULL); + } + + /* Add Name=value pairs until we reach MSG_END or EOF */ + while(1) { + getsret = fgets(buf, MAXMSGLINE, f); + if (!getsret) { + break; + } + + if (strnlen(buf, MAXMSGLINE) > MAXMSGLINE - 2) { + cl_log(LOG_DEBUG + , "msgfromstream: field too long [%s]" + , buf); + } + + if (!strcmp(buf, MSG_END)) { + break; + } + + + /* Add the "name=value" string on this line to the message */ + if (ha_msg_add_nv(ret, buf, bufmax) != HA_OK) { + cl_log(LOG_ERR, "NV failure (msgfromsteam): [%s]" + , buf); + ha_msg_del(ret); ret=NULL; + return(NULL); + } + } + return(ret); +} + + +/* Return the next message found in the stream with netstring format*/ + +struct ha_msg * +msgfromstream_netstring(FILE * f) +{ + struct ha_msg * ret; + + if ((ret = ha_msg_new(0)) == NULL) { + /* Getting an error with EINTR is pretty normal */ + /* (so is EOF) */ + if ( (!ferror(f) || (errno != EINTR && errno != EAGAIN)) + && !feof(f)) { + cl_log(LOG_ERR + , "msgfromstream_netstring(): cannot get message"); + } + return(NULL); + } + + while(1) { + char* nvpair; + int nvlen; + int n; + + if (fscanf(f, "%d:", &nvlen) <= 0 || nvlen <= 0){ + return(ret); + } + + nvpair = malloc(nvlen + 2); + + if ((n =fread(nvpair, 1, nvlen + 1, f)) != nvlen + 1){ + cl_log(LOG_WARNING, "msgfromstream_netstring()" + ": Can't get enough nvpair," + "expecting %d bytes long, got %d bytes", + nvlen + 1, n); + ha_msg_del(ret); + return(NULL); + } + + process_netstring_nvpair(ret, nvpair, nvlen); + + } + +} + +static gboolean ipc_timer_expired = FALSE; + +static void cl_sigalarm_handler(int signum) +{ + if (signum == SIGALRM) { + ipc_timer_expired = TRUE; + } +} + +int +cl_ipc_wait_timeout( + IPC_Channel *chan, int (*waitfun)(IPC_Channel *chan), unsigned int timeout) +{ + int rc = IPC_FAIL; + struct sigaction old_action; + + memset(&old_action, 0, sizeof(old_action)); + cl_signal_set_simple_handler(SIGALRM, cl_sigalarm_handler, &old_action); + + ipc_timer_expired = FALSE; + + alarm(timeout); + rc = waitfun(chan); + if (rc == IPC_INTR && ipc_timer_expired) { + rc = IPC_TIMEOUT; + } + + alarm(0); /* ensure it expires */ + cl_signal_set_simple_handler(SIGALRM, old_action.sa_handler, &old_action); + + + return rc; +} + +/* Return the next message found in the IPC channel */ +static struct ha_msg* +msgfromIPC_ll(IPC_Channel * ch, int flag, unsigned int timeout, int *rc_out) +{ + int rc; + IPC_Message* ipcmsg; + struct ha_msg* hmsg; + int need_auth = flag & MSG_NEEDAUTH; + int allow_intr = flag & MSG_ALLOWINTR; + + startwait: + if(timeout > 0) { + rc = cl_ipc_wait_timeout(ch, ch->ops->waitin, timeout); + } else { + rc = ch->ops->waitin(ch); + } + + if(rc_out) { + *rc_out = rc; + } + + switch(rc) { + default: + case IPC_FAIL: + cl_perror("msgfromIPC: waitin failure"); + return NULL; + + case IPC_TIMEOUT: + return NULL; + + case IPC_BROKEN: + sleep(1); + return NULL; + + case IPC_INTR: + if ( allow_intr){ + goto startwait; + }else{ + return NULL; + } + + case IPC_OK: + break; + } + + + ipcmsg = NULL; + rc = ch->ops->recv(ch, &ipcmsg); +#if 0 + if (DEBUGPKTCONT) { + cl_log(LOG_DEBUG, "msgfromIPC: recv returns %d ipcmsg = 0x%lx" + , rc, (unsigned long)ipcmsg); + } +#endif + if(rc_out) { + *rc_out = rc; + } + + if (rc != IPC_OK) { + return NULL; + } + + hmsg = wirefmt2msg_ll((char *)ipcmsg->msg_body, ipcmsg->msg_len, need_auth); + if (ipcmsg->msg_done) { + ipcmsg->msg_done(ipcmsg); + } + + AUDITMSG(hmsg); + return hmsg; +} + +/* Return the next message found in the IPC channel */ +struct ha_msg* +msgfromIPC_timeout(IPC_Channel *ch, int flag, unsigned int timeout, int *rc_out) +{ + return msgfromIPC_ll(ch, flag, timeout, rc_out); +} + +struct ha_msg* +msgfromIPC(IPC_Channel * ch, int flag) +{ + return msgfromIPC_ll(ch, flag, 0, NULL); +} + + +struct ha_msg* +msgfromIPC_noauth(IPC_Channel * ch) +{ + int flag = 0; + + flag |= MSG_ALLOWINTR; + return msgfromIPC_ll(ch, flag, 0, NULL); +} + +/* Return the next message found in the IPC channel */ +IPC_Message * +ipcmsgfromIPC(IPC_Channel * ch) +{ + int rc; + IPC_Message* ipcmsg; + + rc = ch->ops->waitin(ch); + + switch(rc) { + default: + case IPC_FAIL: + cl_perror("msgfromIPC: waitin failure"); + return NULL; + + case IPC_BROKEN: + sleep(1); + return NULL; + + case IPC_INTR: + return NULL; + + case IPC_OK: + break; + } + + + ipcmsg = NULL; + rc = ch->ops->recv(ch, &ipcmsg); +#if 0 + if (DEBUGPKTCONT) { + cl_log(LOG_DEBUG, "msgfromIPC: recv returns %d ipcmsg = 0x%lx" + , rc, (unsigned long)ipcmsg); + } +#endif + if (rc != IPC_OK) { + return NULL; + } + + + return(ipcmsg); +} + + +/* Writes a message into a stream - used for serial lines */ +int +msg2stream(struct ha_msg* m, FILE * f) +{ + size_t len; + char * s = msg2wirefmt(m, &len); + + if (s != NULL) { + int rc = HA_OK; + if (fputs(s, f) == EOF) { + rc = HA_FAIL; + cl_perror("msg2stream: fputs failure"); + } + if (fflush(f) == EOF) { + cl_perror("msg2stream: fflush failure"); + rc = HA_FAIL; + } + free(s); + return(rc); + }else{ + return(HA_FAIL); + } +} +static void ipcmsg_done(IPC_Message* m); + +static int clmsg_ipcmsg_allocated = 0; +static int clmsg_ipcmsg_freed = 0; + +void dump_clmsg_ipcmsg_stats(void); +void +dump_clmsg_ipcmsg_stats(void) +{ + cl_log(LOG_INFO, "clmsg ipcmsg allocated=%d, freed=%d, diff=%d", + clmsg_ipcmsg_allocated, + clmsg_ipcmsg_freed, + clmsg_ipcmsg_allocated - clmsg_ipcmsg_freed); + + return; +} + +static void +ipcmsg_done(IPC_Message* m) +{ + if (!m) { + return; + } + if (m->msg_buf) { + free(m->msg_buf); + } + free(m); + m = NULL; + clmsg_ipcmsg_freed ++; +} + + + +/* + * create an ipcmsg and copy the data + */ + +IPC_Message* +wirefmt2ipcmsg(void* p, size_t len, IPC_Channel* ch) +{ + IPC_Message* ret = NULL; + + if (p == NULL){ + return(NULL); + } + + ret = MALLOCT(IPC_Message); + if (!ret) { + return(NULL); + } + + memset(ret, 0, sizeof(IPC_Message)); + + if (NULL == (ret->msg_buf = malloc(len + ch->msgpad))) { + free(ret); + return NULL; + } + ret->msg_body = (char*)ret->msg_buf + ch->msgpad; + memcpy(ret->msg_body, p, len); + + ret->msg_done = ipcmsg_done; + ret->msg_private = NULL; + ret->msg_ch = ch; + ret->msg_len = len; + + clmsg_ipcmsg_allocated ++; + + return ret; + +} + +IPC_Message* +hamsg2ipcmsg(struct ha_msg* m, IPC_Channel* ch) +{ + size_t len; + char * s = msg2wirefmt_ll(m, &len, MSG_NEEDCOMPRESS); + IPC_Message* ret = NULL; + + if (s == NULL) { + return ret; + } + ret = MALLOCT(IPC_Message); + if (!ret) { + free(s); + return ret; + } + + memset(ret, 0, sizeof(IPC_Message)); + + if (NULL == (ret->msg_buf = malloc(len + ch->msgpad))) { + free(s); + free(ret); + return NULL; + } + ret->msg_body = (char*)ret->msg_buf + ch->msgpad; + memcpy(ret->msg_body, s, len); + free(s); + + ret->msg_done = ipcmsg_done; + ret->msg_private = NULL; + ret->msg_ch = ch; + ret->msg_len = len; + + clmsg_ipcmsg_allocated ++; + + return ret; +} + +struct ha_msg* +ipcmsg2hamsg(IPC_Message*m) +{ + struct ha_msg* ret = NULL; + + + ret = wirefmt2msg(m->msg_body, m->msg_len,MSG_NEEDAUTH); + return ret; +} + +int +msg2ipcchan(struct ha_msg*m, IPC_Channel*ch) +{ + IPC_Message* imsg; + + if (m == NULL || ch == NULL) { + cl_log(LOG_ERR, "Invalid msg2ipcchan argument"); + errno = EINVAL; + return HA_FAIL; + } + + if ((imsg = hamsg2ipcmsg(m, ch)) == NULL) { + cl_log(LOG_ERR, "hamsg2ipcmsg() failure"); + return HA_FAIL; + } + + if (ch->ops->send(ch, imsg) != IPC_OK) { + if (ch->ch_status == IPC_CONNECT) { + snprintf(ch->failreason,MAXFAILREASON, + "send failed,farside_pid=%d, sendq length=%ld(max is %ld)", + ch->farside_pid, (long)ch->send_queue->current_qlen, + (long)ch->send_queue->max_qlen); + } + imsg->msg_done(imsg); + return HA_FAIL; + } + return HA_OK; +} + +static gboolean (*msg_authentication_method)(const struct ha_msg* ret) = NULL; + + +void +cl_set_oldmsgauthfunc(gboolean (*authfunc)(const struct ha_msg*)) +{ + msg_authentication_method = authfunc; +} + + + +/* Converts a string (perhaps received via UDP) into a message */ +struct ha_msg * +string2msg_ll(const char * s, size_t length, int depth, int need_auth) +{ + struct ha_msg* ret; + int startlen; + int endlen; + const char * sp = s; + const char * smax = s + length; + + + if ((ret = ha_msg_new(0)) == NULL) { + cl_log(LOG_ERR, "%s: creating new msg failed", __FUNCTION__); + return(NULL); + } + + startlen = sizeof(MSG_START)-1; + if (strncmp(sp, MSG_START, startlen) != 0) { + /* This can happen if the sender gets killed */ + /* at just the wrong time... */ + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING, "string2msg_ll: no MSG_START"); + cl_log(LOG_WARNING, "%s: s=%s", __FUNCTION__, s); + cl_log(LOG_WARNING, "depth=%d", depth); + } + ha_msg_del(ret); + return(NULL); + }else{ + sp += startlen; + } + + endlen = sizeof(MSG_END)-1; + + /* Add Name=value pairs until we reach MSG_END or end of string */ + + while (*sp != EOS && strncmp(sp, MSG_END, endlen) != 0) { + + if (sp >= smax) { + cl_log(LOG_ERR, "%s: buffer overflow(sp=%p, smax=%p)", + __FUNCTION__, sp, smax); + return(NULL); + } + /* Skip over initial CR/NL things */ + sp += strspn(sp, NEWLINE); + if (sp >= smax) { + cl_log(LOG_ERR, "%s: buffer overflow after NEWLINE(sp=%p, smax=%p)", + __FUNCTION__, sp, smax); + return(NULL); + } + /* End of message marker? */ + if (strncmp(sp, MSG_END, endlen) == 0) { + break; + } + /* Add the "name=value" string on this line to the message */ + if (ha_msg_add_nv_depth(ret, sp, smax, depth) != HA_OK) { + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_ERR, "NV failure (string2msg_ll):"); + cl_log(LOG_ERR, "Input string: [%s]", s); + cl_log(LOG_ERR, "sp=%s", sp); + cl_log(LOG_ERR, "depth=%d", depth); + cl_log_message(LOG_ERR,ret); + } + ha_msg_del(ret); + return(NULL); + } + if (sp >= smax) { + cl_log(LOG_ERR, "%s: buffer overflow after adding field(sp=%p, smax=%p)", + __FUNCTION__, sp, smax); + return(NULL); + } + sp += strcspn(sp, NEWLINE); + } + + if (need_auth && msg_authentication_method + && !msg_authentication_method(ret)) { + const char* from = ha_msg_value(ret, F_ORIG); + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING, + "string2msg_ll: node [%s]" + " failed authentication", from ? from : "?"); + } + ha_msg_del(ret); + ret = NULL; + } + return(ret); +} + + + +struct ha_msg * +string2msg(const char * s, size_t length) +{ + return(string2msg_ll(s, length, 0, MSG_NEEDAUTH)); +} + + + + + + +/* Converts a message into a string (for sending out UDP interface) + + used in two places: + + 1.called by msg2string as a implementation for computing string for a + message provided the buffer + + 2.called by is_authentic. In this case, there are no start/end string + and the "auth" field is not included in the string + +*/ + +#define NOROOM { \ + cl_log(LOG_ERR, "%s:%d: out of memory bound" \ + ", bp=%p, buf + len=%p, len=%ld" \ + , __FUNCTION__, __LINE__ \ + , bp, buf + len, (long)len); \ + cl_log_message(LOG_ERR, m); \ + return(HA_FAIL); \ + } + +#define CHECKROOM_CONST(c) CHECKROOM_INT(STRLEN_CONST(c)) +#define CHECKROOM_STRING(s) CHECKROOM_INT(strnlen(s, len)) +#define CHECKROOM_STRING_INT(s,i) CHECKROOM_INT(strnlen(s, len)+(i)) +#define CHECKROOM_INT(i) { \ + if ((bp + (i)) > maxp) { \ + NOROOM; \ + } \ + } + + +int +msg2string_buf(const struct ha_msg *m, char* buf, size_t len +, int depth,int needhead) +{ + + char * bp = NULL; + int j; + char* maxp = buf + len; + + buf[0]=0; + bp = buf; + + if (needhead){ + CHECKROOM_CONST(MSG_START); + strcpy(bp, MSG_START); + bp += STRLEN_CONST(MSG_START); + } + + for (j=0; j < m->nfields; ++j) { + + int truelen; + int (*tostring)(char*, char*, void*, size_t, int); + + if (needhead == NOHEAD && strcmp(m->names[j], F_AUTH) == 0) { + continue; + } + + if (m->types[j] != FT_STRING){ + CHECKROOM_STRING_INT(FT_strings[m->types[j]],2); + strcat(bp, "("); + bp++; + strcat(bp, FT_strings[m->types[j]]); + bp++; + strcat(bp,")"); + bp++; + } + + CHECKROOM_STRING_INT(m->names[j],1); + strcat(bp, m->names[j]); + bp += m->nlens[j]; + strcat(bp, "="); + bp++; + + if(m->types[j] < DIMOF(fieldtypefuncs)){ + tostring = fieldtypefuncs[m->types[j]].tostring; + } else { + cl_log(LOG_ERR, "type(%d) unrecognized", m->types[j]); + return HA_FAIL; + } + if (!tostring || + (truelen = tostring(bp, maxp, m->values[j], m->vlens[j], depth)) + < 0){ + cl_log(LOG_ERR, "tostring failed for field %d", j); + return HA_FAIL; + } + + CHECKROOM_INT(truelen+1); + bp +=truelen; + + strcat(bp,"\n"); + bp++; + } + if (needhead){ + CHECKROOM_CONST(MSG_END); + strcat(bp, MSG_END); + bp += strlen(MSG_END); + } + + CHECKROOM_INT(1); + bp[0] = EOS; + + return(HA_OK); +} + + +char * +msg2string(const struct ha_msg *m) +{ + void *buf; + int len; + + AUDITMSG(m); + if (m->nfields <= 0) { + cl_log(LOG_ERR, "msg2string: Message with zero fields"); + return(NULL); + } + + len = get_stringlen(m); + + buf = malloc(len); + + if (buf == NULL) { + cl_log(LOG_ERR, "msg2string: no memory for string"); + return(NULL); + } + + if (msg2string_buf(m, buf, len ,0, NEEDHEAD) != HA_OK){ + cl_log(LOG_ERR, "msg2string: msg2string_buf failed"); + free(buf); + return(NULL); + } + + return(buf); +} + +gboolean +must_use_netstring(const struct ha_msg* msg) +{ + int i; + + for ( i = 0; i < msg->nfields; i++){ + if (msg->types[i] == FT_COMPRESS + || msg->types[i] == FT_UNCOMPRESS + || msg->types[i] == FT_STRUCT){ + return TRUE; + } + } + + return FALSE; + +} + +#define use_netstring(m) (msgfmt == MSGFMT_NETSTRING || must_use_netstring(m)) + +static char* +msg2wirefmt_ll(struct ha_msg*m, size_t* len, int flag) +{ + + int wirefmtlen; + int i; + int netstg = use_netstring(m); + + wirefmtlen = netstg ? get_netstringlen(m) : get_stringlen(m); + if (use_traditional_compression + &&(flag & MSG_NEEDCOMPRESS) + && (wirefmtlen> compression_threshold) + && cl_get_compress_fns() != NULL){ + return cl_compressmsg(m, len); + } + + if (flag & MSG_NEEDCOMPRESS){ + for (i=0 ;i < m->nfields; i++){ + int type = m->types[i]; + if (fieldtypefuncs[type].prepackaction){ + fieldtypefuncs[type].prepackaction(m,i); + } + } + } + + wirefmtlen = netstg ? get_netstringlen(m) : get_stringlen(m); + if (wirefmtlen >= MAXMSG){ + if (flag&MSG_NEEDCOMPRESS) { + if (cl_get_compress_fns() != NULL) + return cl_compressmsg(m, len); + } + cl_log(LOG_ERR, "%s: msg too big(%d)", + __FUNCTION__, wirefmtlen); + return NULL; + } + if (flag & MSG_NEEDAUTH) { + return msg2netstring(m, len); + } + return msg2wirefmt_noac(m, len); +} + +char* +msg2wirefmt(struct ha_msg*m, size_t* len){ + return msg2wirefmt_ll(m, len, MSG_NEEDAUTH|MSG_NEEDCOMPRESS); +} + +char* +msg2wirefmt_noac(struct ha_msg*m, size_t* len) +{ + if (use_netstring(m)) { + return msg2netstring_noauth(m, len); + } else { + char *tmp; + + tmp = msg2string(m); + if(tmp == NULL){ + *len = 0; + return NULL; + } + *len = strlen(tmp) + 1; + return tmp; + } +} + +static struct ha_msg* +wirefmt2msg_ll(const char* s, size_t length, int need_auth) +{ + + size_t startlen; + struct ha_msg* msg = NULL; + + + startlen = sizeof(MSG_START)-1; + + if (startlen > length){ + return NULL; + } + + if (strncmp( s, MSG_START, startlen) == 0) { + msg = string2msg_ll(s, length, 0, need_auth); + goto out; + } + + startlen = sizeof(MSG_START_NETSTRING) - 1; + + if (startlen > length){ + return NULL; + } + + if (strncmp(s, MSG_START_NETSTRING, startlen) == 0) { + msg = netstring2msg(s, length, need_auth); + goto out; + } + +out: + if (msg && is_compressed_msg(msg)){ + struct ha_msg* ret; + if ((ret = cl_decompressmsg(msg))==NULL){ + cl_log(LOG_ERR, "decompress msg failed"); + ha_msg_del(msg); + return NULL; + } + ha_msg_del(msg); + return ret; + } + return msg; + +} + + + + +struct ha_msg* +wirefmt2msg(const char* s, size_t length, int flag) +{ + return wirefmt2msg_ll(s, length, flag& MSG_NEEDAUTH); + +} + + +void +cl_log_message (int log_level, const struct ha_msg *m) +{ + int j; + + if(m == NULL) { + cl_log(log_level, "MSG: No message to dump"); + return; + } + + cl_log(log_level, "MSG: Dumping message with %d fields", m->nfields); + + for (j=0; j < m->nfields; ++j) { + + if(m->types[j] < DIMOF(fieldtypefuncs)){ + fieldtypefuncs[m->types[j]].display(log_level, j, + m->names[j], + m->values[j], + m->vlens[j]); + } + } +} + + +#ifdef TESTMAIN_MSGS +int +main(int argc, char ** argv) +{ + struct ha_msg* m; + while (!feof(stdin)) { + if ((m=controlfifo2msg(stdin)) != NULL) { + fprintf(stderr, "Got message!\n"); + if (msg2stream(m, stdout) == HA_OK) { + fprintf(stderr, "Message output OK!\n"); + }else{ + fprintf(stderr, "Could not output Message!\n"); + } + }else{ + fprintf(stderr, "Could not get message!\n"); + } + } + return(0); +} +#endif diff --git a/lib/clplumbing/cl_msg_types.c b/lib/clplumbing/cl_msg_types.c new file mode 100644 index 0000000..56cf56a --- /dev/null +++ b/lib/clplumbing/cl_msg_types.c @@ -0,0 +1,1736 @@ +/* + * Heartbeat message type functions + * + * Copyright (C) 2004 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + +#include <lha_internal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <sys/utsname.h> +#include <ha_msg.h> +#include <unistd.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/ipc.h> +#include <clplumbing/base64.h> +#include <clplumbing/netstring.h> +#include <glib.h> + +#ifndef MAX +# define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif + + +extern const char* FT_strings[]; + + + +#define NL_TO_SYM 0 +#define SYM_TO_NL 1 + +static const int SPECIAL_SYMS[MAXDEPTH] = { + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 15, + 16, + 17, + 18, +}; + +#define SPECIAL_SYM 19 + +struct ha_msg* string2msg_ll(const char*, size_t, int, int); +int compose_netstring(char*, const char*, const char*, size_t, size_t*); +int msg2netstring_buf(const struct ha_msg*, char*, size_t, size_t*); +int struct_display_print_spaces(char *buffer, int depth); +int struct_display_as_xml(int log_level, int depth, struct ha_msg *data, + const char *prefix, gboolean formatted); +int struct_stringlen(size_t namlen, size_t vallen, const void* value); +int struct_netstringlen(size_t namlen, size_t vallen, const void* value); +int convert_nl_sym(char* s, int len, char sym, int direction); +int bytes_for_int(int x); + +int +bytes_for_int(int x) +{ + int len = 0; + if(x < 0) { + x = 0-x; + len=1; + } + while(x > 9) { + x /= 10; + len++; + } + return len+1; +} + +int +netstring_extra(int x) +{ + return (bytes_for_int(x) + x + 2); +} + +int +get_netstringlen(const struct ha_msg *m) +{ + int i; + int total_len =0 ; + + if (m == NULL){ + cl_log(LOG_ERR, "get_netstringlen:" + "asking netstringlen of a NULL message"); + return 0; + } + + total_len = sizeof(MSG_START_NETSTRING) + + sizeof(MSG_END_NETSTRING) -2 ; + + + for (i = 0; i < m->nfields; i++){ + int len; + len = fieldtypefuncs[m->types[i]].netstringlen(m->nlens[i], + m->vlens[i], + m->values[i]); + total_len += netstring_extra(len); + } + + + return total_len; + + +} + + + +int +get_stringlen(const struct ha_msg *m) +{ + int i; + int total_len =0 ; + + if (m == NULL){ + cl_log(LOG_ERR, "get_stringlen:" + "asking stringlen of a NULL message"); + return 0; + } + + total_len = sizeof(MSG_START)+sizeof(MSG_END)-1; + + for (i = 0; i < m->nfields; i++){ + total_len += fieldtypefuncs[m->types[i]].stringlen(m->nlens[i], + m->vlens[i], + m->values[i]); + } + + return total_len; +} + + + +/* + compute the total size of the resulted string + if the string list is to be converted + +*/ +size_t +string_list_pack_length(const GList* _list) +{ + size_t i; + GList* list = UNCONST_CAST_POINTER(GList *, _list); + size_t total_length = 0; + + if (list == NULL){ + cl_log(LOG_WARNING, "string_list_pack_length():" + "list is NULL"); + + return 0; + } + for (i = 0; i < g_list_length(list) ; i++){ + + int len = 0; + char * element = g_list_nth_data(list, i); + if (element == NULL){ + cl_log(LOG_ERR, "string_list_pack_length: " + "%luth element of the string list is NULL" + , (unsigned long)i); + return 0; + } + len = strlen(element); + total_length += bytes_for_int(len) + len + 2; + /* 2 is for ":" and "," */ + } + return total_length ; +} + + + +/* + convert a string list into a single string + the format to convert is similar to netstring: + <length> ":" <the actual string> "," + + for example, a list containing two strings "abc" "defg" + will be converted into + 3:abc,4:defg, + @list: the list to be converted + @buf: the converted string should be put in the @buf + @maxp: max pointer +*/ + + +int +string_list_pack(GList* list, char* buf, char* maxp) +{ + size_t i; + char* p = buf; + + for (i = 0; i < g_list_length(list) ; i++){ + char * element = g_list_nth_data(list, i); + int element_len; + + if (element == NULL){ + cl_log(LOG_ERR, "string_list_pack: " + "%luth element of the string list is NULL" + , (unsigned long)i); + return 0; + } + element_len = strlen(element); + if (p + 2 + element_len + bytes_for_int(element_len)> maxp){ + cl_log(LOG_ERR, "%s: memory out of boundary", + __FUNCTION__); + return 0; + } + p += sprintf(p, "%d:%s,", element_len,element); + + if (p > maxp){ + cl_log(LOG_ERR, "string_list_pack: " + "buffer overflowed "); + return 0; + } + } + + + return (p - buf); +} + + + +/* + this is reverse process of pack_string_list +*/ +GList* +string_list_unpack(const char* packed_str_list, size_t length) +{ + GList* list = NULL; + const char* psl = packed_str_list; + const char * maxp= packed_str_list + length; + int len = 0; + + + while(TRUE){ + char* buf; + + if (*psl == '\0' || psl >= maxp){ + break; + } + + if (sscanf( psl, "%d:", &len) <= 0 ){ + break; + } + + if (len <=0){ + cl_log(LOG_ERR, "unpack_string_list:" + "reading len of string error"); + if (list){ + list_cleanup(list); + } + return NULL; + } + + while (*psl != ':' && *psl != '\0' ){ + psl++; + } + + if (*psl == '\0'){ + break; + } + + psl++; + + buf = malloc(len + 1); + if (buf == NULL){ + cl_log(LOG_ERR, "unpack_string_list:" + "unable to allocate buf"); + if(list){ + list_cleanup(list); + } + return NULL; + + } + memcpy(buf, psl, len); + buf[len] = '\0'; + list = g_list_append(list, buf); + psl +=len; + + if (*psl != ','){ + cl_log(LOG_ERR, "unpack_string_list:" + "wrong format, s=%s",packed_str_list); + } + psl++; + } + + return list; + +} + + +static void +string_memfree(void* value) +{ + if (value){ + free(value); + }else { + cl_log(LOG_ERR, "string_memfree: " + "value is NULL"); + } + + + return; +} + +static void +binary_memfree(void* value) +{ + string_memfree(value); +} + + +static void +struct_memfree( void* value) +{ + struct ha_msg* msg; + + if (!value){ + cl_log(LOG_ERR, + "value is NULL"); + return ; + } + + msg = (struct ha_msg*) value; + ha_msg_del(msg); + return ; +} + +static void +list_memfree(void* value) +{ + + if (!value){ + cl_log(LOG_ERR, + "value is NULL"); + return ; + } + + list_cleanup(value); + +} + + +static void* +binary_dup(const void* value, size_t len) +{ + + char* dupvalue; + + /* 0 byte binary field is allowed*/ + + if (value == NULL && len > 0){ + cl_log(LOG_ERR, "binary_dup:" + "NULL value with non-zero len=%d", + (int)len); + return NULL; + } + + dupvalue = malloc(len + 1); + if (dupvalue == NULL){ + cl_log(LOG_ERR, "binary_dup:" + "malloc failed"); + return NULL; + } + + if (value != NULL) { + memcpy(dupvalue, value, len); + } + + dupvalue[len] =0; + + return dupvalue; +} + +static void* +string_dup(const void* value, size_t len) +{ + return binary_dup(value, len); +} + + +static void* +struct_dup(const void* value, size_t len) +{ + char* dupvalue; + + (void)len; + + if (!value){ + cl_log(LOG_ERR,"struct_dup:" + "value is NULL"); + return NULL ; + } + + + dupvalue = (void*)ha_msg_copy((const struct ha_msg*)value); + if (dupvalue == NULL){ + cl_log(LOG_ERR, "struct_dup: " + "ha_msg_copy failed"); + return NULL; + } + + return dupvalue; +} + +static GList* +list_copy(const GList* _list) +{ + size_t i; + GList* newlist = NULL; + GList* list = UNCONST_CAST_POINTER(GList *, _list); + + for (i = 0; i < g_list_length(list); i++){ + char* dup_element = NULL; + char* element = g_list_nth_data(list, i); + int len; + if (element == NULL){ + cl_log(LOG_WARNING, "list_copy:" + "element is NULL"); + continue; + } + + len = strlen(element); + dup_element= malloc(len + 1); + if ( dup_element == NULL){ + cl_log(LOG_ERR, "duplicate element failed"); + continue; + } + memcpy(dup_element, element,len); + dup_element[len] = 0; + + newlist = g_list_append(newlist, dup_element); + } + + return newlist; +} + +static void* +list_dup( const void* value, size_t len) +{ + char* dupvalue; + + (void)len; + if (!value){ + cl_log(LOG_ERR,"struct_dup:" + "value is NULL"); + return NULL ; + } + + dupvalue = (void*)list_copy((const GList*)value); + + if (!dupvalue){ + cl_log(LOG_ERR, "list_dup: " + "list_copy failed"); + return NULL; + } + + return dupvalue; +} + + +static void +general_display(int log_level, int seq, char* name, void* value, int vlen, int type) +{ + int netslen; + int slen; + HA_MSG_ASSERT(value); + HA_MSG_ASSERT(name); + + slen = fieldtypefuncs[type].stringlen(strlen(name), vlen, value); + netslen = fieldtypefuncs[type].netstringlen(strlen(name), vlen, value); + cl_log(log_level, "MSG[%d] : [(%s)%s=%p(%d %d)]", + seq, FT_strings[type], + name, value, slen, netslen); + +} +static void +string_display(int log_level, int seq, char* name, void* value, int vlen) +{ + HA_MSG_ASSERT(name); + HA_MSG_ASSERT(value); + cl_log(log_level, "MSG[%d] : [%s=%s]", + seq, name, (const char*)value); + return; +} + +static void +binary_display(int log_level, int seq, char* name, void* value, int vlen) +{ + general_display(log_level, seq, name, value, vlen, FT_BINARY); +} + +static void +compress_display(int log_level, int seq, char* name, void* value, int vlen){ + general_display(log_level, seq, name, value, vlen, FT_COMPRESS); +} + + +static void +general_struct_display(int log_level, int seq, char* name, void* value, int vlen, int type) +{ + int slen; + int netslen; + + HA_MSG_ASSERT(name); + HA_MSG_ASSERT(value); + + slen = fieldtypefuncs[type].stringlen(strlen(name), vlen, value); + netslen = fieldtypefuncs[type].netstringlen(strlen(name), vlen, value); + + cl_log(log_level, "MSG[%d] : [(%s)%s=%p(%d %d)]", + seq, FT_strings[type], + name, value, slen, netslen); + if(cl_get_string((struct ha_msg*) value, F_XML_TAGNAME) == NULL) { + cl_log_message(log_level, (struct ha_msg*) value); + } else { + /* use a more friendly output format for nested messages */ + struct_display_as_xml(log_level, 0, value, NULL, TRUE); + } +} +static void +struct_display(int log_level, int seq, char* name, void* value, int vlen) +{ + general_struct_display(log_level, seq, name, value, vlen, FT_STRUCT); + +} +static void +uncompress_display(int log_level, int seq, char* name, void* value, int vlen) +{ + general_struct_display(log_level, seq, name, value, vlen, FT_UNCOMPRESS); +} + +#define update_buffer_head(buffer, len) if(len < 0) { \ + (*buffer) = EOS; return -1; \ + } else { \ + buffer += len; \ + } + +int +struct_display_print_spaces(char *buffer, int depth) +{ + int lpc = 0; + int spaces = 2*depth; + /* <= so that we always print 1 space - prevents problems with syslog */ + for(lpc = 0; lpc <= spaces; lpc++) { + if(sprintf(buffer, "%c", ' ') < 1) { + return -1; + } + buffer += 1; + } + return lpc; +} + +int +struct_display_as_xml( + int log_level, int depth, struct ha_msg *data, + const char *prefix, gboolean formatted) +{ + int lpc = 0; + int printed = 0; + gboolean has_children = FALSE; + char print_buffer[1000]; + char *buffer = print_buffer; + const char *name = cl_get_string(data, F_XML_TAGNAME); + + if(data == NULL) { + return 0; + + } else if(name == NULL) { + cl_log(LOG_WARNING, "Struct at depth %d had no name", depth); + cl_log_message(log_level, data); + return 0; + } + + if(formatted) { + printed = struct_display_print_spaces(buffer, depth); + update_buffer_head(buffer, printed); + } + + printed = sprintf(buffer, "<%s", name); + update_buffer_head(buffer, printed); + + for (lpc = 0; lpc < data->nfields; lpc++) { + const char *prop_name = data->names[lpc]; + const char *prop_value = data->values[lpc]; + if(data->types[lpc] != FT_STRING) { + continue; + } else if(prop_name == NULL) { + continue; + } else if(prop_name[0] == '_' && prop_name[1] == '_') { + continue; + } + printed = sprintf(buffer, " %s=\"%s\"", prop_name, prop_value); + update_buffer_head(buffer, printed); + } + + for (lpc = 0; lpc < data->nfields; lpc++) { + if(data->types[lpc] == FT_STRUCT) { + has_children = TRUE; + break; + } + } + + printed = sprintf(buffer, "%s>", has_children==0?"/":""); + update_buffer_head(buffer, printed); + cl_log(log_level, "%s%s", prefix?prefix:"", print_buffer); + buffer = print_buffer; + + if(has_children == FALSE) { + return 0; + } + + for (lpc = 0; lpc < data->nfields; lpc++) { + if(data->types[lpc] != FT_STRUCT) { + continue; + } else if(0 > struct_display_as_xml( + log_level, depth+1, data->values[lpc], + prefix, formatted)) { + return -1; + } + } + + if(formatted) { + printed = struct_display_print_spaces(buffer, depth); + update_buffer_head(buffer, printed); + } + cl_log(log_level, "%s%s</%s>", prefix?prefix:"", print_buffer, name); + + return 0; +} + + + + +static int +liststring(GList* list, char* buf, int maxlen) +{ + char* p = buf; + char* maxp = buf + maxlen; + size_t i; + + for ( i = 0; i < g_list_length(list); i++){ + char* element = g_list_nth_data(list, i); + if (element == NULL) { + cl_log(LOG_ERR, "%luth element is NULL " + , (unsigned long)i); + return HA_FAIL; + } else{ + if (i == 0){ + p += sprintf(p,"%s",element); + }else{ + p += sprintf(p," %s",element); + } + + } + if ( p > maxp){ + cl_log(LOG_ERR, "buffer overflow"); + return HA_FAIL; + } + + } + + return HA_OK; +} + +static void +list_display(int log_level, int seq, char* name, void* value, int vlen) +{ + GList* list; + char buf[MAXLENGTH]; + + HA_MSG_ASSERT(name); + HA_MSG_ASSERT(value); + + list = value; + + if (liststring(list, buf, MAXLENGTH) != HA_OK){ + cl_log(LOG_ERR, "liststring error"); + return; + } + cl_log(log_level, "MSG[%d] :[(%s)%s=%s]", + seq, FT_strings[FT_LIST], + name, buf); + + return ; + +} + + +/* + * This function changes each new line in the input string + * into a special symbol, or the other way around + */ + +int +convert_nl_sym(char* s, int len, char sym, int direction) +{ + int i; + + if (direction != NL_TO_SYM && direction != SYM_TO_NL){ + cl_log(LOG_ERR, "convert_nl_sym(): direction not defined!"); + return(HA_FAIL); + } + + + for (i = 0; i < len && s[i] != EOS; i++){ + + switch(direction){ + case NL_TO_SYM : + if (s[i] == '\n'){ + s[i] = sym; + break; + } + + if (s[i] == sym){ + cl_log(LOG_ERR + , "convert_nl_sym(): special symbol \'0x%x\' (%c) found" + " in string at %d (len=%d)", s[i], s[i], i, len); + i -= 10; + if(i < 0) { + i = 0; + } + cl_log(LOG_ERR, "convert_nl_sym(): %s", s + i); + return(HA_FAIL); + } + + break; + + case SYM_TO_NL: + + if (s[i] == sym){ + s[i] = '\n'; + break; + } + break; + default: + /* nothing, never executed*/; + + } + } + + return(HA_OK); +} + + +/* + * This function changes each new line in the input string + * into a special symbol, or the other way around + */ + +static int +convert(char* s, int len, int depth, int direction) +{ + + if (direction != NL_TO_SYM && direction != SYM_TO_NL){ + cl_log(LOG_ERR, "convert(): direction not defined!"); + return(HA_FAIL); + } + + + if (depth >= MAXDEPTH ){ + cl_log(LOG_ERR, "convert(): MAXDEPTH exceeded: %d", depth); + return(HA_FAIL); + } + + return convert_nl_sym(s, len, SPECIAL_SYMS[depth], direction); +} + + + + +static int +string_stringlen(size_t namlen, size_t vallen, const void* value) +{ + + HA_MSG_ASSERT(value); +/* HA_MSG_ASSERT( vallen == strlen(value)); */ + return namlen + vallen + 2; +} + +static int +binary_netstringlen(size_t namlen, size_t vallen, const void* value) +{ + int length; + + HA_MSG_ASSERT(value); + + length = 3 + namlen + 1 + vallen; + + return length; +} + + +static int +string_netstringlen(size_t namlen, size_t vallen, const void* value) +{ + HA_MSG_ASSERT(value); + HA_MSG_ASSERT( vallen == strlen(value)); + + return binary_netstringlen(namlen, vallen, value); +} + + +static int +binary_stringlen(size_t namlen, size_t vallen, const void* value) +{ + HA_MSG_ASSERT(value); + + return namlen + B64_stringlen(vallen) + 2 + 3; + /*overhead 3 is for type*/ +} + + + + + +int +struct_stringlen(size_t namlen, size_t vallen, const void* value) +{ + const struct ha_msg* childmsg; + + HA_MSG_ASSERT(value); + + (void)vallen; + childmsg = (const struct ha_msg*)value; + + return namlen +2 + 3 + get_stringlen(childmsg); + /*overhead 3 is for type*/ +} + +int +struct_netstringlen(size_t namlen, size_t vallen, const void* value) +{ + + int ret; + const struct ha_msg* childmsg; + int len; + + HA_MSG_ASSERT(value); + + (void)vallen; + childmsg = (const struct ha_msg*)value; + + len = get_netstringlen(childmsg); + + ret = 3 + namlen + 1 + len; + + return ret; + +} + + +static int +list_stringlen(size_t namlen, size_t vallen, const void* value) +{ + (void)value; + return namlen + vallen + 2 + 3; + /*overhead 3 is for type (FT_STRUCT)*/ +} + +static int +list_netstringlen(size_t namlen, size_t vallen, const void* value) +{ + int ret; + const GList* list; + + list = (const GList*)value; + + ret = 3 + namlen + 1 + string_list_pack_length(list); + + return ret; + +} + +static int +add_binary_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + + int next; + + if ( !msg || !name || !value + || depth < 0){ + cl_log(LOG_ERR, "add_binary_field:" + " invalid input argument"); + return HA_FAIL; + } + + + next = msg->nfields; + msg->names[next] = name; + msg->nlens[next] = namelen; + msg->values[next] = value; + msg->vlens[next] = vallen; + msg->types[next] = FT_BINARY; + msg->nfields++; + + return HA_OK; +} + + +static int +add_struct_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + int next; + + if ( !msg || !name || !value + || depth < 0){ + cl_log(LOG_ERR, "add_struct_field:" + " invalid input argument"); + return HA_FAIL; + } + + next = msg->nfields; + msg->names[next] = name; + msg->nlens[next] = namelen; + msg->values[next] = value; + msg->vlens[next] = vallen; + msg->types[next] = FT_STRUCT; + + msg->nfields++; + + return HA_OK; +} + + + + +static int +add_list_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + int next; + int j; + GList* list = NULL; + + if ( !msg || !name || !value + || namelen <= 0 + || vallen <= 0 + || depth < 0){ + cl_log(LOG_ERR, "add_list_field:" + " invalid input argument"); + return HA_FAIL; + } + + + for (j=0; j < msg->nfields; ++j) { + if (strcmp(name, msg->names[j]) == 0) { + break; + } + } + + if ( j >= msg->nfields){ + list = (GList*)value; + + next = msg->nfields; + msg->names[next] = name; + msg->nlens[next] = namelen; + msg->values[next] = value; + msg->vlens[next] = vallen; + msg->types[next] = FT_LIST; + msg->nfields++; + + } else if( msg->types[j] == FT_LIST ){ + + GList* oldlist = (GList*) msg->values[j]; + int listlen; + size_t i; + + for ( i =0; i < g_list_length((GList*)value); i++){ + list = g_list_append(oldlist, g_list_nth_data((GList*)value, i)); + } + if (list == NULL){ + cl_log(LOG_ERR, "add_list_field:" + " g_list_append() failed"); + return HA_FAIL; + } + + listlen = string_list_pack_length(list); + + msg->values[j] = list; + msg->vlens[j] = listlen; + g_list_free((GList*)value); /*we don't free each element + because they are used in new list*/ + free(name); /* this name is no longer necessary + because msg->names[j] is reused */ + + } else { + cl_log(LOG_ERR, "field already exists " + "with differnt type=%d", msg->types[j]); + return (HA_FAIL); + } + + return HA_OK; +} + + +static int +add_compress_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + + int next; + + if ( !msg || !name || !value + || depth < 0){ + cl_log(LOG_ERR, "add_binary_field:" + " invalid input argument"); + return HA_FAIL; + } + + + next = msg->nfields; + msg->names[next] = name; + msg->nlens[next] = namelen; + msg->values[next] = value; + msg->vlens[next] = vallen; + msg->types[next] = FT_COMPRESS; + msg->nfields++; + + return HA_OK; +} + + + + +static int +add_uncompress_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + int next; + + if ( !msg || !name || !value + || depth < 0){ + cl_log(LOG_ERR, "add_struct_field:" + " invalid input argument"); + return HA_FAIL; + } + + next = msg->nfields; + msg->names[next] = name; + msg->nlens[next] = namelen; + msg->values[next] = value; + msg->vlens[next] = vallen; + msg->types[next] = FT_UNCOMPRESS; + + msg->nfields++; + + return HA_OK; +} + + + +/*print a string to a string, + pretty simple one :) +*/ +static int +str2string(char* buf, char* maxp, void* value, size_t len, int depth) +{ + char* s = value; + char* p = buf; + (void)maxp; + (void)depth; + + if (buf + len > maxp){ + cl_log(LOG_ERR, "%s: out of boundary", + __FUNCTION__); + return -1; + } + + if ( strlen(s) != len){ + cl_log(LOG_ERR, "str2string:" + "the input len != string length"); + return -1; + } + + strcat(buf, s); + while(*p != '\0'){ + if (*p == '\n'){ + *p = SPECIAL_SYM; + } + p++; + } + + return len; + +} + +/*print a binary value to a string using base64 + library +*/ + +static int +binary2string(char* buf, char* maxp, void* value, size_t len, int depth) +{ + int baselen; + int truelen = 0; + + (void)depth; + baselen = B64_stringlen(len) + 1; + + if ( buf + baselen > maxp){ + cl_log(LOG_ERR, "binary2string: out of bounary"); + return -1; + } + + truelen = binary_to_base64(value, len, buf, baselen); + + return truelen; +} + +/*print a struct(ha_msg) to a string + @depth denotes the number of recursion +*/ + +static int +struct2string(char* buf, char* maxp, void* value, size_t len, int depth) +{ + + struct ha_msg* msg = value; + int baselen = get_stringlen(msg); + + (void)len; + + if ( buf + baselen > maxp){ + cl_log(LOG_ERR, "struct2string: not enough buffer" + "for the struct to generate a string"); + return -1; + } + + if (msg2string_buf(msg, buf ,baselen,depth + 1, NEEDHEAD) + != HA_OK){ + + cl_log(LOG_ERR + , "struct2string(): msg2string_buf for" + " child message failed"); + return -1; + + } + + if (convert(buf, baselen, depth, NL_TO_SYM) != HA_OK){ + cl_log(LOG_ERR , "struct2string(): convert failed"); + return -1; + } + + return strlen(buf); +} + + + + +/* print a list to a string + */ + +static int +list2string(char* buf, char* maxp, void* value, size_t len, int depth) +{ + int listlen; + GList* list = (GList*) value; + + (void)len; + (void)depth; + listlen = string_list_pack(list , buf, maxp); + if (listlen == 0){ + cl_log(LOG_ERR, "list2string():" + "string_list_pack() failed"); + return -1; + } + + return listlen; + +} + + +static int +string2str(void* value, size_t len, int depth, void** nv, size_t* nlen ) +{ + if (!value || !nv || !nlen || depth < 0){ + cl_log(LOG_ERR, "string2str:invalid input"); + return HA_FAIL; + } + + if (convert_nl_sym(value, len, SPECIAL_SYM, SYM_TO_NL) != HA_OK){ + cl_log(LOG_ERR, "string2str:convert_nl_sym" + "from symbol to new line failed"); + return HA_FAIL; + } + *nv = value; + *nlen = len; + + return HA_OK; +} + +static int +string2binary(void* value, size_t len, int depth, void** nv, size_t* nlen) +{ + char tmpbuf[MAXLINE]; + char* buf = NULL; + int buf_malloced = 0; + int ret = HA_FAIL; + if (len > MAXLINE){ + buf = malloc(len); + if (buf == NULL){ + cl_log(LOG_ERR, "%s: malloc failed", + __FUNCTION__); + goto out; + } + buf_malloced = 1; + }else { + buf = &tmpbuf[0]; + } + + if (value == NULL && len == 0){ + *nv = NULL; + *nlen = 0; + ret = HA_OK; + goto out; + } + + if ( !value || !nv || depth < 0){ + cl_log(LOG_ERR, "string2binary:invalid input"); + ret = HA_FAIL; + goto out; + } + + memcpy(buf, value, len); + *nlen = base64_to_binary(buf, len, value, len); + + *nv = value; + ret = HA_OK; + out: + if (buf_malloced && buf){ + free(buf); + } + return ret; +} + +static int +string2struct(void* value, size_t vallen, int depth, void** nv, size_t* nlen) +{ + + struct ha_msg *tmpmsg; + + if (!value || !nv || depth < 0){ + cl_log(LOG_ERR, "string2struct:invalid input"); + return HA_FAIL; + } + + + if (convert(value, vallen, depth,SYM_TO_NL) != HA_OK){ + cl_log(LOG_ERR + , "ha_msg_addraw_ll(): convert failed"); + return(HA_FAIL); + } + + tmpmsg = string2msg_ll(value, vallen,depth + 1, 0); + if (tmpmsg == NULL){ + cl_log(LOG_ERR + , "string2struct()" + ": string2msg_ll failed"); + return(HA_FAIL); + } + free(value); + *nv = tmpmsg; + *nlen = 0; + + return HA_OK; + +} + +static int +string2list(void* value, size_t vallen, int depth, void** nv, size_t* nlen) +{ + GList* list; + + if (!value || !nv || !nlen || depth < 0){ + cl_log(LOG_ERR, "string2struct:invalid input"); + return HA_FAIL; + } + + list = string_list_unpack(value, vallen); + if (list == NULL){ + cl_log(LOG_ERR, "ha_msg_addraw_ll():" + "unpack_string_list failed: %s", (char*)value); + return(HA_FAIL); + } + free(value); + + *nv = (void*)list; + *nlen = string_list_pack_length(list); + + return HA_OK; + +} + +static int +fields2netstring(char* sp, char* smax, char* name, size_t nlen, + void* value, size_t vallen, int type, size_t* comlen) +{ + size_t fieldlen; + size_t slen; + int ret = HA_OK; + char* sp_save = sp; + char* tmpsp; + + fieldlen = fieldtypefuncs[type].netstringlen(nlen, vallen, value); + /* this check seems to be superfluous because of the next one + if (fieldlen > MAXMSG){ + cl_log(LOG_INFO, "%s: field too big(%d)", __FUNCTION__, (int)fieldlen); + return HA_FAIL; + } + */ + tmpsp = sp + netstring_extra(fieldlen); + if (tmpsp > smax){ + cl_log(LOG_ERR, "%s: memory out of boundary, tmpsp=%p, smax=%p", + __FUNCTION__, tmpsp, smax); + return HA_FAIL; + } + sp += sprintf(sp , "%d:(%d)%s=", (int)fieldlen, type, name); + switch (type){ + + case FT_STRING: + case FT_BINARY: + case FT_COMPRESS: + memcpy(sp, value, vallen); + slen = vallen; + break; + + case FT_UNCOMPRESS: + case FT_STRUCT: + { + struct ha_msg* msg = (struct ha_msg*) value; + /* infinite recursion? Must say that I got lost at + * this point + */ + ret = msg2netstring_buf(msg, sp,get_netstringlen(msg), + &slen); + break; + } + case FT_LIST: + { + + char buf[MAXLENGTH]; + GList* list = NULL; + int tmplen; + + list = (GList*) value; + + tmplen = string_list_pack_length(list); + if (tmplen >= MAXLENGTH){ + cl_log(LOG_ERR, + "string list length exceeds limit"); + return(HA_FAIL); + } + + if (string_list_pack(list, buf, buf + MAXLENGTH) + != tmplen ){ + cl_log(LOG_ERR, + "packing string list return wrong length"); + return(HA_FAIL); + } + + + memcpy(sp, buf, tmplen); + slen = tmplen; + ret = HA_OK; + break; + } + + default: + ret = HA_FAIL; + cl_log(LOG_ERR, "%s: Wrong type (%d)", __FUNCTION__,type); + } + + if (ret == HA_FAIL){ + return ret; + } + + sp +=slen; + *sp++ = ','; + *comlen = sp - sp_save; + + return HA_OK; + + +} + + +static int +netstring2string(const void* value, size_t vlen, void** retvalue, size_t* ret_vlen) +{ + char* dupvalue; + + if (value == NULL && vlen == 0){ + *retvalue = NULL; + *ret_vlen = 0; + return HA_OK; + } + + if ( !value || !retvalue || !ret_vlen){ + cl_log(LOG_ERR, " netstring2string:" + "invalid input arguments"); + return HA_FAIL; + } + + dupvalue = binary_dup(value, vlen); + if (!dupvalue){ + cl_log(LOG_ERR, "netstring2string:" + "duplicating value failed"); + return HA_FAIL; + } + + *retvalue = dupvalue; + *ret_vlen = vlen; + + return HA_OK; +} + +static int +netstring2binary(const void* value, size_t vlen, void** retvalue, size_t* ret_vlen) +{ + return netstring2string(value, vlen, retvalue, ret_vlen); + +} + +static int +netstring2struct(const void* value, size_t vlen, void** retvalue, size_t* ret_vlen) +{ + struct ha_msg* msg; + + if ( !value || !retvalue || !ret_vlen){ + cl_log(LOG_ERR, " netstring2struct:" + "invalid input arguments"); + return HA_FAIL; + } + + msg = netstring2msg(value, vlen, 0); + if (!msg){ + cl_log(LOG_ERR, "netstring2struct:" + "netstring2msg failed"); + return HA_FAIL; + } + + *retvalue =(void* ) msg; + *ret_vlen = 0; + + return HA_OK; + +} + +static int +netstring2list(const void* value, size_t vlen, void** retvalue, size_t* ret_vlen) +{ + GList* list; + + if ( !value || !retvalue || !ret_vlen){ + cl_log(LOG_ERR, " netstring2struct:" + "invalid input arguments"); + return HA_FAIL; + } + + + list = string_list_unpack(value, vlen); + if (list == NULL){ + cl_log(LOG_ERR, "netstring2list: unpacking string list failed"); + cl_log(LOG_INFO, "thisbuf=%s", (const char*)value); + return HA_FAIL; + } + *retvalue = (void*)list; + + *ret_vlen = string_list_pack_length(list); + + return HA_OK; + +} + + + + + +static int +add_string_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + + size_t internal_type; + unsigned long tmptype; + char *cp_name = NULL; + size_t cp_namelen; + size_t cp_vallen; + void *cp_value = NULL; + int next; + + if ( !msg || !name || !value + || namelen <= 0 + || depth < 0){ + cl_log(LOG_ERR, "add_string_field:" + " invalid input argument"); + return HA_FAIL; + } + + + + internal_type = FT_STRING; + if (name[0] == '('){ + + int nlo = 3; /*name length overhead */ + if (name[2] != ')'){ + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_ERR + , "ha_msg_addraw_ll(): no closing parentheses"); + } + return(HA_FAIL); + } + tmptype = name[1] - '0'; + if (tmptype < 0 || tmptype > 9) { + cl_log(LOG_ERR + , "ha_msg_addraw_ll(): not a number."); + return(HA_FAIL); + } + + internal_type = tmptype; + + if (internal_type == FT_STRING){ + cl_log(LOG_ERR + , "ha_msg_addraw_ll(): wrong type"); + return(HA_FAIL); + } + + cp_name = name; + cp_namelen = namelen - nlo ; + memmove(cp_name, name + nlo, namelen - nlo); + cp_name[namelen - nlo] = EOS; + }else { + cp_name = name; + cp_namelen = namelen; + + } + + if(internal_type < DIMOF(fieldtypefuncs)){ + int (*stringtofield)(void*, size_t, int depth, void**, size_t* ); + int (*fieldstringlen)( size_t, size_t, const void*); + + stringtofield= fieldtypefuncs[internal_type].stringtofield; + + if (!stringtofield || stringtofield(value, vallen, depth, &cp_value, &cp_vallen) != HA_OK){ + cl_log(LOG_ERR, "add_string_field: stringtofield failed"); + return HA_FAIL; + } + + fieldstringlen = fieldtypefuncs[internal_type].stringlen; + if (!fieldstringlen || + fieldstringlen(cp_namelen, cp_vallen, cp_value) <= 0 ){ + + cl_log(LOG_ERR, "add_string_field: stringlen failed"); + return HA_FAIL; + } + + } else { + cl_log(LOG_ERR, "add_string_field():" + " wrong type %lu", (unsigned long)internal_type); + return HA_FAIL; + } + + + next = msg->nfields; + msg->values[next] = cp_value; + msg->vlens[next] = cp_vallen; + msg->names[next] = cp_name; + msg->nlens[next] = cp_namelen; + msg->types[next] = internal_type; + msg->nfields++; + + return HA_OK; + +} + +static int +uncompress2compress(struct ha_msg* msg, int index) +{ + char* buf; + size_t buflen = MAXMSG; + int rc = HA_FAIL; + + buf = malloc(buflen); + if (!buf) { + cl_log(LOG_ERR, "%s: failed to allocate buffer", + __FUNCTION__); + goto err; + } + + if (msg->types[index] != FT_UNCOMPRESS){ + cl_log(LOG_ERR, "%s: the %dth field is not FT_UNCOMPRESS type", + __FUNCTION__, index); + goto err; + } + + + if (cl_compress_field(msg, index, buf, &buflen) != HA_OK){ + cl_log(LOG_ERR, "%s: compressing %dth field failed", __FUNCTION__, index); + goto err; + } + + rc = cl_msg_replace(msg, index, buf, buflen, FT_COMPRESS); + +err: + if (buf) { + free(buf); + } + + return rc; +} + +static int +compress2uncompress(struct ha_msg* msg, int index) +{ + char *buf = NULL; + size_t buflen = MAXUNCOMPRESSED; + struct ha_msg* msgfield; + int err = HA_FAIL; + + buf = malloc(buflen); + + if (!buf) { + cl_log(LOG_ERR, "%s: allocating buffer for uncompression failed", + __FUNCTION__); + goto out; + } + + if (cl_decompress_field(msg, index, buf, &buflen) != HA_OK){ + cl_log(LOG_ERR, "%s: compress field failed", + __FUNCTION__); + goto out; + } + + msgfield = wirefmt2msg(buf, buflen, 0); + if (msgfield == NULL){ + cl_log(LOG_ERR, "%s: wirefmt to msg failed", + __FUNCTION__); + goto out; + } + + err = cl_msg_replace(msg, index, (char*)msgfield, 0, FT_UNCOMPRESS); + + ha_msg_del(msgfield); + +out: + if (buf) { + free(buf); + } + + return err; +} + +/* + * string FT_STRING + * string is the basic type used in heartbeat, it is used for printable ascii value + * + * binary FT_BINARY + * binary means the value can be any binary value, including non-printable ascii value + * + * struct FT_STRUCT + * struct means the value is also an ha_msg (actually it is a pointer to an ha message) + * + * list FT_LIST + * LIST means the value is a GList. Right now we only suppport a Glist of strings + * + * compress FT_COMPRESS + * This field and the next one(FT_UNCOMPRESS) is designed to optimize compression in message + * (see cl_compression.c for more about compression). This field is similar to the binary field. + * It stores a compressed field, which will be an ha_msg if uncompressed. Most of time this field + * act like a binary field until compress2uncompress() is called. That function will be called + * when someone calls cl_get_struct() to get this field value. After that this field is converted + * to a new type FT_UNCOMPRESS + * + * uncompress FT_UNCOMPRESS + * As said above, this field is used to optimize compression. This field is similar to the struct + * field. It's value is a pointer to an ha_msg. This field will be converted to a new type FT_COMPRESS + * when msg2wirefmt() is called, where uncompress2compress is called to do the field compression + */ + +struct fieldtypefuncs_s fieldtypefuncs[NUM_MSG_TYPES]= + { {string_memfree, string_dup, string_display, add_string_field, + string_stringlen,string_netstringlen, str2string,fields2netstring, + string2str, netstring2string, NULL, NULL}, + + {binary_memfree, binary_dup, binary_display, add_binary_field, + binary_stringlen,binary_netstringlen, binary2string,fields2netstring, + string2binary, netstring2binary, NULL, NULL}, + + {struct_memfree, struct_dup, struct_display, add_struct_field, + struct_stringlen, struct_netstringlen, struct2string, fields2netstring, \ + string2struct, netstring2struct, NULL, NULL}, + + {list_memfree, list_dup, list_display, add_list_field, + list_stringlen, list_netstringlen, list2string, fields2netstring, + string2list, netstring2list, NULL, NULL}, + + {binary_memfree, binary_dup, compress_display, add_compress_field, + binary_stringlen,binary_netstringlen, binary2string ,fields2netstring, + string2binary , netstring2binary, NULL, compress2uncompress}, /*FT_COMPRESS*/ + + {struct_memfree, struct_dup, uncompress_display, add_uncompress_field, + struct_stringlen, struct_netstringlen, NULL , fields2netstring, + NULL , netstring2struct, uncompress2compress, NULL}, /*FT_UNCOMPRSS*/ + }; + + diff --git a/lib/clplumbing/cl_netstring.c b/lib/clplumbing/cl_netstring.c new file mode 100644 index 0000000..f4040e0 --- /dev/null +++ b/lib/clplumbing/cl_netstring.c @@ -0,0 +1,570 @@ +/* + * netstring implementation + * + * Copyright (c) 2003 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <ha_msg.h> +#include <unistd.h> +#include <clplumbing/ipc.h> +#include <clplumbing/netstring.h> +#include <clplumbing/base64.h> +#include <assert.h> +#include <ctype.h> + +/* + * Avoid sprintf. Use snprintf instead, even if you count your bytes. + * It can detect calculation errors (if used properly) + * and will not make the security audit tools crazy. + */ + +#define MAX_AUTH_BYTES 64 + + +int msg2netstring_buf(const struct ha_msg*, char*, size_t, size_t*); +int compose_netstring(char*, const char*, const char*, size_t, size_t*); +int is_auth_netstring(const char*, size_t, const char*, size_t); +char* msg2netstring(const struct ha_msg*, size_t*); +int process_netstring_nvpair(struct ha_msg* m, const char* nvpair, int nvlen); +extern int bytes_for_int(int x); +extern const char * FT_strings[]; + +static int (*authmethod)(int whichauth +, const void * data +, size_t datalen +, char * authstr +, size_t authlen) = NULL; + +void +cl_set_authentication_computation_method(int (*method)(int whichauth +, const void * data +, size_t datalen +, char * authstr +, size_t authlen)) +{ + authmethod = method; +} + +int cl_parse_int(const char *sp, const char *smax, int* len); + +int +cl_parse_int(const char *sp, const char *smax, int* len) +{ + char ch = 0; + int offset = 0; + *len = 0; + + errno = 0; + for( ; sp+offset < smax; offset++) { + ch = sp[offset] - '0'; + if(ch > 9) { /* ch >= 0 is implied by the data type*/ + break; + } + *len *= 10; + *len += ch; + } + + if(offset == 0) { + cl_log(LOG_ERR, + "cl_parse_int: Couldn't parse an int from: %.5s", sp); + } + return offset; +} + +int +compose_netstring(char * s, const char * smax, const char* data, + size_t len, size_t* comlen) +{ + + char * sp = s; + + /* 2 == ":" + "," */ + if (s + len + 2 + bytes_for_int(len) > smax) { + cl_log(LOG_ERR, + "netstring pointer out of boundary(compose_netstring)"); + return(HA_FAIL); + } + + sp += sprintf(sp, "%ld:", (long)len); + + if(data){ + memcpy(sp, data, len); + } + sp += len; + *sp++ = ','; + + *comlen = sp - s; + + return(HA_OK); +} + + + +/* Converts a message into a netstring */ + +int +msg2netstring_buf(const struct ha_msg *m, char *s, + size_t buflen, size_t * slen) +{ + int i; + char * sp; + char * smax; + int ret = HA_OK; + + sp = s; + smax = s + buflen; + + strcpy(sp, MSG_START_NETSTRING); + + sp += strlen(MSG_START_NETSTRING); + + for (i=0; i < m->nfields; i++) { + size_t flen; + int tmplen; + + /* some of these functions in its turn invoke us again */ + ret = fieldtypefuncs[m->types[i]].tonetstring(sp, + smax, + m->names[i], + m->nlens[i], + m->values[i], + m->vlens[i], + m->types[i], + &flen); + + if (ret != HA_OK){ + cl_log(LOG_ERR, "encoding msg to netstring failed"); + cl_log_message(LOG_ERR, m); + return ret; + } + + tmplen = netstring_extra(fieldtypefuncs[m->types[i]].netstringlen(m->nlens[i], + m->vlens[i], + m->values[i])); + + if (flen != tmplen ){ + cl_log(LOG_ERR,"netstring len discrepency: actual usage is %d bytes" + "it should use %d", (int)flen, tmplen); + } + sp +=flen; + + } + + if (sp + strlen(MSG_END_NETSTRING) > smax){ + cl_log(LOG_ERR, "%s: out of boundary for MSG_END_NETSTRING", + __FUNCTION__); + return HA_FAIL; + } + strcpy(sp, MSG_END_NETSTRING); + sp += sizeof(MSG_END_NETSTRING) -1; + + if (sp > smax){ + cl_log(LOG_ERR, + "msg2netstring: exceed memory boundary sp =%p smax=%p", + sp, smax); + return(HA_FAIL); + } + + *slen = sp - s; + return(HA_OK); +} + + +int get_netstringlen_auth(const struct ha_msg* m); + +int get_netstringlen_auth(const struct ha_msg* m) +{ + int len = get_netstringlen(m) + MAX_AUTH_BYTES; + return len; +} + + + +static char * +msg2netstring_ll(const struct ha_msg *m, size_t * slen, int need_auth) +{ + int len; + char* s; + int authnum; + char authtoken[MAXLINE]; + char authstring[MAXLINE]; + char* sp; + size_t payload_len; + char* smax; + + len= get_netstringlen_auth(m) + 1; + + /* use MAXUNCOMPRESSED for the in memory size check */ + if (len >= MAXUNCOMPRESSED){ + cl_log(LOG_ERR, "%s: msg is too large; len=%d," + " MAX msg allowed=%d", __FUNCTION__, len, MAXUNCOMPRESSED); + return NULL; + } + + s = calloc(1, len); + if (!s){ + cl_log(LOG_ERR, "%s: no memory for netstring", __FUNCTION__); + return(NULL); + } + + smax = s + len; + + if (msg2netstring_buf(m, s, len, &payload_len) != HA_OK){ + cl_log(LOG_ERR, "%s: msg2netstring_buf() failed", __FUNCTION__); + free(s); + return(NULL); + } + + sp = s + payload_len; + + if ( need_auth && authmethod){ + int auth_strlen; + + authnum = authmethod(-1, s, payload_len, authtoken,sizeof(authtoken)); + if (authnum < 0){ + cl_log(LOG_WARNING + , "Cannot compute message authentication!"); + free(s); + return(NULL); + } + + sprintf(authstring, "%d %s", authnum, authtoken); + auth_strlen = strlen(authstring); + if (sp + 2 + auth_strlen + bytes_for_int(auth_strlen) >= smax){ + cl_log(LOG_ERR, "%s: out of boundary for auth", __FUNCTION__); + free(s); + return NULL; + } + sp += sprintf(sp, "%ld:%s,", (long)strlen(authstring), authstring); + + } + *slen = sp - s; + + return(s); +} + +char * +msg2netstring(const struct ha_msg *m, size_t * slen) +{ + char* ret; + ret = msg2netstring_ll(m, slen, TRUE); + + return ret; + +} +char * +msg2netstring_noauth(const struct ha_msg *m, size_t * slen) +{ + char * ret; + + ret = msg2netstring_ll(m, slen, FALSE); + + return ret; +} + + +/* + * Peel one string off in a netstring + */ + +static int +peel_netstring(const char * s, const char * smax, int* len, + const char ** data, int* parselen ) +{ + int offset = 0; + const char * sp = s; + + if (sp >= smax){ + return(HA_FAIL); + } + + offset = cl_parse_int(sp, smax, len); + if (*len < 0 || offset <= 0){ + cl_log(LOG_ERR, "peel_netstring: Couldn't parse an int starting at: %.5s", sp); + return(HA_FAIL); + } + + sp = sp+offset; + while (*sp != ':' && sp < smax) { + sp ++; + } + + if (sp >= smax) { + return(HA_FAIL); + } + + sp ++; + + *data = sp; + + sp += (*len); + if (sp >= smax) { + return(HA_FAIL); + } + if (*sp != ','){ + return(HA_FAIL); + } + sp++; + + *parselen = sp - s; + + return(HA_OK); +} + + +int +process_netstring_nvpair(struct ha_msg* m, const char* nvpair, int nvlen) +{ + + const char *name; + int nlen; + const char *ns_value; + int ns_vlen; + void *value; + size_t vlen; + int type; + void (*memfree)(void*); + int ret = HA_OK; + + assert(*nvpair == '('); + nvpair++; + + type = nvpair[0] - '0'; + nvpair++; + + /* if this condition is no longer true, change the above to: + * nvpair += cl_parse_int(nvpair, nvpair+nvlen, &type) + */ + assert(type >= 0 && type < 10); + + assert(*nvpair == ')'); + nvpair++; + + if ((nlen = strcspn(nvpair, EQUAL)) <= 0 + || nvpair[nlen] != '=') { + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING + , "%s: line doesn't contain '='", __FUNCTION__); + cl_log(LOG_INFO, "%s", nvpair); + } + return(HA_FAIL); + } + + name = nvpair; + ns_value = name +nlen + 1; + ns_vlen = nvpair + nvlen - ns_value -3 ; + if (fieldtypefuncs[type].netstringtofield(ns_value,ns_vlen, &value, &vlen) != HA_OK){ + cl_log(LOG_ERR, "netstringtofield failed in %s", __FUNCTION__); + return HA_FAIL; + + } + + memfree = fieldtypefuncs[type].memfree; + + if (ha_msg_nadd_type(m , name, nlen, value, vlen,type) + != HA_OK) { + cl_log(LOG_ERR, "ha_msg_nadd fails(netstring2msg_rec)"); + ret = HA_FAIL; + } + + + if (memfree && value){ + memfree(value); + } else{ + cl_log(LOG_ERR, "netstring2msg_rec:" + "memfree or ret_value is NULL"); + ret= HA_FAIL; + } + + return ret; + + +} + + +/* Converts a netstring into a message*/ +static struct ha_msg * +netstring2msg_rec(const char *s, size_t length, int* slen) +{ + struct ha_msg* ret = NULL; + const char * sp = s; + const char * smax = s + length; + int startlen; + int endlen; + + if ((ret = ha_msg_new(0)) == NULL){ + return(NULL); + } + + startlen = sizeof(MSG_START_NETSTRING)-1; + + if (strncmp(sp, MSG_START_NETSTRING, startlen) != 0) { + /* This can happen if the sender gets killed */ + /* at just the wrong time... */ + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING, "netstring2msg_rec: no MSG_START"); + ha_msg_del(ret); + } + return(NULL); + }else{ + sp += startlen; + } + + endlen = sizeof(MSG_END_NETSTRING) - 1; + + while (sp < smax && strncmp(sp, MSG_END_NETSTRING, endlen) !=0 ){ + + const char *nvpair; + int nvlen; + int parselen; + + if (peel_netstring(sp , smax, &nvlen, &nvpair,&parselen) != HA_OK){ + cl_log(LOG_ERR + , "%s:peel_netstring fails for name/value pair", __FUNCTION__); + cl_log(LOG_ERR, "sp=%s", sp); + ha_msg_del(ret); + return(NULL); + } + sp += parselen; + + if (process_netstring_nvpair(ret, nvpair, nvlen) != HA_OK){ + cl_log(LOG_ERR, "%s: processing nvpair failed", __FUNCTION__); + return HA_FAIL; + } + + } + + + sp += sizeof(MSG_END_NETSTRING) -1; + *slen = sp - s; + return(ret); + +} + + +struct ha_msg * +netstring2msg(const char* s, size_t length, int needauth) +{ + const char *sp; + struct ha_msg *msg; + const char *smax = s + length; + int parselen; + int authlen; + const char *authstring; + /*actual string length used excluding auth string*/ + int slen = 0; /* assign to keep compiler happy */ + + msg = netstring2msg_rec(s, length, &slen); + + if (needauth == FALSE || !authmethod){ + goto out; + } + + sp = s + slen; + + if (peel_netstring(sp , smax, &authlen, &authstring, &parselen) !=HA_OK){ + cl_log(LOG_ERR, + "peel_netstring() error in getting auth string"); + cl_log(LOG_ERR, "sp=%s", sp); + cl_log(LOG_ERR, "s=%s", s); + ha_msg_del(msg); + return(NULL); + } + + if (sp + parselen > smax){ + cl_log(LOG_ERR, " netstring2msg: smax passed"); + ha_msg_del(msg); + return NULL; + } + + if (!is_auth_netstring(s, slen, authstring,authlen) ){ + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_ERR + , "netstring authentication" + " failed, s=%s, autotoken=%s" + , s, authstring); + cl_log_message(LOG_ERR, msg); + } + ha_msg_del(msg); + return(NULL); + } + + out: + return msg; +} + + + + +int +is_auth_netstring(const char * datap, size_t datalen, + const char * authstring, size_t authlen) +{ + + char authstr[MAXLINE]; /* A copy of authstring */ + int authwhich; + char authtoken[MAXLINE]; + + + /* + * If we don't have any authentication method - everything is authentic... + */ + if (!authmethod) { + return TRUE; + } + strncpy(authstr, authstring, MAXLINE); + authstr[authlen] = 0; + if (sscanf(authstr, "%d %s", &authwhich, authtoken) != 2) { + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING + , "Bad/invalid netstring auth string"); + } + return(0); + } + + memset(authstr, 0, MAXLINE); + if (authmethod(authwhich, datap, datalen, authstr, MAXLINE) + != authwhich) { + + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING + , "Invalid authentication [%d] in message!" + , authwhich); + } + return(FALSE); + } + + if (strcmp(authtoken, authstr) == 0) { + return(TRUE); + } + + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_ERR + , "authtoken does not match, authtoken=%s, authstr=%s" + , authtoken, authstr); + } + return(FALSE); +} + diff --git a/lib/clplumbing/cl_pidfile.c b/lib/clplumbing/cl_pidfile.c new file mode 100644 index 0000000..b94573b --- /dev/null +++ b/lib/clplumbing/cl_pidfile.c @@ -0,0 +1,294 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <ctype.h> +#include <string.h> +#include <clplumbing/cl_signal.h> +#include <clplumbing/cl_pidfile.h> +#include <clplumbing/lsb_exitcodes.h> + +/* + * The following information is from the Filesystem Hierarchy Standard + * version 2.1 dated 12 April, 2000. + * + * 5.6 /var/lock : Lock files + * Lock files should be stored within the /var/lock directory structure. + * Device lock files, such as the serial device lock files that were originally + * found in either /usr/spool/locks or /usr/spool/uucp, must now be stored in + * /var/lock. The naming convention which must be used is LCK.. followed by + * the base name of the device file. For example, to lock /dev/cua0 the file + * LCK..cua0 would be created. + * + * The format used for device lock files must be the HDB UUCP lock file format. + * The HDB format is to store the process identifier (PID) as a ten byte + * ASCII decimal number, with a trailing newline. For example, if process 1230 + * holds a lock file, it would contain the eleven characters: space, space, + * space, space, space, space, one, two, three, zero, and newline. + * Then, anything wishing to use /dev/cua0 can read the lock file and act + * accordingly (all locks in /var/lock should be world-readable). + * + * + * PERMISSIONS NOTE: + * Different linux distributions set the mode of the lock directory differently + * Any process which wants to create lock files must have write permissions + * on FILE_LOCK_D (probably /var/lock). For things like the heartbeat API + * code, this may mean allowing the uid of the processes that use this API + * to join group uucp, or making the binaries setgid to uucp. + */ + +/* The code in this file originally written by Guenther Thomsen */ +/* Somewhat mangled by Alan Robertson */ + +/* + * Lock a tty (using lock files, see linux `man 2 open` close to O_EXCL) + * serial_device has to be _the complete path_, i.e. including '/dev/' to the + * special file, which denotes the tty to lock -tho + * return 0 on success, + * -1 if device is locked (lockfile exists and isn't stale), + * -2 for temporarily failure, try again, + * other negative value, if something unexpected happend (failure anyway) + */ + + +/* This is what the FHS standard specifies for the size of our lock file */ +#define LOCKSTRLEN 11 +#include <clplumbing/cl_log.h> +static int IsRunning(long pid) +{ + int rc = 0; + long mypid; + int running = 0; + char proc_path[PATH_MAX], exe_path[PATH_MAX], myexe_path[PATH_MAX]; + + /* check if pid is running */ + if (CL_KILL(pid, 0) < 0 && errno == ESRCH) { + goto bail; + } + +#ifndef HAVE_PROC_PID + return 1; +#endif + + /* check to make sure pid hasn't been reused by another process */ + snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe", pid); + + rc = readlink(proc_path, exe_path, PATH_MAX-1); + if(rc < 0) { + cl_perror("Could not read from %s", proc_path); + goto bail; + } + exe_path[rc] = 0; + + mypid = (unsigned long) getpid(); + + snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe", mypid); + rc = readlink(proc_path, myexe_path, PATH_MAX-1); + if(rc < 0) { + cl_perror("Could not read from %s", proc_path); + goto bail; + } + myexe_path[rc] = 0; + + if(strcmp(exe_path, myexe_path) == 0) { + running = 1; + } + + bail: + return running; +} + +static int +DoLock(const char *filename) +{ + char lf_name[256], tf_name[256], buf[LOCKSTRLEN+1]; + int fd; + long pid, mypid; + int rc; + struct stat sbuf; + + mypid = (unsigned long) getpid(); + + snprintf(lf_name, sizeof(lf_name), "%s",filename); + + snprintf(tf_name, sizeof(tf_name), "%s.%lu", + filename, mypid); + + if ((fd = open(lf_name, O_RDONLY)) >= 0) { + if (fstat(fd, &sbuf) >= 0 && sbuf.st_size < LOCKSTRLEN) { + sleep(1); /* if someone was about to create one, + * give'm a sec to do so + * Though if they follow our protocol, + * this won't happen. They should really + * put the pid in, then link, not the + * other way around. + */ + } + if (read(fd, buf, sizeof(buf)) < 1) { + /* lockfile empty -> rm it and go on */; + } else { + if (sscanf(buf, "%lu", &pid) < 1) { + /* lockfile screwed up -> rm it and go on */ + } else { + if (pid > 1 && (getpid() != pid) + && IsRunning(pid)) { + /* is locked by existing process + * -> give up */ + close(fd); + return -1; + } else { + /* stale lockfile -> rm it and go on */ + } + } + } + unlink(lf_name); + close(fd); + } + if ((fd = open(tf_name, O_CREAT | O_WRONLY | O_EXCL, 0644)) < 0) { + /* Hmmh, why did we fail? Anyway, nothing we can do about it */ + return -3; + } + + /* Slight overkill with the %*d format ;-) */ + snprintf(buf, sizeof(buf), "%*lu\n", LOCKSTRLEN-1, mypid); + + if (write(fd, buf, LOCKSTRLEN) != LOCKSTRLEN) { + /* Again, nothing we can do about this */ + rc = -3; + close(fd); + goto out; + } + close(fd); + + switch (link(tf_name, lf_name)) { + case 0: + if (stat(tf_name, &sbuf) < 0) { + /* something weird happened */ + rc = -3; + break; + } + if (sbuf.st_nlink < 2) { + /* somehow, it didn't get through - NFS trouble? */ + rc = -2; + break; + } + rc = 0; + break; + case EEXIST: + rc = -1; + break; + default: + rc = -3; + } + out: + unlink(tf_name); + return rc; +} + +static int +DoUnlock(const char * filename) +{ + char lf_name[256]; + + snprintf(lf_name, sizeof(lf_name), "%s", filename); + + return unlink(lf_name); +} + + +int +cl_read_pidfile(const char*filename) +{ + long pid = 0; + + pid = cl_read_pidfile_no_checking(filename); + + if (pid < 0){ + return - LSB_STATUS_STOPPED; + } + + if (IsRunning(pid)){ + return pid; + }else{ + return -LSB_STATUS_VAR_PID; + } +} + + +int +cl_read_pidfile_no_checking(const char*filename) +{ + int fd; + long pid = 0; + char buf[LOCKSTRLEN+1]; + if ((fd = open(filename, O_RDONLY)) < 0) { + return -1; + } + + if (read(fd, buf, sizeof(buf)) < 1) { + close(fd); + return -1; + } + + if (sscanf(buf, "%lu", &pid) <= 0) { + close(fd); + return -1; + } + + if (pid <= 0){ + close(fd); + return -1; + } + close(fd); + return pid; +} + + +int +cl_lock_pidfile(const char *filename) +{ + if (filename == NULL) { + errno = EFAULT; + return -3; + } + return DoLock(filename); +} + +/* + * Unlock a file (remove its lockfile) + * do we need to check, if its (still) ours? No, IMHO, if someone else + * locked our line, it's his fault -tho + * returns 0 on success + * <0 if some failure occured + */ + +int +cl_unlock_pidfile(const char *filename) +{ + if (filename == NULL) { + errno = EFAULT; + return -3; + } + + return(DoUnlock(filename)); +} diff --git a/lib/clplumbing/cl_plugin.c b/lib/clplumbing/cl_plugin.c new file mode 100644 index 0000000..c039a35 --- /dev/null +++ b/lib/clplumbing/cl_plugin.c @@ -0,0 +1,140 @@ + +/* + * cl_plugin.c: This file handle plugin loading and deleting + * + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <lha_internal.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <unistd.h> +#include <assert.h> +#include <glib.h> +#include <ha_msg.h> +#include <clplumbing/netstring.h> +#include <pils/plugin.h> +#include <pils/generic.h> +/* #include <stonith/stonith.h> */ +/* #include <stonith/stonith_plugin.h> */ +#include <clplumbing/cl_plugin.h> + +#define MAXTYPES 16 +#define MAXTYPELEN 64 + +static GHashTable* funcstable[MAXTYPES]; + +static PILPluginUniv* plugin_univ = NULL; + +static PILGenericIfMgmtRqst reqs[] = + { + {"compress", &funcstable[0], NULL, NULL, NULL}, + {"HBcoms", &funcstable[1], NULL, NULL, NULL}, + {"HBauth", &funcstable[2], NULL, NULL, NULL}, + {"RAExec", &funcstable[3], NULL, NULL, NULL}, + {"quorum", &funcstable[4], NULL, NULL, NULL}, + {"tiebreaker", &funcstable[5], NULL, NULL, NULL}, + {"quorumd", &funcstable[6], NULL, NULL, NULL}, + {NULL, NULL, NULL, NULL, NULL} + }; + +static int +init_pluginsys(void){ + + if (plugin_univ) { + return TRUE; + } + + plugin_univ = NewPILPluginUniv(HA_PLUGIN_DIR); + + if (plugin_univ) { + if (PILLoadPlugin(plugin_univ, PI_IFMANAGER, "generic", reqs) + != PIL_OK){ + cl_log(LOG_ERR, "generic plugin load failed\n"); + DelPILPluginUniv(plugin_univ); + plugin_univ = NULL; + } + }else{ + cl_log(LOG_ERR, "pi univ creation failed\n"); + } + return plugin_univ != NULL; + +} + +int +cl_remove_plugin(const char* type, const char* pluginname) +{ + return HA_OK; +} + +void* +cl_load_plugin(const char* type, const char* pluginname) +{ + void* funcs = NULL; + int i = 0; + GHashTable** table = NULL; + + while (reqs[i].iftype != NULL){ + if ( strcmp(reqs[i].iftype,type) != 0){ + i++; + continue; + } + + table = reqs[i].ifmap; + break; + } + + if (table == NULL){ + cl_log(LOG_ERR, "%s: function table not found",__FUNCTION__); + return NULL; + } + + if (!init_pluginsys()){ + cl_log(LOG_ERR, "%s: init plugin universe failed", __FUNCTION__); + return NULL; + } + + if ((funcs = g_hash_table_lookup(*table, pluginname)) + == NULL){ + if (PILPluginExists(plugin_univ, type, pluginname) == PIL_OK){ + PIL_rc rc; + rc = PILLoadPlugin(plugin_univ, type, pluginname, NULL); + if (rc != PIL_OK){ + cl_log(LOG_ERR, + "Cannot load plugin %s[%s]", + pluginname, + PIL_strerror(rc)); + return NULL; + } + funcs = g_hash_table_lookup(*table, + pluginname); + } + + } + if (funcs == NULL){ + cl_log(LOG_ERR, "%s: module(%s) not found", + __FUNCTION__, pluginname); + return NULL; + } + + return funcs; + +} + diff --git a/lib/clplumbing/cl_poll.c b/lib/clplumbing/cl_poll.c new file mode 100644 index 0000000..789eb1a --- /dev/null +++ b/lib/clplumbing/cl_poll.c @@ -0,0 +1,809 @@ +#include <lha_internal.h> +#include <stdlib.h> +#include <unistd.h> +/* + * Substitute poll(2) function using POSIX real time signals. + * + * The poll(2) system call often has significant latencies and realtime + * impacts (probably because of its variable length argument list). + * + * These functions let us use real time signals and sigtimedwait(2) instead + * of poll - for those files which work with real time signals. + * In the 2.4 series of Linux kernels, this does *not* include FIFOs. + * + * NOTE: We (have to) grab the SIGPOLL signal for our own purposes. + * Hope that's OK with you... + * + * Special caution: We can only incompletely simulate the difference between + * the level-triggered interface of poll(2) and the edge-triggered behavior + * of I/O signals. As a result you *must* read all previously-indicated + * incoming data before calling cl_poll() again. Otherwise you may miss + * some incoming data (and possibly hang). + * + * + * Copyright (C) 2003 IBM Corporation + * + * Author: <alanr@unix.sh> + * + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + **************************************************************************/ + + +#define __USE_GNU 1 +# include <fcntl.h> +#undef __USE_GNU + +#include <errno.h> +#include <string.h> +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/cl_poll.h> +#include <clplumbing/cl_signal.h> + + + +/* Turn on to log odd realtime behavior */ + +#define TIME_CALLS 1 +#ifdef TIME_CALLS +# include <clplumbing/longclock.h> +# include <clplumbing/cl_log.h> +#endif + +static int debug = 0; + +int /* Slightly sleazy... */ +cl_glibpoll(GPollFD* ufds, guint nfsd, gint timeout) +{ + (void)debug; + return cl_poll((struct pollfd*)ufds, nfsd, timeout); +} + +#if defined (F_SETSIG) && defined(F_SETOWN) && defined (O_ASYNC) +# define HAVE_FCNTL_F_SETSIG +#endif + +#ifndef HAVE_FCNTL_F_SETSIG + +/* + * Dummy cl_poll() and cl_poll_ignore() functions for systems where + * we don't have all the support we need. + */ + +int +cl_poll(struct pollfd *fds, unsigned int nfds, int timeout) +{ + return poll(fds, (nfds_t)nfds, timeout); +} + +int +cl_poll_ignore(int fd) +{ + return 0; +} + +#else /* HAVE_FCNTL_F_SETSIG */ +static void dump_fd_info(struct pollfd *fds, unsigned int nfds, int timeoutms); +static void check_fd_info(struct pollfd *fds, unsigned int nfds); +static void cl_real_poll_fd(int fd); +static void cl_poll_sigpoll_overflow_sigaction(int nsig, siginfo_t* , void*); +static void cl_poll_sigpoll_overflow(void); +static int cl_poll_get_sigqlimit(void); +typedef unsigned char poll_bool; + +/* + * Here's our strategy: + * We have a set of signals which we use for these file descriptors, + * and we use sigtimedwait(2) to wait on information from these various + * signals. + * + * If we are ever asked to wait for a particular signal, then we will + * enable signals for that file descriptor, and post the events in + * our own cache. The next time you include that signal in a call + * to cl_poll(), you will get the information delivered + * to you in your cl_poll() call. + * + * If you want to stop monitoring a particular file descriptor, use + * cl_poll_ignore() for that purpose. Doing this is a good idea, but + * not fatal if omitted... + */ + +/* Information about a file descriptor we're monitoring */ + +typedef struct poll_fd_info_s { + short nsig; /* Which signal goes with it? */ + short pendevents; /* Pending events */ +}poll_info_t; + +static int max_allocated = 0; +static poll_bool* is_monitored = NULL; /* Sized by max_allocated */ +static poll_info_t* monitorinfo = NULL; /* Sized by max_allocated */ +static int cl_nsig = 0; +static gboolean SigQOverflow = FALSE; + +static int cl_init_poll_sig(struct pollfd *fds, unsigned int nfds); +static short cl_poll_assignsig(int fd); +static void cl_poll_sigaction(int nsig, siginfo_t* info, void* v); +static int cl_poll_prepsig(int nsig); + + +/* + * SignalSet is the set of all file descriptors we're monitoring. + * + * We monitor a file descriptor forever, unless you tell us not to + * by calling cl_poll_ignore(), or you (mistakenly) give it to + * us to look at in another poll call after you've closed it. + */ + +static sigset_t SignalSet; + +/* Select the signal you want us to use (must be a RT signal) */ +int +cl_poll_setsig(int nsig) +{ + if (nsig < SIGRTMIN || nsig >= SIGRTMAX) { + errno = EINVAL; + return -1; + } + if (cl_poll_prepsig(nsig) < 0) { + return -1; + } + cl_nsig = nsig; + return 0; +} + +/* + * It's harmless to call us multiple times on the same signal. + */ +static int +cl_poll_prepsig(int nsig) +{ + static gboolean setinityet=FALSE; + + if (!setinityet) { + CL_SIGEMPTYSET(&SignalSet); + cl_signal_set_simple_action(SIGPOLL + , cl_poll_sigpoll_overflow_sigaction + , NULL); + setinityet = TRUE; + } + if (CL_SIGINTERRUPT(nsig, FALSE) < 0) { + cl_perror("sig_interrupt(%d, FALSE)", nsig); + return -1; + } + if (CL_SIGADDSET(&SignalSet, nsig) < 0) { + cl_perror("sig_addset(&SignalSet, %d)", nsig); + return -1; + } + if (CL_SIGPROCMASK(SIG_BLOCK, &SignalSet, NULL) < 0) { + cl_perror("sig_sigprocmask(SIG_BLOCK, sig %d)", nsig); + return -1; + } + if (debug) { + cl_log(LOG_DEBUG + , "Signal %d belongs to us...", nsig); + cl_log(LOG_DEBUG, "cl_poll_prepsig(%d) succeeded.", nsig); + } + + return 0; +} + +#define FD_CHUNKSIZE 64 + +/* Set of events everyone must monitor whether they want to or not ;-) */ +#define CONSTEVENTS (POLLHUP|POLLERR|POLLNVAL) + +#define RECORDFDEVENT(fd, flags) (monitorinfo[fd].pendevents |= (flags)) + +/* + * Initialized our poll-simulation data structures. + * This means (among other things) registering any monitored + * file descriptors. + */ +static int +cl_init_poll_sig(struct pollfd *fds, unsigned int nfds) +{ + unsigned j; + int maxmonfd = -1; + int nmatch = 0; + + + if (cl_nsig == 0) { + cl_nsig = ((SIGRTMIN+SIGRTMAX)/2); + if (cl_poll_setsig(cl_nsig) < 0) { + return -1; + } + } + for (j=0; j < nfds; ++j) { + const int fd = fds[j].fd; + + if (fd > maxmonfd) { + maxmonfd = fd; + } + } + + /* See if we need to malloc/realloc our data structures */ + + if (maxmonfd >= max_allocated) { + int newsize; + int growthamount; + + newsize = ((maxmonfd + FD_CHUNKSIZE)/FD_CHUNKSIZE) + * FD_CHUNKSIZE; + growthamount = newsize - max_allocated; + + /* This can't happen ;-) */ + if (growthamount <= 0 || newsize <= maxmonfd) { + errno = EINVAL; + return -1; + } + + /* Allocate (more) memory! */ + + if ((is_monitored = (poll_bool*)realloc(is_monitored + , newsize * sizeof(poll_bool))) == NULL + || (monitorinfo = (poll_info_t*) realloc(monitorinfo + , newsize * sizeof(poll_info_t))) == NULL) { + + if (is_monitored) { + free(is_monitored); + is_monitored = NULL; + } + if (monitorinfo) { + free(monitorinfo); + monitorinfo = NULL; + } + max_allocated = 0; + errno = ENOMEM; + return -1; + } + memset(monitorinfo+max_allocated, 0 + , growthamount * sizeof(monitorinfo[0])); + memset(is_monitored+max_allocated, FALSE + , growthamount*sizeof(is_monitored[0])); + max_allocated = newsize; + } + + if (fds->events != 0 && debug) { + cl_log(LOG_DEBUG + , "Current event mask for fd [0] {%d} 0x%x" + , fds->fd, fds->events); + } + /* + * Examine each fd for the following things: + * Is it already monitored? + * if not, set it up for monitoring. + * Do we have events for it? + * if so, post events... + */ + + for (j=0; j < nfds; ++j) { + const int fd = fds[j].fd; + poll_info_t* moni = monitorinfo+fd; + short nsig; + int badfd = FALSE; + + is_monitored[fd] = TRUE; + + if (moni->nsig <= 0) { + nsig = cl_poll_assignsig(fd); + if (nsig < 0) { + RECORDFDEVENT(fd, POLLERR); + badfd = TRUE; + }else{ + /* Use real poll(2) to get initial + * event status + */ + moni->nsig = nsig; + cl_real_poll_fd(fd); + } + }else if (fcntl(fd, F_GETFD) < 0) { + cl_log(LOG_ERR, "bad fd(%d)", fd); + RECORDFDEVENT(fd, POLLNVAL); + badfd = TRUE; + } + + /* Look for pending events... */ + + fds[j].revents = (moni->pendevents + & (fds[j].events|CONSTEVENTS)); + + if (fds[j].revents) { + ++nmatch; + moni->pendevents &= ~(fds[j].revents); + if (debug) { + cl_log(LOG_DEBUG + , "revents for fd %d: 0x%x" + , fds[j].fd, fds[j].revents); + cl_log(LOG_DEBUG + , "events for fd %d: 0x%x" + , fds[j].fd, fds[j].events); + } + }else if (fds[j].events && debug) { + cl_log(LOG_DEBUG + , "pendevents for fd %d: 0x%x" + , fds[j].fd, moni->pendevents); + } + if (badfd) { + cl_poll_ignore(fd); + } + } + if (nmatch != 0 && debug) { + cl_log(LOG_DEBUG, "Returning %d events from cl_init_poll_sig()" + , nmatch); + } + return nmatch; +} + +/* + * Initialize our current state of the world with info from the + * real poll(2) call. + * + * We call this when we first see a particular fd, and after a signal + * queue overflow. + */ +static void +cl_real_poll_fd(int fd) +{ + struct pollfd pfd[1]; + + if (fd >= max_allocated || !is_monitored[fd]) { + return; + } + + if (debug) { + cl_log(LOG_DEBUG + , "Calling poll(2) on fd %d", fd); + } + /* Get the current state of affaris from poll(2) */ + pfd[0].fd = fd; + pfd[0].revents = 0; + pfd[0].events = ~0; + if (poll(pfd, 1, 0) >= 0) { + RECORDFDEVENT(fd, pfd[0].revents); + if (pfd[0].revents & (POLLNVAL|POLLERR)) { + cl_log(LOG_INFO, "cl_poll_real_fd(%d): error in revents [%d]" + , fd, pfd[0].revents); + } + if (debug) { + cl_log(LOG_DEBUG + , "Old news from poll(2) for fd %d: 0x%x" + , fd, pfd[0].revents); + } + }else{ + if (fcntl(fd, F_GETFL) < 0) { + cl_perror("cl_poll_real_fd(%d): F_GETFL failure" + , fd); + RECORDFDEVENT(fd, POLLNVAL); + }else{ + RECORDFDEVENT(fd, POLLERR); + } + } +} + +/* + * Assign a signal for monitoring the given file descriptor + */ + +static short +cl_poll_assignsig(int fd) +{ + int flags; + + + if (debug) { + cl_log(LOG_DEBUG + , "Signal %d monitors fd %d...", cl_nsig, fd); + } + + /* Test to see if the file descriptor is good */ + if ((flags = fcntl(fd, F_GETFL)) < 0) { + cl_perror("cl_poll_assignsig(%d) F_GETFL failure" + , fd); + return -1; + } + + /* Associate the right signal with the fd */ + + if (fcntl(fd, F_SETSIG, cl_nsig) < 0) { + cl_perror("cl_poll_assignsig(%d) F_SETSIG failure" + , fd); + return -1; + } + + /* Direct the signals to us */ + if (fcntl(fd, F_SETOWN, getpid()) < 0) { + cl_perror("cl_poll_assignsig(%d) F_SETOWN failure", fd); + return -1; + } + + /* OK... Go ahead and send us signals! */ + + if (fcntl(fd, F_SETFL, flags|O_ASYNC) < 0) { + cl_perror("cl_poll_assignsig(%d) F_SETFL(O_ASYNC) failure" + , fd); + return -1; + } + + return cl_nsig; +} + + +/* + * This is a function we call as a (fake) signal handler. + * + * It records events to our "monitorinfo" structure. + * + * Except for the cl_log() call, it could be called in a signal + * context. + */ + +static void +cl_poll_sigaction(int nsig, siginfo_t* info, void* v) +{ + int fd; + + /* What do you suppose all the various si_code values mean? */ + + fd = info->si_fd; + if (debug) { + cl_log(LOG_DEBUG + , "cl_poll_sigaction(nsig=%d fd=%d" + ", si_code=%d si_band=0x%lx)" + , nsig, fd, info->si_code + , (unsigned long)info->si_band); + } + + if (fd <= 0) { + return; + } + + + if (fd >= max_allocated || !is_monitored[fd]) { + return; + } + + /* We should not call logging functions in (real) signal handlers */ + if (nsig != monitorinfo[fd].nsig) { + cl_log(LOG_ERR, "cl_poll_sigaction called with signal %d/%d" + , nsig, monitorinfo[fd].nsig); + } + + /* Record everything as a pending event. */ + RECORDFDEVENT(fd, info->si_band); +} + + + +/* + * This is called whenever a file descriptor shouldn't be + * monitored any more. + */ +int +cl_poll_ignore(int fd) +{ + int flags; + + if (debug) { + cl_log(LOG_DEBUG + , "cl_poll_ignore(%d)", fd); + } + if (fd < 0 || fd >= max_allocated) { + errno = EINVAL; + return -1; + } + if (!is_monitored[fd]) { + return 0; + } + + is_monitored[fd] = FALSE; + memset(monitorinfo+fd, 0, sizeof(monitorinfo[0])); + + if ((flags = fcntl(fd, F_GETFL)) >= 0) { + flags &= ~O_ASYNC; + if (fcntl(fd, F_SETFL, flags) < 0) { + return -1; + } + }else{ + return flags; + } + return 0; +} + + +/* + * cl_poll: fake poll routine based on POSIX realtime signals. + * + * We want to emulate poll as exactly as possible, but poll has a couple + * of problems: scaleability, and it tends to sleep in the kernel + * because the first argument is an argument of arbitrary size, and + * generally requires allocating memory. + * + * The challenge is that poll is level-triggered, but the POSIX + * signals (and sigtimedwait(2)) are edge triggered. This is + * one of the reasons why we have the cl_real_poll_fd() function + * - to get the current "level" before we start. + * Once we have this level we can compute something like the current + * level + */ + +int +cl_poll(struct pollfd *fds, unsigned int nfds, int timeoutms) +{ + int nready; + struct timespec ts; + static const struct timespec zerotime = {0L, 0L}; + const struct timespec* itertime = &ts; + siginfo_t info; + int eventcount = 0; + unsigned int j; + int savederrno = errno; + int stw_errno; + int rc; + longclock_t starttime; + longclock_t endtime; + const int msfudge + = 2* 1000/hz_longclock(); + int mselapsed = 0; + + /* Do we have any old news to report? */ + if ((nready=cl_init_poll_sig(fds, nfds)) != 0) { + /* Return error or old news to report */ + if (debug) { + cl_log(LOG_DEBUG, "cl_poll: early return(%d)", nready); + } + return nready; + } + + /* Nothing to report yet... */ + + /* So, we'll do a sigtimedwait(2) to wait for signals + * and see if we can find something to report... + * + * cl_init_poll() prepared a set of file signals to watch... + */ + +recalcandwaitagain: + if (timeoutms >= 0) { + ts.tv_sec = timeoutms / 1000; + ts.tv_nsec = (((unsigned long)timeoutms) % 1000UL)*1000000UL; + }else{ + ts.tv_sec = G_MAXLONG; + ts.tv_nsec = 99999999UL; + } + + /* + * Perform a timed wait for any of our signals... + * + * We shouldn't sleep for any call but (possibly) the first one. + * Subsequent calls should just pick up other events without + * sleeping. + */ + + starttime = time_longclock(); + /* + * Wait up to the prescribed time for a signal. + * If we get a signal, then loop grabbing all other + * pending signals. Note that subsequent iterations will + * use &zerotime to get the minimum wait time. + */ + if (debug) { + check_fd_info(fds, nfds); + dump_fd_info(fds, nfds, timeoutms); + } +waitagain: + while (sigtimedwait(&SignalSet, &info, itertime) >= 0) { + int nsig = info.si_signo; + + /* Call signal handler to simulate signal reception */ + + cl_poll_sigaction(nsig, &info, NULL); + itertime = &zerotime; + } + stw_errno=errno; /* Save errno for later use */ + endtime = time_longclock(); + mselapsed = longclockto_ms(sub_longclock(endtime, starttime)); + +#ifdef TIME_CALLS + if (timeoutms >= 0 && mselapsed > timeoutms + msfudge) { + /* We slept too long... */ + cl_log(LOG_WARNING + , "sigtimedwait() sequence for %d ms took %d ms" + , timeoutms, mselapsed); + } +#endif + + if (SigQOverflow) { + /* OOPS! Better recover from this! */ + /* This will use poll(2) to correct our current status */ + cl_poll_sigpoll_overflow(); + } + + /* Post observed events and count them... */ + + for (j=0; j < nfds; ++j) { + int fd = fds[j].fd; + poll_info_t* moni = monitorinfo+fd; + fds[j].revents = (moni->pendevents + & (fds[j].events|CONSTEVENTS)); + if (fds[j].revents) { + ++eventcount; + moni->pendevents &= ~(fds[j].revents); + /* Make POLLHUP persistent */ + if (fds[j].revents & POLLHUP) { + moni->pendevents |= POLLHUP; + /* Don't lose input events at EOF */ + if (fds[j].events & POLLIN) { + cl_real_poll_fd(fds[j].fd); + } + } + } + } + if (eventcount == 0 && stw_errno == EAGAIN && timeoutms != 0) { + /* We probably saw an event the user didn't ask to see. */ + /* Consquently, we may have more waiting to do */ + if (timeoutms < 0) { + /* Restore our infinite wait time */ + itertime = &ts; + goto waitagain; + }else if (timeoutms > 0) { + if (mselapsed < timeoutms) { + timeoutms -= mselapsed; + goto recalcandwaitagain; + } + } + } + rc = (eventcount > 0 ? eventcount : (stw_errno == EAGAIN ? 0 : -1)); + + if (rc >= 0) { + errno = savederrno; + } + return rc; +} +/* + * Debugging routine for printing current poll arguments, etc. + */ +static void +dump_fd_info(struct pollfd *fds, unsigned int nfds, int timeoutms) +{ + unsigned j; + + cl_log(LOG_DEBUG, "timeout: %d milliseconds", timeoutms); + for (j=0; j < nfds; ++j) { + int fd = fds[j].fd; + poll_info_t* moni = monitorinfo+fd; + + cl_log(LOG_DEBUG, "fd %d flags: 0%o, signal: %d, events: 0x%x" + ", revents: 0x%x, pendevents: 0x%x" + , fd, fcntl(fd, F_GETFL), moni->nsig + , fds[j].events, fds[j].revents, moni->pendevents); + } + for (j=SIGRTMIN; j < (unsigned)SIGRTMAX; ++j) { + if (!sigismember(&SignalSet, j)) { + continue; + } + cl_log(LOG_DEBUG, "Currently monitoring RT signal %d", j); + } +} + +/* + * Debugging routine for auditing our file descriptors, etc. + */ +static void +check_fd_info(struct pollfd *fds, unsigned int nfds) +{ + unsigned j; + + for (j=0; j < nfds; ++j) { + int fd = fds[j].fd; + poll_info_t* moni = monitorinfo+fd; + + if (!sigismember(&SignalSet, moni->nsig)) { + cl_log(LOG_ERR, "SIGNAL %d not in monitored SignalSet" + , moni->nsig); + } + } + for (j=0; j < 10; ++j) { + int sig; + int flags; + int pid; + if ((flags = fcntl(j, F_GETFL)) < 0 || (flags & O_ASYNC) ==0){ + continue; + } + sig = fcntl(j, F_GETSIG); + if (sig == 0) { + cl_log(LOG_ERR, "FD %d will get SIGIO", j); + } + if (!sigismember(&SignalSet, sig)) { + cl_log(LOG_ERR, "FD %d (signal %d) is not in SignalSet" + , j, sig); + } + if (sig < SIGRTMIN || sig >= SIGRTMAX) { + cl_log(LOG_ERR, "FD %d (signal %d) is not RealTime" + , j, sig); + } + pid = fcntl(j, F_GETOWN); + if (pid != getpid()) { + cl_log(LOG_ERR, "FD %d (signal %d) owner is pid %d" + , j, sig, pid); + } + } +} + +/* Note that the kernel signalled an event queue overflow */ +static void +cl_poll_sigpoll_overflow_sigaction(int nsig, siginfo_t* info, void* v) +{ + SigQOverflow = TRUE; +} + +#define MAXQNAME "rtsig-max" +/* + * Called when signal queue overflow is suspected. + * We then use poll(2) to get the current data. It's slow, but it + * should work quite nicely. + */ +static void +cl_poll_sigpoll_overflow(void) +{ + int fd; + int limit; + + if (!SigQOverflow) { + return; + } + cl_log(LOG_WARNING, "System signal queue overflow."); + limit = cl_poll_get_sigqlimit(); + if (limit > 0) { + cl_log(LOG_WARNING, "Increase '%s'. Current limit is %d" + " (see sysctl(8)).", MAXQNAME, limit); + } + + SigQOverflow = FALSE; + + for (fd = 0; fd < max_allocated; ++fd) { + if (is_monitored[fd]) { + cl_real_poll_fd(fd); + } + } +} + +#define PSK "/proc/sys/kernel/" + +/* Get current kernel signal queue limit */ +/* This only works on Linux - but that's not a big problem... */ +static int +cl_poll_get_sigqlimit(void) +{ + int limit = -1; + int pfd; + char result[32]; + + pfd = open(PSK MAXQNAME, O_RDONLY); + if (pfd >= 0 && read(pfd, result, sizeof(result)) > 1) { + limit = atoi(result); + if (limit < 1) { + limit = -1; + } + } + if (pfd >= 0) { + close(pfd); + } + return limit; +} +#endif /* HAVE_FCNTL_F_SETSIG */ diff --git a/lib/clplumbing/cl_random.c b/lib/clplumbing/cl_random.c new file mode 100644 index 0000000..4bafcfe --- /dev/null +++ b/lib/clplumbing/cl_random.c @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * Copyright (C) 2005 International Business Machines Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include <lha_internal.h> +#include <strings.h> +#include <clplumbing/cl_misc.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/cl_misc.h> +#include <clplumbing/Gmain_timeout.h> +#include <clplumbing/cl_random.h> +#include <clplumbing/longclock.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> + +#ifdef HAVE_TIME_H +#include <time.h> +#endif +#include <sys/time.h> +#include <sys/times.h> + +/* Used to provide seed to the random number generator */ +unsigned int +cl_randseed(void) +{ + char buf[16]; + FILE* fs; + struct timeval tv; + const char * randdevname [] = {"/dev/urandom", "/dev/random"}; + int idev; +#if 0 + long horrid; +#endif + + /* + * Notes, based on reading of man pages of Solaris, FreeBSD and Linux, + * and on proof-of-concept tests on Solaris and Linux (32- and 64-bit). + * + * Reminder of a subtlety: our intention is not to return a random + * number, but rather to return a random-enough seed for future + * random numbers. So don't bother trying (e.g.) "rand()" and + * "random()". + * + * /dev/random and dev/urandom seem to be a related pair. In the + * words of the song: "You can't have one without the other". + * + * /dev/random is probably the best. But it can block. The Solaris + * implementation can apparently honour "O_NONBLOCK" and "O_NDELAY". + * But can others? For this reason, I chose not to use it at present. + * + * /dev/urandom (with the "u") is also good. This doesn't block. + * But some OSes may lack it. It is tempting to detect its presence + * with autoconf and use the result in a "hash-if" here. BUT... in + * at least one OS, its presence can vary depending upon patch levels, + * so a binary/package built on an enabled machine might hit trouble + * when run on one where it is absent. (And vice versa: a build on a + * disabled machine would be unable to take advantage of it on an + * enabled machine.) Therefore always try for it at run time. + * + * "gettimeofday()" returns a random-ish number in its millisecond + * component. + * + * -- David Lee, Jan 2006 + */ + + /* + * Each block below is logically of the form: + * if good-feature appears present { + * try feature + * if feature worked { + * return its result + * } + * } + * -- fall through to not-quite-so-good feature -- + */ + + /* + * Does any of the random device names work? + */ + for (idev=0; idev < DIMOF(randdevname); ++idev) { + fs = fopen(randdevname[idev], "r"); + if (fs == NULL) { + cl_log(LOG_INFO, "%s: Opening file %s failed" + , __FUNCTION__, randdevname[idev]); + }else{ + if (fread(buf, 1, sizeof(buf), fs)!= sizeof(buf)){ + cl_log(LOG_INFO, "%s: reading file %s failed" + , __FUNCTION__, randdevname[idev]); + fclose(fs); + }else{ + fclose(fs); + return (unsigned int)cl_binary_to_int(buf, sizeof(buf)); + } + } + } + + /* + * Try "gettimeofday()"; use its microsecond output. + * (Might it be prudent to let, say, the seconds further adjust this, + * in case the microseconds are too predictable?) + */ + if (gettimeofday(&tv, NULL) != 0) { + cl_log(LOG_INFO, "%s: gettimeofday failed", + __FUNCTION__); + }else{ + return (unsigned int) tv.tv_usec; + } + /* + * times(2) returns the number of clock ticks since + * boot. Fairly predictable, but not completely so... + */ + return (unsigned int) cl_times(); + + +#if 0 + /* + * If all else has failed, return (as a number) the address of + * something on the stack. + * Poor, but at least it has a chance of some sort of variability. + */ + horrid = (long) &tv; + return (unsigned int) horrid; /* pointer to local variable exposed */ +#endif +} + +static gboolean inityet = FALSE; + +static void +cl_init_random(void) +{ + if (inityet) + return; + + inityet=TRUE; + srand(cl_randseed()); +} + +int +get_next_random(void) +{ + if (!inityet) + cl_init_random(); + + return rand(); +} diff --git a/lib/clplumbing/cl_reboot.c b/lib/clplumbing/cl_reboot.c new file mode 100644 index 0000000..c4c3ab0 --- /dev/null +++ b/lib/clplumbing/cl_reboot.c @@ -0,0 +1,59 @@ +#include <lha_internal.h> +#include <clplumbing/cl_reboot.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#ifdef HAVE_SYS_REBOOT_H +# include <sys/reboot.h> +#endif +#ifdef HAVE_STDLIB_H +# include <stdlib.h> +#endif +#include <clplumbing/cl_log.h> +#include <clplumbing/timers.h> + +enum rebootopt { + REBOOT_DEFAULT = 0, + REBOOT_NOCOREDUMP = 1, + REBOOT_COREDUMP = 2, +}; +static enum rebootopt coredump = REBOOT_DEFAULT; + +void +cl_enable_coredump_before_reboot(gboolean yesno) +{ + coredump = (yesno ? REBOOT_COREDUMP : REBOOT_NOCOREDUMP); +} + + +void cl_reboot(int msdelaybeforereboot, const char * reason) +{ + int rebootflag = 0; + int systemrc = 0; +#ifdef RB_AUTOBOOT + rebootflag = RB_AUTOBOOT; +#endif +#ifdef RB_NOSYNC + rebootflag = RB_NOSYNC; +#endif +#ifdef RB_DUMP + if (coredump == REBOOT_COREDUMP) { + rebootflag = RB_DUMP; + } +#endif + cl_log(LOG_EMERG, "Rebooting system. Reason: %s", reason); + sync(); + mssleep(msdelaybeforereboot); +#if REBOOT_ARGS == 1 + reboot(rebootflag); +#elif REBOOT_ARGS == 2 + reboot(rebootflag, NULL); +#else +#error "reboot() call needs to take one or two args" +#endif + /* Shouldn't ever get here, but just in case... */ + systemrc=system(REBOOT " " REBOOT_OPTIONS); + cl_log(LOG_EMERG, "ALL REBOOT OPTIONS FAILED: %s returned %d" + , REBOOT " " REBOOT_OPTIONS, systemrc); + exit(1); +} diff --git a/lib/clplumbing/cl_signal.c b/lib/clplumbing/cl_signal.c new file mode 100644 index 0000000..feedb3d --- /dev/null +++ b/lib/clplumbing/cl_signal.c @@ -0,0 +1,209 @@ +/* + * cl_signal.c: signal handling routines to be used by Linux-HA programmes + * + * Copyright (C) 2002 Horms <horms@verge.net.au> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> +#include <string.h> +#include <errno.h> + +#include <clplumbing/cl_signal.h> +#include <clplumbing/cl_log.h> + + +int +cl_signal_set_handler(int sig, void (*handler)(int), sigset_t *mask +, int flags, struct sigaction *oldact) +{ + struct sigaction sa; + + sa.sa_handler = handler; + sa.sa_mask = *mask; + sa.sa_flags = flags; + + if (sigaction(sig, &sa, oldact) < 0) { + cl_perror("cl_signal_set_handler(): sigaction()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_set_simple_handler(int sig, void (*handler)(int) +, struct sigaction *oldact) +{ + struct sigaction sa; + sigset_t mask; + + if(sigemptyset(&mask) < 0) { + cl_perror("cl_signal_set_simple_handler(): " + "sigemptyset()"); + return(-1); + } + + sa.sa_handler = handler; + sa.sa_mask = mask; + sa.sa_flags = 0; + + if(sigaction(sig, &sa, oldact) < 0) { + cl_perror("cl_signal_set_simple_handler()" + ": sigaction()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_set_action(int sig, void (*action)(int, siginfo_t *, void *) +, sigset_t *mask, int flags, struct sigaction *oldact) +{ + struct sigaction sa; + + sa.sa_sigaction = action; + sa.sa_mask = *mask; + sa.sa_flags = flags; + + if(sigaction(sig, &sa, oldact) < 0) { + cl_perror("cl_signal_set_action(): sigaction()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_set_simple_action(int sig, void (*action)(int, siginfo_t *, void *) +, struct sigaction *oldact) +{ + struct sigaction sa; + sigset_t mask; + + if(sigemptyset(&mask) < 0) { + cl_perror("cl_signal_set_simple_action()" + ": sigemptyset()"); + return(-1); + } + + sa.sa_sigaction = action; + sa.sa_mask = mask; + sa.sa_flags = 0; + + if(sigaction(sig, &sa, oldact) < 0) { + cl_perror("cl_signal_set_simple_action()" + ": sigaction()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_set_interrupt(int sig, int flag) +{ + if(siginterrupt(sig, flag) < 0) { + cl_perror("cl_signal_set_interrupt(): siginterrupt()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_block(int how, int signal, sigset_t *oldset) +{ + sigset_t set; + + if(sigemptyset(&set) < 0) { + cl_perror("cl_signal_block(): sigemptyset()"); + return(-1); + } + + if(sigaddset(&set, signal) < 0) { + cl_perror("cl_signal_block(): sigaddset()"); + return(-1); + } + + if(sigprocmask(how, &set, oldset) < 0) { + cl_perror("cl_signal_block(): sigprocmask()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_block_set(int how, const sigset_t *set, sigset_t *oldset) +{ + if(sigprocmask(how, set, oldset) < 0) { + cl_perror("cl_signal_block_mask(): sigprocmask()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_set_handler_mode(const cl_signal_mode_t *mode, sigset_t *set) +{ + size_t i; + sigset_t our_set; + sigset_t *use_set; + + use_set = (set) ? set : &our_set; + + for (i=0; mode[i].sig; ++i) { + if(sigaddset(use_set, mode[i].sig) < 0) { + cl_perror("cl_signal_set_handler_mode(): " + "sigaddset() [signum=%d]", mode[i].sig); + return(-1); + } + } + + if (sigprocmask(SIG_UNBLOCK, use_set, NULL) < 0) { + cl_perror("cl_signal_set_handler_mode()" + ": sigprocmask()"); + return(-1); + } + + for (i=0; mode[i].sig; ++i) { + if(cl_signal_set_handler(mode[i].sig, mode[i]. handler + , use_set, SA_NOCLDSTOP, NULL) < 0) { + cl_log(LOG_ERR, "cl_signal_set_handler_mode(): " + "ha_set_sig_handler()"); + return(-1); + } + if(cl_signal_set_interrupt(mode[i].sig, mode[i].interrupt) < 0) { + cl_log(LOG_ERR, "cl_signal_set_handler_mode(): " + "hb_signal_interrupt()"); + return(-1); + } + } + + return(0); +} + diff --git a/lib/clplumbing/cl_syslog.c b/lib/clplumbing/cl_syslog.c new file mode 100644 index 0000000..6920bd5 --- /dev/null +++ b/lib/clplumbing/cl_syslog.c @@ -0,0 +1,149 @@ +/* + * Functions to support syslog. + * David Lee (c) 2005 + */ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> + +/* + * Some OSes already have tables to convert names into corresponding numbers. + * For instance Linux makes these available if SYSLOG_NAMES is defined. + */ +#define SYSLOG_NAMES +#include <stdlib.h> +#include <clplumbing/cl_syslog.h> + +#include <syslog.h> +#include <string.h> + +struct _syslog_code { + const char *c_name; + int c_val; +}; + +#if defined(HAVE_SYSLOG_FACILITYNAMES) + +/* + * <cl_syslog.h> will have included a table called "facilitynames" structured + * as a "struct _syslog_code" but the tag "_syslog_code" may be something else. + */ + +#else + +struct _syslog_code facilitynames[] = +{ +#ifdef LOG_AUTH + { "auth", LOG_AUTH }, + { "security", LOG_AUTH }, /* DEPRECATED */ +#endif +#ifdef LOG_AUTHPRIV + { "authpriv", LOG_AUTHPRIV }, +#endif +#ifdef LOG_CRON + { "cron", LOG_CRON }, +#endif +#ifdef LOG_DAEMON + { "daemon", LOG_DAEMON }, +#endif +#ifdef LOG_FTP + { "ftp", LOG_FTP }, +#endif +#ifdef LOG_KERN + { "kern", LOG_KERN }, +#endif +#ifdef LOG_LPR + { "lpr", LOG_LPR }, +#endif +#ifdef LOG_MAIL + { "mail", LOG_MAIL }, +#endif + +/* { "mark", INTERNAL_MARK }, * INTERNAL */ + +#ifdef LOG_NEWS + { "news", LOG_NEWS }, +#endif +#ifdef LOG_SYSLOG + { "syslog", LOG_SYSLOG }, +#endif +#ifdef LOG_USER + { "user", LOG_USER }, +#endif +#ifdef LOG_UUCP + { "uucp", LOG_UUCP }, +#endif +#ifdef LOG_LOCAL0 + { "local0", LOG_LOCAL0 }, +#endif +#ifdef LOG_LOCAL1 + { "local1", LOG_LOCAL1 }, +#endif +#ifdef LOG_LOCAL2 + { "local2", LOG_LOCAL2 }, +#endif +#ifdef LOG_LOCAL3 + { "local3", LOG_LOCAL3 }, +#endif +#ifdef LOG_LOCAL4 + { "local4", LOG_LOCAL4 }, +#endif +#ifdef LOG_LOCAL5 + { "local5", LOG_LOCAL5 }, +#endif +#ifdef LOG_LOCAL6 + { "local6", LOG_LOCAL6 }, +#endif +#ifdef LOG_LOCAL7 + { "local7", LOG_LOCAL7 }, +#endif + { NULL, -1 } +}; + +#endif /* HAVE_SYSLOG_FACILITYNAMES */ + +/* Convert string "auth" to equivalent number "LOG_AUTH" etc. */ +int +cl_syslogfac_str2int(const char *fname) +{ + int i; + + if(fname == NULL || strcmp("none", fname) == 0) { + return 0; + } + + for (i = 0; facilitynames[i].c_name != NULL; i++) { + if (strcmp(fname, facilitynames[i].c_name) == 0) { + return facilitynames[i].c_val; + } + } + return -1; +} + +/* Convert number "LOG_AUTH" to equivalent string "auth" etc. */ +const char * +cl_syslogfac_int2str(int fnum) +{ + int i; + + for (i = 0; facilitynames[i].c_name != NULL; i++) { + if (facilitynames[i].c_val == fnum) { + return facilitynames[i].c_name; + } + } + return NULL; +} diff --git a/lib/clplumbing/cl_uuid.c b/lib/clplumbing/cl_uuid.c new file mode 100644 index 0000000..d0dfcb6 --- /dev/null +++ b/lib/clplumbing/cl_uuid.c @@ -0,0 +1,180 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +/* + * uuid: wrapper declarations. + * + * heartbeat originally used "uuid" functionality by calling directly, + * and only, onto the "e2fsprogs" implementation. + * + * The run-time usages in the code have since been abstracted, funnelled + * through a thin, common interface layer: a Good Thing. + * + * Similarly, the compile-time usages of "include <uuid/uuid.h>" are + * replaced, being funnelled through a reference to this header file. + * + * This header file interfaces onto the actual underlying implementation. + * In the case of the "e2fsprogs" implementation, it is simply a stepping + * stone onto "<uuid/uuid.h>". As other implementations are accommodated, + * so their header requirements can be accommodated here. + * + * Copyright (C) 2004 David Lee <t.d.lee@durham.ac.uk> + */ + +#if defined (HAVE_UUID_UUID_H) +/* + * Almost certainly the "e2fsprogs" implementation. + */ +# include <uuid/uuid.h> + +/* elif defined(HAVE...UUID_OTHER_1 e.g. OSSP ...) */ + +/* elif defined(HAVE...UUID_OTHER_2...) */ +#else +# include <replace_uuid.h> +#endif + +#include <clplumbing/cl_uuid.h> +#include <clplumbing/cl_log.h> +#include <assert.h> + +void +cl_uuid_copy(cl_uuid_t* dst, cl_uuid_t* src) +{ + if (dst == NULL || src == NULL){ + cl_log(LOG_ERR, "cl_uuid_copy: " + "wrong argument %s is NULL", + dst == NULL?"dst":"src"); + assert(0); + } + + uuid_copy(dst->uuid, src->uuid); +} + +void +cl_uuid_clear(cl_uuid_t* uu) +{ + if (uu == NULL){ + cl_log(LOG_ERR, "cl_uuid_clear: " + "wrong argument (uu is NULL)"); + assert(0); + } + + uuid_clear(uu->uuid); + +} + +int +cl_uuid_compare(const cl_uuid_t* uu1, const cl_uuid_t* uu2) +{ + if (uu1 == NULL || uu2 == NULL){ + cl_log(LOG_ERR, "cl_uuid_compare: " + " wrong argument (%s is NULL)", + uu1 == NULL?"uu1":"uu2"); + assert(0); + } + + return uuid_compare(uu1->uuid, uu2->uuid); + +} + + + +void +cl_uuid_generate(cl_uuid_t* out) +{ + if (out == NULL){ + cl_log(LOG_ERR, "cl_uuid_generate: " + " wrong argument (out is NULL)"); + assert(0); + } + + uuid_generate(out->uuid); + +} + +int +cl_uuid_is_null(cl_uuid_t* uu) +{ + if (uu == NULL){ + cl_log(LOG_ERR, "cl_uuid_is_null: " + "wrong argument (uu is NULL)"); + assert(0); + } + + return uuid_is_null(uu->uuid); + +} + +int +cl_uuid_parse( char *in, cl_uuid_t* uu) +{ + if (in == NULL || uu == NULL){ + + cl_log(LOG_ERR, "cl_uuid_parse: " + "wrong argument (%s is NULL)", + in == NULL? "in":"uu"); + assert(0); + } + + return uuid_parse(in, uu->uuid); +} + + +void +cl_uuid_unparse(const cl_uuid_t* uu, char *out){ + + if (uu == NULL || out == NULL){ + cl_log(LOG_ERR, "cl_uuid_unparse: " + "wrong argument (%s is NULL)", + uu == NULL? "uu":"out"); + assert(0); + } + + uuid_unparse(uu->uuid, out); +} + + +guint +cl_uuid_g_hash(gconstpointer uuid_ptr) +{ + guint ret = 0U; + guint32 value32; + int index; + const unsigned char * uuid_char = uuid_ptr; + + /* It is probably not strictly necessary, but I'm trying to get the + * same hash result on all platforms. After all, the uuids are the + * same on every platform. + */ + + for (index = 0; index < sizeof(cl_uuid_t); index += sizeof(value32)) { + memcpy(&value32, uuid_char+index, sizeof (value32)); + ret += g_ntohl(value32); + } + return ret; +} +gboolean +cl_uuid_g_equal(gconstpointer uuid_ptr_a, gconstpointer uuid_ptr_b) +{ + return cl_uuid_compare(uuid_ptr_a, uuid_ptr_b) == 0; +} diff --git a/lib/clplumbing/coredumps.c b/lib/clplumbing/coredumps.c new file mode 100644 index 0000000..79da737 --- /dev/null +++ b/lib/clplumbing/coredumps.c @@ -0,0 +1,309 @@ +/* + * Basic Core dump control functions. + * + * Author: Alan Robertson + * + * Copyright (C) 2004 IBM Corporation + * + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> + +#include <unistd.h> +#include <sys/time.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <fcntl.h> +#include <pwd.h> +#include <string.h> +#include <stdlib.h> +#ifdef HAVE_SYS_PRCTL_H +# include <sys/prctl.h> +#endif +#include <clplumbing/coredumps.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/uids.h> +#include <clplumbing/cl_signal.h> + +static char * coreroot = NULL; + +/* Set the root directory of our core directory hierarchy */ +int +cl_set_corerootdir(const char * dir) +{ + if (dir == NULL || *dir != '/') { + cl_perror("Invalid dir in cl_set_corerootdir() [%s]" + , dir ? dir : "<NULL>"); + errno = EINVAL; + return -1; + } + if (coreroot != NULL) { + free(coreroot); + coreroot = NULL; + } + coreroot = strdup(dir); + if (coreroot == NULL) { + return -1; + } + return 0; +} + +/* + * Change directory to the directory our core file needs to go in + * Call after you establish the userid you're running under. + */ +int +cl_cdtocoredir(void) +{ + const char * dir = coreroot; + int rc; + struct passwd* pwent; + + if (dir == NULL) { + dir = HA_COREDIR; + } + if ((rc=chdir(dir)) < 0) { + int errsave = errno; + cl_perror("Cannot chdir to [%s]", dir); + errno = errsave; + return rc; + } + pwent = getpwuid(getuid()); + if (pwent == NULL) { + int errsave = errno; + cl_perror("Cannot get name for uid [%d]", getuid()); + errno = errsave; + return -1; + } + if ((rc=chdir(pwent->pw_name)) < 0) { + int errsave = errno; + cl_perror("Cannot chdir to [%s/%s]", dir, pwent->pw_name); + errno = errsave; + } + return rc; +} + +#define CHECKED_KERNEL_CORE_ENV "_PROC_SYS_CORE_CHECKED_" +#define PROC_SYS_KERNEL_CORE_PID "/proc/sys/kernel/core_uses_pid" +#define PROC_SYS_KERNEL_CORE_PAT "/proc/sys/kernel/core_pattern" + +static void cl_coredump_signal_handler(int nsig); + +/* + * core_uses_pid(): + * + * returns {-1, 0, 1} + * -1: not supported + * 0: supported and disabled + * 1: supported and enabled + */ +#define BUF_MAX 256 +static int +core_uses_pid(void) +{ + const char * uses_pid_pathnames[] = {PROC_SYS_KERNEL_CORE_PID}; + const char * corepats_pathnames[] = {PROC_SYS_KERNEL_CORE_PAT}; + const char * goodpats [] = {"%t", "%p"}; + int j; + + + for (j=0; j < DIMOF(corepats_pathnames); ++j) { + int fd; + char buf[BUF_MAX]; + int rc; + int k; + + if ((fd = open(corepats_pathnames[j], O_RDONLY)) < 0) { + continue; + } + + memset(buf, 0, BUF_MAX); + rc = read(fd, buf, BUF_MAX - 1); /* Ensure it is always NULL terminated */ + close(fd); + + for (k=0; rc > 0 && k < DIMOF(goodpats); ++k) { + if (strstr(buf, goodpats[k]) != NULL) { + return 1; + } + } + + break; + } + for (j=0; j < DIMOF(uses_pid_pathnames); ++j) { + int fd; + char buf[2]; + int rc; + if ((fd = open(uses_pid_pathnames[j], O_RDONLY)) < 0) { + continue; + } + rc = read(fd, buf, sizeof(buf)); + close(fd); + if (rc < 1) { + continue; + } + return (buf[0] == '1'); + } + setenv(CHECKED_KERNEL_CORE_ENV, "1", TRUE); + return -1; +} + +/* Enable/disable core dumps for ourselves and our child processes */ +int +cl_enable_coredumps(int doenable) +{ + int rc; + struct rlimit rlim; + + if ((rc = getrlimit(RLIMIT_CORE, &rlim)) < 0) { + int errsave = errno; + cl_perror("Cannot get current core limit value."); + errno = errsave; + return rc; + } + if (rlim.rlim_max == 0 && geteuid() == 0) { + rlim.rlim_max = RLIM_INFINITY; + } + + rlim.rlim_cur = (doenable ? rlim.rlim_max : 0); + + if (doenable && rlim.rlim_max == 0) { + cl_log(LOG_WARNING + , "Not possible to enable core dumps (rlim_max is 0)"); + } + + if ((rc = setrlimit(RLIMIT_CORE, &rlim)) < 0) { + int errsave = errno; + cl_perror("Unable to %s core dumps" + , doenable ? "enable" : "disable"); + errno = errsave; + return rc; + } + if (getenv(CHECKED_KERNEL_CORE_ENV) == NULL + && core_uses_pid() == 0) { + cl_log(LOG_WARNING + , "Core dumps could be lost if multiple dumps occur."); + cl_log(LOG_WARNING + , "Consider setting non-default value in %s" + " (or equivalent) for maximum supportability", PROC_SYS_KERNEL_CORE_PAT); + cl_log(LOG_WARNING + , "Consider setting %s (or equivalent) to" + " 1 for maximum supportability", PROC_SYS_KERNEL_CORE_PID); + } + return 0; +} + +/* + * SIGQUIT 3 Core Quit from keyboard + * SIGILL 4 Core Illegal Instruction + * SIGABRT 6 Core Abort signal from abort(3) + * SIGFPE 8 Core Floating point exception + * SIGSEGV 11 Core Invalid memory reference + * SIGBUS 10,7,10 Core Bus error (bad memory access) + * SIGSYS 2,-,12 Core Bad argument to routine (SVID) + * SIGTRAP 5 Core Trace/breakpoint trap + * SIGXCPU 24,24,30 Core CPU time limit exceeded (4.2 BSD) + * SIGXFSZ 25,25,31 Core File size limit exceeded (4.2 BSD) + */ + +/* + * This function exists to allow security-sensitive programs + * to safely take core dumps. Such programs can't can't call + * cl_untaint_coredumps() alone - because it might cause a + * leak of confidential information - as information which should + * only be known by the "high-privilege" user id will be written + * into a core dump which is readable by the "low-privilege" user id. + * This is a bad thing. + * + * This function causes this program to call a special signal handler + * on receipt of any core dumping signal. This handler then does + * the following four things on receipt of a core dumping signal: + * + * 1) Set privileges to "maximum" on receipt of a signal + * 2) "untaint" themselves with regard to core dumping + * 3) set SIG_DFLT for the received signal + * 4) Kill themselves with the received core-dumping signal + * + * Any process *could* do this to get core dumps, but if your stack + * is screwed up, then the signal handler might not work. + * If you're core dumping because of a stack overflow, it certainly won't work. + * + * On the other hand, this function may work on some OSes that don't support + * prctl(2). This is an untested theory at this time... + */ +void +cl_set_all_coredump_signal_handlers(void) +{ + static const int coresigs [] = {SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGSEGV +#ifdef SIGBUS +, SIGBUS +#endif +#ifdef SIGSYS +, SIGSYS +#endif +#ifdef SIGTRAP +, SIGTRAP +#endif +#ifdef SIGXCPU +, SIGXCPU +#endif +#ifdef SIGXFSZ +, SIGXFSZ +#endif +}; + int j; + + for (j=0; j < DIMOF(coresigs); ++j) { + cl_set_coredump_signal_handler(coresigs[j]); + } +} + +/* + * See note above about why using this function directly is sometimes + * a bad idea, and you might need to use cl_set_all_coredump_signal_handlers() + * instead. + */ +void +cl_untaint_coredumps(void) +{ +#if defined(PR_SET_DUMPABLE) + prctl(PR_SET_DUMPABLE, (unsigned long)TRUE, 0UL, 0UL, 0UL); +#endif +} +static void +cl_coredump_signal_handler(int nsig) +{ + return_to_orig_privs(); + if (geteuid() == 0) { + /* Put ALL privileges back to root... */ + if (setuid(0) < 0) { + cl_perror("cl_coredump_signal_handler: unable to setuid(0)"); + } + } + cl_untaint_coredumps(); /* Do the best we know how to do... */ + CL_SIGNAL(nsig, SIG_DFL); + kill(getpid(), nsig); +} + +void +cl_set_coredump_signal_handler(int nsig) +{ + CL_SIGNAL(nsig, cl_coredump_signal_handler); +} diff --git a/lib/clplumbing/cpulimits.c b/lib/clplumbing/cpulimits.c new file mode 100644 index 0000000..4c03f23 --- /dev/null +++ b/lib/clplumbing/cpulimits.c @@ -0,0 +1,219 @@ +/* + * Functions to put dynamic limits on CPU consumption. + * + * Copyright (C) 2003 IBM Corporation + * + * Author: <alanr@unix.sh> + * + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + ************************************************************************** + * + * This allows us to better catch runaway realtime processes that + * might otherwise hang the whole system (if they're POSIX realtime + * processes). + * + * We do this by getting a "lease" on CPU time, and then extending + * the lease every so often as real time elapses. Since we only + * extend the lease by a bounded amount computed on the basis of an + * upper bound of how much CPU the code is "expected" to consume during + * the lease interval, this means that if we go into an infinite + * loop, it is highly probable that this will be detected and our + * process will be terminated by the operating system with a SIGXCPU. + * + * If you want to handle this signal, then fine... Do so... + * + * If not, the default is to terminate the process and produce a core + * dump. This is a great default for debugging... + * + * + * The process is basically this: + * - Set the CPU percentage limit with cl_cpu_limit_setpercent() + * according to what you expect the CPU percentage to top out at + * measured over an interval at >= 60 seconds + * - Call cl_cpu_limit_ms_interval() to figure out how often to update + * the CPU limit (it returns milliseconds) + * - At least as often as indicated above, call cl_cpu_limit_update() + * to update our current CPU limit. + * + * These limits are approximate, so be a little conservative. + * If you've gone into an infinite loop, it'll likely get caught ;-) + * + * As of this writing, this code will never set the soft CPU limit less + * than four seconds, or greater than 60 seconds. + * + */ +#include <lha_internal.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <clplumbing/longclock.h> +#include <unistd.h> +#include <clplumbing/cpulimits.h> +#include <clplumbing/cl_log.h> + +static longclock_t nexttimetoupdate; + +/* How long between checking out CPU usage? */ +static int cpuinterval_ms = 0; + +/* How much cpu (in seconds) allowed at each check interval? */ +static int cpusecs; + +#define ROUND(foo) ((int)((foo)+0.5)) + + +/* + * Update our current CPU limit (via setrlimit) according to our + * current resource consumption, and our current cpu % limit + * + * We only set the soft CPU limit, and do not change the maximum + * (hard) CPU limit, but we respect it if it's already set. + * + * As a result, this code can be used by privileged and non-privileged + * processes. + */ + +static int +update_cpu_interval(void) +{ + struct rusage ru; + struct rlimit rlim; + unsigned long timesecs; + unsigned long microsec; + + /* Compute how much CPU we've used so far... */ + + getrusage(RUSAGE_SELF, &ru); + timesecs = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec; + microsec = ru.ru_utime.tv_usec + ru.ru_stime.tv_usec; + + /* Round up to the next higher second */ + if (microsec > 1000000) { + timesecs += 2; + }else{ + timesecs += 1; + } + + /* Compute our next CPU limit */ + timesecs += cpusecs; + + /* Figure out when we next need to update our CPU limit */ + nexttimetoupdate = add_longclock(time_longclock() + , msto_longclock(cpuinterval_ms)); + + getrlimit(RLIMIT_CPU, &rlim); + + /* Make sure we don't exceed the hard CPU limit (if set) */ + if (rlim.rlim_max != RLIM_INFINITY && timesecs > rlim.rlim_max) { + timesecs = rlim.rlim_max; + } +#if 0 + cl_log(LOG_DEBUG + , "Setting max CPU limit to %ld seconds", timesecs); +#endif + + /* Update the OS-level soft CPU limit */ + rlim.rlim_cur = timesecs; + return setrlimit(RLIMIT_CPU, &rlim); +} + +#define MININTERVAL 60 /* seconds */ + +int +cl_cpu_limit_setpercent(int ipercent) +{ + float percent; + int interval; + + if (ipercent > 99) { + ipercent = 99; + } + if (ipercent < 1) { + ipercent = 1; + } + percent = ipercent; + percent /= (float)100; + + interval= MININTERVAL; + + /* + * Compute how much CPU we will allow to be used + * for each check interval. + * + * Rules: + * - we won't require checking more often than + * every 60 seconds + * - we won't limit ourselves to less than + * 4 seconds of CPU per checking interval + */ + for (;;) { + cpusecs = ROUND((float)interval*percent); + if (cpusecs >= 4) { + break; + } + interval *= 2; + } + + /* + * Now compute how long to go between updates to our CPU limit + * from the perspective of the OS (via setrlimit(2)). + * + * We do the computation this way because the CPU limit + * can only be set to the nearest second, but timers can + * generally be set more accurately. + */ + cpuinterval_ms = (int)(((float)cpusecs / percent)*1000.0); + + cl_log(LOG_DEBUG + , "Limiting CPU: %d CPU seconds every %d milliseconds" + , cpusecs, cpuinterval_ms); + + return update_cpu_interval(); +} + +int +cl_cpu_limit_ms_interval(void) +{ + return cpuinterval_ms; +} + +int +cl_cpu_limit_update(void) +{ + longclock_t now = time_longclock(); + long msleft; + + if (cpuinterval_ms <= 0) { + return 0; + } + if (cmp_longclock(now, nexttimetoupdate) > 0) { + return update_cpu_interval(); + } + msleft = longclockto_ms(sub_longclock(nexttimetoupdate, now)); + if (msleft < 500) { + return update_cpu_interval(); + } + return 0; +} +int +cl_cpu_limit_disable(void) +{ + struct rlimit rlim; + getrlimit(RLIMIT_CPU, &rlim); + rlim.rlim_cur = rlim.rlim_max; + return setrlimit(RLIMIT_CPU, &rlim); +} diff --git a/lib/clplumbing/ipcsocket.c b/lib/clplumbing/ipcsocket.c new file mode 100644 index 0000000..14c3504 --- /dev/null +++ b/lib/clplumbing/ipcsocket.c @@ -0,0 +1,2767 @@ +/* + * ipcsocket unix domain socket implementation of IPC abstraction. + * + * Copyright (c) 2002 Xiaoxiang Liu <xiliu@ncsa.uiuc.edu> + * + * Stream support (c) 2004,2006 David Lee <t.d.lee@durham.ac.uk> + * Note: many of the variable/function names "*socket*" should be + * interpreted as having a more generic "ipc-channel-type" meaning. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> + +#include <clplumbing/ipc.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/realtime.h> +#include <clplumbing/cl_poll.h> + +#include <ha_msg.h> +/* avoid including cib.h - used in gshi's "late message" code to avoid + * printing insanely large messages + */ +#define F_CIB_CALLDATA "cib_calldata" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <sched.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <sys/uio.h> +#ifdef HAVE_SYS_FILIO_H +# include <sys/filio.h> +#endif +#ifdef HAVE_SYS_SYSLIMITS_H +# include <sys/syslimits.h> +#endif +#ifdef HAVE_SYS_CRED_H +# include <sys/cred.h> +#endif +#ifdef HAVE_SYS_UCRED_H +# include <sys/ucred.h> +#endif + +/* For 'getpeerucred()' (Solaris 10 upwards) */ +#ifdef HAVE_UCRED_H +# include <ucred.h> +#endif + +#ifdef HAVE_SYS_SOCKET_H +# include <sys/socket.h> +#endif + +/* + * Normally use "socket" code. But on some OSes alternatives may be + * preferred (or necessary). + */ +#define HB_IPC_SOCKET 1 +#define HB_IPC_STREAM 2 +/* #define HB_IPC_ANOTHER 3 */ + +#ifndef HB_IPC_METHOD +# if defined(SO_PEERCRED) || defined(HAVE_GETPEEREID) \ + || defined(SCM_CREDS) || defined(HAVE_GETPEERUCRED) +# define HB_IPC_METHOD HB_IPC_SOCKET +# elif defined(HAVE_STROPTS_H) +# define HB_IPC_METHOD HB_IPC_STREAM +# else +# error. Surely we have sockets or streams... +# endif +#endif + +#if HB_IPC_METHOD == HB_IPC_SOCKET +# include <sys/poll.h> +# include <netinet/in.h> +# include <sys/un.h> +#elif HB_IPC_METHOD == HB_IPC_STREAM +# include <stropts.h> +#else +# error "IPC type invalid" +#endif + +#include <sys/ioctl.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#ifndef UNIX_PATH_MAX +# define UNIX_PATH_MAX 108 +#endif + +#if HB_IPC_METHOD == HB_IPC_SOCKET + +# define MAX_LISTEN_NUM 128 + +# ifndef MSG_NOSIGNAL +# define MSG_NOSIGNAL 0 +# endif + +# ifndef AF_LOCAL +# define AF_LOCAL AF_UNIX +# endif + +#endif /* HB_IPC_METHOD */ + +/*********************************************************************** + * + * Determine the IPC authentication scheme... More machine dependent than + * we'd like, but don't know any better way... + * + ***********************************************************************/ +#ifdef SO_PEERCRED +# define USE_SO_PEERCRED +#elif HAVE_GETPEEREID +# define USE_GETPEEREID +#elif defined(SCM_CREDS) +# define USE_SCM_CREDS +#elif HAVE_GETPEERUCRED /* e.g. Solaris 10 upwards */ +# define USE_GETPEERUCRED +#elif HB_IPC_METHOD == HB_IPC_STREAM +# define USE_STREAM_CREDS +#else +# define USE_DUMMY_CREDS +/* This will make it compile, but attempts to authenticate + * will fail. This is a stopgap measure ;-) + */ +#endif + +#if HB_IPC_METHOD == HB_IPC_SOCKET + +# ifdef USE_BINDSTAT_CREDS +# ifndef SUN_LEN +# define SUN_LEN(ptr) ((size_t) (offsetof (sockaddr_un, sun_path) + strlen ((ptr)->sun_path)) +# endif +# endif + +#endif /* HB_IPC_METHOD */ + +/* wait connection private data. */ +struct SOCKET_WAIT_CONN_PRIVATE{ + /* the path name wich the connection will be built on. */ + char path_name[UNIX_PATH_MAX]; +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* the domain socket. */ + int s; +#elif HB_IPC_METHOD == HB_IPC_STREAM + /* the streams pipe */ + int pipefds[2]; +#endif +}; + +/* channel private data. */ +struct SOCKET_CH_PRIVATE{ + /* the path name wich the connection will be built on. */ + char path_name[UNIX_PATH_MAX]; + /* the domain socket. */ + int s; + /* the size of expecting data for below buffered message buf_msg */ + int remaining_data; + +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* The address of our peer - used by USE_BINDSTAT_CREDS version of + * socket_verify_auth() + */ + struct sockaddr_un *peer_addr; +#elif HB_IPC_METHOD == HB_IPC_STREAM + uid_t farside_uid; + gid_t farside_gid; +#endif + + /* the buf used to save unfinished message */ + struct IPC_MESSAGE *buf_msg; +}; + +struct IPC_Stats { + long nsent; + long noutqueued; + long send_count; + long nreceived; + long ninqueued; + long recv_count; + int last_recv_errno; + int last_recv_rc; + int last_send_errno; + int last_send_rc; +}; + +static struct IPC_Stats SocketIPCStats = {0, 0, 0, 0}; +extern int debug_level; + +/* unix domain socket implementations of IPC functions. */ + +static int socket_resume_io(struct IPC_CHANNEL *ch); + +static struct IPC_MESSAGE* socket_message_new(struct IPC_CHANNEL*ch +, int msg_len); + +struct IPC_WAIT_CONNECTION *socket_wait_conn_new(GHashTable* ch_attrs); + +/* *** FIXME: This is also declared in 'ocf_ipc.c'. */ +struct IPC_CHANNEL* socket_client_channel_new(GHashTable *attrs); + +static struct IPC_CHANNEL* socket_server_channel_new(int sockfd); + +static struct IPC_CHANNEL * channel_new(int sockfd, int conntype, const char *pathname); +static int client_channel_new_auth(int sockfd); +static int verify_creds(struct IPC_AUTH *auth_info, uid_t uid, gid_t gid); + +typedef void (*DelProc)(IPC_Message*); + +static struct IPC_MESSAGE * ipcmsg_new(struct IPC_CHANNEL* ch, + const void* data, int len, void* private, DelProc d); + +static pid_t socket_get_farside_pid(int sockfd); + +extern int (*ipc_pollfunc_ptr)(struct pollfd *, nfds_t, int); + +static int socket_resume_io_read(struct IPC_CHANNEL *ch, int*, gboolean read1anyway); + +static struct IPC_OPS socket_ops; +static gboolean ipc_time_debug_flag = TRUE; + +void +set_ipc_time_debug_flag(gboolean flag) +{ + ipc_time_debug_flag = flag; +} + +#ifdef IPC_TIME_DEBUG + +extern struct ha_msg* wirefmt2msg(const char* s, size_t length, int flag); +void cl_log_message (int log_level, const struct ha_msg *m); +int timediff(longclock_t t1, longclock_t t2); +void ha_msg_del(struct ha_msg* msg); +void ipc_time_debug(IPC_Channel* ch, IPC_Message* ipcmsg, int whichpos); + +#define SET_ENQUEUE_TIME(x,t) memcpy(&((struct SOCKET_MSG_HEAD*)x->msg_buf)->enqueue_time, &t, sizeof(longclock_t)) +#define SET_SEND_TIME(x,t) memcpy(&((struct SOCKET_MSG_HEAD*)x->msg_buf)->send_time, &t, sizeof(longclock_t)) +#define SET_RECV_TIME(x,t) memcpy(&((struct SOCKET_MSG_HEAD*)x->msg_buf)->recv_time, &t, sizeof(longclock_t)) +#define SET_DEQUEUE_TIME(x,t) memcpy(&((struct SOCKET_MSG_HEAD*)x->msg_buf)->dequeue_time, &t, sizeof(longclock_t)) + +static +longclock_t +get_enqueue_time(IPC_Message *ipcmsg) +{ + longclock_t t; + + memcpy(&t, + &(((struct SOCKET_MSG_HEAD *)ipcmsg->msg_buf)->enqueue_time), + sizeof(longclock_t)); + + return t; +} + +int +timediff(longclock_t t1, longclock_t t2) +{ + longclock_t remain; + + remain = sub_longclock(t1, t2); + + return longclockto_ms(remain); +} + +void +ipc_time_debug(IPC_Channel* ch, IPC_Message* ipcmsg, int whichpos) +{ + int msdiff = 0; + longclock_t lnow = time_longclock(); + char positions[4][16]={ + "enqueue", + "send", + "recv", + "dequeue"}; + + if (ipc_time_debug_flag == FALSE) { + return ; + } + + if (ipcmsg->msg_body == NULL + || ipcmsg->msg_buf == NULL) { + cl_log(LOG_ERR, "msg_body =%p, msg_bu=%p", + ipcmsg->msg_body, ipcmsg->msg_buf); + abort(); + return; + } + + switch(whichpos) { + case MSGPOS_ENQUEUE: + SET_ENQUEUE_TIME(ipcmsg, lnow); + break; + case MSGPOS_SEND: + SET_SEND_TIME(ipcmsg, lnow); + goto checktime; + case MSGPOS_RECV: + SET_RECV_TIME(ipcmsg, lnow); + goto checktime; + case MSGPOS_DEQUEUE: + SET_DEQUEUE_TIME(ipcmsg, lnow); + + checktime: + msdiff = timediff(lnow, get_enqueue_time(ipcmsg)); + if (msdiff > MAXIPCTIME) { + struct ha_msg* hamsg = NULL; + cl_log(LOG_WARNING, + " message delayed from enqueue to %s %d ms " + "(enqueue-time=%lu, peer pid=%d) ", + positions[whichpos], + msdiff, + longclockto_ms(get_enqueue_time(ipcmsg)), + ch->farside_pid); + + (void)hamsg; +#if 0 + hamsg = wirefmt2msg(ipcmsg->msg_body, ipcmsg->msg_len, 0); + if (hamsg != NULL) { + struct ha_msg *crm_data = NULL; + crm_data = cl_get_struct( + hamsg, F_CRM_DATA); + + if(crm_data == NULL) { + crm_data = cl_get_struct( + hamsg, F_CIB_CALLDATA); + } + if(crm_data != NULL) { + cl_msg_remove_value( + hamsg, crm_data); + } + + cl_log_message(LOG_DEBUG, hamsg); + ha_msg_del(hamsg); + } else { + if (!ipcmsg) { + cl_log(LOG_ERR, + "IPC msg 0x%lx is unallocated" + , (gulong)ipcmsg); + return; + } + if (!ipcmsg->msg_body) { + cl_log(LOG_ERR, + "IPC msg body 0x%lx is unallocated" + , (gulong)ipcmsg->msg_body); + return; + } + } +#endif + + } + break; + default: + cl_log(LOG_ERR, "wrong position value in IPC:%d", whichpos); + return; + } +} +#endif + +void dump_ipc_info(const IPC_Channel* chan); + +#undef AUDIT_CHANNELS + +#ifndef AUDIT_CHANNELS +# define CHANAUDIT(ch) /*NOTHING */ +#else +# define CHANAUDIT(ch) socket_chan_audit(ch) +# define MAXPID 65535 + +static void +socket_chan_audit(const struct IPC_CHANNEL* ch) +{ + int badch = FALSE; + + struct SOCKET_CH_PRIVATE *chp; + struct stat b; + + if ((chp = ch->ch_private) == NULL) { + cl_log(LOG_CRIT, "Bad ch_private"); + badch = TRUE; + } + if (ch->ops != &socket_ops) { + cl_log(LOG_CRIT, "Bad socket_ops"); + badch = TRUE; + } + if (ch->ch_status == IPC_DISCONNECT) { + return; + } + if (!IPC_ISRCONN(ch)) { + cl_log(LOG_CRIT, "Bad ch_status [%d]", ch->ch_status); + badch = TRUE; + } + if (ch->farside_pid < 0 || ch->farside_pid > MAXPID) { + cl_log(LOG_CRIT, "Bad farside_pid"); + badch = TRUE; + } + if (fstat(chp->s, &b) < 0) { + badch = TRUE; + } else if ((b.st_mode & S_IFMT) != S_IFSOCK) { + cl_log(LOG_CRIT, "channel @ 0x%lx: not a socket" + , (unsigned long)ch); + badch = TRUE; + } + if (chp->remaining_data < 0) { + cl_log(LOG_CRIT, "Negative remaining_data"); + badch = TRUE; + } + if (chp->remaining_data < 0 || chp->remaining_data > MAXMSG) { + cl_log(LOG_CRIT, "Excessive/bad remaining_data"); + badch = TRUE; + } + if (chp->remaining_data && chp->buf_msg == NULL) { + cl_log(LOG_CRIT + , "inconsistent remaining_data [%ld]/buf_msg[0x%lx]" + , (long)chp->remaining_data, (unsigned long)chp->buf_msg); + badch = TRUE; + } + if (chp->remaining_data == 0 && chp->buf_msg != NULL) { + cl_log(LOG_CRIT + , "inconsistent remaining_data [%ld]/buf_msg[0x%lx] (2)" + , (long)chp->remaining_data, (unsigned long)chp->buf_msg); + badch = TRUE; + } + if (ch->send_queue == NULL || ch->recv_queue == NULL) { + cl_log(LOG_CRIT, "bad send/recv queue"); + badch = TRUE; + } + if (ch->recv_queue->current_qlen < 0 + || ch->recv_queue->current_qlen > ch->recv_queue->max_qlen) { + cl_log(LOG_CRIT, "bad recv queue"); + badch = TRUE; + } + if (ch->send_queue->current_qlen < 0 + || ch->send_queue->current_qlen > ch->send_queue->max_qlen) { + cl_log(LOG_CRIT, "bad send_queue"); + badch = TRUE; + } + if (badch) { + cl_log(LOG_CRIT, "Bad channel @ 0x%lx", (unsigned long)ch); + dump_ipc_info(ch); + abort(); + } +} +#endif + +#ifdef CHEAT_CHECKS +long SeqNums[32]; + +static long +cheat_get_sequence(IPC_Message* msg) +{ + const char header [] = "String-"; + size_t header_len = sizeof(header)-1; + char * body; + + if (msg == NULL || msg->msg_len < sizeof(header) + || msg->msg_len > sizeof(header) + 10 + || strncmp(msg->msg_body, header, header_len) != 0) { + return -1L; + } + body = msg->msg_body; + return atol(body+header_len); +} +static char SavedReadBody[32]; +static char SavedReceivedBody[32]; +static char SavedQueuedBody[32]; +static char SavedSentBody[32]; +#ifndef MIN +# define MIN(a,b) (a < b ? a : b) +#endif + +static void +save_body(struct IPC_MESSAGE *msg, char * savearea, size_t length) +{ + int mlen = strnlen(msg->msg_body, MIN(length, msg->msg_len)); + memcpy(savearea, msg->msg_body, mlen); + savearea[mlen] = EOS; +} + +static void +audit_readmsgq_msg(gpointer msg, gpointer user_data) +{ + long cheatseq = cheat_get_sequence(msg); + + if (cheatseq < SeqNums[1] || cheatseq > SeqNums[2]) { + cl_log(LOG_ERR + , "Read Q Message %ld not in range [%ld:%ld]" + , cheatseq, SeqNums[1], SeqNums[2]); + } +} + +static void +saveandcheck(struct IPC_CHANNEL * ch, struct IPC_MESSAGE* msg, char * savearea +, size_t savesize, long* lastseq, const char * text) +{ + long cheatseq = cheat_get_sequence(msg); + + save_body(msg, savearea, savesize); + if (*lastseq != 0 ) { + if (cheatseq != *lastseq +1) { + int j; + cl_log(LOG_ERR + , "%s packets out of sequence! %ld versus %ld [pid %d]" + , text, cheatseq, *lastseq, (int)getpid()); + dump_ipc_info(ch); + for (j=0; j < 4; ++j) { + cl_log(LOG_DEBUG + , "SeqNums[%d] = %ld" + , j, SeqNums[j]); + } + cl_log(LOG_ERR + , "SocketIPCStats.nsent = %ld" + , SocketIPCStats.nsent); + cl_log(LOG_ERR + , "SocketIPCStats.noutqueued = %ld" + , SocketIPCStats.noutqueued); + cl_log(LOG_ERR + , "SocketIPCStats.nreceived = %ld" + , SocketIPCStats.nreceived); + cl_log(LOG_ERR + , "SocketIPCStats.ninqueued = %ld" + , SocketIPCStats.ninqueued); + } + + } + g_list_foreach(ch->recv_queue->queue, audit_readmsgq_msg, NULL); + if (cheatseq > 0) { + *lastseq = cheatseq; + } +} + +# define CHECKFOO(which, ch, msg, area, text) { \ + saveandcheck(ch,msg,area,sizeof(area),SeqNums+which,text); \ + } +#else +# define CHECKFOO(which, ch, msg, area, text) /* Nothing */ +#endif + +static void +dump_msg(struct IPC_MESSAGE *msg, const char * label) +{ +#ifdef CHEAT_CHECKS + cl_log(LOG_DEBUG, "%s packet (length %d) [%s] %ld pid %d" + , label, (int)msg->msg_len, (char*)msg->msg_body + , cheat_get_sequence(msg), (int)getpid()); +#else + cl_log(LOG_DEBUG, "%s length %d [%s] pid %d" + , label, (int)msg->msg_len, (char*)msg->msg_body + , (int)getpid()); +#endif +} + +static void +dump_msgq_msg(gpointer data, gpointer user_data) +{ + dump_msg(data, user_data); +} + +void +dump_ipc_info(const IPC_Channel* chan) +{ + char squeue[] = "Send queue"; + char rqueue[] = "Receive queue"; +#ifdef CHEAT_CHECKS + cl_log(LOG_DEBUG, "Saved Last Body read[%s]", SavedReadBody); + cl_log(LOG_DEBUG, "Saved Last Body received[%s]", SavedReceivedBody); + cl_log(LOG_DEBUG, "Saved Last Body Queued[%s]", SavedQueuedBody); + cl_log(LOG_DEBUG, "Saved Last Body Sent[%s]", SavedSentBody); +#endif + g_list_foreach(chan->send_queue->queue, dump_msgq_msg, squeue); + g_list_foreach(chan->recv_queue->queue, dump_msgq_msg, rqueue); + CHANAUDIT(chan); +} + +/* destroy socket wait channel */ +static void +socket_destroy_wait_conn(struct IPC_WAIT_CONNECTION * wait_conn) +{ + struct SOCKET_WAIT_CONN_PRIVATE * wc = wait_conn->ch_private; + + if (wc != NULL) { +#if HB_IPC_METHOD == HB_IPC_SOCKET + if (wc->s >= 0) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: closing socket %d" + , __FUNCTION__, wc->s); + } + close(wc->s); + cl_poll_ignore(wc->s); + unlink(wc->path_name); + wc->s = -1; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + cl_poll_ignore(wc->pipefds[0]); + if (wc->pipefds[0] >= 0) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: closing pipe[0] %d" + , __FUNCTION__, wc->pipefds[0]); + } + wc->pipefds[0] = -1; + } + if (wc->pipefds[1] >= 0) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: closing pipe[1] %d" + , __FUNCTION__, wc->pipefds[1]); + } + wc->pipefds[0] = -1; + } + unlink(wc->path_name); +#endif + g_free(wc); + } + g_free((void*) wait_conn); +} + +/* return a fd which can be listened on for new connections. */ +static int +socket_wait_selectfd(struct IPC_WAIT_CONNECTION *wait_conn) +{ + struct SOCKET_WAIT_CONN_PRIVATE * wc = wait_conn->ch_private; + +#if HB_IPC_METHOD == HB_IPC_SOCKET + return (wc == NULL ? -1 : wc->s); +#elif HB_IPC_METHOD == HB_IPC_STREAM + return (wc == NULL ? -1 : wc->pipefds[0]); +#endif +} + +/* socket accept connection. */ +static struct IPC_CHANNEL* +socket_accept_connection(struct IPC_WAIT_CONNECTION * wait_conn +, struct IPC_AUTH *auth_info) +{ + struct IPC_CHANNEL * ch = NULL; + int s; + int new_sock; + struct SOCKET_WAIT_CONN_PRIVATE* conn_private; + struct SOCKET_CH_PRIVATE * ch_private ; + int auth_result = IPC_FAIL; + int saveerrno=errno; + gboolean was_error = FALSE; +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* make peer_addr a pointer so it can be used by the + * USE_BINDSTAT_CREDS implementation of socket_verify_auth() + */ + struct sockaddr_un * peer_addr = NULL; + socklen_t sin_size; +#elif HB_IPC_METHOD == HB_IPC_STREAM + struct strrecvfd strrecvfd; +#endif + + /* get select fd */ + + s = wait_conn->ops->get_select_fd(wait_conn); + if (s < 0) { + cl_log(LOG_ERR, "get_select_fd: invalid fd"); + return NULL; + } + + /* Get client connection. */ +#if HB_IPC_METHOD == HB_IPC_SOCKET + peer_addr = g_new(struct sockaddr_un, 1); + *peer_addr->sun_path = '\0'; + sin_size = sizeof(struct sockaddr_un); + new_sock = accept(s, (struct sockaddr *)peer_addr, &sin_size); +#elif HB_IPC_METHOD == HB_IPC_STREAM + if (ioctl(s, I_RECVFD, &strrecvfd) == -1) { + new_sock = -1; + } + else { + new_sock = strrecvfd.fd; + } +#endif + saveerrno=errno; + if (new_sock == -1) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + cl_perror("socket_accept_connection: accept(sock=%d)" + , s); + } + was_error = TRUE; + + } else { + if ((ch = socket_server_channel_new(new_sock)) == NULL) { + cl_log(LOG_ERR + , "socket_accept_connection:" + " Can't create new channel"); + was_error = TRUE; + } else { + conn_private=(struct SOCKET_WAIT_CONN_PRIVATE*) + ( wait_conn->ch_private); + ch_private = (struct SOCKET_CH_PRIVATE *)(ch->ch_private); + strncpy(ch_private->path_name,conn_private->path_name + , sizeof(conn_private->path_name)); + +#if HB_IPC_METHOD == HB_IPC_SOCKET + ch_private->peer_addr = peer_addr; +#elif HB_IPC_METHOD == HB_IPC_STREAM + ch_private->farside_uid = strrecvfd.uid; + ch_private->farside_gid = strrecvfd.gid; +#endif + } + } + + /* Verify the client authorization information. */ + if(was_error == FALSE) { + auth_result = ch->ops->verify_auth(ch, auth_info); + if (auth_result == IPC_OK) { + ch->ch_status = IPC_CONNECT; + ch->farside_pid = socket_get_farside_pid(new_sock); + return ch; + } + saveerrno=errno; + } + +#if HB_IPC_METHOD == HB_IPC_SOCKET + g_free(peer_addr); + peer_addr = NULL; +#endif + errno=saveerrno; + return NULL; +} + +/* + * Called by socket_destroy(). Disconnect the connection + * and set ch_status to IPC_DISCONNECT. + * + * parameters : + * ch (IN) the pointer to the channel. + * + * return values : + * IPC_OK the connection is disconnected successfully. + * IPC_FAIL operation fails. +*/ + +static int +socket_disconnect(struct IPC_CHANNEL* ch) +{ + struct SOCKET_CH_PRIVATE* conn_info; + + conn_info = (struct SOCKET_CH_PRIVATE*) ch->ch_private; + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s(sock=%d, ch=0x%lx){" + , __FUNCTION__ + , conn_info->s, (unsigned long)ch); + } +#if 0 + if (ch->ch_status != IPC_DISCONNECT) { + cl_log(LOG_INFO, "forced disconnect for fd %d", conn_info->s); + } +#endif + if (ch->ch_status == IPC_CONNECT) { + socket_resume_io(ch); + } + + if (conn_info->s >= 0) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: closing socket %d" + , __FUNCTION__, conn_info->s); + } + close(conn_info->s); + cl_poll_ignore(conn_info->s); + conn_info->s = -1; + } + ch->ch_status = IPC_DISCONNECT; + if (debug_level > 1) { + cl_log(LOG_DEBUG, "}/*%s(sock=%d, ch=0x%lx)*/" + , __FUNCTION__, conn_info->s, (unsigned long)ch); + } + return IPC_OK; +} + +/* + * destroy a ipc queue and clean all memory space assigned to this queue. + * parameters: + * q (IN) the pointer to the queue which should be destroied. + * + * FIXME: This function does not free up messages that might + * be in the queue. + */ + +static void +socket_destroy_queue(struct IPC_QUEUE * q) +{ + g_list_free(q->queue); + + g_free((void *) q); +} + +static void +socket_destroy_channel(struct IPC_CHANNEL * ch) +{ + --ch->refcount; + if (ch->refcount > 0) { + return; + } + if (ch->ch_status == IPC_CONNECT) { + socket_resume_io(ch); + } + if (debug_level > 1) { + cl_log(LOG_DEBUG, "socket_destroy(ch=0x%lx){" + , (unsigned long)ch); + } + socket_disconnect(ch); + socket_destroy_queue(ch->send_queue); + socket_destroy_queue(ch->recv_queue); + + if (ch->pool) { + ipc_bufpool_unref(ch->pool); + } + + if (ch->ch_private != NULL) { +#if HB_IPC_METHOD == HB_IPC_SOCKET + struct SOCKET_CH_PRIVATE *priv = (struct SOCKET_CH_PRIVATE *) + ch->ch_private; + if(priv->peer_addr != NULL) { + if (*priv->peer_addr->sun_path) { + unlink(priv->peer_addr->sun_path); + } + g_free((void*)(priv->peer_addr)); + } +#endif + g_free((void*)(ch->ch_private)); + } + memset(ch, 0xff, sizeof(*ch)); + g_free((void*)ch); + if (debug_level > 1) { + cl_log(LOG_DEBUG, "}/*socket_destroy(ch=0x%lx)*/" + , (unsigned long)ch); + } +} + +static int +socket_check_disc_pending(struct IPC_CHANNEL* ch) +{ + int rc; + struct pollfd sockpoll; + + if (ch->ch_status == IPC_DISCONNECT) { + cl_log(LOG_ERR, "check_disc_pending() already disconnected"); + return IPC_BROKEN; + } + if (ch->recv_queue->current_qlen > 0) { + return IPC_OK; + } + sockpoll.fd = ch->ops->get_recv_select_fd(ch); + sockpoll.events = POLLIN; + + rc = ipc_pollfunc_ptr(&sockpoll, 1, 0); + + if (rc < 0) { + cl_log(LOG_INFO + , "socket_check_disc_pending() bad poll call"); + ch->ch_status = IPC_DISCONNECT; + return IPC_BROKEN; + } + + if (sockpoll.revents & POLLHUP) { + if (sockpoll.revents & POLLIN) { + ch->ch_status = IPC_DISC_PENDING; + } else { +#if 1 + cl_log(LOG_INFO, "HUP without input"); +#endif + ch->ch_status = IPC_DISCONNECT; + return IPC_BROKEN; + } + } + if (sockpoll.revents & POLLIN) { + int dummy; + socket_resume_io_read(ch, &dummy, FALSE); + } + return IPC_OK; +} + +static int +socket_initiate_connection(struct IPC_CHANNEL * ch) +{ + struct SOCKET_CH_PRIVATE* conn_info; +#if HB_IPC_METHOD == HB_IPC_SOCKET + struct sockaddr_un peer_addr; /* connector's address information */ +#elif HB_IPC_METHOD == HB_IPC_STREAM +#endif + + conn_info = (struct SOCKET_CH_PRIVATE*) ch->ch_private; + +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* Prepare the socket */ + memset(&peer_addr, 0, sizeof(peer_addr)); + peer_addr.sun_family = AF_LOCAL; /* host byte order */ + + if (strlen(conn_info->path_name) >= sizeof(peer_addr.sun_path)) { + return IPC_FAIL; + } + strncpy(peer_addr.sun_path, conn_info->path_name, sizeof(peer_addr.sun_path)); + + /* Send connection request */ + if (connect(conn_info->s, (struct sockaddr *)&peer_addr + , sizeof(struct sockaddr_un)) == -1) { + return IPC_FAIL; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + +#endif + + ch->ch_status = IPC_CONNECT; + ch->farside_pid = socket_get_farside_pid(conn_info->s); + return IPC_OK; +} + +static void +socket_set_high_flow_callback(IPC_Channel* ch, + flow_callback_t callback, + void* userdata) { + ch->high_flow_callback = callback; + ch->high_flow_userdata = userdata; +} + +static void +socket_set_low_flow_callback(IPC_Channel* ch, + flow_callback_t callback, + void* userdata) { + ch->low_flow_callback = callback; + ch->low_flow_userdata = userdata; +} + +static void +socket_check_flow_control(struct IPC_CHANNEL* ch, + int orig_qlen, + int curr_qlen) +{ + if (!IPC_ISRCONN(ch)) { + return; + } + + if (curr_qlen >= ch->high_flow_mark + && ch->high_flow_callback) { + ch->high_flow_callback(ch, ch->high_flow_userdata); + } + + if (curr_qlen <= ch->low_flow_mark + && orig_qlen > ch->low_flow_mark + && ch->low_flow_callback) { + ch->low_flow_callback(ch, ch->low_flow_userdata); + } +} + +static int +socket_send(struct IPC_CHANNEL * ch, struct IPC_MESSAGE* msg) +{ + int orig_qlen; + int diff; + struct IPC_MESSAGE* newmsg; + + if (msg->msg_len > MAXMSG) { + cl_log(LOG_ERR, "%s: sorry, cannot send messages " + "bigger than %d (requested %lu)", + __FUNCTION__, MAXMSG, (unsigned long)msg->msg_len); + return IPC_FAIL; + } + if (msg->msg_len < 0) { + cl_log(LOG_ERR, "socket_send: " + "invalid message"); + return IPC_FAIL; + } + + if (ch->ch_status != IPC_CONNECT) { + return IPC_FAIL; + } + + ch->ops->resume_io(ch); + + if (ch->send_queue->maxqlen_cnt && + time(NULL) - ch->send_queue->last_maxqlen_warn >= 60) { + cl_log(LOG_ERR, "%u messages dropped on a non-blocking channel (send queue maximum length %d)", + ch->send_queue->maxqlen_cnt, (int)ch->send_queue->max_qlen); + ch->send_queue->maxqlen_cnt = 0; + } + if ( !ch->should_send_block && + ch->send_queue->current_qlen >= ch->send_queue->max_qlen) { + if (!ch->send_queue->maxqlen_cnt) { + ch->send_queue->last_maxqlen_warn = time(NULL); + } + ch->send_queue->maxqlen_cnt++; + + if (ch->should_block_fail) { + return IPC_FAIL; + } else { + return IPC_OK; + } + } + + while (ch->send_queue->current_qlen >= ch->send_queue->max_qlen) { + if (ch->ch_status != IPC_CONNECT) { + cl_log(LOG_WARNING, "socket_send:" + " message queue exceeded and IPC not connected"); + return IPC_FAIL; + } + cl_shortsleep(); + ch->ops->resume_io(ch); + } + + /* add the message into the send queue */ + CHECKFOO(0,ch, msg, SavedQueuedBody, "queued message"); + SocketIPCStats.noutqueued++; + + diff = 0; + if (msg->msg_buf ) { + diff = (char*)msg->msg_body - (char*)msg->msg_buf; + } + if ( diff < (int)sizeof(struct SOCKET_MSG_HEAD) ) { + /* either we don't have msg->msg_buf set + * or we don't have enough bytes for socket head + * we delete this message and creates + * a new one and delete the old one + */ + + newmsg= socket_message_new(ch, msg->msg_len); + if (newmsg == NULL) { + cl_log(LOG_ERR, "socket_resume_io_write: " + "allocating memory for new ipc msg failed"); + return IPC_FAIL; + } + + memcpy(newmsg->msg_body, msg->msg_body, msg->msg_len); + + if(msg->msg_done) { + msg->msg_done(msg); + }; + msg = newmsg; + } +#ifdef IPC_TIME_DEBUG + ipc_time_debug(ch,msg, MSGPOS_ENQUEUE); +#endif + ch->send_queue->queue = g_list_append(ch->send_queue->queue, + msg); + orig_qlen = ch->send_queue->current_qlen++; + + socket_check_flow_control(ch, orig_qlen, orig_qlen +1 ); + + /* resume io */ + ch->ops->resume_io(ch); + return IPC_OK; +} + +static int +socket_recv(struct IPC_CHANNEL * ch, struct IPC_MESSAGE** message) +{ + GList *element; + + int nbytes; + int result; + + socket_resume_io(ch); + result = socket_resume_io_read(ch, &nbytes, TRUE); + + *message = NULL; + + if (ch->recv_queue->current_qlen == 0) { + return result != IPC_OK ? result : IPC_FAIL; + /*return IPC_OK;*/ + } + element = g_list_first(ch->recv_queue->queue); + + if (element == NULL) { + /* Internal accounting error, but correctable */ + cl_log(LOG_ERR + , "recv failure: qlen (%ld) > 0, but no message found." + , (long)ch->recv_queue->current_qlen); + ch->recv_queue->current_qlen = 0; + return IPC_FAIL; + } + *message = (struct IPC_MESSAGE *) (element->data); +#ifdef IPC_TIME_DEBUG + ipc_time_debug(ch, *message, MSGPOS_DEQUEUE); +#endif + + CHECKFOO(1,ch, *message, SavedReadBody, "read message"); + SocketIPCStats.nreceived++; + ch->recv_queue->queue = g_list_remove(ch->recv_queue->queue + , element->data); + ch->recv_queue->current_qlen--; + return IPC_OK; +} + +static int +socket_check_poll(struct IPC_CHANNEL * ch +, struct pollfd * sockpoll) +{ + if (ch->ch_status == IPC_DISCONNECT) { + return IPC_OK; + } + if (sockpoll->revents & POLLHUP) { + /* If input present, or this is an output-only poll... */ + if (sockpoll->revents & POLLIN + || (sockpoll-> events & POLLIN) == 0 ) { + ch->ch_status = IPC_DISC_PENDING; + return IPC_OK; + } +#if 1 + cl_log(LOG_INFO, "socket_check_poll(): HUP without input"); +#endif + ch->ch_status = IPC_DISCONNECT; + return IPC_BROKEN; + + } else if (sockpoll->revents & (POLLNVAL|POLLERR)) { + /* Have we already closed the socket? */ + if (fcntl(sockpoll->fd, F_GETFL) < 0) { + cl_perror("socket_check_poll(pid %d): bad fd [%d]" + , (int) getpid(), sockpoll->fd); + ch->ch_status = IPC_DISCONNECT; + return IPC_OK; + } + cl_log(LOG_ERR + , "revents failure: fd %d, flags 0x%x" + , sockpoll->fd, sockpoll->revents); + errno = EINVAL; + return IPC_FAIL; + } + return IPC_OK; +} + +static int +socket_waitfor(struct IPC_CHANNEL * ch +, gboolean (*finished)(struct IPC_CHANNEL * ch)) +{ + struct pollfd sockpoll; + + CHANAUDIT(ch); + if (finished(ch)) { + return IPC_OK; + } + + if (ch->ch_status == IPC_DISCONNECT) { + return IPC_BROKEN; + } + sockpoll.fd = ch->ops->get_recv_select_fd(ch); + + while (!finished(ch) && IPC_ISRCONN(ch)) { + int rc; + + sockpoll.events = POLLIN; + + /* Cannot call resume_io after the call to finished() + * and before the call to poll because we might + * change the state of the thing finished() is + * waiting for. + * This means that the poll call below would be + * not only pointless, but might + * make us hang forever waiting for this + * event which has already happened + */ + if (ch->send_queue->current_qlen > 0) { + sockpoll.events |= POLLOUT; + } + + rc = ipc_pollfunc_ptr(&sockpoll, 1, -1); + + if (rc < 0) { + return (errno == EINTR ? IPC_INTR : IPC_FAIL); + } + + rc = socket_check_poll(ch, &sockpoll); + if (sockpoll.revents & POLLIN) { + socket_resume_io(ch); + } + if (rc != IPC_OK) { + CHANAUDIT(ch); + return rc; + } + } + + CHANAUDIT(ch); + return IPC_OK; +} + +static int +socket_waitin(struct IPC_CHANNEL * ch) +{ + return socket_waitfor(ch, ch->ops->is_message_pending); +} +static gboolean +socket_is_output_flushed(struct IPC_CHANNEL * ch) +{ + return ! ch->ops->is_sending_blocked(ch); +} + +static int +socket_waitout(struct IPC_CHANNEL * ch) +{ + int rc; + CHANAUDIT(ch); + rc = socket_waitfor(ch, socket_is_output_flushed); + + if (rc != IPC_OK) { + cl_log(LOG_ERR + , "socket_waitout failure: rc = %d", rc); + } else if (ch->ops->is_sending_blocked(ch)) { + cl_log(LOG_ERR, "socket_waitout output still blocked"); + } + CHANAUDIT(ch); + return rc; +} + +static gboolean +socket_is_message_pending(struct IPC_CHANNEL * ch) +{ + int nbytes; + + socket_resume_io_read(ch, &nbytes, TRUE); + ch->ops->resume_io(ch); + if (ch->recv_queue->current_qlen > 0) { + return TRUE; + } + + return !IPC_ISRCONN(ch); +} + +static gboolean +socket_is_output_pending(struct IPC_CHANNEL * ch) +{ + socket_resume_io(ch); + return ch->ch_status == IPC_CONNECT + && ch->send_queue->current_qlen > 0; +} + +static gboolean +socket_is_sendq_full(struct IPC_CHANNEL * ch) +{ + ch->ops->resume_io(ch); + return(ch->send_queue->current_qlen == ch->send_queue->max_qlen); +} + +static gboolean +socket_is_recvq_full(struct IPC_CHANNEL * ch) +{ + ch->ops->resume_io(ch); + return(ch->recv_queue->current_qlen == ch->recv_queue->max_qlen); +} + +static int +socket_get_conntype(struct IPC_CHANNEL* ch) +{ + return ch->conntype; +} + +static int +socket_assert_auth(struct IPC_CHANNEL *ch, GHashTable *auth) +{ + cl_log(LOG_ERR + , "the assert_auth function for domain socket is not implemented"); + return IPC_FAIL; +} + +static int +socket_resume_io_read(struct IPC_CHANNEL *ch, int* nbytes, gboolean read1anyway) +{ + struct SOCKET_CH_PRIVATE* conn_info; + int retcode = IPC_OK; + struct pollfd sockpoll; + int debug_loopcount = 0; + int debug_bytecount = 0; + size_t maxqlen = ch->recv_queue->max_qlen; + struct ipc_bufpool* pool = ch->pool; + int nmsgs = 0; + int spaceneeded; + *nbytes = 0; + + CHANAUDIT(ch); + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + if (ch->ch_status == IPC_DISCONNECT) { + return IPC_BROKEN; + } + + if (pool == NULL) { + ch->pool = pool = ipc_bufpool_new(0); + if (pool == NULL) { + cl_log(LOG_ERR, "socket_resume_io_read: " + "memory allocation for ipc pool failed"); + return IPC_FAIL; + } + } + + if (ipc_bufpool_full(pool, ch, &spaceneeded)) { + struct ipc_bufpool* newpool; + + newpool = ipc_bufpool_new(spaceneeded); + if (newpool == NULL) { + cl_log(LOG_ERR, "socket_resume_io_read: " + "memory allocation for a new ipc pool failed"); + return IPC_FAIL; + } + + ipc_bufpool_partial_copy(newpool, pool); + ipc_bufpool_unref(pool); + ch->pool = pool = newpool; + } + if (maxqlen <= 0 && read1anyway) { + maxqlen = 1; + } + if (ch->recv_queue->current_qlen < maxqlen && retcode == IPC_OK) { + void * msg_begin; + int msg_len; + int len; +#if HB_IPC_METHOD == HB_IPC_STREAM + struct strbuf d; + int flags, rc; +#endif + + CHANAUDIT(ch); + ++debug_loopcount; + + len = ipc_bufpool_spaceleft(pool); + msg_begin = pool->currpos; + + CHANAUDIT(ch); + + /* Now try to receive some data */ + +#if HB_IPC_METHOD == HB_IPC_SOCKET + msg_len = recv(conn_info->s, msg_begin, len, MSG_DONTWAIT); +#elif HB_IPC_METHOD == HB_IPC_STREAM + d.maxlen = len; + d.len = 0; + d.buf = msg_begin; + flags = 0; + rc = getmsg(conn_info->s, NULL, &d, &flags); + msg_len = (rc < 0) ? rc : d.len; +#endif + SocketIPCStats.last_recv_rc = msg_len; + SocketIPCStats.last_recv_errno = errno; + ++SocketIPCStats.recv_count; + + /* Did we get an error? */ + if (msg_len < 0) { + switch (errno) { + case EAGAIN: + if (ch->ch_status==IPC_DISC_PENDING) { + ch->ch_status =IPC_DISCONNECT; + retcode = IPC_BROKEN; + } + break; + + case ECONNREFUSED: + case ECONNRESET: + ch->ch_status = IPC_DISC_PENDING; + retcode= socket_check_disc_pending(ch); + break; + + default: + cl_perror("socket_resume_io_read" + ": unknown recv error, peerpid=%d", + ch->farside_pid); + ch->ch_status = IPC_DISCONNECT; + retcode = IPC_FAIL; + break; + } + + } else if (msg_len == 0) { + ch->ch_status = IPC_DISC_PENDING; + if(ch->recv_queue->current_qlen <= 0) { + ch->ch_status = IPC_DISCONNECT; + retcode = IPC_FAIL; + } + } else { + /* We read something! */ + /* Note that all previous cases break out of the loop */ + debug_bytecount += msg_len; + *nbytes = msg_len; + nmsgs = ipc_bufpool_update(pool, ch, msg_len, ch->recv_queue) ; + + if (nmsgs < 0) { + /* we didn't like the other side */ + cl_log(LOG_ERR, "socket_resume_io_read: " + "disconnecting the other side"); + ch->ch_status = IPC_DISCONNECT; + retcode = IPC_FAIL; + } else { + SocketIPCStats.ninqueued += nmsgs; + } + } + } + + /* Check for errors uncaught by recv() */ + /* NOTE: It doesn't seem right we have to do this every time */ + /* FIXME?? */ + + memset(&sockpoll,0, sizeof(struct pollfd)); + if ((retcode == IPC_OK) + && (sockpoll.fd = conn_info->s) >= 0) { + /* Just check for errors, not for data */ + sockpoll.events = 0; + ipc_pollfunc_ptr(&sockpoll, 1, 0); + retcode = socket_check_poll(ch, &sockpoll); + } + + CHANAUDIT(ch); + if (retcode != IPC_OK) { + return retcode; + } + + return IPC_ISRCONN(ch) ? IPC_OK : IPC_BROKEN; +} + +static int +socket_resume_io_write(struct IPC_CHANNEL *ch, int* nmsg) +{ + int retcode = IPC_OK; + struct SOCKET_CH_PRIVATE* conn_info; + + CHANAUDIT(ch); + *nmsg = 0; + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + while (ch->ch_status == IPC_CONNECT + && retcode == IPC_OK + && ch->send_queue->current_qlen > 0) { + + GList * element; + struct IPC_MESSAGE * msg; + struct SOCKET_MSG_HEAD head; + struct IPC_MESSAGE* oldmsg = NULL; + int sendrc = 0; + struct IPC_MESSAGE* newmsg; + char* p; + unsigned int bytes_remaining; + int diff; + + CHANAUDIT(ch); + element = g_list_first(ch->send_queue->queue); + if (element == NULL) { + /* OOPS! - correct consistency problem */ + ch->send_queue->current_qlen = 0; + break; + } + msg = (struct IPC_MESSAGE *) (element->data); + + diff = 0; + if (msg->msg_buf ) { + diff = (char*)msg->msg_body - (char*)msg->msg_buf; + } + if ( diff < (int)sizeof(struct SOCKET_MSG_HEAD) ) { + /* either we don't have msg->msg_buf set + * or we don't have enough bytes for socket head + * we delete this message and creates + * a new one and delete the old one + */ + + newmsg= socket_message_new(ch, msg->msg_len); + if (newmsg == NULL) { + cl_log(LOG_ERR, "socket_resume_io_write: " + "allocating memory for new ipc msg failed"); + return IPC_FAIL; + } + + memcpy(newmsg->msg_body, msg->msg_body, msg->msg_len); + oldmsg = msg; + msg = newmsg; + } + + head.msg_len = msg->msg_len; + head.magic = HEADMAGIC; + memcpy(msg->msg_buf, &head, sizeof(struct SOCKET_MSG_HEAD)); + + if (ch->bytes_remaining == 0) { + /*we start to send a new message*/ +#ifdef IPC_TIME_DEBUG + ipc_time_debug(ch, msg, MSGPOS_SEND); +#endif + bytes_remaining = msg->msg_len + ch->msgpad; + p = msg->msg_buf; + } else { + bytes_remaining = ch->bytes_remaining; + p = ((char*)msg->msg_buf) + msg->msg_len + ch->msgpad + - bytes_remaining; + + } + + sendrc = 0; + + do { +#if HB_IPC_METHOD == HB_IPC_STREAM + struct strbuf d; + int msglen, putmsgrc; +#endif + + CHANAUDIT(ch); + +#if HB_IPC_METHOD == HB_IPC_SOCKET + sendrc = send(conn_info->s, p + , bytes_remaining, (MSG_DONTWAIT|MSG_NOSIGNAL)); +#elif HB_IPC_METHOD == HB_IPC_STREAM + d.maxlen = 0; + d.len = msglen = bytes_remaining; + d.buf = p; + putmsgrc = putmsg(conn_info->s, NULL, &d, 0); + sendrc = putmsgrc == 0 ? msglen : -1; +#endif + SocketIPCStats.last_send_rc = sendrc; + SocketIPCStats.last_send_errno = errno; + ++SocketIPCStats.send_count; + + if (sendrc <= 0) { + break; + } else { + p = p + sendrc; + bytes_remaining -= sendrc; + } + + } while(bytes_remaining > 0 ); + + ch->bytes_remaining = bytes_remaining; + + if (sendrc < 0) { + switch (errno) { + case EAGAIN: + retcode = IPC_OK; + break; + case EPIPE: + ch->ch_status = IPC_DISC_PENDING; + socket_check_disc_pending(ch); + retcode = IPC_BROKEN; + break; + default: + cl_perror("socket_resume_io_write" + ": send2 bad errno"); + ch->ch_status = IPC_DISCONNECT; + retcode = IPC_FAIL; + break; + } + break; + } else { + int orig_qlen; + + CHECKFOO(3,ch, msg, SavedSentBody, "sent message") + + if (oldmsg) { + if (msg->msg_done != NULL) { + msg->msg_done(msg); + } + msg=oldmsg; + } + + if(ch->bytes_remaining ==0) { + ch->send_queue->queue = g_list_remove(ch->send_queue->queue, msg); + if (msg->msg_done != NULL) { + msg->msg_done(msg); + } + + SocketIPCStats.nsent++; + orig_qlen = ch->send_queue->current_qlen--; + socket_check_flow_control(ch, orig_qlen, orig_qlen -1 ); + (*nmsg)++; + } + } + } + CHANAUDIT(ch); + if (retcode != IPC_OK) { + return retcode; + } + return IPC_ISRCONN(ch) ? IPC_OK : IPC_BROKEN; +} + +static int +socket_resume_io(struct IPC_CHANNEL *ch) +{ + int rc1 = IPC_OK; + int rc2 = IPC_OK; + int nwmsg = 1; + int nbytes_r = 1; + gboolean OKonce = FALSE; + + CHANAUDIT(ch); + if (!IPC_ISRCONN(ch)) { + return IPC_BROKEN; + } + + do { + if (nbytes_r > 0) { + rc1 = socket_resume_io_read(ch, &nbytes_r, FALSE); + } + if (nwmsg > 0) { + nwmsg = 0; + rc2 = socket_resume_io_write(ch, &nwmsg); + } + if (rc1 == IPC_OK || rc2 == IPC_OK) { + OKonce = TRUE; + } + } while ((nbytes_r > 0 || nwmsg > 0) && IPC_ISRCONN(ch)); + + if (IPC_ISRCONN(ch)) { + if (rc1 != IPC_OK) { + cl_log(LOG_ERR + , "socket_resume_io_read() failure"); + } + if (rc2 != IPC_OK && IPC_CONNECT == ch->ch_status) { + cl_log(LOG_ERR + , "socket_resume_io_write() failure"); + } + } else { + return (OKonce ? IPC_OK : IPC_BROKEN); + } + + return (rc1 != IPC_OK ? rc1 : rc2); +} + +static int +socket_get_recv_fd(struct IPC_CHANNEL *ch) +{ + struct SOCKET_CH_PRIVATE* chp = ch ->ch_private; + + return (chp == NULL ? -1 : chp->s); +} + +static int +socket_get_send_fd(struct IPC_CHANNEL *ch) +{ + return socket_get_recv_fd(ch); +} + +static void +socket_adjust_buf(struct IPC_CHANNEL *ch, int optname, unsigned q_len) +{ + const char *direction = optname == SO_SNDBUF ? "snd" : "rcv"; + int fd = socket_get_send_fd(ch); + unsigned byte; + + /* Arbitrary scaling. + * DEFAULT_MAX_QLEN is 64, default socket buf is often 64k to 128k, + * at least on those linux I checked. + * Keep that ratio, and allow for some overhead. */ + if (q_len == 0) + /* client does not want anything, + * reduce system buffers as well */ + byte = 4096; + else if (q_len < 512) + byte = (32 + q_len) * 1024; + else + byte = q_len * 1024; + + if (0 == setsockopt(fd, SOL_SOCKET, optname, &byte, sizeof(byte))) { + if (debug_level > 1) { + cl_log(LOG_DEBUG, "adjusted %sbuf size to %u", + direction, byte); + } + } else { + /* If this fails, you may need to adjust net.core.rmem_max, + * ...wmem_max, or equivalent */ + cl_log(LOG_WARNING, "adjust %sbuf size to %u failed: %s", + direction, byte, strerror(errno)); + } +} + +static int +socket_set_send_qlen (struct IPC_CHANNEL* ch, int q_len) +{ + /* This seems more like an assertion failure than a normal error */ + if (ch->send_queue == NULL) { + return IPC_FAIL; + } + socket_adjust_buf(ch, SO_SNDBUF, q_len); + ch->send_queue->max_qlen = q_len; + return IPC_OK; +} + +static int +socket_set_recv_qlen (struct IPC_CHANNEL* ch, int q_len) +{ + /* This seems more like an assertion failure than a normal error */ + if (ch->recv_queue == NULL) { + return IPC_FAIL; + } + socket_adjust_buf(ch, SO_RCVBUF, q_len); + ch->recv_queue->max_qlen = q_len; + return IPC_OK; +} + +static int ipcmsg_count_allocated = 0; +static int ipcmsg_count_freed = 0; +void socket_ipcmsg_dump_stats(void); +void +socket_ipcmsg_dump_stats(void) { + cl_log(LOG_INFO, "ipcsocket ipcmsg allocated=%d, freed=%d, diff=%d", + ipcmsg_count_allocated, + ipcmsg_count_freed, + ipcmsg_count_allocated - ipcmsg_count_freed); +} + +static void +socket_del_ipcmsg(IPC_Message* m) +{ + if (m == NULL) { + cl_log(LOG_ERR, "socket_del_ipcmsg:" + "msg is NULL"); + return; + } + + if (m->msg_body) { + memset(m->msg_body, 0, m->msg_len); + } + if (m->msg_buf) { + g_free(m->msg_buf); + } + + memset(m, 0, sizeof(*m)); + g_free(m); + + ipcmsg_count_freed ++; +} + +static IPC_Message* +socket_new_ipcmsg(IPC_Channel* ch, const void* data, int len, void* private) +{ + IPC_Message* hdr; + + if (ch == NULL || len < 0) { + cl_log(LOG_ERR, "socket_new_ipcmsg:" + " invalid parameter"); + return NULL; + } + + if (ch->msgpad > MAX_MSGPAD) { + cl_log(LOG_ERR, "socket_new_ipcmsg: too many pads " + "something is wrong"); + return NULL; + } + + hdr = ipcmsg_new(ch, data, len, private, socket_del_ipcmsg); + + if (hdr) ipcmsg_count_allocated ++; + + return hdr; +} + +static +struct IPC_MESSAGE * +ipcmsg_new(struct IPC_CHANNEL * ch, const void* data, int len, void* private, + DelProc delproc) +{ + struct IPC_MESSAGE * hdr; + char* copy = NULL; + char* buf; + char* body; + + if ((hdr = g_new(struct IPC_MESSAGE, 1)) == NULL) { + return NULL; + } + memset(hdr, 0, sizeof(*hdr)); + + if (len > 0) { + if ((copy = (char*)g_malloc(ch->msgpad + len)) == NULL) { + g_free(hdr); + return NULL; + } + if (data) { + memcpy(copy + ch->msgpad, data, len); + } + buf = copy; + body = copy + ch->msgpad;; + } else { + len = 0; + buf = body = NULL; + } + hdr->msg_len = len; + hdr->msg_buf = buf; + hdr->msg_body = body; + hdr->msg_ch = ch; + hdr->msg_done = delproc; + hdr->msg_private = private; + + return hdr; +} + +static int +socket_get_chan_status(IPC_Channel* ch) +{ + socket_resume_io(ch); + return ch->ch_status; +} + +/* socket object of the function table */ +static struct IPC_WAIT_OPS socket_wait_ops = { + socket_destroy_wait_conn, + socket_wait_selectfd, + socket_accept_connection, +}; + +/* + * create a new ipc queue whose length = 0 and inner queue = NULL. + * return the pointer to a new ipc queue or NULL is the queue can't be created. + */ + +static struct IPC_QUEUE* +socket_queue_new(void) +{ + struct IPC_QUEUE *temp_queue; + + /* temp queue with length = 0 and inner queue = NULL. */ + temp_queue = g_new(struct IPC_QUEUE, 1); + temp_queue->current_qlen = 0; + temp_queue->max_qlen = DEFAULT_MAX_QLEN; + temp_queue->queue = NULL; + temp_queue->last_maxqlen_warn = 0; + temp_queue->maxqlen_cnt = 0; + return temp_queue; +} + +/* + * socket_wait_conn_new: + * Called by ipc_wait_conn_constructor to get a new socket + * waiting connection. + * (better explanation of this role might be nice) + * + * Parameters : + * ch_attrs (IN) the attributes used to create this connection. + * + * Return : + * the pointer to the new waiting connection or NULL if the connection + * can't be created. + * + * NOTE : + * for domain socket implementation, the only attribute needed is path name. + * so the user should + * create the hash table like this: + * GHashTable * attrs; + * attrs = g_hash_table_new(g_str_hash, g_str_equal); + * g_hash_table_insert(attrs, PATH_ATTR, path_name); + * here PATH_ATTR is defined as "path". + * + * NOTE : + * The streams implementation uses "Streams Programming Guide", Solaris 8, + * as its guide (sample code near end of "Configuration" chapter 11). + */ +struct IPC_WAIT_CONNECTION * +socket_wait_conn_new(GHashTable *ch_attrs) +{ + struct IPC_WAIT_CONNECTION * temp_ch; + char *path_name; + char *mode_attr; + int s, flags; + struct SOCKET_WAIT_CONN_PRIVATE *wait_private; + mode_t s_mode; +#if HB_IPC_METHOD == HB_IPC_SOCKET + struct sockaddr_un my_addr; +#elif HB_IPC_METHOD == HB_IPC_STREAM + int pipefds[2]; +#endif + + path_name = (char *) g_hash_table_lookup(ch_attrs, IPC_PATH_ATTR); + mode_attr = (char *) g_hash_table_lookup(ch_attrs, IPC_MODE_ATTR); + + if (mode_attr != NULL) { + s_mode = (mode_t)strtoul((const char *)mode_attr, NULL, 8); + } else { + s_mode = 0777; + } + if (path_name == NULL) { + return NULL; + } + +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* prepare the unix domain socket */ + if ((s = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) { + cl_perror("socket_wait_conn_new: socket() failure"); + return NULL; + } + + if (unlink(path_name) < 0 && errno != ENOENT) { + cl_perror("socket_wait_conn_new: unlink failure(%s)", + path_name); + } + memset(&my_addr, 0, sizeof(my_addr)); + my_addr.sun_family = AF_LOCAL; /* host byte order */ + + if (strlen(path_name) >= sizeof(my_addr.sun_path)) { + close(s); + return NULL; + } + + strncpy(my_addr.sun_path, path_name, sizeof(my_addr.sun_path)); + + if (bind(s, (struct sockaddr *)&my_addr, sizeof(my_addr)) == -1) { + cl_perror("socket_wait_conn_new: trying to create in %s bind:" + , path_name); + close(s); + return NULL; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + /* Set up the communication channel the clients will use to us (server) */ + if (pipe(pipefds) == -1) { + cl_perror("pipe() failure"); + return NULL; + } + + /* Let clients have unique connections to us */ + if (ioctl(pipefds[1], I_PUSH, "connld") == -1) { + cl_perror("ioctl(%d, I_PUSH, \"connld\") failure", pipefds[1]); + return NULL; + } + + if (unlink(path_name) < 0 && errno != ENOENT) { + cl_perror("socket_wait_conn_new: unlink failure(%s)", + path_name); + } + + if (mkfifo(path_name, s_mode) == -1) { + cl_perror("socket_wait_conn_new: mkfifo(%s, ...) failure", path_name); + return NULL; + } + + if (fattach(pipefds[1], path_name) == -1) { + cl_perror("socket_wait_conn_new: fattach(..., %s) failure", path_name); + return NULL; + } + + /* the pseudo-socket is the other part of the pipe */ + s = pipefds[0]; +#endif + + /* Change the permission of the socket */ + if (chmod(path_name,s_mode) < 0) { + cl_perror("socket_wait_conn_new: failure trying to chmod %s" + , path_name); + close(s); + return NULL; + } + +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* listen to the socket */ + if (listen(s, MAX_LISTEN_NUM) == -1) { + cl_perror("socket_wait_conn_new: listen(MAX_LISTEN_NUM)"); + close(s); + return NULL; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + +#endif + + flags = fcntl(s, F_GETFL); + if (flags == -1) { + cl_perror("socket_wait_conn_new: cannot read file descriptor flags"); + close(s); + return NULL; + } + flags |= O_NONBLOCK; + if (fcntl(s, F_SETFL, flags) < 0) { + cl_perror("socket_wait_conn_new: cannot set O_NONBLOCK"); + close(s); + return NULL; + } + + wait_private = g_new(struct SOCKET_WAIT_CONN_PRIVATE, 1); +#if HB_IPC_METHOD == HB_IPC_SOCKET + wait_private->s = s; +#elif HB_IPC_METHOD == HB_IPC_STREAM + wait_private->pipefds[0] = pipefds[0]; + wait_private->pipefds[1] = pipefds[1]; +#endif + strncpy(wait_private->path_name, path_name, sizeof(wait_private->path_name)); + temp_ch = g_new(struct IPC_WAIT_CONNECTION, 1); + temp_ch->ch_private = (void *) wait_private; + temp_ch->ch_status = IPC_WAIT; + temp_ch->ops = (struct IPC_WAIT_OPS *)&socket_wait_ops; + + return temp_ch; +} + +/* + * will be called by ipc_channel_constructor to create a new socket channel. + * parameters : + * attrs (IN) the hash table of the attributes used to create this channel. + * + * return: + * the pointer to the new waiting channel or NULL if the channel can't be created. +*/ + +struct IPC_CHANNEL * +socket_client_channel_new(GHashTable *ch_attrs) { + char *path_name; + int sockfd; + + /* + * I don't really understand why the client and the server use different + * parameter names... + * + * It's a really bad idea to store both integers and strings + * in the same table. + * + * Maybe we need an internal function with a different set of parameters? + */ + + /* + * if we want to seperate them. I suggest + * <client side> + * user call ipc_channel_constructor(ch_type,attrs) to create a new channel. + * ipc_channel_constructor() call socket_channel_new(GHashTable*)to + * create a new socket channel. + * <server side> + * wait_conn->accept_connection() will call another function to create a + * new channel. This function will take socketfd as the parameter to + * create a socket channel. + */ + + path_name = (char *) g_hash_table_lookup(ch_attrs, IPC_PATH_ATTR); + if (path_name == NULL) { + return NULL; + } + +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* prepare the socket */ + if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) { + cl_perror("socket_client_channel_new: socket"); + return NULL; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + sockfd = open(path_name, O_RDWR|O_NONBLOCK); + if (sockfd == -1) { + cl_perror("socket_client_channel_new: open(%s, ...) failure", path_name); + return NULL; + } +#endif + + if (client_channel_new_auth(sockfd) < 0) { + close(sockfd); + return NULL; + } + return channel_new(sockfd, IPC_CLIENT, path_name); +} + +static +int client_channel_new_auth(int sockfd) { +#ifdef USE_BINDSTAT_CREDS + char rand_id[16]; + char uuid_str_tmp[40]; + struct sockaddr_un sock_addr; + + /* Prepare the socket */ + memset(&sock_addr, 0, sizeof(sock_addr)); + sock_addr.sun_family = AF_UNIX; + + /* make sure socket paths never clash */ + uuid_generate(rand_id); + uuid_unparse(rand_id, uuid_str_tmp); + + snprintf(sock_addr.sun_path, sizeof(sock_addr.sun_path), + "%s/%s", HA_VARLIBHBDIR, uuid_str_tmp); + + unlink(sock_addr.sun_path); + if(bind(sockfd, (struct sockaddr*)&sock_addr, SUN_LEN(&sock_addr)) < 0) { + perror("Client bind() failure"); + return 0; + } +#endif + + return 0; +} + +static +struct IPC_CHANNEL * +socket_server_channel_new(int sockfd) { + return channel_new(sockfd, IPC_SERVER, "?"); +} + +static +struct IPC_CHANNEL * +channel_new(int sockfd, int conntype, const char *path_name) { + struct IPC_CHANNEL * temp_ch; + struct SOCKET_CH_PRIVATE* conn_info; + int flags; + + if (path_name == NULL || strlen(path_name) >= sizeof(conn_info->path_name)) { + return NULL; + } + + temp_ch = g_new(struct IPC_CHANNEL, 1); + if (temp_ch == NULL) { + cl_log(LOG_ERR, "channel_new: allocating memory for channel failed"); + return NULL; + } + memset(temp_ch, 0, sizeof(struct IPC_CHANNEL)); + + conn_info = g_new(struct SOCKET_CH_PRIVATE, 1); + + flags = fcntl(sockfd, F_GETFL); + if (flags == -1) { + cl_perror("channel_new: cannot read file descriptor flags"); + g_free(conn_info); conn_info = NULL; + g_free(temp_ch); + if (conntype == IPC_CLIENT) close(sockfd); + return NULL; + } + flags |= O_NONBLOCK; + if (fcntl(sockfd, F_SETFL, flags) < 0) { + cl_perror("channel_new: cannot set O_NONBLOCK"); + g_free(conn_info); conn_info = NULL; + g_free(temp_ch); + if (conntype == IPC_CLIENT) close(sockfd); + return NULL; + } + + conn_info->s = sockfd; + conn_info->remaining_data = 0; + conn_info->buf_msg = NULL; +#if HB_IPC_METHOD == HB_IPC_SOCKET + conn_info->peer_addr = NULL; +#endif + strncpy(conn_info->path_name, path_name, sizeof(conn_info->path_name)); + +#ifdef DEBUG + cl_log(LOG_INFO, "Initializing socket %d to DISCONNECT", sockfd); +#endif + temp_ch->ch_status = IPC_DISCONNECT; + temp_ch->ch_private = (void*) conn_info; + temp_ch->ops = (struct IPC_OPS *)&socket_ops; + temp_ch->msgpad = sizeof(struct SOCKET_MSG_HEAD); + temp_ch->bytes_remaining = 0; + temp_ch->should_send_block = FALSE; + temp_ch->should_block_fail = TRUE; + temp_ch->send_queue = socket_queue_new(); + temp_ch->recv_queue = socket_queue_new(); + temp_ch->pool = NULL; + temp_ch->high_flow_mark = temp_ch->send_queue->max_qlen; + temp_ch->low_flow_mark = -1; + temp_ch->conntype = conntype; + temp_ch->refcount = 0; + temp_ch->farside_uid = -1; + temp_ch->farside_gid = -1; + + return temp_ch; +} + +/* + * Create a new pair of pre-connected IPC channels similar to + * the result of pipe(2), or socketpair(2). + */ + +int +ipc_channel_pair(IPC_Channel* channels[2]) +{ + int sockets[2]; + int rc; + int j; + const char *pname; + +#if HB_IPC_METHOD == HB_IPC_SOCKET + pname = "[socketpair]"; + + if ((rc = socketpair(AF_LOCAL, SOCK_STREAM, 0, sockets)) < 0) { + return IPC_FAIL; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + pname = "[pipe]"; + + if ((rc = pipe(sockets)) < 0) { + return IPC_FAIL; + } + rc = 0; + for (j=0; j < 2; ++j) { + if (fcntl(sockets[j], F_SETFL, O_NONBLOCK) < 0) { + cl_perror("ipc_channel_pair: cannot set O_NONBLOCK"); + rc = -1; + } + } + if (rc < 0) { + close(sockets[0]); + close(sockets[1]); + return IPC_FAIL; + } +#endif + + if ((channels[0] = socket_server_channel_new(sockets[0])) == NULL) { + close(sockets[0]); + close(sockets[1]); + return IPC_FAIL; + } + if ((channels[1] = socket_server_channel_new(sockets[1])) == NULL) { + close(sockets[0]); + close(sockets[1]); + channels[0]->ops->destroy(channels[0]); + return IPC_FAIL; + } + for (j=0; j < 2; ++j) { + struct SOCKET_CH_PRIVATE* p = channels[j]->ch_private; + channels[j]->ch_status = IPC_CONNECT; + channels[j]->conntype = IPC_PEER; + /* Valid, but not terribly meaningful */ + channels[j]->farside_pid = getpid(); + strncpy(p->path_name, pname, sizeof(p->path_name)); + } + + return IPC_OK; +} + +/* brief free the memory space allocated to msg and destroy msg. */ + +static void +socket_free_message(struct IPC_MESSAGE * msg) { +#if 0 + memset(msg->msg_body, 0xff, msg->msg_len); +#endif + if (msg->msg_buf) { + g_free(msg->msg_buf); + } else { + g_free(msg->msg_body); + } +#if 0 + memset(msg, 0xff, sizeof(*msg)); +#endif + g_free((void *)msg); +} + +/* + * create a new ipc message whose msg_body's length is msg_len. + * + * parameters : + * msg_len (IN) the length of this message body in this message. + * + * return : + * the pointer to the new message or NULL if the message can't be created. + */ + +static struct IPC_MESSAGE* +socket_message_new(struct IPC_CHANNEL *ch, int msg_len) +{ + return ipcmsg_new(ch, NULL, msg_len, NULL, socket_free_message); +} + +/*********************************************************************** + * + * IPC authentication schemes... More machine dependent than + * we'd like, but don't know any better way... + * + ***********************************************************************/ + +static int +verify_creds(struct IPC_AUTH *auth_info, uid_t uid, gid_t gid) +{ + int ret = IPC_FAIL; + + if (!auth_info || (!auth_info->uid && !auth_info->gid)) { + return IPC_OK; + } + if ( auth_info->uid + && (g_hash_table_lookup(auth_info->uid + , GUINT_TO_POINTER((guint)uid)) != NULL)) { + ret = IPC_OK; + } else if (auth_info->gid + && (g_hash_table_lookup(auth_info->gid + , GUINT_TO_POINTER((guint)gid)) != NULL)) { + ret = IPC_OK; + } + return ret; +} + +/*********************************************************************** + * SO_PEERCRED VERSION... (Linux) + ***********************************************************************/ + +#ifdef USE_SO_PEERCRED +/* verify the authentication information. */ +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + struct SOCKET_CH_PRIVATE * conn_info; + int ret = IPC_FAIL; + struct ucred cred; + socklen_t n = sizeof(cred); + + if (ch == NULL || ch->ch_private == NULL) { + return IPC_FAIL; + } + if (auth_info == NULL + || (auth_info->uid == NULL && auth_info->gid == NULL)) { + ret = IPC_OK; /* no restriction for authentication */ + } + + /* Get the credential information for our peer */ + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + if (getsockopt(conn_info->s, SOL_SOCKET, SO_PEERCRED, &cred, &n) != 0 + || (size_t)n != sizeof(cred)) { + return ret; + } + + ch->farside_uid = cred.uid; + ch->farside_gid = cred.gid; + if (ret == IPC_OK) { + return ret; + } +#if 0 + cl_log(LOG_DEBUG, "SO_PEERCRED returned [%d, (%ld:%ld)]" + , cred.pid, (long)cred.uid, (long)cred.uid); + cl_log(LOG_DEBUG, "Verifying authentication: cred.uid=%d cred.gid=%d" + , cred.uid, cred.gid); + cl_log(LOG_DEBUG, "Verifying authentication: uidptr=0x%lx gidptr=0x%lx" + , (unsigned long)auth_info->uid + , (unsigned long)auth_info->gid); +#endif + /* verify the credential information. */ + return verify_creds(auth_info, cred.uid, cred.gid); +} + +/* get farside pid for our peer process */ + +static +pid_t +socket_get_farside_pid(int sockfd) +{ + socklen_t n; + struct ucred *cred; + pid_t f_pid; + + /* Get the credential information from peer */ + n = sizeof(struct ucred); + cred = g_new(struct ucred, 1); + if (getsockopt(sockfd, SOL_SOCKET, SO_PEERCRED, cred, &n) != 0) { + g_free(cred); + return -1; + } + + f_pid = cred->pid; + g_free(cred); + return f_pid; +} +#endif /* SO_PEERCRED version */ + +#ifdef USE_GETPEEREID +/* + * This is implemented in OpenBSD and FreeBSD. + * + * It's not a half-bad interface... + * + * This should probably be our standard way of doing it, and put it + * as a replacement library. That would simplify things... + */ + +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + struct SOCKET_CH_PRIVATE *conn_info; + uid_t euid; + gid_t egid; + int ret = IPC_FAIL; + + if (auth_info == NULL + || (auth_info->uid == NULL && auth_info->gid == NULL)) { + ret = IPC_OK; /* no restriction for authentication */ + } + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + if (getpeereid(conn_info->s, &euid, &egid) < 0) { + cl_perror("getpeereid() failure"); + return ret; + } + + ch->farside_uid = euid; + ch->farside_gid = egid; + + /* verify the credential information. */ + return verify_creds(auth_info, euid, egid); +} + +static +pid_t +socket_get_farside_pid(int sock) +{ + return -1; +} +#endif /* USE_GETPEEREID */ + +/*********************************************************************** + * SCM_CREDS VERSION... (*BSD systems) + ***********************************************************************/ +#ifdef USE_SCM_CREDS +/* FIXME! Need to implement SCM_CREDS mechanism for BSD-based systems + * This isn't an emergency, but should be done in the future... + * Hint: * Postgresql does both types of authentication... + * see src/backend/libpq/auth.c + * Not clear its SO_PEERCRED implementation works though ;-) + */ + +/* Done.... Haven't tested yet. */ +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + struct msghdr msg; + /* Credentials structure */ + +#define EXTRASPACE 0 + +#ifdef HAVE_STRUCT_CMSGCRED + /* FreeBSD */ + typedef struct cmsgcred Cred; +# define crRuid cmcred_uid +# define crEuid cmcred_euid +# define crRgid cmcred_gid +# define crEgid cmcred_groups[0] /* Best guess */ +# define crpid cmcred_pid +# define crngrp cmcred_ngroups +# define crgrps cmcred_groups + +#elif HAVE_STRUCT_FCRED + /* Stevens' book */ + typedef struct fcred Cred; +# define crRuid fc_uid +# define crRgid fc_rgid +# define crEgid fc_gid +# define crngrp fc_ngroups +# define crgrps fc_groups + +#elif HAVE_STRUCT_SOCKCRED + /* NetBSD */ + typedef struct sockcred Cred; +# define crRuid sc_uid +# define crEuid sc_euid +# define crRgid sc_gid +# define crEgid sc_egid +# define crngrp sc_ngroups +# define crgrps sc_groups +# undef EXTRASPACE +# define EXTRASPACE SOCKCREDSIZE(ngroups) + +#elif HAVE_STRUCT_CRED + typedef struct cred Cred; +#define cruid c_uid + +#elif HAVE_STRUCT_UCRED + typedef struct ucred Cred; + + /* reuse this define for the moment */ +# if HAVE_STRUCT_UCRED_DARWIN +# define crEuid cr_uid +# define crEgid cr_groups[0] /* Best guess */ +# define crgrps cr_groups +# define crngrp cr_ngroups +# else +# define crEuid c_uid +# define crEgid c_gid +# endif +#else +# error "No credential type found!" +#endif + + struct SOCKET_CH_PRIVATE *conn_info; + int ret = IPC_FAIL; + char buf; + + /* Compute size without padding */ + #define CMSGSIZE (sizeof(struct cmsghdr)+(sizeof(Cred))+EXTRASPACE) + + union { + char mem[CMSGSIZE]; + struct cmsghdr hdr; + Cred credu; + }cmsgmem; + Cred cred; + + /* Point to start of first structure */ + struct cmsghdr *cmsg = &cmsgmem.hdr; + + if (auth_info == NULL + || (auth_info->uid == NULL && auth_info->gid == NULL)) { + ret = IPC_OK; /* no restriction for authentication */ + } + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = g_new(struct iovec, 1); + msg.msg_iovlen = 1; + msg.msg_control = (char *) cmsg; + msg.msg_controllen = CMSGSIZE; + memset(cmsg, 0, sizeof(cmsgmem)); + + /* + * The one character which is received here is not meaningful; its + * purpose is only to make sure that recvmsg() blocks long enough for + * the other side to send its credentials. + */ + msg.msg_iov->iov_base = &buf; + msg.msg_iov->iov_len = 1; + + if (recvmsg(conn_info->s, &msg, 0) < 0 + || cmsg->cmsg_len < CMSGSIZE + || cmsg->cmsg_type != SCM_CREDS) { + cl_perror("can't get credential information from peer"); + return ret; + } + + /* Avoid alignment issues - just copy it! */ + memcpy(&cred, CMSG_DATA(cmsg), sizeof(cred)); + + ch->farside_uid = cred.crEuid; + ch->farside_gid = cred.crEgid; + if (ret == IPC_OK) { + return ret; + } + + /* verify the credential information. */ + return verify_creds(auth_info, cred.crEuid, cred.crEgid); +} + +/* + * FIXME! Need to implement SCM_CREDS mechanism for BSD-based systems + * this is similar to the SCM_CREDS mechanism for verify_auth() function. + * here we just want to get the pid of the other side from the credential + * information. + */ + +static +pid_t +socket_get_farside_pid(int sock) +{ + /* FIXME! */ + return -1; +} +#endif /* SCM_CREDS version */ + +/*********************************************************************** + * Bind/Stat VERSION... (Supported on OSX/Darwin and 4.3+BSD at least...) + * + * This is for use on systems such as OSX-Darwin where + * none of the other options is available. + * + * This implementation has been adapted from "Advanced Programming + * in the Unix Environment", Section 15.5.2, by W. Richard Stevens. + * + */ +#ifdef USE_BINDSTAT_CREDS + +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + int len = 0; + int ret = IPC_FAIL; + struct stat stat_buf; + struct sockaddr_un *peer_addr = NULL; + struct SOCKET_CH_PRIVATE *ch_private = NULL; + + if(ch != NULL) { + ch_private = (struct SOCKET_CH_PRIVATE *)(ch->ch_private); + if(ch_private != NULL) { + peer_addr = ch_private->peer_addr; + } + } + + if(ch == NULL) { + cl_log(LOG_ERR, "No channel to authenticate"); + return IPC_FAIL; + + } else if (auth_info == NULL + || (auth_info->uid == NULL && auth_info->gid == NULL)) { + ret = IPC_OK; /* no restriction for authentication */ + + } + + if(ch_private == NULL) { + cl_log(LOG_ERR, "No channel private data available"); + return ret; + + } else if(peer_addr == NULL) { + cl_log(LOG_ERR, "No peer information available"); + return ret; + } + + len = SUN_LEN(peer_addr); + + if(len < 1) { + cl_log(LOG_ERR, "No peer information available"); + return ret; + } + peer_addr->sun_path[len] = 0; + stat(peer_addr->sun_path, &stat_buf); + + ch->farside_uid = stat_buf.st_uid; + ch->farside_gid = stat_buf.st_gid; + if (ret == IPC_OK) { + return ret; + } + + if ((auth_info->uid == NULL || g_hash_table_size(auth_info->uid) == 0) + && auth_info->gid != NULL + && g_hash_table_size(auth_info->gid) != 0) { + cl_log(LOG_WARNING, + "GID-Only IPC security is not supported" + " on this platform."); + return IPC_BROKEN; + } + + /* verify the credential information. */ + return verify_creds(auth_info, stat_buf.st_uid, stat_buf.st_gid); +} + +static pid_t +socket_get_farside_pid(int sock) +{ + return -1; +} +#endif /* Bind/stat version */ + +/*********************************************************************** + * USE_STREAM_CREDS VERSION... (e.g. Solaris pre-10) + ***********************************************************************/ +#ifdef USE_STREAM_CREDS +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + struct SOCKET_CH_PRIVATE *conn_info; + + if (ch == NULL || ch->ch_private == NULL) { + return IPC_FAIL; + } + + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + ch->farside_uid = conn_info->farside_uid; + ch->farside_gid = conn_info->farside_gid; + + /* verify the credential information. */ + return verify_creds(auth_info, + conn_info->farside_uid, conn_info->farside_gid); +} + +static +pid_t +socket_get_farside_pid(int sock) +{ + return -1; +} +#endif + +/*********************************************************************** + * GETPEERUCRED VERSION... (e.g. Solaris 10 upwards) + ***********************************************************************/ + +#ifdef USE_GETPEERUCRED +/* verify the authentication information. */ +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + struct SOCKET_CH_PRIVATE *conn_info; + ucred_t *ucred = NULL; + int rc = IPC_FAIL; + + if (ch == NULL || ch->ch_private == NULL) { + return IPC_FAIL; + } + + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + if (auth_info == NULL + || (auth_info->uid == NULL && auth_info->gid == NULL)) { + rc = IPC_OK; /* no restriction for authentication */ + } + + if (getpeerucred(conn_info->s, &ucred) < 0) { + cl_perror("getpeereid() failure"); + return rc; + } + + ch->farside_uid = ucred_geteuid(ucred); + ch->farside_gid = ucred_getegid(ucred); + if (rc == IPC_OK) { + return rc; + } + + /* verify the credential information. */ + rc = verify_creds(auth_info, + ucred_geteuid(ucred), ucred_getegid(ucred)); + ucred_free(ucred); + return rc; +} + +static +pid_t +socket_get_farside_pid(int sockfd) +{ + ucred_t *ucred = NULL; + pid_t pid; + + if (getpeerucred(sockfd, &ucred) < 0) { + cl_perror("getpeereid() failure"); + return IPC_FAIL; + } + + pid = ucred_getpid(ucred); + + ucred_free(ucred); + + return pid; +} +#endif + +/*********************************************************************** + * DUMMY VERSION... (other systems...) + * + * Other options that seem to be out there include + * SCM_CREDENTIALS and LOCAL_CREDS + * There are some kludgy things you can do with SCM_RIGHTS + * to pass an fd which could only be opened by the user id to + * validate the user id, but I don't know of a similar kludge which + * would work for group ids. And, even the uid one will fail + * if normal users are allowed to give away (chown) files. + * + * Unfortunately, this set of authentication routines have become + * very important to this API and its users (like heartbeat). + * + ***********************************************************************/ + +#ifdef USE_DUMMY_CREDS +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + return IPC_FAIL; +} + +static +pid_t +socket_get_farside_pid(int sock) +{ + return -1; +} +#endif /* Dummy version */ + +/* socket object of the function table */ +static struct IPC_OPS socket_ops = { + socket_destroy_channel, + socket_initiate_connection, + socket_verify_auth, + socket_assert_auth, + socket_send, + socket_recv, + socket_waitin, + socket_waitout, + socket_is_message_pending, + socket_is_output_pending, + socket_resume_io, + socket_get_send_fd, + socket_get_recv_fd, + socket_set_send_qlen, + socket_set_recv_qlen, + socket_set_high_flow_callback, + socket_set_low_flow_callback, + socket_new_ipcmsg, + socket_get_chan_status, + socket_is_sendq_full, + socket_is_recvq_full, + socket_get_conntype, + socket_disconnect, +}; diff --git a/lib/clplumbing/ipctest.c b/lib/clplumbing/ipctest.c new file mode 100644 index 0000000..333d3a0 --- /dev/null +++ b/lib/clplumbing/ipctest.c @@ -0,0 +1,1377 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#undef _GNU_SOURCE /* in case it was defined on the command line */ +#define _GNU_SOURCE +#include <lha_internal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +/* libgen.h: for 'basename()' on Solaris */ +#include <libgen.h> +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/cl_poll.h> +#include <clplumbing/GSource.h> +#include <clplumbing/ipc.h> + +#define MAXERRORS 1000 +#define MAXERRORS_RECV 10 + +typedef int (*TestFunc_t)(IPC_Channel*chan, int count); + +static int channelpair(TestFunc_t client, TestFunc_t server, int count); +#if 0 +static void clientserverpair(IPC_Channel* channels[2]); +#endif + +static int echoserver(IPC_Channel*, int repcount); +static int echoclient(IPC_Channel*, int repcount); +static int asyn_echoserver(IPC_Channel*, int repcount); +static int asyn_echoclient(IPC_Channel*, int repcount); +static int mainloop_server(IPC_Channel* chan, int repcount); +static int mainloop_client(IPC_Channel* chan, int repcount); + +static int checksock(IPC_Channel* channel); +static void checkifblocked(IPC_Channel* channel); + +static int (*PollFunc)(struct pollfd * fds, unsigned int, int) += (int (*)(struct pollfd * fds, unsigned int, int)) poll; +static gboolean checkmsg(IPC_Message* rmsg, const char * who, int rcount); + +static const char *procname; + +static const int iter_def = 10000; /* number of iterations */ +static int verbosity; /* verbosity level */ + +/* + * The ipc interface can be invoked as either: + * 1. pair (pipe/socketpair); + * 2. separate connect/accept (like server with multiple independent clients). + * + * If number of clients is given as 0, the "pair" mechanism is used, + * otherwise the client/server mechanism. + */ +/* *** CLIENTS_MAX currently 1 while coding *** */ +#define CLIENTS_MAX 1 /* max. number of independent clients */ +static int clients_def; /* number of independent clients */ + +static int +channelpair(TestFunc_t clientfunc, TestFunc_t serverfunc, int count) +{ + IPC_Channel* channels[2]; + int rc = 0; + int waitstat = 0; + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main process", + procname, (int)getpid(), __LINE__); + } + switch (fork()) { + case -1: + cl_perror("can't fork"); + exit(1); + break; + default: /* Parent */ + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main waiting...", + procname, (int)getpid(), __LINE__); + } + while (wait(&waitstat) > 0) { + if (WIFEXITED(waitstat)) { + rc += WEXITSTATUS(waitstat); + }else{ + rc += 1; + } + } + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main ended rc: %d", + procname, (int)getpid(), __LINE__, rc); + } + if (rc > 127) { + rc = 127; + } + exit(rc); + break; + case 0: /* Child */ + break; + } + /* Child continues here... */ + if (ipc_channel_pair(channels) != IPC_OK) { + cl_perror("Can't create ipc channel pair"); + exit(1); + } + checksock(channels[0]); + checksock(channels[1]); + switch (fork()) { + case -1: + cl_perror("can't fork"); + exit(1); + break; + + case 0: /* echo "client" Child */ + channels[1]->ops->destroy(channels[1]); + channels[1] = NULL; + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: client starting...", + procname, (int)getpid(), __LINE__); + } + rc = clientfunc(channels[0], count); + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: client ended rc:%d", + procname, (int)getpid(), __LINE__, rc); + } + exit (rc > 127 ? 127 : rc); + break; + + default: + break; + } + channels[0]->ops->destroy(channels[0]); + channels[0] = NULL; + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: server starting...", + procname, (int)getpid(), __LINE__); + } + rc = serverfunc(channels[1], count); + wait(&waitstat); + if (WIFEXITED(waitstat)) { + rc += WEXITSTATUS(waitstat); + }else{ + rc += 1; + } + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: server ended rc:%d", + procname, (int)getpid(), __LINE__, rc); + } + return(rc); +} + +/* server with many clients */ +static int +clientserver(TestFunc_t clientfunc, TestFunc_t serverfunc, int count, int clients) +{ + IPC_Channel* channel; + int rc = 0; + int waitstat = 0; + struct IPC_WAIT_CONNECTION *wconn; + char path[] = IPC_PATH_ATTR; + char commpath[] = "/tmp/foobar"; /* *** CHECK/FIX: Is this OK? */ + GHashTable * wattrs; + int i; + pid_t pid; + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main process", + procname, (int)getpid(), __LINE__); + } + + switch (fork()) { + case -1: + cl_perror("can't fork"); + exit(1); + break; + default: /* Parent */ + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main waiting...", + procname, (int)getpid(), __LINE__); + } + while ((pid = wait(&waitstat)) > 0) { + if (WIFEXITED(waitstat)) { + rc += WEXITSTATUS(waitstat); + }else{ + rc += 1; + } + } + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main ended rc: %d", + procname, (int)getpid(), __LINE__, rc); + } + if (rc > 127) { + rc = 127; + } + exit(rc); + break; + case 0: /* Child */ + break; + } + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d:", + procname, (int)getpid(), __LINE__); + } + + /* set up a server */ + wattrs = g_hash_table_new(g_str_hash, g_str_equal); + if (! wattrs) { + cl_perror("g_hash_table_new() failed"); + exit(1); + } + g_hash_table_insert(wattrs, path, commpath); + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d:", + procname, (int)getpid(), __LINE__); + } + + wconn = ipc_wait_conn_constructor(IPC_ANYTYPE, wattrs); + if (! wconn) { + cl_perror("could not establish server"); + exit(1); + } + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d:", + procname, (int)getpid(), __LINE__); + } + + /* spawn the clients */ + for (i = 1; i <= clients; i++) { + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: fork client %d of %d", + procname, (int)getpid(), __LINE__, i, clients); + } + switch (fork()) { + case -1: + cl_perror("can't fork"); + exit(1); + break; + + case 0: /* echo "client" Child */ + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: client %d starting...", + procname, (int)getpid(), __LINE__, i); + } + channel = ipc_channel_constructor(IPC_ANYTYPE, wattrs); + if (channel == NULL) { + cl_perror("client: channel creation failed"); + exit(1); + } + + rc = channel->ops->initiate_connection(channel); + if (rc != IPC_OK) { + cl_perror("channel[1] failed to connect"); + exit(1); + } + checksock(channel); + rc = clientfunc(channel, count); + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: client %d ended rc:%d", + procname, (int)getpid(), __LINE__, rc, i); + } + exit (rc > 127 ? 127 : rc); + break; + + default: + break; + } + } + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: server starting...", + procname, (int)getpid(), __LINE__); + } + /* accept on server */ + /* *** + * Two problems (or more) here: + * 1. What to do if no incoming call pending? + * At present, fudge by sleeping a little so client gets started. + * 2. How to handle multiple clients? + * Would need to be able to await both new connections and + * data on existing connections. + * At present, fudge CLIENTS_MAX as 1. + * *** + */ + sleep(1); /* *** */ + channel = wconn->ops->accept_connection(wconn, NULL); + if (channel == NULL) { + cl_perror("server: acceptance failed"); + } + + checksock(channel); + + rc = serverfunc(channel, count); + + /* server finished: tidy up */ + wconn->ops->destroy(wconn); + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: server ended rc:%d", + procname, (int)getpid(), __LINE__, rc); + } + + /* reap the clients */ + for (i = 1; i <= clients; i++) { + pid_t pid; + + pid = wait(&waitstat); + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: client %d reaped:%d", + procname, (int)getpid(), __LINE__, + (int) pid, WIFEXITED(waitstat)); + } + if (WIFEXITED(waitstat)) { + rc += WEXITSTATUS(waitstat); + }else{ + rc += 1; + } + } + + return(rc); +} + +static void +echomsgbody(void * body, int n, int niter, size_t * len) +{ + char *str = body; + int l; + + l = snprintf(str, n-1, "String-%d", niter); + if (l < (n-1)) { + memset(&str[l], 'a', (n - (l+1))); + } + str[n-1] = '\0'; + *len = n; +} + +static void +checkifblocked(IPC_Channel* chan) +{ + if (chan->ops->is_sending_blocked(chan)) { + cl_log(LOG_INFO, "Sending is blocked."); + chan->ops->resume_io(chan); + } +} + +#ifdef CHEAT_CHECKS +extern long SeqNums[32]; +#endif + +static int +transport_tests(int iterations, int clients) +{ + int rc = 0; + +#ifdef CHEAT_CHECKS + memset(SeqNums, 0, sizeof(SeqNums)); +#endif + rc += (clients <= 0) + ? channelpair(echoclient, echoserver, iterations) + : clientserver(echoclient, echoserver, iterations, clients); + +#ifdef CHEAT_CHECKS + memset(SeqNums, 0, sizeof(SeqNums)); +#endif + rc += (clients <= 0) + ? channelpair(asyn_echoclient, asyn_echoserver, iterations) + : clientserver(asyn_echoclient, asyn_echoserver, iterations, clients); + +#ifdef CHEAT_CHECKS + memset(SeqNums, 0, sizeof(SeqNums)); +#endif + rc += (clients <= 0) + ? channelpair(mainloop_client, mainloop_server, iterations) + : clientserver(mainloop_client, mainloop_server, iterations, clients); + + return rc; +} + +static int data_size = 20; + +int +main(int argc, char ** argv) +{ + int argflag, argerrs; + int iterations; + int clients; + int rc = 0; + + /* + * Check and process arguments. + * -v: verbose + * -i: number of iterations + * -c: number of clients (invokes client/server mechanism) + * -s: data-size + */ + procname = basename(argv[0]); + + argerrs = 0; + iterations = iter_def; + clients = clients_def; + while ((argflag = getopt(argc, argv, "i:vuc:s:")) != EOF) { + switch (argflag) { + case 'i': /* iterations */ + iterations = atoi(optarg); + break; + case 'v': /* verbosity */ + verbosity++; + break; + case 'c': /* number of clients */ + clients = atoi(optarg); + if (clients < 1 || clients > CLIENTS_MAX) { + fprintf(stderr, "number of clients out of range" + "(1 to %d)\n", CLIENTS_MAX); + argerrs++; + } + break; + case 's': /* data size */ + data_size = atoi(optarg); + if (data_size < 0) { + fprintf(stderr, "data size must be >=0\n"); + argerrs++; + } + if (data_size > MAXMSG) { + fprintf(stderr, "maximum data size is %d\n", MAXMSG); + argerrs++; + } + break; + default: + argerrs++; + break; + } + } + if (argerrs) { + fprintf(stderr, + "Usage: %s [-v] [-i iterations] [-c clients] [-s size]\n" + "\t-v : verbose\n" + "\t-i : iterations (default %d)\n" + "\t-c : number of clients (default %d; nonzero invokes client/server)\n" + "\t-s : data size (default 20 bytes)\n", + procname, iter_def, clients_def); + exit(1); + } + + cl_log_set_entity(procname); + cl_log_enable_stderr(TRUE); + + + + rc += transport_tests(iterations, clients); + +#if 0 + /* Broken for the moment - need to fix it long term */ + cl_log(LOG_INFO, "NOTE: Enabling poll(2) replacement code."); + PollFunc = cl_poll; + g_main_set_poll_func(cl_glibpoll); + ipc_set_pollfunc(cl_poll); + + rc += transport_tests(5 * iterations, clients); +#endif + + cl_log(LOG_INFO, "TOTAL errors: %d", rc); + + return (rc > 127 ? 127 : rc); +} + +static int +checksock(IPC_Channel* channel) +{ + + if (!channel) { + cl_log(LOG_ERR, "Channel null"); + return 1; + } + if (!IPC_ISRCONN(channel)) { + cl_log(LOG_ERR, "Channel status is %d" + ", not IPC_CONNECT", channel->ch_status); + return 1; + } + return 0; +} + +static void +EOFcheck(IPC_Channel* chan) +{ + int fd = chan->ops->get_recv_select_fd(chan); + struct pollfd pf[1]; + int rc; + + cl_log(LOG_INFO, "channel state: %d", chan->ch_status); + + if (chan->recv_queue->current_qlen > 0) { + cl_log(LOG_INFO, "EOF Receive queue has %ld messages in it" + , (long)chan->recv_queue->current_qlen); + } + if (fd <= 0) { + cl_log(LOG_INFO, "EOF receive fd: %d", fd); + } + + + pf[0].fd = fd; + pf[0].events = POLLIN|POLLHUP; + pf[0].revents = 0; + + rc = poll(pf, 1, 0); + + if (rc < 0) { + cl_perror("failed poll(2) call in EOFcheck"); + return; + } + + /* Got input? */ + if (pf[0].revents & POLLIN) { + cl_log(LOG_INFO, "EOF socket %d (still) has input ready (real poll)" + , fd); + } + if ((pf[0].revents & ~(POLLIN|POLLHUP)) != 0) { + cl_log(LOG_INFO, "EOFcheck poll(2) bits: 0x%lx" + , (unsigned long)pf[0].revents); + } + pf[0].fd = fd; + pf[0].events = POLLIN|POLLHUP; + pf[0].revents = 0; + rc = PollFunc(pf, 1, 0); + if (rc < 0) { + cl_perror("failed PollFunc() call in EOFcheck"); + return; + } + + /* Got input? */ + if (pf[0].revents & POLLIN) { + cl_log(LOG_INFO, "EOF socket %d (still) has input ready (PollFunc())" + , fd); + } + if ((pf[0].revents & ~(POLLIN|POLLHUP)) != 0) { + cl_log(LOG_INFO, "EOFcheck PollFunc() bits: 0x%lx" + , (unsigned long)pf[0].revents); + } +} + +static int +echoserver(IPC_Channel* wchan, int repcount) +{ + char *str; + int j; + int errcount = 0; + IPC_Message wmsg; + IPC_Message* rmsg = NULL; + + if (!(str = malloc(data_size))) { + cl_log(LOG_ERR, "Out of memory"); + exit(1); + } + + memset(&wmsg, 0, sizeof(wmsg)); + wmsg.msg_private = NULL; + wmsg.msg_done = NULL; + wmsg.msg_body = str; + wmsg.msg_buf = NULL; + wmsg.msg_ch = wchan; + + cl_log(LOG_INFO, "Echo server: %d reps pid %d.", repcount, getpid()); + for (j=1; j <= repcount + ;++j, rmsg != NULL && (rmsg->msg_done(rmsg),1)) { + int rc; + + echomsgbody(str, data_size, j, &(wmsg.msg_len)); + if ((rc = wchan->ops->send(wchan, &wmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "echotest: send failed %d rc iter %d" + , rc, j); + ++errcount; + continue; + } + + /*fprintf(stderr, "+"); */ + wchan->ops->waitout(wchan); + checkifblocked(wchan); + /*fprintf(stderr, "S"); */ + + /* Try and induce a failure... */ + if (j == repcount) { + sleep(1); + } + + while ((rc = wchan->ops->waitin(wchan)) == IPC_INTR); + + if (rc != IPC_OK) { + cl_log(LOG_ERR + , "echotest server: waitin failed %d rc iter %d" + " errno=%d" + , rc, j, errno); + cl_perror("waitin"); + exit(1); + } + + /*fprintf(stderr, "-"); */ + if ((rc = wchan->ops->recv(wchan, &rmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "echotest server: recv failed %d rc iter %d" + " errno=%d" + , rc, j, errno); + cl_perror("recv"); + ++errcount; + rmsg=NULL; + continue; + } + /*fprintf(stderr, "s"); */ + if (rmsg->msg_len != wmsg.msg_len) { + cl_log(LOG_ERR + , "echotest: length mismatch [%lu,%lu] iter %d" + , (unsigned long)rmsg->msg_len + , (unsigned long)wmsg.msg_len, j); + ++errcount; + continue; + } + if (strncmp(rmsg->msg_body, wmsg.msg_body, wmsg.msg_len) + != 0) { + cl_log(LOG_ERR + , "echotest: data mismatch. iteration %d" + , j); + ++errcount; + continue; + } + + } + cl_log(LOG_INFO, "echoserver: %d errors", errcount); +#if 0 + cl_log(LOG_INFO, "destroying channel 0x%lx", (unsigned long)wchan); +#endif + wchan->ops->destroy(wchan); wchan = NULL; + + free(str); + + return errcount; +} +static int +echoclient(IPC_Channel* rchan, int repcount) +{ + int j; + int errcount = 0; + IPC_Message* rmsg; + + + + cl_log(LOG_INFO, "Echo client: %d reps pid %d." + , repcount, (int)getpid()); + for (j=1; j <= repcount ;++j) { + + int rc; + + while ((rc = rchan->ops->waitin(rchan)) == IPC_INTR); + + if (rc != IPC_OK) { + cl_log(LOG_ERR + , "echotest client: waitin failed %d rc iter %d" + " errno=%d" + , rc, j, errno); + cl_perror("waitin"); + exit(1); + } + /*fprintf(stderr, "/"); */ + + if ((rc = rchan->ops->recv(rchan, &rmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "echoclient: recv failed %d rc iter %d" + " errno=%d" + , rc, j, errno); + cl_perror("recv"); + ++errcount; + if (errcount > MAXERRORS_RECV) { + cl_log(LOG_ERR, + "echoclient: errcount excessive: %d: abandoning", + errcount); + exit(1); + } + --j; + rmsg=NULL; + continue; + } + /*fprintf(stderr, "c"); */ + if ((rc = rchan->ops->send(rchan, rmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "echoclient: send failed %d rc iter %d" + , rc, j); + cl_log(LOG_INFO, "Message being sent: %s" + , (char*)rmsg->msg_body); + ++errcount; + continue; + } + /*fprintf(stderr, "%%"); */ + rchan->ops->waitout(rchan); + checkifblocked(rchan); + /*fprintf(stderr, "C"); */ + } + cl_log(LOG_INFO, "echoclient: %d errors", errcount); +#if 0 + cl_log(LOG_INFO, "destroying channel 0x%lx", (unsigned long)rchan); +#endif + rchan->ops->destroy(rchan); rchan = NULL; + return errcount; +} + +void dump_ipc_info(IPC_Channel* chan); + +static int +checkinput(IPC_Channel* chan, const char * where, int* rdcount, int maxcount) +{ + IPC_Message* rmsg = NULL; + int errs = 0; + int rc; + + while (chan->ops->is_message_pending(chan) + && errs < 10 && *rdcount < maxcount) { + + if (chan->ch_status == IPC_DISCONNECT && *rdcount < maxcount){ + cl_log(LOG_ERR + , "checkinput1[0x%lx %s]: EOF in iter %d" + , (unsigned long)chan, where, *rdcount); + EOFcheck(chan); + } + + if (rmsg != NULL) { + rmsg->msg_done(rmsg); + rmsg = NULL; + } + + if ((rc = chan->ops->recv(chan, &rmsg)) != IPC_OK) { + if (chan->ch_status == IPC_DISCONNECT) { + cl_log(LOG_ERR + , "checkinput2[0x%lx %s]: EOF in iter %d" + , (unsigned long)chan, where, *rdcount); + EOFcheck(chan); + return errs; + } + cl_log(LOG_ERR + , "checkinput[%s]: recv" + " failed: rc %d rdcount %d errno=%d" + , where, rc, *rdcount, errno); + cl_perror("recv"); + rmsg=NULL; + ++errs; + continue; + } + *rdcount += 1; + if (!checkmsg(rmsg, where, *rdcount)) { + dump_ipc_info(chan); + ++errs; + } + if (*rdcount < maxcount && chan->ch_status == IPC_DISCONNECT){ + cl_log(LOG_ERR + , "checkinput3[0x%lx %s]: EOF in iter %d" + , (unsigned long)chan, where, *rdcount); + EOFcheck(chan); + } + + } + return errs; +} + +static void +async_high_flow_callback(IPC_Channel* ch, void* userdata) +{ + int* stopsending = userdata; + + if (userdata == NULL){ + cl_log(LOG_ERR, "userdata is NULL"); + return; + } + + *stopsending = 1; + +} + +static void +async_low_flow_callback(IPC_Channel* ch, void* userdata) +{ + + int* stopsending = userdata; + + if (userdata == NULL){ + cl_log(LOG_ERR, "userdata is NULL"); + return; + } + + *stopsending = 0; + +} + + +static int +asyn_echoserver(IPC_Channel* wchan, int repcount) +{ + int rdcount = 0; + int wrcount = 0; + int errcount = 0; + int blockedcount = 0; + IPC_Message* wmsg; + const char* w = "asyn_echoserver"; + int stopsending = 0; + + cl_log(LOG_INFO, "Asyn echo server: %d reps pid %d." + , repcount, (int)getpid()); + + (void)async_high_flow_callback; + (void)async_low_flow_callback; + + + wchan->ops->set_high_flow_callback(wchan, async_high_flow_callback, &stopsending); + wchan->ops->set_low_flow_callback(wchan, async_low_flow_callback, &stopsending); + + wchan->low_flow_mark = 2; + wchan->high_flow_mark = 20; + + while (rdcount < repcount) { + int rc; + + while (wrcount < repcount && blockedcount < 10 + && wchan->ch_status != IPC_DISCONNECT + ){ + + if (!stopsending){ + ++wrcount; + if (wrcount > repcount) { + break; + } + wmsg = wchan->ops->new_ipcmsg(wchan, NULL, data_size, NULL); + echomsgbody(wmsg->msg_body, data_size, wrcount, &wmsg->msg_len); + if ((rc = wchan->ops->send(wchan, wmsg)) != IPC_OK){ + + cl_log(LOG_INFO, "channel sstatus in echo server is %d", + wchan->ch_status); + if (wchan->ch_status != IPC_CONNECT) { + cl_log(LOG_ERR + , "asyn_echoserver: send failed" + " %d rc iter %d" + , rc, wrcount); + ++errcount; + continue; + }else {/*send failed because of channel busy + * roll back + */ + --wrcount; + } + } + + if (wchan->ops->is_sending_blocked(wchan)) { + /* fprintf(stderr, "b"); */ + ++blockedcount; + }else{ + blockedcount = 0; + } + } + + + errcount += checkinput(wchan, w, &rdcount, repcount); + if (wrcount < repcount + && wchan->ch_status == IPC_DISCONNECT) { + ++errcount; + break; + } + } + +/* cl_log(LOG_INFO, "async_echoserver: wrcount =%d rdcount=%d B", wrcount, rdcount); */ + + wchan->ops->waitout(wchan); + errcount += checkinput(wchan, w, &rdcount, repcount); + if (wrcount >= repcount && rdcount < repcount) { + while ((rc = wchan->ops->waitin(wchan)) == IPC_INTR); + + if (rc != IPC_OK) { + cl_log(LOG_ERR + , "asyn_echoserver: waitin()" + " failed %d rc rdcount %d errno=%d" + , rc, rdcount, errno); + cl_perror("waitin"); + exit(1); + } + } + if (wchan->ch_status == IPC_DISCONNECT + && rdcount < repcount) { + cl_log(LOG_ERR, + "asyn_echoserver: EOF in iter %d (wrcount=%d)", + rdcount, wrcount); + EOFcheck(wchan); + ++errcount; + break; + } + + blockedcount = 0; + + } + + cl_log(LOG_INFO, "asyn_echoserver: %d errors", errcount); +#if 0 + cl_log(LOG_INFO, "%d destroying channel 0x%lx", getpid(), (unsigned long)wchan); +#endif + wchan->ops->destroy(wchan); wchan = NULL; + return errcount; +} + +static int +asyn_echoclient(IPC_Channel* chan, int repcount) +{ + int rdcount = 0; + int wrcount = 0; + int errcount = 0; + IPC_Message* rmsg; + int rfd = chan->ops->get_recv_select_fd(chan); + int wfd = chan->ops->get_send_select_fd(chan); + gboolean rdeqwr = (rfd == wfd); + + + cl_log(LOG_INFO, "Async Echo client: %d reps pid %d." + , repcount, (int)getpid()); + ipc_set_pollfunc(PollFunc); + + while (rdcount < repcount && errcount < repcount) { + + int rc; + struct pollfd pf[2]; + int nfd = 1; + + pf[0].fd = rfd; + pf[0].events = POLLIN|POLLHUP; + + + if (chan->ops->is_sending_blocked(chan)) { + if (rdeqwr) { + pf[0].events |= POLLOUT; + }else{ + nfd = 2; + pf[1].fd = wfd; + pf[1].events = POLLOUT|POLLHUP; + } + } + + /* Have input? */ + /* fprintf(stderr, "i"); */ + while (chan->ops->is_message_pending(chan) + && rdcount < repcount) { + /*fprintf(stderr, "r"); */ + + if ((rc = chan->ops->recv(chan, &rmsg)) != IPC_OK) { + if (!IPC_ISRCONN(chan)) { + cl_log(LOG_ERR + , "Async echoclient: disconnect" + " iter %d", rdcount+1); + ++errcount; + return errcount; + } + cl_log(LOG_ERR + , "Async echoclient: recv" + " failed %d rc iter %d errno=%d" + , rc, rdcount+1, errno); + cl_perror("recv"); + rmsg=NULL; + ++errcount; + cl_log(LOG_INFO, "sleep(1)"); + sleep(1); + continue; + } + /*fprintf(stderr, "c"); */ + ++rdcount; + + + do { + rc = chan->ops->send(chan, rmsg); + + }while (rc != IPC_OK && chan->ch_status == IPC_CONNECT); + + if (chan->ch_status != IPC_CONNECT){ + ++errcount; + cl_perror("send"); + cl_log(LOG_ERR + , "Async echoclient: send failed" + " rc %d, iter %d", rc, rdcount); + cl_log(LOG_INFO, "Message being sent: %s" + , (char*)rmsg->msg_body); + if (!IPC_ISRCONN(chan)) { + cl_log(LOG_ERR + , "Async echoclient: EOF(2)" + " iter %d", rdcount+1); + EOFcheck(chan); + return errcount; + } + continue; + + } + + + ++wrcount; + /*fprintf(stderr, "x"); */ + } + if (rdcount >= repcount) { + break; + } + /* + * At this point it is possible that the POLLOUT bit + * being on is no longer necessary, but this will only + * cause an extra (false) output poll iteration at worst... + * This is because (IIRC) both is_sending_blocked(), and + * is_message_pending() both perform a resume_io(). + * This might be confusing, but -- oh well... + */ + + /* + fprintf(stderr, "P"); + cl_log(LOG_INFO, "poll[%d, 0x%x]" + , pf[0].fd, pf[0].events); + cl_log(LOG_DEBUG, "poll[%d, 0x%x]..." + , pf[0].fd, pf[0].events); + fprintf(stderr, "%%"); + cl_log(LOG_DEBUG, "CallingPollFunc()"); + */ + rc = PollFunc(pf, nfd, -1); + + /* Bad poll? */ + if (rc <= 0) { + cl_perror("Async echoclient: bad poll rc." + " %d rc iter %d", rc, rdcount); + ++errcount; + continue; + } + + /* Error indication? */ + if ((pf[0].revents & (POLLERR|POLLNVAL)) != 0) { + cl_log(LOG_ERR + , "Async echoclient: bad poll revents." + " revents: 0x%x iter %d", pf[0].revents, rdcount); + ++errcount; + continue; + } + + /* HUP without input... Premature EOF... */ + if ((pf[0].revents & POLLHUP) + && ((pf[0].revents&POLLIN) == 0)) { + cl_log(LOG_ERR + , "Async echoclient: premature pollhup." + " revents: 0x%x iter %d", pf[0].revents, rdcount); + EOFcheck(chan); + ++errcount; + continue; + } + + /* Error indication? */ + if (nfd > 1 + && (pf[1].revents & (POLLERR|POLLNVAL)) != 0) { + cl_log(LOG_ERR + , "Async echoclient: bad poll revents[1]." + " revents: 0x%x iter %d", pf[1].revents, rdcount); + ++errcount; + continue; + } + + /* Output unblocked (only) ? */ + if (pf[nfd-1].revents & POLLOUT) { + /*fprintf(stderr, "R");*/ + chan->ops->resume_io(chan); + }else if ((pf[0].revents & POLLIN) == 0) { + /* Neither I nor O available... */ + cl_log(LOG_ERR + , "Async echoclient: bad events." + " revents: 0x%x iter %d", pf[0].revents, rdcount); + ++errcount; + } + } + cl_poll_ignore(rfd); + cl_poll_ignore(wfd); + cl_log(LOG_INFO, "Async echoclient: %d errors, %d reads, %d writes", + errcount, rdcount, wrcount); +#if 0 + cl_log(LOG_INFO, "%d destroying channel 0x%lx",getpid(), (unsigned long)chan); +#endif + + + chan->ops->waitout(chan); + + chan->ops->destroy(chan); chan = NULL; + return errcount; +} + + +struct iterinfo { + int wcount; + int rcount; + int errcount; + IPC_Channel* chan; + int max; + gboolean sendingsuspended; +}; + +static GMainLoop* loop = NULL; + + + + +static gboolean +s_send_msg(gpointer data) +{ + struct iterinfo*i = data; + IPC_Message* wmsg; + int rc; + + ++i->wcount; + + wmsg = i->chan->ops->new_ipcmsg(i->chan, NULL, data_size, NULL); + echomsgbody(wmsg->msg_body, data_size, i->wcount, &wmsg->msg_len); + + /*cl_log(LOG_INFO, "s_send_msg: sending out %d", i->wcount);*/ + + if ((rc = i->chan->ops->send(i->chan, wmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "s_send_msg: send failed" + " %d rc iter %d" + , rc, i->wcount); + cl_log(LOG_ERR + , "s_send_msg: channel status: %d qlen: %ld" + , i->chan->ch_status + , (long)i->chan->send_queue->current_qlen); + ++i->errcount; + if (i->chan->ch_status != IPC_CONNECT) { + cl_log(LOG_ERR, "s_send_msg: Exiting."); + return FALSE; + } + if (i->errcount >= MAXERRORS) { + g_main_quit(loop); + return FALSE; + } + } + return !i->sendingsuspended?i->wcount < i->max: FALSE; +} + + + + +static void +mainloop_low_flow_callback(IPC_Channel* ch, void* userdata) +{ + + struct iterinfo* i = (struct iterinfo*) userdata; + + if (userdata == NULL){ + cl_log(LOG_ERR, "userdata is NULL"); + return; + } + + if (i->sendingsuspended){ + i->sendingsuspended = FALSE; + g_idle_add(s_send_msg, i); + } + + return; + +} + +static void +mainloop_high_flow_callback(IPC_Channel* ch, void* userdata) +{ + struct iterinfo* i = (struct iterinfo*) userdata; + + if (userdata == NULL){ + cl_log(LOG_ERR, "userdata is NULL"); + return; + } + + i->sendingsuspended = TRUE; + +} + + +static gboolean +s_rcv_msg(IPC_Channel* chan, gpointer data) +{ + struct iterinfo*i = data; + + i->errcount += checkinput(chan, "s_rcv_msg", &i->rcount, i->max); + + if (chan->ch_status == IPC_DISCONNECT + || i->rcount >= i->max || i->errcount > MAXERRORS) { + if (i->rcount < i->max) { + ++i->errcount; + cl_log(LOG_INFO, "Early exit from s_rcv_msg"); + } + g_main_quit(loop); + return FALSE; + } + + return TRUE; +} + +static gboolean +checkmsg(IPC_Message* rmsg, const char * who, int rcount) +{ + char *str; + size_t len; + + if (!(str = malloc(data_size))) { + cl_log(LOG_ERR, "Out of memory"); + exit(1); + } + + echomsgbody(str, data_size, rcount, &len); + + if (rmsg->msg_len != len) { + cl_log(LOG_ERR + , "checkmsg[%s]: length mismatch" + " [expected %u, got %lu] iteration %d" + , who, (unsigned)len + , (unsigned long)rmsg->msg_len + , rcount); + cl_log(LOG_ERR + , "checkmsg[%s]: expecting [%s]" + , who, str); + cl_log(LOG_ERR + , "checkmsg[%s]: got [%s] instead" + , who, (const char *)rmsg->msg_body); + return FALSE; + } + if (strncmp(rmsg->msg_body, str, len) != 0) { + cl_log(LOG_ERR + , "checkmsg[%s]: data mismatch" + ". input iteration %d" + , who, rcount); + cl_log(LOG_ERR + , "checkmsg[%s]: expecting [%s]" + , who, str); + cl_log(LOG_ERR + , "checkmsg[%s]: got [%s] instead" + , who, (const char *)rmsg->msg_body); + return FALSE; +#if 0 + }else if (strcmp(who, "s_rcv_msg") == 0) { +#if 0 + + || strcmp(who, "s_echo_msg") == 0) { +#endif + cl_log(LOG_ERR + , "checkmsg[%s]: data Good" + "! input iteration %d" + , who, rcount); +#endif + } + + free(str); + + return TRUE; +} + +static gboolean +s_echo_msg(IPC_Channel* chan, gpointer data) +{ + struct iterinfo* i = data; + int rc; + IPC_Message* rmsg; + + while (chan->ops->is_message_pending(chan)) { + if (chan->ch_status == IPC_DISCONNECT) { + break; + } + + if ((rc = chan->ops->recv(chan, &rmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "s_echo_msg: recv failed %d rc iter %d" + " errno=%d" + , rc, i->rcount+1, errno); + cl_perror("recv"); + ++i->errcount; + goto retout; + } + i->rcount++; + if (!checkmsg(rmsg, "s_echo_msg", i->rcount)) { + ++i->errcount; + } + + + + /*cl_log(LOG_INFO, "s_echo_msg: rcount= %d, wcount =%d", i->rcount, i->wcount);*/ + + + do { + rc = chan->ops->send(chan, rmsg); + + }while (rc != IPC_OK && chan->ch_status == IPC_CONNECT); + + if (chan->ch_status != IPC_CONNECT){ + cl_log(LOG_ERR, + "s_echo_msg: send failed %d rc iter %d qlen %ld", + rc, i->rcount, (long)chan->send_queue->current_qlen); + cl_perror("send"); + i->errcount ++; + + } + + i->wcount+=1; + /*cl_log(LOG_INFO, "s_echo_msg: end of this ite");*/ + } + retout: + /*fprintf(stderr, "%%");*/ + if (i->rcount >= i->max || chan->ch_status == IPC_DISCONNECT + || i->errcount > MAXERRORS) { + chan->ops->waitout(chan); + g_main_quit(loop); + return FALSE; + } + return TRUE; +} + +static void +init_iterinfo(struct iterinfo * i, IPC_Channel* chan, int max) +{ + memset(i, 0, sizeof(*i)); + i->chan = chan; + i->max = max; + i->sendingsuspended = FALSE; +} + +static int +mainloop_server(IPC_Channel* chan, int repcount) +{ + struct iterinfo info; + guint sendmsgsrc; + + + + loop = g_main_new(FALSE); + init_iterinfo(&info, chan, repcount); + + chan->ops->set_high_flow_callback(chan, mainloop_high_flow_callback, &info); + chan->ops->set_low_flow_callback(chan, mainloop_low_flow_callback, &info); + chan->high_flow_mark = 20; + chan->low_flow_mark = 2; + + sendmsgsrc = g_idle_add(s_send_msg, &info); + G_main_add_IPC_Channel(G_PRIORITY_DEFAULT, chan + , FALSE, s_rcv_msg, &info, NULL); + cl_log(LOG_INFO, "Mainloop echo server: %d reps pid %d.", repcount, (int)getpid()); + g_main_run(loop); + g_main_destroy(loop); + g_source_remove(sendmsgsrc); + loop = NULL; + cl_log(LOG_INFO, "Mainloop echo server: %d errors", info.errcount); + return info.errcount; +} +static int +mainloop_client(IPC_Channel* chan, int repcount) +{ + struct iterinfo info; + loop = g_main_new(FALSE); + init_iterinfo(&info, chan, repcount); + G_main_add_IPC_Channel(G_PRIORITY_DEFAULT, chan + , FALSE, s_echo_msg, &info, NULL); + cl_log(LOG_INFO, "Mainloop echo client: %d reps pid %d.", repcount, (int)getpid()); + g_main_run(loop); + g_main_destroy(loop); + loop = NULL; + cl_log(LOG_INFO, "Mainloop echo client: %d errors, %d read %d written" + , info.errcount, info.rcount, info.wcount); + return info.errcount; +} diff --git a/lib/clplumbing/ipctransient.h b/lib/clplumbing/ipctransient.h new file mode 100644 index 0000000..9c1746c --- /dev/null +++ b/lib/clplumbing/ipctransient.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2007 Andrew Beekhof <andrew@beekhof.net> + * + * 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.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#undef _GNU_SOURCE /* in case it was defined on the command line */ +#define _GNU_SOURCE +#include <lha_internal.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/cl_poll.h> +#include <clplumbing/GSource.h> +#include <clplumbing/ipc.h> +#include <clplumbing/realtime.h> +#include <clplumbing/lsb_exitcodes.h> +#include <errno.h> + +#define MAXERRORS 1000 +#define MAX_IPC_FAIL 10 +#define FIFO_LEN 1024 + +extern const char *procname; + +extern const char *commdir; + +void trans_getargs(int argc, char **argv); + +void default_ipctest_input_destroy(gpointer user_data); + +IPC_Message * create_simple_message(const char *text, IPC_Channel *ch); diff --git a/lib/clplumbing/ipctransientclient.c b/lib/clplumbing/ipctransientclient.c new file mode 100644 index 0000000..080acf2 --- /dev/null +++ b/lib/clplumbing/ipctransientclient.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net> + * + * 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.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <ipctransient.h> + +#define MAX_MESSAGES 3 +static char *messages[MAX_MESSAGES]; + +IPC_Message *create_simple_message(const char *text, IPC_Channel *ch); +IPC_Channel *init_client_ipctest_comms( + const char *child, gboolean (*dispatch)( + IPC_Channel* source_data, gpointer user_data), + void *user_data); +gboolean transient_client_callback(IPC_Channel* server, void* private_data); +void client_send_message( + const char *message_text, IPC_Channel *server_channel, int iteration); + +#define MAXTSTMSG 1000 + +int +main(int argc, char ** argv) +{ + int lpc =0, iteration=0; + GMainLoop* client_main = NULL; + IPC_Channel *server_channel = NULL; + + trans_getargs(argc, argv); + + cl_log_set_entity(procname); + cl_log_enable_stderr(TRUE); + + /* give the server a chance to start */ + cl_log(LOG_INFO, "#--#--#--#--# Beginning test run %d against server %d...", lpc, iteration); + client_main = g_main_new(FALSE); + + /* connect, send messages */ + server_channel = init_client_ipctest_comms("echo", transient_client_callback, client_main); + + if(server_channel == NULL) { + cl_log(LOG_ERR, "[Client %d] Could not connect to server", lpc); + return 1; + } + + for(lpc = 0; lpc < MAX_MESSAGES; lpc++) { + messages[lpc] = (char *)malloc(sizeof(char)*MAXTSTMSG); + } + snprintf(messages[0], MAXTSTMSG + , "%s_%ld%c", "hello", (long)getpid(), '\0'); + snprintf(messages[1], MAXTSTMSG + , "%s_%ld%c", "hello_world", (long)getpid(), '\0'); + snprintf(messages[2], MAXTSTMSG + , "%s_%ld%c", "hello_world_again", (long)getpid(), '\0'); + + for(lpc = 0; lpc < MAX_MESSAGES; lpc++) { + client_send_message(messages[lpc], server_channel, lpc); + } + + server_channel->ops->waitout(server_channel); + + /* wait for the reply by creating a mainloop and running it until + * the callbacks are invoked... + */ + + cl_log(LOG_DEBUG, "Waiting for replies from the echo server"); + g_main_run(client_main); + cl_log(LOG_INFO, "[Iteration %d] Client %d completed successfully", iteration, lpc); + + return 0; +} + + +IPC_Channel * +init_client_ipctest_comms(const char *child, + gboolean (*dispatch)(IPC_Channel* source_data + ,gpointer user_data), + void *user_data) +{ + IPC_Channel *ch; + GHashTable * attrs; + int local_sock_len = 2; /* 2 = '/' + '\0' */ + char *commpath = NULL; + static char path[] = IPC_PATH_ATTR; + + local_sock_len += strlen(child); + local_sock_len += strlen(commdir); + + commpath = (char*)malloc(sizeof(char)*local_sock_len); + if (commpath == NULL){ + cl_log(LOG_ERR, "%s: allocating memory failed", __FUNCTION__); + return NULL; + } + sprintf(commpath, "%s/%s", commdir, child); + commpath[local_sock_len - 1] = '\0'; + + cl_log(LOG_DEBUG, "[Client] Attempting to talk on: %s", commpath); + + attrs = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(attrs, path, commpath); + ch = ipc_channel_constructor(IPC_ANYTYPE, attrs); + g_hash_table_destroy(attrs); + + if (ch == NULL) { + cl_log(LOG_ERR, "[Client] Could not access channel on: %s", commpath); + return NULL; + } else if(ch->ops->initiate_connection(ch) != IPC_OK) { + cl_log(LOG_ERR, "[Client] Could not init comms on: %s", commpath); + return NULL; + } + + G_main_add_IPC_Channel(G_PRIORITY_LOW, + ch, FALSE, dispatch, user_data, + default_ipctest_input_destroy); + + return ch; +} + + +gboolean +transient_client_callback(IPC_Channel* server, void* private_data) +{ + int lpc = 0; + IPC_Message *msg = NULL; + char *buffer = NULL; + static int received_responses = 0; + + GMainLoop *mainloop = (GMainLoop*)private_data; + + while(server->ops->is_message_pending(server) == TRUE) { + if (server->ch_status == IPC_DISCONNECT) { + /* The message which was pending for us is the + * new status of IPC_DISCONNECT */ + break; + } + if(server->ops->recv(server, &msg) != IPC_OK) { + cl_log(LOG_ERR, "[Client] Error while invoking recv()"); + perror("[Client] Receive failure:"); + return FALSE; + } + + if (msg != NULL) { + buffer = (char*)msg->msg_body; + cl_log(LOG_DEBUG, "[Client] Got text [text=%s]", buffer); + received_responses++; + + if(lpc < MAX_MESSAGES && strcmp(messages[lpc], buffer) != 0) + { + cl_log(LOG_ERR, "[Client] Received someone else's message [%s] instead of [%s]", buffer, messages[lpc]); + } + else if(lpc >= MAX_MESSAGES) + { + cl_log(LOG_ERR, "[Client] Receivedan extra message [%s]", buffer); + } + + lpc++; + msg->msg_done(msg); + } else { + cl_log(LOG_ERR, "[Client] No message this time"); + } + } + + if(server->ch_status == IPC_DISCONNECT) { + cl_log(LOG_ERR, "[Client] Client received HUP"); + return FALSE; + } + + cl_log(LOG_DEBUG, "[Client] Processed %d IPC messages this time, %d total", lpc, received_responses); + + if(received_responses > 2) { + cl_log(LOG_INFO, "[Client] Processed %d IPC messages, all done.", received_responses); + received_responses = 0; + g_main_quit(mainloop); + cl_log(LOG_INFO, "[Client] Exiting."); + return FALSE; + } + + return TRUE; +} + +void +client_send_message(const char *message_text, + IPC_Channel *server_channel, + int iteration) +{ + IPC_Message *a_message = NULL; + if(server_channel->ch_status != IPC_CONNECT) { + cl_log(LOG_WARNING, "[Client %d] Channel is in state %d before sending message [%s]", + iteration, server_channel->ch_status, message_text); + return; + } + + a_message = create_simple_message(message_text, server_channel); + if(a_message == NULL) { + cl_log(LOG_ERR, "Could not create message to send"); + } else { + cl_log(LOG_DEBUG, "[Client %d] Sending message: %s", iteration, (char*)a_message->msg_body); + while(server_channel->ops->send(server_channel, a_message) == IPC_FAIL) { + cl_log(LOG_ERR, "[Client %d] IPC channel is blocked", iteration); + cl_shortsleep(); + } + + if(server_channel->ch_status != IPC_CONNECT) { + cl_log(LOG_WARNING, + "[Client %d] Channel is in state %d after sending message [%s]", + iteration, server_channel->ch_status, message_text); + } + } +} diff --git a/lib/clplumbing/ipctransientlib.c b/lib/clplumbing/ipctransientlib.c new file mode 100644 index 0000000..7a6721e --- /dev/null +++ b/lib/clplumbing/ipctransientlib.c @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net> + * + * 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.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <ipctransient.h> + +/* for basename() on some OSes (e.g. Solaris) */ +#include <libgen.h> + +#define WORKING_DIR HA_VARLIBHBDIR + +const char *procname = NULL; + +const char *commdir = WORKING_DIR; + +void +trans_getargs(int argc, char **argv) +{ + int argflag, argerrs; + + procname = basename(argv[0]); + + argerrs = 0; + while ((argflag = getopt(argc, argv, "C:")) != EOF) { + switch (argflag) { + case 'C': /* directory to commpath */ + commdir = optarg; + break; + default: + argerrs++; + break; + } + } + if (argerrs) { + fprintf(stderr, + "Usage: %s [-C commdir]\n" + "\t-C : directory to commpath (default %s)\n", + procname, WORKING_DIR); + exit(1); + } + +} + +void +default_ipctest_input_destroy(gpointer user_data) +{ + cl_log(LOG_INFO, "default_ipctest_input_destroy:received HUP"); +} + +IPC_Message * +create_simple_message(const char *text, IPC_Channel *ch) +{ + IPC_Message *ack_msg = NULL; + char *copy_text = NULL; + + if(text == NULL) { + cl_log(LOG_ERR, "ERROR: can't create IPC_Message with no text"); + return NULL; + } else if(ch == NULL) { + cl_log(LOG_ERR, "ERROR: can't create IPC_Message with no channel"); + return NULL; + } + + ack_msg = (IPC_Message *)malloc(sizeof(IPC_Message)); + if (ack_msg == NULL){ + cl_log(LOG_ERR, "create_simple_message:" + "allocating memory for IPC_Message failed"); + return NULL; + } + + memset(ack_msg, 0, sizeof(IPC_Message)); + + copy_text = strdup(text); + + ack_msg->msg_private = NULL; + ack_msg->msg_done = NULL; + ack_msg->msg_body = copy_text; + ack_msg->msg_ch = ch; + + ack_msg->msg_len = strlen(text)+1; + + return ack_msg; +} diff --git a/lib/clplumbing/ipctransientserver.c b/lib/clplumbing/ipctransientserver.c new file mode 100644 index 0000000..d7ee61d --- /dev/null +++ b/lib/clplumbing/ipctransientserver.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net> + * + * 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.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <ipctransient.h> + +gboolean transient_server_callback(IPC_Channel *client, gpointer user_data); +gboolean transient_server_connect(IPC_Channel *client_channel, gpointer user_data); +int init_server_ipc_comms(const char *child, + gboolean (*channel_client_connect)(IPC_Channel *newclient, gpointer user_data), + void (*channel_input_destroy)(gpointer user_data), + gboolean usenormalpoll); + +int +main(int argc, char ** argv) +{ + int iteration = 0; + GMainLoop* mainloop = NULL; + + trans_getargs(argc, argv); + + cl_log_set_entity(procname); + cl_log_enable_stderr(TRUE); + + init_server_ipc_comms("echo", transient_server_connect, default_ipctest_input_destroy, FALSE); + + /* wait for the reply by creating a mainloop and running it until + * the callbacks are invoked... + */ + mainloop = g_main_new(FALSE); + + cl_log(LOG_INFO, "#--#--#--# Echo Server %d is active...", iteration); + g_main_run(mainloop); + cl_log(LOG_INFO, "#--#--#--# Echo Server %d is stopped...", iteration); + + return 0; +} + + +int +init_server_ipc_comms(const char *child, + gboolean (*channel_client_connect)(IPC_Channel *newclient, gpointer user_data), + void (*channel_input_destroy)(gpointer user_data), + gboolean usenormalpoll) +{ + /* the clients wait channel is the other source of events. + * This source delivers the clients connection events. + * listen to this source at a relatively lower priority. + */ + mode_t mask; + IPC_WaitConnection *wait_ch; + GHashTable * attrs; + int local_sock_len = 2; /* 2 = '/' + '\0' */ + char *commpath = NULL; + static char path[] = IPC_PATH_ATTR; + + local_sock_len += strlen(child); + local_sock_len += strlen(commdir); + + commpath = (char*)malloc(sizeof(char)*local_sock_len); + if (commpath == NULL){ + cl_log(LOG_ERR, "%s: allocating memory failed", __FUNCTION__); + exit(1); + } + snprintf(commpath, local_sock_len, "%s/%s", commdir, child); + commpath[local_sock_len - 1] = '\0'; + + attrs = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(attrs, path, commpath); + + mask = umask(0); + wait_ch = ipc_wait_conn_constructor(IPC_ANYTYPE, attrs); + if (wait_ch == NULL){ + cl_perror("[Server] Can't create wait channel of type %s", IPC_ANYTYPE); + exit(1); + } + mask = umask(mask); + g_hash_table_destroy(attrs); + + G_main_add_IPC_WaitConnection(G_PRIORITY_LOW, + wait_ch, + NULL, + FALSE, + channel_client_connect, + wait_ch, /* user data passed to ?? */ + channel_input_destroy); + + cl_log(LOG_INFO, "[Server] Listening on: %s", commpath); + +/* if (!usenormalpoll) { */ +/* g_main_set_poll_func(cl_glibpoll); */ +/* ipc_set_pollfunc(cl_poll); */ +/* } */ + return 0; +} + +gboolean +transient_server_callback(IPC_Channel *client, gpointer user_data) +{ + int lpc = 0; + IPC_Message *msg = NULL; + char *buffer = NULL; + IPC_Message *reply = NULL; + int llpc = 0; + + cl_log(LOG_DEBUG, "channel: %p", client); + + cl_log(LOG_DEBUG, "Client status %d (disconnect=%d)", client->ch_status, IPC_DISCONNECT); + + while(client->ops->is_message_pending(client)) { + if (client->ch_status == IPC_DISCONNECT) { + /* The message which was pending for us is that + * the IPC status is now IPC_DISCONNECT */ + break; + } + if(client->ops->recv(client, &msg) != IPC_OK) { + cl_perror("[Server] Receive failure"); + return FALSE; + } + + if (msg != NULL) { + lpc++; + buffer = (char*)g_malloc(msg->msg_len+1); + memcpy(buffer,msg->msg_body, msg->msg_len); + buffer[msg->msg_len] = '\0'; + cl_log(LOG_DEBUG, "[Server] Got xml [text=%s]", buffer); + + reply = create_simple_message(strdup(buffer), client); + if (!reply) { + cl_log(LOG_ERR, "[Server] Could allocate reply msg."); + return FALSE; + } + + llpc = 0; + while(llpc++ < MAX_IPC_FAIL && client->ops->send(client, reply) == IPC_FAIL) { + cl_log(LOG_WARNING, "[Server] ipc channel blocked"); + cl_shortsleep(); + } + + if(lpc == MAX_IPC_FAIL) { + cl_log(LOG_ERR, "[Server] Could not send IPC, message. Channel is dead."); + free(reply); + return FALSE; + } + + cl_log(LOG_DEBUG, "[Server] Sent reply"); + msg->msg_done(msg); + } else { + cl_log(LOG_ERR, "[Server] No message this time"); + continue; + } + } + + cl_log(LOG_DEBUG, "[Server] Processed %d messages", lpc); + + cl_log(LOG_DEBUG, "[Server] Client status %d", client->ch_status); + if(client->ch_status != IPC_CONNECT) { + cl_log(LOG_INFO, "[Server] Server received HUP from child"); + return FALSE; + } + + return TRUE; +} + + +gboolean +transient_server_connect(IPC_Channel *client_channel, gpointer user_data) +{ + /* assign the client to be something, or put in a hashtable */ + cl_log(LOG_DEBUG, "A client tried to connect... and there was much rejoicing."); + + if(client_channel == NULL) { + cl_log(LOG_ERR, "[Server] Channel was NULL"); + } else if(client_channel->ch_status == IPC_DISCONNECT) { + cl_log(LOG_ERR, "[Server] Channel was disconnected"); + } else { + cl_log(LOG_DEBUG, "[Server] Client is %s %p", client_channel == NULL?"NULL":"valid", client_channel); + cl_log(LOG_DEBUG, "[Server] Client status %d (disconnect=%d)", client_channel->ch_status, IPC_DISCONNECT); + + cl_log(LOG_DEBUG, "[Server] Adding IPC Channel to main thread."); + G_main_add_IPC_Channel(G_PRIORITY_LOW, + client_channel, + FALSE, + transient_server_callback, + NULL, + default_ipctest_input_destroy); + } + + return TRUE; +} diff --git a/lib/clplumbing/longclock.c b/lib/clplumbing/longclock.c new file mode 100644 index 0000000..594c9c5 --- /dev/null +++ b/lib/clplumbing/longclock.c @@ -0,0 +1,275 @@ +/* + * Longclock operations + * + * Copyright (c) 2002 International Business Machines + * Author: Alan Robertson <alanr@unix.sh> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> +#include <unistd.h> +#include <sys/times.h> +#include <errno.h> +#include <clplumbing/longclock.h> +#include <clplumbing/cl_log.h> + +static unsigned Hz = 0; +static longclock_t Lc_Hz; +static double d_Hz; + + +const longclock_t zero_longclock = 0UL; + +#ifndef CLOCK_T_IS_LONG_ENOUGH +# undef time_longclock +#endif + +#ifdef HAVE_LONGCLOCK_ARITHMETIC +# undef msto_longclock +# undef longclockto_ms +# undef secsto_longclock +# undef add_longclock +# undef sub_longclock +# undef cmp_longclock +#endif + + +unsigned +hz_longclock(void) +{ + if (Hz == 0) { + /* Compute various hz-related constants */ + + Hz = sysconf(_SC_CLK_TCK); + Lc_Hz = (longclock_t)Hz; + d_Hz = (double) Hz; + } + return Hz; +} + +#ifdef TIMES_ALLOWS_NULL_PARAM +# define TIMES_PARAM NULL +#else + static struct tms dummy_longclock_tms_struct; +# define TIMES_PARAM &dummy_longclock_tms_struct +#endif + +unsigned long +cl_times(void) /* Make times(2) behave rationally on Linux */ +{ + clock_t ret; +#ifndef DISABLE_TIMES_KLUDGE + int save_errno = errno; + + /* + * times(2) really returns an unsigned value ... + * + * We don't check to see if we got back the error value (-1), because + * the only possibility for an error would be if the address of + * longclock_dummy_tms_struct was invalid. Since it's a + * compiler-generated address, we assume that errors are impossible. + * And, unfortunately, it is quite possible for the correct return + * from times(2) to be exactly (clock_t)-1. Sigh... + * + */ + errno = 0; +#endif /* DISABLE_TIMES_KLUDGE */ + ret = times(TIMES_PARAM); + +#ifndef DISABLE_TIMES_KLUDGE +/* + * This is to work around a bug in the system call interface + * for times(2) found in glibc on Linux (and maybe elsewhere) + * It changes the return values from -1 to -4096 all into + * -1 and then dumps the -(return value) into errno. + * + * This totally bizarre behavior seems to be widespread in + * versions of Linux and glibc. + * + * Many thanks to Wolfgang Dumhs <wolfgang.dumhs (at) gmx.at> + * for finding and documenting this bizarre behavior. + */ + if (errno != 0) { + ret = (clock_t) (-errno); + } + errno = save_errno; +#endif /* DISABLE_TIMES_KLUDGE */ + + /* sizeof(long) may be larger than sizeof(clock_t). + * Don't jump from 0x7fffffff to 0xffffffff80000000 + * because of sign extension. + * We do expect sizeof(clock_t) <= sizeof(long), however. + */ + BUILD_BUG_ON(sizeof(clock_t) > sizeof(unsigned long)); +#define CLOCK_T_MAX (~0UL >> (8*(sizeof(unsigned long) - sizeof(clock_t)))) + return (unsigned long)ret & CLOCK_T_MAX; +} + +#ifdef CLOCK_T_IS_LONG_ENOUGH +longclock_t +time_longclock(void) +{ + /* See note below about deliberately ignoring errors... */ + return (longclock_t)cl_times(); +} + +#else /* clock_t is shorter than 64 bits */ + +#define BITSPERBYTE 8 +#define WRAPSHIFT (BITSPERBYTE*sizeof(clock_t)) +#define WRAPAMOUNT (((longclock_t) 1) << WRAPSHIFT) +#define MINJUMP ((CLOCK_T_MAX/100UL)*99UL) + +longclock_t +time_longclock(void) +{ + /* Internal note: This updates the static fields; care should be + * taken to not call a function like cl_log (which internally + * calls time_longclock() as well) just before this happens, + * because then this can recurse infinitely; that is why the + * cl_log call is where it is; found by Simon Graham. */ + static gboolean calledbefore = FALSE; + static unsigned long lasttimes = 0L; + static unsigned long callcount = 0L; + static longclock_t lc_wrapcount = 0L; + unsigned long timesval; + + ++callcount; + + timesval = cl_times(); + + if (calledbefore && timesval < lasttimes) { + unsigned long jumpbackby = lasttimes - timesval; + + if (jumpbackby < MINJUMP) { + /* Kernel weirdness */ + cl_log(LOG_CRIT + , "%s: clock_t from times(2) appears to" + " have jumped backwards (in error)!" + , __FUNCTION__); + cl_log(LOG_CRIT + , "%s: old value was %lu" + ", new value is %lu, diff is %lu, callcount %lu" + , __FUNCTION__ + , (unsigned long)lasttimes + , (unsigned long)timesval + , (unsigned long)jumpbackby + , callcount); + /* Assume jump back was the error and ignore it */ + /* (i.e., hope it goes away) */ + }else{ + /* Normal looking wraparound */ + /* update last time BEFORE loging as log call + can call this routine recursively leading + to double update of wrapcount! */ + + lasttimes = timesval; + lc_wrapcount += WRAPAMOUNT; + + cl_log(LOG_INFO + , "%s: clock_t wrapped around (uptime)." + , __FUNCTION__); + } + } + else { + lasttimes = timesval; + calledbefore = TRUE; + } + return (lc_wrapcount | timesval); +} +#endif /* ! CLOCK_T_IS_LONG_ENOUGH */ + +longclock_t +msto_longclock(unsigned long ms) +{ + unsigned long secs = ms / 1000UL; + unsigned long msec = ms % 1000; + longclock_t result; + + (void)(Hz == 0 && hz_longclock()); + + if (ms == 0) { + return (longclock_t)0UL; + } + result = secs * Lc_Hz + (msec * Lc_Hz)/1000; + + if (result == 0) { + result = 1; + } + return result; +} + +longclock_t +secsto_longclock(unsigned long Secs) +{ + longclock_t secs = Secs; + + (void)(Hz == 0 && hz_longclock()); + + return secs * Lc_Hz; +} + +longclock_t +dsecsto_longclock(double v) +{ + (void)(Hz == 0 && hz_longclock()); + + return (longclock_t) ((v * d_Hz)+0.5); + +} + +unsigned long +longclockto_ms(longclock_t t) +{ + (void)(Hz == 0 && hz_longclock()); + + if (t == 0) { + return 0UL; + } + return (unsigned long) ((t*1000UL)/Lc_Hz); +} +#ifndef CLOCK_T_IS_LONG_ENOUGH +long +longclockto_long(longclock_t t) +{ + return ((long)(t)); +} + +longclock_t +add_longclock(longclock_t l, longclock_t r) +{ + return l + r; +} + +longclock_t +sub_longclock(longclock_t l, longclock_t r) +{ + return l - r; +} + +int +cmp_longclock(longclock_t l, longclock_t r) +{ + if (l < r) { + return -1; + } + if (l > r) { + return 1; + } + return 0; +} +#endif /* CLOCK_T_IS_LONG_ENOUGH */ diff --git a/lib/clplumbing/md5.c b/lib/clplumbing/md5.c new file mode 100644 index 0000000..b893483 --- /dev/null +++ b/lib/clplumbing/md5.c @@ -0,0 +1,335 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + * Cleaned up for heartbeat by + * Mitja Sarp <mitja@lysator.liu.se> + * Sun Jiang Dong <sunjd@cn.ibm.com> + * Pan Jia Ming <jmltc@cn.ibm.com> + * + */ + +#include <lha_internal.h> + +#ifdef HAVE_STDINT_H +#include <stdint.h> +#endif +#include <stdio.h> /* for sprintf() */ +#include <string.h> /* for memcpy() */ +#include <sys/types.h> /* for stupid systems */ +#include <netinet/in.h> /* for ntohl() */ +#include <clplumbing/cl_log.h> +#include <clplumbing/md5.h> + +#define MD5_DIGESTSIZE 16 +#define MD5_BLOCKSIZE 64 + +typedef struct MD5Context_st { + uint32_t buf[4]; + uint32_t bytes[2]; + uint32_t in[16]; +}MD5Context; + +#define md5byte unsigned char + +struct MD5Context { + uint32_t buf[4]; + uint32_t bytes[2]; + uint32_t in[16]; +}; + +void MD5Init(MD5Context *context); +void MD5Update(MD5Context *context, md5byte const *buf, unsigned len); +void MD5Final(unsigned char digest[16], MD5Context *context); +void MD5Transform(uint32_t buf[4], uint32_t const in[16]); + + +#ifdef CONFIG_BIG_ENDIAN +static inline void byteSwap(uint32_t * buf, uint32_t len); + +static inline void +byteSwap(uint32_t * buf, uint32_t len) +{ + int i; + for (i = 0; i < len; i ++) { + uint32_t tmp = buf[i]; + buf[i] = ( (uint32_t) ((unsigned char *) &tmp)[0] ) | + (((uint32_t) ((unsigned char *) &tmp)[1]) << 8) | + (((uint32_t) ((unsigned char *) &tmp)[2]) << 16) | + (((uint32_t) ((unsigned char *) &tmp)[3]) << 24); + } +} +#elif defined(CONFIG_LITTLE_ENDIAN) + #define byteSwap(buf,words) +#else + #error "Neither CONFIG_BIG_ENDIAN nor CONFIG_LITTLE_ENDIAN defined!" +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301ul; + ctx->buf[1] = 0xefcdab89ul; + ctx->buf[2] = 0x98badcfeul; + ctx->buf[3] = 0x10325476ul; + + ctx->bytes[0] = 0; + ctx->bytes[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(MD5Context *ctx, md5byte const *buf, unsigned len) +{ + uint32_t t; + + /* Update byte count */ + + t = ctx->bytes[0]; + if ((ctx->bytes[0] = t + len) < t) + ctx->bytes[1]++; /* Carry from low to high */ + + t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */ + if (t > len) { + memcpy((md5byte *)ctx->in + 64 - t, buf, len); + return; + } + /* First chunk is an odd size */ + memcpy((md5byte *)ctx->in + 64 - t, buf, t); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += t; + len -= t; + + /* Process data in 64-byte chunks */ + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(md5byte digest[16], MD5Context *ctx) +{ + int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */ + md5byte *p = (md5byte *)ctx->in + count; + + /* Set the first char of padding to 0x80. There is always room. */ + *p++ = 0x80; + + /* Bytes of padding needed to make 56 bytes (-8..55) */ + count = 56 - 1 - count; + + if (count < 0) { /* Padding forces an extra block */ + memset(p, 0, count + 8); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + p = (md5byte *)ctx->in; + count = 56; + } + memset(p, 0, count); + byteSwap(ctx->in, 14); + + /* Append length in bits and transform */ + ctx->in[14] = ctx->bytes[0] << 3; + ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29; + MD5Transform(ctx->buf, ctx->in); + + byteSwap(ctx->buf, 16); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ +} + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) ((x) ^ (y) ^ (z)) +#define F4(x, y, z) ((y) ^ ((x) | ~(z))) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f,w,x,y,z,in,s) \ + (w += f(x,y,z) + (in), (w) = ((w)<<(s) | (w)>>(32-(s))) + (x)) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void +MD5Transform(uint32_t buf[4], uint32_t const in[16]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478ul, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756ul, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070dbul, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceeeul, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faful, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62aul, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613ul, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501ul, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8ul, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7aful, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1ul, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7beul, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122ul, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193ul, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438eul, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821ul, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562ul, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340ul, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51ul, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aaul, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105dul, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453ul, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681ul, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8ul, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6ul, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6ul, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87ul, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14edul, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905ul, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8ul, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9ul, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8aul, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942ul, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681ul, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122ul, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380cul, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44ul, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9ul, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60ul, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70ul, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6ul, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127faul, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085ul, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05ul, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039ul, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5ul, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8ul, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665ul, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244ul, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97ul, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7ul, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039ul, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3ul, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92ul, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47dul, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1ul, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4ful, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0ul, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314ul, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1ul, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82ul, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235ul, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bbul, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391ul, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +int MD5( const unsigned char *data + , unsigned long len + , unsigned char *digest) +{ + MD5Context context; + + MD5Init(&context); + MD5Update(&context, data, len); + MD5Final(digest, &context); + + return 0; +} + +int HMAC( const unsigned char * key + , unsigned int key_len + , const unsigned char * text + , unsigned long textlen + , unsigned char * digest) +{ + MD5Context context; + /* inner padding - key XORd with ipad */ + unsigned char k_ipad[65]; + /* outer padding - * key XORd with opad */ + unsigned char k_opad[65]; + unsigned char tk[MD5_DIGESTSIZE]; + int i; + + /* if key is longer than MD5_BLOCKSIZE bytes reset it to key=MD5(key) */ + if (key_len > MD5_BLOCKSIZE) { + MD5Context tctx; + MD5Init(&tctx); + MD5Update(&tctx, (const unsigned char *)key, key_len); + MD5Final(tk, &tctx); + + key = (unsigned char *)tk; + key_len = MD5_DIGESTSIZE; + } + /* start out by storing key in pads */ + memset(k_ipad, 0, sizeof k_ipad); + memset(k_opad, 0, sizeof k_opad); + memcpy(k_ipad, key, key_len); + memcpy(k_opad, key, key_len); + + /* XOR key with ipad and opad values */ + for (i=0; i<MD5_BLOCKSIZE; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + /* perform inner MD5 */ + MD5Init(&context); /* init context for 1st pass */ + MD5Update(&context, k_ipad, MD5_BLOCKSIZE); /* start with inner pad */ + MD5Update(&context, text, textlen); /* then text of datagram */ + MD5Final(digest, &context); /* finish up 1st pass */ + + /* perform outer MD5 */ + MD5Init(&context); /* init context for 2nd pass */ + MD5Update(&context, k_opad, MD5_BLOCKSIZE); /* start with outer pad */ + MD5Update(&context, digest, MD5_DIGESTSIZE); /* then results of 1st hash */ + + MD5Final(digest, &context); /* finish up 2nd pass */ + + return 0; +} diff --git a/lib/clplumbing/mkstemp_mode.c b/lib/clplumbing/mkstemp_mode.c new file mode 100644 index 0000000..69c080b --- /dev/null +++ b/lib/clplumbing/mkstemp_mode.c @@ -0,0 +1,56 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <clplumbing/mkstemp_mode.h> + + +/* + * A slightly safer version of mkstemp(3) + * + * In this version, the file is initially created mode 0, and then chmod-ed + * to the requested permissions. This guarantees that the file is never + * open to others beyond the specified permissions at any time. + */ +int +mkstemp_mode(char* template, mode_t filemode) +{ + + mode_t maskval; + int fd; + + maskval = umask(0777); + + /* created file should now be mode 0000 */ + fd = mkstemp(template); + + umask(maskval); /* cannot fail :-) */ + + if (fd >= 0) { + if (chmod(template, filemode) < 0) { + int save = errno; + close(fd); + errno = save; + fd = -1; + } + } + return fd; +} diff --git a/lib/clplumbing/netstring_test.c b/lib/clplumbing/netstring_test.c new file mode 100644 index 0000000..1f498ec --- /dev/null +++ b/lib/clplumbing/netstring_test.c @@ -0,0 +1,255 @@ +/* + * netstring_test: Test program for testing the heartbeat binary/struct API + * + * Copyright (C) 2000 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This library 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.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <lha_internal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/cl_signal.h> +#include <sys/types.h> +#include <sys/utsname.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <stdarg.h> +#include <syslog.h> +#include <hb_api_core.h> +#include <hb_api.h> + +/* + * A heartbeat API test program... + */ + +void NodeStatus(const char * node, const char * status, void * private); +void LinkStatus(const char * node, const char *, const char *, void*); +void gotsig(int nsig); + +void +NodeStatus(const char * node, const char * status, void * private) +{ + cl_log(LOG_NOTICE, "Status update: Node %s now has status %s" + , node, status); +} + +void +LinkStatus(const char * node, const char * lnk, const char * status +, void * private) +{ + cl_log(LOG_NOTICE, "Link Status update: Link %s/%s now has status %s" + , node, lnk, status); +} + +int quitnow = 0; +void gotsig(int nsig) +{ + (void)nsig; + quitnow = 1; +} + +#define BUFSIZE 16 +extern int netstring_format; + +int +main(int argc, char ** argv) +{ + struct ha_msg* reply; + struct ha_msg* pingreq = NULL; + unsigned fmask; + ll_cluster_t* hb; + int msgcount=0; + char databuf[BUFSIZE]; + int i; +#if 0 + char * ctmp; + const char * cval; + int j; +#endif + + netstring_format = 0; + + cl_log_set_entity(argv[0]); + cl_log_enable_stderr(TRUE); + cl_log_set_facility(LOG_USER); + hb = ll_cluster_new("heartbeat"); + cl_log(LOG_INFO, "PID=%ld", (long)getpid()); + cl_log(LOG_INFO, "Signing in with heartbeat"); + if (hb->llc_ops->signon(hb, "ping")!= HA_OK) { + cl_log(LOG_ERR, "Cannot sign on with heartbeat"); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(1); + } + + if (hb->llc_ops->set_nstatus_callback(hb, NodeStatus, NULL) !=HA_OK){ + cl_log(LOG_ERR, "Cannot set node status callback"); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(2); + } + + if (hb->llc_ops->set_ifstatus_callback(hb, LinkStatus, NULL)!=HA_OK){ + cl_log(LOG_ERR, "Cannot set if status callback"); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(3); + } + +#if 0 + fmask = LLC_FILTER_RAW; +#else + fmask = LLC_FILTER_DEFAULT; +#endif + /* This isn't necessary -- you don't need this call - it's just for testing... */ + cl_log(LOG_INFO, "Setting message filter mode"); + if (hb->llc_ops->setfmode(hb, fmask) != HA_OK) { + cl_log(LOG_ERR, "Cannot set filter mode"); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(4); + } + + CL_SIGINTERRUPT(SIGINT, 1); + CL_SIGNAL(SIGINT, gotsig); + + pingreq = ha_msg_new(0); + ha_msg_add(pingreq, F_TYPE, "ping"); + { + struct ha_msg *childmsg; + struct ha_msg *grandchildmsg; + + for(i = 0 ;i < BUFSIZE;i ++){ + databuf[i] = 1 + i ; + } + databuf[4] = 0; + + ha_msg_addbin(pingreq, "data",databuf , BUFSIZE); + + + childmsg = ha_msg_new(0); + ha_msg_add(childmsg, "name","testchild"); + ha_msg_addbin(childmsg, "data",databuf , BUFSIZE); + + grandchildmsg = ha_msg_new(0); + ha_msg_add(grandchildmsg, "name","grandchild"); + ha_msg_addstruct(childmsg, "child",grandchildmsg); + + if( ha_msg_addstruct(pingreq, "child", childmsg) != HA_OK){ + cl_log(LOG_ERR, "adding a child message to the message failed"); + exit(1); + } + + } + + cl_log(LOG_INFO, "printing out the pingreq message:"); + + ha_log_message(pingreq); + if (hb->llc_ops->sendclustermsg(hb, pingreq) == HA_OK) { + cl_log(LOG_INFO, "Sent ping request to cluster"); + }else{ + cl_log(LOG_ERR, "PING request FAIL to cluster"); + } + errno = 0; + for(; !quitnow && (reply=hb->llc_ops->readmsg(hb, 1)) != NULL;) { + const char * type; + const char * orig; + ++msgcount; + if ((type = ha_msg_value(reply, F_TYPE)) == NULL) { + type = "?"; + } + if ((orig = ha_msg_value(reply, F_ORIG)) == NULL) { + orig = "?"; + } + cl_log(LOG_INFO, " "); + cl_log(LOG_NOTICE, "Got message %d of type [%s] from [%s]" + , msgcount, type, orig); + + if (strcmp(type, "ping") ==0) { + int datalen = 0; + const char *data; + struct ha_msg *childmsg; + + cl_log(LOG_INFO, "****************************************"); + ha_log_message(reply); + + data = cl_get_binary(reply, "data", &datalen); + if(data){ + cl_log(LOG_INFO, " "); + cl_log(LOG_INFO, "%d of data received,data=%s", datalen,data); + for(i = 0; i < datalen; i++){ + if( databuf[i] != data[i]){ + cl_log(LOG_ERR, "data does not match at %d",i); + break; + } + } + if(i == datalen){ + cl_log(LOG_INFO,"data matches"); + } + }else { + cl_log(LOG_WARNING, "cl_get_binary failed"); + } + + childmsg = cl_get_struct(reply,"child"); + if(childmsg){ + cl_log(LOG_INFO, " "); + cl_log(LOG_INFO, "child message found"); + ha_log_message(childmsg); + }else{ + cl_log(LOG_WARNING, "cl_get_struct failed"); + } + + } + +#if 1 + { + struct ha_msg *cpmsg; + cl_log(LOG_INFO, " "); + cl_log(LOG_INFO, "****************************************************"); + cl_log(LOG_INFO, "Testing ha_msg_copy():"); + cpmsg = ha_msg_copy(reply); + cl_log(LOG_INFO, " "); + cl_log(LOG_INFO, "orginal message is :"); + cl_log(LOG_INFO, " "); + ha_log_message(reply); + cl_log(LOG_INFO, " "); + cl_log(LOG_INFO, "copied message is: "); + cl_log(LOG_INFO, " "); + ha_log_message(cpmsg); + ha_msg_del(cpmsg); + } + + ha_msg_del(reply); reply=NULL; +#endif + } + + if (!quitnow) { + cl_log(LOG_ERR, "read_hb_msg returned NULL"); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + } + if (hb->llc_ops->signoff(hb, TRUE) != HA_OK) { + cl_log(LOG_ERR, "Cannot sign off from heartbeat."); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(10); + } + if (hb->llc_ops->delete(hb) != HA_OK) { + cl_log(LOG_ERR, "Cannot delete API object."); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(11); + } + return 0; +} diff --git a/lib/clplumbing/ocf_ipc.c b/lib/clplumbing/ocf_ipc.c new file mode 100644 index 0000000..c243934 --- /dev/null +++ b/lib/clplumbing/ocf_ipc.c @@ -0,0 +1,594 @@ +/* + * + * ocf_ipc.c: IPC abstraction implementation. + * + * + * Copyright (c) 2002 Xiaoxiang Liu <xiliu@ncsa.uiuc.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <lha_internal.h> +#include <clplumbing/ipc.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/poll.h> +#include <clplumbing/cl_log.h> +#include <sys/types.h> +#include <unistd.h> +#include <ctype.h> +#include <pwd.h> +#include <grp.h> + +static int num_pool_allocated = 0; +static int num_pool_freed = 0; + +#ifdef IPC_TIME_DEBUG +struct ha_msg; +void cl_log_message (int log_level, const struct ha_msg *m); +int timediff(longclock_t t1, longclock_t t2); +void ha_msg_del(struct ha_msg* msg); +void ipc_time_debug(IPC_Channel* ch, IPC_Message* ipcmsg, int whichpos); +#endif + +struct IPC_WAIT_CONNECTION * socket_wait_conn_new(GHashTable* ch_attrs); +struct IPC_CHANNEL * socket_client_channel_new(GHashTable* ch_attrs); + +int (*ipc_pollfunc_ptr)(struct pollfd*, unsigned int, int) += (int (*)(struct pollfd*, unsigned int, int)) poll; + +/* Set the IPC poll function to the given function */ +void +ipc_set_pollfunc(int (*pf)(struct pollfd*, unsigned int, int)) +{ + ipc_pollfunc_ptr = pf; +} + +struct IPC_WAIT_CONNECTION * +ipc_wait_conn_constructor(const char * ch_type, GHashTable* ch_attrs) +{ + if (strcmp(ch_type, "domain_socket") == 0 + || strcmp(ch_type, IPC_UDS_CRED) == 0 + || strcmp(ch_type, IPC_ANYTYPE) == 0 + || strcmp(ch_type, IPC_DOMAIN_SOCKET) == 0) { + return socket_wait_conn_new(ch_attrs); + } + return NULL; +} + +struct IPC_CHANNEL * +ipc_channel_constructor(const char * ch_type, GHashTable* ch_attrs) +{ + if (strcmp(ch_type, "domain_socket") == 0 + || strcmp(ch_type, IPC_UDS_CRED) == 0 + || strcmp(ch_type, IPC_ANYTYPE) == 0 + || strcmp(ch_type, IPC_DOMAIN_SOCKET) == 0) { + + return socket_client_channel_new(ch_attrs); + } + return NULL; +} + +static int +gnametonum(const char * gname, int gnlen) +{ + char grpname[64]; + struct group* grp; + + if (isdigit((int) gname[0])) { + return atoi(gname); + } + if (gnlen >= (int)sizeof(grpname)) { + return -1; + } + strncpy(grpname, gname, gnlen); + grpname[gnlen] = EOS; + if ((grp = getgrnam(grpname)) == NULL) { + cl_log(LOG_ERR + , "Invalid group name [%s]", grpname); + return -1; + } + return (int)grp->gr_gid; +} + +static int +unametonum(const char * lname, int llen) +{ + char loginname[64]; + struct passwd* pwd; + + if (llen >= (int)sizeof(loginname)) { + cl_log(LOG_ERR + , "user id name [%s] is too long", loginname); + return -1; + } + strncpy(loginname, lname, llen); + loginname[llen] = EOS; + + if (isdigit((int) loginname[0])) { + return atoi(loginname); + } + if ((pwd = getpwnam(loginname)) == NULL) { + cl_log(LOG_ERR + , "Invalid user id name [%s]", loginname); + return -1; + } + return (int)pwd->pw_uid; +} + +static GHashTable* +make_id_table(const char * list, int listlen, int (*map)(const char *, int)) +{ + GHashTable* ret; + const char * id; + const char * lastid = list + listlen; + int idlen; + int idval; + static int one = 1; + + ret = g_hash_table_new(g_direct_hash, g_direct_equal); + + id = list; + while (id < lastid && *id != EOS) { + idlen = strcspn(id, ","); + if (id+idlen >= lastid) { + idlen = (lastid - id); + } + idval = map(id, idlen); + if (idval < 0) { + g_hash_table_destroy(ret); + return NULL; + } +#if 0 + cl_log(LOG_DEBUG + , "Adding [ug]id %*s [%d] to authorization g_hash_table" + , idlen, id, idval); +#endif + g_hash_table_insert(ret, GUINT_TO_POINTER(idval), &one); + id += idlen; + if (id < lastid) { + id += strspn(id, ","); + } + } + return ret; +} + +struct IPC_AUTH* +ipc_str_to_auth(const char* uidlist, int uidlen, const char* gidlist, int gidlen) +{ + struct IPC_AUTH* auth; + + auth = malloc(sizeof(struct IPC_AUTH)); + if (auth == NULL) { + cl_log(LOG_ERR, "Out of memory for IPC_AUTH"); + return NULL; + } + + memset(auth, 0, sizeof(*auth)); + + if (uidlist) { + auth->uid = make_id_table(uidlist, uidlen, unametonum); + if (auth->uid == NULL) { + cl_log(LOG_ERR, + "Bad uid list [%*s]", + uidlen, uidlist); + goto errout; + } + } + if (gidlist) { + auth->gid = make_id_table(gidlist, gidlen, gnametonum); + if (auth->gid == NULL) { + cl_log(LOG_ERR , + "Bad gid list [%*s]", + gidlen, gidlist); + goto errout; + } + } + return auth; + + errout: + if (auth->uid) { + g_hash_table_destroy(auth->uid); + auth->uid = NULL; + } + if (auth->gid) { + g_hash_table_destroy(auth->gid); + auth->gid = NULL; + } + free(auth); + auth = NULL; + return NULL; +} + +struct IPC_AUTH * +ipc_set_auth(uid_t * a_uid, gid_t * a_gid, int num_uid, int num_gid) +{ + struct IPC_AUTH *temp_auth; + int i; + static int v = 1; + + temp_auth = malloc(sizeof(struct IPC_AUTH)); + if (temp_auth == NULL) { + cl_log(LOG_ERR, "%s: memory allocation failed",__FUNCTION__); + return NULL; + } + temp_auth->uid = g_hash_table_new(g_direct_hash, g_direct_equal); + temp_auth->gid = g_hash_table_new(g_direct_hash, g_direct_equal); + + if (num_uid > 0) { + for (i=0; i<num_uid; i++) { + g_hash_table_insert(temp_auth->uid, GINT_TO_POINTER((gint)a_uid[i]) + , &v); + } + } + + if (num_gid > 0) { + for (i=0; i<num_gid; i++) { + g_hash_table_insert(temp_auth->gid, GINT_TO_POINTER((gint)a_gid[i]) + , &v); + } + } + + return temp_auth; +} + +void +ipc_destroy_auth(struct IPC_AUTH *auth) +{ + if (auth != NULL) { + if (auth->uid) { + g_hash_table_destroy(auth->uid); + } + if (auth->gid) { + g_hash_table_destroy(auth->gid); + } + free((void *)auth); + } +} + +static void +ipc_bufpool_display(struct ipc_bufpool* pool) +{ + if (pool == NULL) { + return; + } + cl_log(LOG_INFO, "pool: refcount=%d, startpos=%p, currpos=%p," + "consumepos=%p, endpos=%p, size=%d", + pool->refcount, pool->startpos, + pool->currpos, pool->consumepos, + pool->endpos, pool->size); +} + +void +ipc_bufpool_dump_stats(void) +{ + cl_log(LOG_INFO, "num_pool_allocated=%d, num_pool_freed=%d, diff=%d", + num_pool_allocated, + num_pool_freed, + num_pool_allocated - num_pool_freed); +} + +#define POOLHDR_SIZE \ + (sizeof(struct ipc_bufpool) + 2*sizeof(struct SOCKET_MSG_HEAD)) + +struct ipc_bufpool* +ipc_bufpool_new(int size) +{ + struct ipc_bufpool* pool; + int totalsize; + + /* there are memories for two struct SOCKET_MSG_HEAD + * one for the big message, the other one for the next + * message. This code prevents allocating + * <big memory> <4k> <big memory><4k> ... + * from happening when a client sends big messages + * constantly*/ + + totalsize = size + POOLHDR_SIZE; + + if (totalsize < POOL_SIZE) { + totalsize = POOL_SIZE; + } + + if (totalsize > MAXMSG + POOLHDR_SIZE) { + cl_log(LOG_INFO, "ipc_bufpool_new: " + "asking for buffer with size %d; " + "corrupted data len???", totalsize); + return NULL; + } + + pool = (struct ipc_bufpool*)malloc(totalsize+1); + if (pool == NULL) { + cl_log(LOG_ERR, "%s: memory allocation failed", __FUNCTION__); + return NULL; + } + memset(pool, 0, totalsize); + pool->refcount = 1; + pool->startpos = pool->currpos = pool->consumepos = + ((char*)pool) + sizeof(struct ipc_bufpool); + + pool->endpos = ((char*)pool) + totalsize; + pool->size = totalsize; + + num_pool_allocated ++ ; + + return pool; +} + +void +ipc_bufpool_del(struct ipc_bufpool* pool) +{ + if (pool == NULL) { + return; + } + + if (pool->refcount > 0) { + cl_log(LOG_ERR," ipc_bufpool_del:" + " IPC buffer pool reference count > 0"); + return; + } + + memset(pool, 0, pool->size); + free(pool); + num_pool_freed ++ ; +} + +int +ipc_bufpool_spaceleft(struct ipc_bufpool* pool) +{ + if( pool == NULL) { + cl_log(LOG_ERR, "ipc_bufpool_spaceleft:" + " invalid input argument"); + return 0; + } + return pool->endpos - pool->currpos; +} + +/* brief free the memory space allocated to msg and destroy msg. */ + +static void +ipc_bufpool_msg_done(struct IPC_MESSAGE * msg) +{ + struct ipc_bufpool* pool; + + if (msg == NULL) { + cl_log(LOG_ERR, "ipc_bufpool_msg_done: invalid input"); + return; + } + + pool = (struct ipc_bufpool*)msg->msg_private; + + ipc_bufpool_unref(pool); + free(msg); +} + +static struct IPC_MESSAGE* +ipc_bufpool_msg_new(void) +{ + struct IPC_MESSAGE * temp_msg; + + temp_msg = malloc(sizeof(struct IPC_MESSAGE)); + if (temp_msg == NULL) { + cl_log(LOG_ERR, "ipc_bufpool_msg_new:" + "allocating new msg failed"); + return NULL; + } + + memset(temp_msg, 0, sizeof(struct IPC_MESSAGE)); + + return temp_msg; +} + +static void +ipcmsg_display(IPC_Message* ipcmsg) +{ + if (ipcmsg == NULL) { + cl_log(LOG_ERR, "ipcmsg is NULL"); + return; + } + cl_log(LOG_INFO, "ipcmsg: msg_len=%lu, msg_buf=%p, msg_body=%p," + "msg_done=%p, msg_private=%p, msg_ch=%p", + (unsigned long)ipcmsg->msg_len, + ipcmsg->msg_buf, + ipcmsg->msg_body, + ipcmsg->msg_done, + ipcmsg->msg_private, + ipcmsg->msg_ch); +} + +/* after a recv call, we have new data + * in the pool buf, we need to update our + * pool struct to consume it + * + */ + +int +ipc_bufpool_update(struct ipc_bufpool* pool, + struct IPC_CHANNEL * ch, + int msg_len, + IPC_Queue* rqueue) +{ + IPC_Message* ipcmsg; + struct SOCKET_MSG_HEAD localhead; + struct SOCKET_MSG_HEAD* head = &localhead; + int nmsgs = 0 ; + + if (rqueue == NULL) { + cl_log(LOG_ERR, "ipc_update_bufpool:" + "invalid input"); + return 0; + } + + pool->currpos += msg_len; + + while(TRUE) { + /*not enough data for head*/ + if ((int)(pool->currpos - pool->consumepos) < (int)ch->msgpad) { + break; + } + + memcpy(head, pool->consumepos, sizeof(struct SOCKET_MSG_HEAD)); + + if (head->magic != HEADMAGIC) { + GList* last = g_list_last(rqueue->queue); + cl_log(LOG_ERR, "ipc_bufpool_update: " + "magic number in head does not match. " + "Something very bad happened, farside pid =%d", + ch->farside_pid); + cl_log(LOG_ERR, "magic=%x, expected value=%x", head->magic, HEADMAGIC); + ipc_bufpool_display(pool); + cl_log(LOG_INFO, "nmsgs=%d", nmsgs); + /*print out the last message in queue*/ + if (last) { + IPC_Message* m = (IPC_Message*)last; + ipcmsg_display(m); + } + return -1; + } + + if ( head->msg_len > MAXMSG) { + cl_log(LOG_ERR, "ipc_update_bufpool:" + "msg length is corruptted(%d)", + head->msg_len); + break; + } + + if (pool->consumepos + ch->msgpad + head->msg_len + > pool->currpos) { + break; + } + + ipcmsg = ipc_bufpool_msg_new(); + if (ipcmsg == NULL) { + cl_log(LOG_ERR, "ipc_update_bufpool:" + "allocating memory for new ipcmsg failed"); + break; + + } + ipcmsg->msg_buf = pool->consumepos; + ipcmsg->msg_body = pool->consumepos + ch->msgpad; + ipcmsg->msg_len = head->msg_len; + ipcmsg->msg_private = pool; + ipcmsg->msg_done = ipc_bufpool_msg_done; +#ifdef IPC_TIME_DEBUG + ipc_time_debug(ch,ipcmsg, MSGPOS_RECV); +#endif + rqueue->queue = g_list_append(rqueue->queue, ipcmsg); + rqueue->current_qlen ++; + nmsgs++; + + pool->consumepos += ch->msgpad + head->msg_len; + ipc_bufpool_ref(pool); + } + return nmsgs; +} + +gboolean +ipc_bufpool_full(struct ipc_bufpool* pool, + struct IPC_CHANNEL* ch, + int* dataspaceneeded) +{ + struct SOCKET_MSG_HEAD localhead; + struct SOCKET_MSG_HEAD* head = &localhead; + + *dataspaceneeded = 0; + /* not enough space for head */ + if ((int)(pool->endpos - pool->consumepos) < (int)ch->msgpad) { + return TRUE; + } + + /*enough space for head*/ + if ((int)(pool->currpos - pool->consumepos) >= (int)ch->msgpad) { + memcpy(head, pool->consumepos, sizeof(struct SOCKET_MSG_HEAD)); + + /* not enough space for data*/ + if ( pool->consumepos + ch->msgpad + head->msg_len >= pool->endpos) { + *dataspaceneeded = head->msg_len; + return TRUE; + } + } + + /* Either we are sure we have enough space + * or we cannot tell because we have not received + * head yet. But we are sure we have enough space + * for head + */ + return FALSE; +} + +int +ipc_bufpool_partial_copy(struct ipc_bufpool* dstpool, + struct ipc_bufpool* srcpool) +{ + struct SOCKET_MSG_HEAD localhead; + struct SOCKET_MSG_HEAD *head = &localhead; + int space_needed; + int nbytes; + + if (dstpool == NULL + || srcpool == NULL) { + cl_log(LOG_ERR, "ipc_bufpool_partial_ipcmsg_cp:" + "invalid input"); + return IPC_FAIL; + } + + if (srcpool->currpos - srcpool->consumepos >= + (ssize_t)sizeof(struct SOCKET_MSG_HEAD)) { + + memcpy(head, srcpool->consumepos, sizeof(struct SOCKET_MSG_HEAD)); + space_needed = head->msg_len + sizeof(*head); + + if (space_needed > ipc_bufpool_spaceleft(dstpool)) { + cl_log(LOG_ERR, "ipc_bufpool_partial_ipcmsg_cp:" + " not enough space left in dst pool,spaced needed=%d", + space_needed); + return IPC_FAIL; + } + } + + nbytes = srcpool->currpos - srcpool->consumepos; + memcpy(dstpool->consumepos, srcpool->consumepos,nbytes); + + srcpool->currpos = srcpool->consumepos; + dstpool->currpos = dstpool->consumepos + nbytes; + + return IPC_OK; +} + +void +ipc_bufpool_ref(struct ipc_bufpool* pool) +{ + if (pool == NULL) { + cl_log(LOG_ERR, "ref_pool:" + " invalid input"); + return; + } + pool->refcount ++; +} + +void +ipc_bufpool_unref(struct ipc_bufpool* pool) +{ + if (pool == NULL) { + cl_log(LOG_ERR, "unref_pool:" + " invalid input"); + return; + } + pool->refcount --; + if (pool->refcount <= 0) { + ipc_bufpool_del(pool); + } +} diff --git a/lib/clplumbing/proctrack.c b/lib/clplumbing/proctrack.c new file mode 100644 index 0000000..f6a9df2 --- /dev/null +++ b/lib/clplumbing/proctrack.c @@ -0,0 +1,515 @@ +/* + * Process tracking object. + * + * Copyright (c) 2002 International Business Machines + * Author: Alan Robertson <alanr@unix.sh> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> +#include <errno.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <signal.h> +#include <time.h> +#include <clplumbing/proctrack.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/uids.h> +#include <clplumbing/cl_signal.h> +#include <clplumbing/Gmain_timeout.h> + +#define DEBUGPROCTRACK debugproctrack + + +int debugproctrack = 0; +static int LoggingIsEnabled = 1; +static GHashTable* ProcessTable = NULL; +static void InitProcTable(void); +static void ForEachProcHelper(gpointer key, gpointer value +, void * helper); +static gboolean TrackedProcTimeoutFunction(gpointer p); + +static void +InitProcTable() +{ + if (ProcessTable) { + return; + } + + ProcessTable = g_hash_table_new(g_direct_hash, g_direct_equal); +} + +/* Create/Log a new tracked process */ +void +NewTrackedProc(pid_t pid, int isapgrp, ProcTrackLogType loglevel +, void * privatedata, ProcTrack_ops* ops) +{ + ProcTrack* p = g_new(ProcTrack, 1); + + InitProcTable(); + p->pid = pid; + p->isapgrp = isapgrp; + p->loglevel = loglevel; + p->privatedata = privatedata; + p->ops = ops; + p->startticks = time_longclock(); + p->starttime = time(NULL); + p->timerid = 0; + p->timeoutseq = -1; + p->killinfo = NULL; + + g_hash_table_insert(ProcessTable, GINT_TO_POINTER(pid), p); + + /* Tell them that someone registered a process */ + if (p->ops->procregistered) { + p->ops->procregistered(p); + } +} + +static struct signal_info_s { + int signo; + const char * sigdefine; + const char* sigwords; +} signal_info [] = { + +#ifdef SIGHUP + {SIGHUP, "SIGHUP", "Hangup"}, +#endif +#ifdef SIGINT + {SIGINT, "SIGINT", "Interrupt"}, +#endif +#ifdef SIGQUIT + {SIGQUIT, "SIGQUIT", "Quit"}, +#endif +#ifdef SIGILL + {SIGILL, "SIGILL", "Illegal instruction"}, +#endif +#ifdef SIGTRAP + {SIGTRAP, "SIGTRAP", "Trace"}, +#endif +#ifdef SIGABRT + {SIGABRT, "SIGABRT", "Abort"}, +#endif +#ifdef SIGIOT + {SIGIOT, "SIGIOT", "IOT trap"}, +#endif +#ifdef SIGBUS + {SIGBUS, "SIGBUS", "BUS error"}, +#endif +#ifdef SIGFPE + {SIGFPE, "SIGFPE", "Floating-point exception"}, +#endif +#ifdef SIGKILL + {SIGKILL, "SIGKILL", "Kill, unblockable"}, +#endif +#ifdef SIGUSR1 + {SIGUSR1, "SIGUSR1", "User-defined signal 1"}, +#endif +#ifdef SIGSEGV + {SIGSEGV, "SIGSEGV", "Segmentation violation"}, +#endif +#ifdef SIGUSR2 + {SIGUSR2, "SIGUSR2", "User-defined signal 2"}, +#endif +#ifdef SIGPIPE + {SIGPIPE, "SIGPIPE", "Broken pipe (POSIX)"}, +#endif +#ifdef SIGALRM + {SIGALRM, "SIGALRM", "Alarm clock (POSIX)"}, +#endif +#ifdef SIGTERM + {SIGTERM, "SIGTERM", "Termination (ANSI)"}, +#endif +#ifdef SIGSTKFLT + {SIGSTKFLT, "SIGSTKFLT", "Stack fault"}, +#endif +#ifdef SIGCHLD + {SIGCHLD, "SIGCHLD", "Child status has changed"}, +#endif +#ifdef SIGCLD + {SIGCLD, "SIGCLD ", "Child status has changed"}, +#endif +#ifdef SIGCONT + {SIGCONT, "SIGCONT", "Continue"}, +#endif +#ifdef SIGSTOP + {SIGSTOP, "SIGSTOP", "Stop, unblockable"}, +#endif +#ifdef SIGTSTP + {SIGTSTP, "SIGTSTP", "Keyboard stop"}, +#endif +#ifdef SIGTTIN + {SIGTTIN, "SIGTTIN", "Background read from tty"}, +#endif +#ifdef SIGTTOU + {SIGTTOU, "SIGTTOU", "Background write to tty"}, +#endif +#ifdef SIGURG + {SIGURG, "SIGURG ", "Urgent condition on socket"}, +#endif +#ifdef SIGXCPU + {SIGXCPU, "SIGXCPU", "CPU limit exceeded"}, +#endif +#ifdef SIGXFSZ + {SIGXFSZ, "SIGXFSZ", "File size limit exceeded"}, +#endif +#ifdef SIGVTALRM + {SIGVTALRM, "SIGVTALRM", "Virtual alarm clock"}, +#endif +#ifdef SIGPROF + {SIGPROF, "SIGPROF", "Profiling alarm clock"}, +#endif +#ifdef SIGWINCH + {SIGWINCH, "SIGWINCH", "Window size change"}, +#endif +#ifdef SIGPOLL + {SIGPOLL, "SIGPOLL", "Pollable event occurred"}, +#endif +#ifdef SIGIO + {SIGIO, "SIGIO", "I/O now possible"}, +#endif +#ifdef SIGPWR + {SIGPWR, "SIGPWR", "Power failure restart"}, +#endif +#ifdef SIGSYS + {SIGSYS, "SIGSYS", "Bad system call"}, +#endif +}; +static const char * +signal_name(int signo, const char ** sigdescription) +{ + int j; + for (j=0; j < DIMOF(signal_info); ++j) { + if (signal_info[j].signo == signo) { + if (sigdescription) { + *sigdescription = signal_info[j].sigwords; + } + return signal_info[j].sigdefine; + } + } + if (sigdescription) { + *sigdescription = NULL; + } + return NULL; +} + +/* returns TRUE if 'pid' was registered */ +int +ReportProcHasDied(int pid, int status) +{ + ProcTrack* p; + int signo=0; + int deathbyexit=0; + int deathbysig=0; + int exitcode=0; + int doreport = 0; + int debugreporting = 0; + const char * type; + ProcTrackLogType level; +#ifdef WCOREDUMP + int didcoredump = 0; +#endif + if ((p = GetProcInfo(pid)) == NULL) { + if (DEBUGPROCTRACK) { + cl_log(LOG_DEBUG + , "Process %d died (%d) but is not tracked." + , pid, status); + } + type = "untracked process"; + level = PT_LOGNONE; + }else{ + type = p->ops->proctype(p); + level = p->loglevel; + } + + if (WIFEXITED(status)) { + deathbyexit=1; + exitcode = WEXITSTATUS(status); + }else if (WIFSIGNALED(status)) { + deathbysig=1; + signo = WTERMSIG(status); + doreport=1; + } + switch(level) { + case PT_LOGVERBOSE: doreport=1; + break; + + case PT_LOGNONE: doreport = 0; + break; + + case PT_LOGNORMAL: break; + } + + if (!LoggingIsEnabled) { + doreport = 0; + } +#ifdef WCOREDUMP + if (WCOREDUMP(status)) { + /* Force a report on all core dumping processes */ + didcoredump=1; + doreport=1; + } +#endif + if (DEBUGPROCTRACK && !doreport) { + doreport = 1; + debugreporting = 1; + } + + if (doreport) { + if (deathbyexit) { + cl_log((exitcode == 0 ? LOG_INFO : LOG_WARNING) + , "Managed %s process %d exited with return code %d." + , type, pid, exitcode); + }else if (deathbysig) { + const char * signame = NULL; + const char * sigwords = NULL; + int logtype; + signame = signal_name(signo, &sigwords); + logtype = (debugreporting ? LOG_INFO : LOG_WARNING); + /* + * Processes being killed isn't an error if + * we're only logging because of debugging. + */ + if (signame && sigwords) { + cl_log(logtype + , "Managed %s process %d killed by" + " signal %d [%s - %s]." + , type, pid, signo + , signame, sigwords); + }else{ + cl_log(logtype + , "Managed %s process %d killed by signal %d." + , type, pid, signo); + } + }else{ + cl_log(LOG_ERR, "Managed %s process %d went away" + " strangely (!)" + , type, pid); + } + } +#ifdef WCOREDUMP + if (didcoredump) { + /* We report ALL core dumps without exception */ + cl_log(LOG_ERR, "Managed %s process %d dumped core" + , type, pid); + } +#endif + + if (p) { + RemoveTrackedProcTimeouts(pid); + /* + * From clplumbing/proctrack.h: + * (ProcTrack* p, int status, int signo, int exitcode + * , int waslogged); + */ + p->ops->procdied(p, status, signo, exitcode, doreport); + if (p->privatedata) { + /* They may have forgotten to free something... */ + cl_log(LOG_ERR, "Managed %s process %d did not" + " clean up private data!" + , type, pid); + } + g_hash_table_remove(ProcessTable, GINT_TO_POINTER(pid)); + g_free(p); + } + + return doreport; +} + +/* Return information associated with the given PID (or NULL) */ +ProcTrack* +GetProcInfo(pid_t pid) +{ + return (ProcessTable + ? g_hash_table_lookup(ProcessTable, GINT_TO_POINTER(pid)) + : NULL); +} + +/* "info" is 0-terminated (terminated by a 0 signal) */ +int +SetTrackedProcTimeouts(pid_t pid, ProcTrackKillInfo* info) +{ + long mstimeout; + ProcTrack* pinfo; + pinfo = GetProcInfo(pid); + + if (pinfo == NULL) { + return 0; + } + + pinfo->timeoutseq = 0; + pinfo->killinfo = info; + mstimeout = pinfo->killinfo[0].mstimeout; + pinfo->timerid = Gmain_timeout_add(mstimeout + , TrackedProcTimeoutFunction + , GINT_TO_POINTER(pid)); + return pinfo->timerid; +} + +void +RemoveTrackedProcTimeouts(pid_t pid) +{ + ProcTrack* pinfo; + pinfo = GetProcInfo(pid); + + if (pinfo == NULL) { + return; + } + + if (pinfo->killinfo && pinfo->timerid) { + Gmain_timeout_remove(pinfo->timerid); + } + pinfo->killinfo = NULL; + pinfo->timerid = 0; +} + +static gboolean +TrackedProcTimeoutFunction(gpointer p) +{ + /* This is safe - Pids are relatively small ints */ + pid_t pid = POINTER_TO_SIZE_T(p); /*pointer cast as int*/ + ProcTrack* pinfo; + int nsig; + long mstimeout; + int hadprivs; + + pinfo = GetProcInfo(pid); + + if (pinfo == NULL) { + cl_log(LOG_ERR, "%s: bad pinfo in call (pid %d)", __FUNCTION__, pid); + return FALSE; + } + if (pinfo->timeoutseq < 0 || pinfo->killinfo == NULL) { + cl_log(LOG_ERR + , "%s: bad call (pid %d): killinfo (%d, 0x%lx)" + , __FUNCTION__, pid + , pinfo->timeoutseq + , (unsigned long)POINTER_TO_SIZE_T(pinfo->killinfo)); + return FALSE; + } + + pinfo->timerid = 0; + nsig = pinfo->killinfo[pinfo->timeoutseq].signalno; + + if (nsig == 0) { + if (CL_PID_EXISTS(pid)) { + cl_log(LOG_ERR + , "%s: %s process (PID %d) will not die!" + , __FUNCTION__ + , pinfo->ops->proctype(pinfo) + , (int)pid); + } + return FALSE; + } + pinfo->timeoutseq++; + cl_log(LOG_WARNING, "%s process (PID %d) timed out (try %d)" + ". Killing with signal %s (%d)." + , pinfo->ops->proctype(pinfo), (int)pid + , pinfo->timeoutseq + , signal_name(nsig, NULL) + , nsig); + + if (pinfo->isapgrp && nsig > 0) { + pid = -pid; + } + + if (!(hadprivs = cl_have_full_privs())) { + return_to_orig_privs(); + } + if (kill(pid, nsig) < 0) { + if (errno == ESRCH) { + /* Mission accomplished! */ + cl_log(LOG_INFO, "%s process (PID %d) died before killing (try %d)" + , pinfo->ops->proctype(pinfo), (int)pid + , pinfo->timeoutseq); + return FALSE; + }else{ + cl_perror("%s: kill(%d,%d) failed" + , __FUNCTION__, pid, nsig); + } + } + if (!hadprivs) { + return_to_dropped_privs(); + } + mstimeout = pinfo->killinfo[pinfo->timeoutseq].mstimeout; + pinfo->timerid = Gmain_timeout_add(mstimeout + , TrackedProcTimeoutFunction + , p); + if (pinfo->timerid <= 0) { + cl_log(LOG_ERR, "%s: Could not add new kill timer [%u]" + , __FUNCTION__, pinfo->timerid); + kill(pid, SIGKILL); + } + if (debugproctrack) { + cl_log(LOG_DEBUG, "%s process (PID %d) scheduled to be killed again" + " (try %d) in %ld ms [timerid %u]" + , pinfo->ops->proctype(pinfo), (int)pid + , pinfo->timeoutseq + , mstimeout + , pinfo->timerid); + } + return FALSE; +} + +/* Helper struct to allow us to stuff 3 args into one ;-) */ +struct prochelper { + ProcTrack_ops* type; + ProcTrackFun fun; + void* data; +}; + +/* Helper function to call user's function with right args... */ +static void +ForEachProcHelper(gpointer key, gpointer value, void * helper) +{ + ProcTrack* p = value; + struct prochelper* ph = helper; + + if (ph->type != NULL && ph->type != p->ops) { + return; + } + + ph->fun(p, ph->data); +} +/* + * Iterate over the set of tracked processes. + * If proctype is NULL, then walk through them all, otherwise only those + * of the given type. + */ +void +ForEachProc(ProcTrack_ops* proctype, ProcTrackFun f, void * data) +{ + struct prochelper ph; + + InitProcTable(); + ph.fun = f; + ph.type = proctype; + ph.data = data; + g_hash_table_foreach(ProcessTable, ForEachProcHelper, &ph); +} + +void +DisableProcLogging() +{ + LoggingIsEnabled = 0; +} + +void +EnableProcLogging() +{ + LoggingIsEnabled = 1; +} diff --git a/lib/clplumbing/realtime.c b/lib/clplumbing/realtime.c new file mode 100644 index 0000000..9271204 --- /dev/null +++ b/lib/clplumbing/realtime.c @@ -0,0 +1,354 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <sys/types.h> +#include <stdlib.h> +#include <stddef.h> +/* The BSD's do not use malloc.h directly. */ +/* They use stdlib.h instead */ +#ifndef BSD +#ifdef HAVE_MALLOC_H +# include <malloc.h> +#endif +#endif +#include <unistd.h> +#ifdef _POSIX_MEMLOCK +# include <sys/mman.h> +# include <sys/time.h> +# include <sys/resource.h> +#endif +#ifdef _POSIX_PRIORITY_SCHEDULING +# include <sched.h> +#endif +#include <string.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/realtime.h> +#include <clplumbing/uids.h> +#include <time.h> +#include <errno.h> + +static gboolean cl_realtimepermitted = TRUE; +static void cl_rtmalloc_setup(void); + +#define HOGRET 0xff +/* + * Slightly wacko recursive function to touch requested amount + * of stack so we have it pre-allocated inside our realtime code + * as per suggestion from mlockall(2) + */ +#ifdef _POSIX_MEMLOCK +static unsigned char +cl_stack_hogger(unsigned char * inbuf, int kbytes) +{ + unsigned char buf[1024]; + + if (inbuf == NULL) { + memset(buf, HOGRET, sizeof(buf)); + }else{ + memcpy(buf, inbuf, sizeof(buf)); + } + + if (kbytes > 0) { + return cl_stack_hogger(buf, kbytes-1); + }else{ + return buf[sizeof(buf)-1]; + } +/* #else + return HOGRET; +*/ +} +#endif +/* + * We do things this way to hopefully defeat "smart" malloc code which + * handles large mallocs as special cases using mmap(). + */ +static void +cl_malloc_hogger(int kbytes) +{ + long size = kbytes * 1024; + int chunksize = 1024; + long nchunks = (int)(size / chunksize); + int chunkbytes = nchunks * sizeof(void *); + void** chunks; + int j; + +#ifdef HAVE_MALLOPT +# ifdef M_MMAP_MAX + /* Keep malloc from using mmap */ + mallopt(M_MMAP_MAX, 0); +#endif +# ifdef M_TRIM_THRESHOLD + /* Keep malloc from giving memory back to the system */ + mallopt(M_TRIM_THRESHOLD, -1); +# endif +#endif + chunks=malloc(chunkbytes); + if (chunks == NULL) { + cl_log(LOG_INFO, "Could not preallocate (%d) bytes" + , chunkbytes); + return; + } + memset(chunks, 0, chunkbytes); + + for (j=0; j < nchunks; ++j) { + chunks[j] = malloc(chunksize); + if (chunks[j] == NULL) { + cl_log(LOG_INFO, "Could not preallocate (%d) bytes" + , chunksize); + }else{ + memset(chunks[j], 0, chunksize); + } + } + for (j=0; j < nchunks; ++j) { + if (chunks[j]) { + free(chunks[j]); + chunks[j] = NULL; + } + } + free(chunks); + chunks = NULL; +} + +/* + * Make us behave like a soft real-time process. + * We need scheduling priority and being locked in memory. + * If you ask us nicely, we'll even grow the stack and heap + * for you before locking you into memory ;-). + */ +void +cl_make_realtime(int spolicy, int priority, int stackgrowK, int heapgrowK) +{ +#ifdef DEFAULT_REALTIME_POLICY + struct sched_param sp; + int staticp; +#endif + + if (heapgrowK > 0) { + cl_malloc_hogger(heapgrowK); + } + +#ifdef _POSIX_MEMLOCK + if (stackgrowK > 0) { + unsigned char ret; + if ((ret=cl_stack_hogger(NULL, stackgrowK)) != HOGRET) { + cl_log(LOG_INFO, "Stack hogger failed 0x%x" + , ret); + } + } +#endif + cl_rtmalloc_setup(); + + if (!cl_realtimepermitted) { + cl_log(LOG_INFO + , "Request to set pid %ld to realtime ignored." + , (long)getpid()); + return; + } + +#ifdef DEFAULT_REALTIME_POLICY + if (spolicy < 0) { + spolicy = DEFAULT_REALTIME_POLICY; + } + + if (priority <= 0) { + priority = sched_get_priority_min(spolicy); + } + + if (priority > sched_get_priority_max(spolicy)) { + priority = sched_get_priority_max(spolicy); + } + + + if ((staticp=sched_getscheduler(0)) < 0) { + cl_perror("unable to get scheduler parameters."); + }else{ + memset(&sp, 0, sizeof(sp)); + sp.sched_priority = priority; + + if (sched_setscheduler(0, spolicy, &sp) < 0) { + cl_perror("Unable to set scheduler parameters."); + } + } +#endif + +#if defined _POSIX_MEMLOCK +# ifdef RLIMIT_MEMLOCK +# define THRESHOLD(lim) (((lim))/2) + { + unsigned long growsize = ((stackgrowK+heapgrowK)*1024); + struct rlimit memlocklim; + + getrlimit(RLIMIT_MEMLOCK, &memlocklim); /* Allow for future added fields */ + memlocklim.rlim_max = RLIM_INFINITY; + memlocklim.rlim_cur = RLIM_INFINITY; + /* Try and remove memory locking limits -- if we can */ + if (setrlimit(RLIMIT_MEMLOCK, &memlocklim) < 0) { + /* Didn't work - get what we can */ + getrlimit(RLIMIT_MEMLOCK, &memlocklim); + memlocklim.rlim_cur = memlocklim.rlim_max; + setrlimit(RLIMIT_MEMLOCK, &memlocklim); + } + + /* Could we get 'enough' ? */ + /* (this is a guess - might not be right if we're not root) */ + if (getrlimit(RLIMIT_MEMLOCK, &memlocklim) >= 0 + && memlocklim.rlim_cur != RLIM_INFINITY + && (growsize >= THRESHOLD(memlocklim.rlim_cur))) { + cl_log(LOG_ERR + , "Cannot lock ourselves into memory: System limits" + " on locked-in memory are too small."); + return; + } + } +# endif /*RLIMIT_MEMLOCK*/ + if (mlockall(MCL_CURRENT|MCL_FUTURE) >= 0) { + if (ANYDEBUG) { + cl_log(LOG_DEBUG, "pid %d locked in memory.", (int) getpid()); + } + + } else if(errno == ENOSYS) { + const char *err = strerror(errno); + cl_log(LOG_WARNING, "Unable to lock pid %d in memory: %s", + (int) getpid(), err); + + } else { + cl_perror("Unable to lock pid %d in memory", (int) getpid()); + } +#endif +} + +void +cl_make_normaltime(void) +{ +#ifdef DEFAULT_REALTIME_POLICY + struct sched_param sp; + + memset(&sp, 0, sizeof(sp)); + sp.sched_priority = sched_get_priority_min(SCHED_OTHER); + if (sched_setscheduler(0, SCHED_OTHER, &sp) < 0) { + cl_perror("unable to (re)set scheduler parameters."); + } +#endif +#ifdef _POSIX_MEMLOCK + /* Not strictly necessary. */ + munlockall(); +#endif +} + +void +cl_disable_realtime(void) +{ + cl_realtimepermitted = FALSE; +} + +void +cl_enable_realtime(void) +{ + cl_realtimepermitted = TRUE; +} + +/* Give up the CPU for a little bit */ +/* This is similar to sched_yield() but allows lower prio processes to run */ +int +cl_shortsleep(void) +{ + static const struct timespec req = {0,2000001L}; + + return nanosleep(&req, NULL); +} + + +static int post_rt_morecore_count = 0; +static unsigned long init_malloc_arena = 0L; + +#ifdef HAVE_MALLINFO +# define MALLOC_TOTALSIZE() (((unsigned long)mallinfo().arena)+((unsigned long)mallinfo().hblkhd)) +#else +# define MALLOC_TOTALSIZE() (0L) +#endif + + + +/* Return the number of times we went after more core */ +int +cl_nonrealtime_malloc_count(void) +{ + return post_rt_morecore_count; +} +unsigned long +cl_nonrealtime_malloc_size(void) +{ + return (MALLOC_TOTALSIZE() - init_malloc_arena); +} +/* Log the number of times we went after more core */ +void +cl_realtime_malloc_check(void) +{ + static int lastcount = 0; + static unsigned long oldarena = 0UL; + + if (oldarena == 0UL) { + oldarena = init_malloc_arena; + } + + if (post_rt_morecore_count > lastcount) { + + if (MALLOC_TOTALSIZE() > oldarena) { + + cl_log(LOG_WARNING, + "Performed %d more non-realtime malloc calls.", + post_rt_morecore_count - lastcount); + + cl_log(LOG_INFO, + "Total non-realtime malloc bytes: %ld", + MALLOC_TOTALSIZE() - init_malloc_arena); + oldarena = MALLOC_TOTALSIZE(); + + } + + lastcount = post_rt_morecore_count; + } +} + +#ifdef HAVE___DEFAULT_MORECORE + +static void (*our_save_morecore_hook)(void) = NULL; +static void cl_rtmalloc_morecore_fun(void); + +static void +cl_rtmalloc_morecore_fun(void) +{ + post_rt_morecore_count++; + if (our_save_morecore_hook) { + our_save_morecore_hook(); + } +} +#endif + +static void +cl_rtmalloc_setup(void) +{ + static gboolean inityet = FALSE; + if (!inityet) { + init_malloc_arena = MALLOC_TOTALSIZE(); +#ifdef HAVE___DEFAULT_MORECORE + our_save_morecore_hook = __after_morecore_hook; + __after_morecore_hook = cl_rtmalloc_morecore_fun; + inityet = TRUE; +#endif + } + } diff --git a/lib/clplumbing/replytrack.c b/lib/clplumbing/replytrack.c new file mode 100644 index 0000000..8c7c38e --- /dev/null +++ b/lib/clplumbing/replytrack.c @@ -0,0 +1,643 @@ + +/* + * Reply tracking library. + * + * Copyright (c) 2007 Alan Robertson + * Author: Alan Robertson <alanr@unix.sh> + * + ****************************************************************** + * This library is useful for tracking replies to multicast messages + * sent to cluster members. It tracks incremental membership changes + * according to any desired criteria, and then keeps track of when + * the last expected reply is received according to the dynamically + * updated membership as of when the message was sent out. + ****************************************************************** + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <signal.h> +#include <memory.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/replytrack.h> +#include <clplumbing/Gmain_timeout.h> + +/* + * These are the only data items that go in our GHashTables + */ +struct rt_node_info { + char * nodename; + cl_uuid_t nodeid; +}; + +struct node_tables { + + GHashTable* uuidmap; /* Keyed by uuid */ + int uuidcount; + GHashTable* namemap; /* Keyed by nodename*/ + int namecount; +}; +struct _nodetrack { + struct node_tables nt; + int refcount; + nodetrack_callback_t callback; + gpointer user_data; + nodetrack_callback_t extra_callback; + gpointer ext_data; +}; + +/* + * Things we use to track outstanding replies + * This is the structure used by the replytrack_t typedef + */ +struct _replytrack { + replytrack_callback_t callback; + gpointer user_data; + unsigned timerid; + struct node_tables tables; + gboolean expectingmore; + nodetrack_t* membership; +}; + +struct _nodetrack_intersection { + nodetrack_t** tables; + int ntables; + nodetrack_callback_t callback; + gpointer user_data; + nodetrack_t* intersection; +}; + +static cl_uuid_t nulluuid; +static int nodetrack_t_count = 0; +static int replytrack_t_count = 0; +static int replytrack_intersection_t_count = 0; + +static struct rt_node_info * +rt_node_info_new(const char * nodename, cl_uuid_t nodeid) +{ + struct rt_node_info* ret; + + if (!nodename) { + return NULL; + } + ret = MALLOCT(struct rt_node_info); + + if (!ret) { + return ret; + } + ret->nodename = strdup(nodename); + if (!ret->nodename) { + free(ret); + ret = NULL; + return ret; + } + ret->nodeid = nodeid; + return ret; +} + +static void +rt_node_info_del(struct rt_node_info * ni) +{ + if (ni != NULL) { + if (ni->nodename != NULL) { + free(ni->nodename); + } + memset(ni, 0, sizeof(*ni)); + free(ni); + } +} + +/* + * namehash cannot be NULL, idhash cannot be NULL, and nodename cannot be NULL + * + * 'id' can be a NULL uuid, in which case it goes into only the name table + * 'nodename' can change over time - in which case we update our tables. + * It is possible for one nodename to have more than one uuid. + * We allow for that. + * + * Changing the uuid but keeping the nodename the same is considered to be + * adding a new node with the same nodename. + * Exception: A node with a null uuid is presumed to have acquired a proper + * uuid if it is later seen with a non-null UUID + */ + +static gboolean +del_node_from_hashtables(struct node_tables *t +, const char * nodename, cl_uuid_t id) +{ + struct rt_node_info * entry; + if (cl_uuid_is_null(&id)) { + if ((entry = g_hash_table_lookup(t->namemap,nodename))!=NULL){ + g_hash_table_remove(t->namemap, nodename); + rt_node_info_del(entry); + t->namecount--; + } + return TRUE; + } + if ((entry=g_hash_table_lookup(t->uuidmap, &id)) != NULL) { + g_hash_table_remove(t->uuidmap, &id); + rt_node_info_del(entry); + t->uuidcount--; + } + return TRUE; +} + + +static gboolean +add_node_to_hashtables(struct node_tables * t +, const char * nodename, cl_uuid_t id) +{ + struct rt_node_info* idinfo = NULL; + + if (cl_uuid_is_null(&id)) { + /* Supplied uuid is the null UUID - insert in name table */ + struct rt_node_info* ninfo; + if (g_hash_table_lookup(t->namemap, nodename) == NULL) { + if (NULL == (ninfo = rt_node_info_new(nodename, id))){ + goto outofmem; + } + g_hash_table_insert(t->namemap,ninfo->nodename,ninfo); + t->namecount++; + } + return TRUE; + } + + /* Supplied uuid is not the null UUID */ + + if (g_hash_table_lookup(t->uuidmap,&id) == NULL) { + /* See if a corresponding name is in name map */ + /* If so, delete it - assume uuid was missing before */ + + if (g_hash_table_lookup(t->namemap, nodename) != NULL) { + del_node_from_hashtables(t, nodename, nulluuid); + } + /* Not yet in our uuid hash table */ + idinfo = rt_node_info_new(nodename, id); + if (idinfo == NULL) { + goto outofmem; + } + g_hash_table_insert(t->uuidmap, &idinfo->nodeid, idinfo); + t->uuidcount++; + } + return TRUE; +outofmem: + cl_log(LOG_ERR, "%s: out of memory", __FUNCTION__); + return FALSE; +} + +static gboolean +create_new_hashtables(struct node_tables*t) +{ + t->namemap = g_hash_table_new(g_str_hash, g_str_equal); + if (t->namemap == NULL) { + return FALSE; + } + t->uuidmap = g_hash_table_new(cl_uuid_g_hash, cl_uuid_g_equal); + if (t->uuidmap == NULL) { + g_hash_table_destroy(t->namemap); + t->namemap = NULL; + return FALSE; + } + return TRUE; +} + +static gboolean +hashtable_destroy_rt_node_info(gpointer key, gpointer rti, gpointer unused) +{ + rt_node_info_del(rti); + rti = key = NULL; + return TRUE; +} + +static void +destroy_map_hashtable(GHashTable*t) +{ + g_hash_table_foreach_remove(t, hashtable_destroy_rt_node_info,NULL); + g_hash_table_destroy(t); + t = NULL; +} + +struct tablehelp { + struct node_tables* t; + gboolean ret; +}; + +static void +copy_hashtables_helper(gpointer key_unused, gpointer value +, gpointer user_data) +{ + struct tablehelp * th = user_data; + struct rt_node_info* ni = value; + if (!add_node_to_hashtables(th->t, ni->nodename, ni->nodeid)) { + th->ret = FALSE; + } +} + +static gboolean +copy_hashtables(struct node_tables* tin, struct node_tables* tout) +{ + struct tablehelp newtables; + if (!create_new_hashtables(tout)){ + return FALSE; + } + newtables.t = tout; + newtables.ret = TRUE; + + g_hash_table_foreach(tout->namemap,copy_hashtables_helper,&newtables); + if (!newtables.ret) { + return FALSE; + } + g_hash_table_foreach(tout->uuidmap,copy_hashtables_helper,&newtables); + return newtables.ret; +} + +static gboolean mbr_inityet = FALSE; +static void +init_global_membership(void) +{ + if (mbr_inityet) { + return; + } + mbr_inityet = TRUE; + memset(&nulluuid, 0, sizeof(nulluuid)); +} + +gboolean /* Call us when an expected replier joins / comes up */ +nodetrack_nodeup(nodetrack_t * mbr, const char * node, cl_uuid_t uuid) +{ + gboolean ret; + ret = add_node_to_hashtables(&mbr->nt, node, uuid); + if (ret && mbr->callback) { + mbr->callback(mbr, node, uuid, NODET_UP, mbr->user_data); + } + if (mbr->extra_callback) { + mbr->extra_callback(mbr, node, uuid, NODET_UP,mbr->ext_data); + } + return ret; +} + +gboolean /* Call us when an expected replier goes down / away */ +nodetrack_nodedown(nodetrack_t* mbr, const char* node, cl_uuid_t uuid) +{ + if (mbr->callback) { + mbr->callback(mbr, node, uuid, NODET_DOWN, mbr->user_data); + } + if (mbr->extra_callback) { + mbr->extra_callback(mbr, node,uuid,NODET_DOWN,mbr->ext_data); + } + return del_node_from_hashtables(&mbr->nt, node, uuid); +} + +/* This function calls the user's timeout callback */ +static gboolean +replytrack_timeout_helper(gpointer rldata) +{ + replytrack_t* rl = rldata; + rl->expectingmore = FALSE; + rl->timerid = 0; + if (rl->callback) { + rl->callback(rl, rl->user_data, REPLYT_TIMEOUT); + } + return FALSE; +} + +replytrack_t* /* replytrack_t constructor */ +replytrack_new(nodetrack_t * membership +, replytrack_callback_t callback +, unsigned long timeout_ms +, gpointer user_data) +{ + replytrack_t* ret = MALLOCT(replytrack_t); + if (!ret) { + return ret; + } + if (!copy_hashtables(&membership->nt, &ret->tables)) { + free(ret); + ret = NULL; + return ret; + } + replytrack_t_count++; + ret->membership = membership; + ret->membership->refcount++; + ret->callback = callback; + ret->user_data = user_data; + ret->expectingmore = TRUE; + ret->timerid = 0; + if (timeout_ms != 0 && callback != NULL) { + ret->timerid = Gmain_timeout_add(timeout_ms + , replytrack_timeout_helper, ret); + } + return ret; +} + +void /* replytrack_t destructor */ +replytrack_del(replytrack_t * rl) +{ + rl->membership->refcount--; + replytrack_t_count++; + if (rl->expectingmore && rl->timerid > 0) { + cl_log(LOG_INFO + , "%s: destroying replytrack while still expecting" + " %d replies" + , __FUNCTION__ + , (rl->tables.namecount + rl->tables.uuidcount)); + } + if (rl->timerid > 0) { + g_source_remove(rl->timerid); + rl->timerid = 0; + } + destroy_map_hashtable(rl->tables.namemap); + rl->tables.namemap=NULL; + destroy_map_hashtable(rl->tables.uuidmap); + rl->tables.uuidmap=NULL; + memset(&rl, 0, sizeof(rl)); + free(rl); + rl=NULL; +} + +gboolean /* Call replytrack_gotreply when you receive an expected reply */ +replytrack_gotreply(replytrack_t*rl, const char * node, cl_uuid_t uuid) +{ + gboolean lastone; + del_node_from_hashtables(&rl->tables, node, uuid); + lastone = (rl->tables.namecount + rl->tables.uuidcount) == 0; + if (lastone) { + rl->expectingmore = FALSE; + if (rl->timerid > 0) { + g_source_remove(rl->timerid); + rl->timerid = 0; + } + if (rl->callback){ + rl->callback(rl, rl->user_data, REPLYT_ALLRCVD); + } + } + return lastone; +} + +struct replytrack_iterator_data { + replytrack_t* rlist; + replytrack_iterator_t f; + int count; + gpointer user_data; +}; + + +static void /* g_hash_table user-level iteration helper */ +replytrack_iterator_helper(gpointer key_unused, gpointer entry +, gpointer user_data) +{ + struct replytrack_iterator_data* ri = user_data; + struct rt_node_info* ni = entry; + if (ri && ri->rlist) { + ++ri->count; + if (ri->f) { + ri->f(ri->rlist, ri->user_data + , ni->nodename, ni->nodeid); + } + } +} + + + +int /* iterate through the outstanding expected replies */ +replytrack_outstanding_iterate(replytrack_t* rl +, replytrack_iterator_t i, gpointer user_data) +{ + struct replytrack_iterator_data id; + id.rlist = rl; + id.f = i; + id.count = 0; + id.user_data = user_data; + g_hash_table_foreach(rl->tables.namemap, replytrack_iterator_helper + , &id); + g_hash_table_foreach(rl->tables.uuidmap, replytrack_iterator_helper + , &id); + if (id.count != (rl->tables.namecount + rl->tables.uuidcount)) { + cl_log(LOG_ERR + , "%s: iteration count %d disagrees with" + " (namecount %d+uuidcount %d)" + , __FUNCTION__, id.count + , rl->tables.namecount,rl->tables.uuidcount); + } + return id.count; +} +int /* return count of outstanding expected replies */ +replytrack_outstanding_count(replytrack_t* rl) +{ + return (rl->tables.namecount + rl->tables.uuidcount); +} + +nodetrack_t* +nodetrack_new(nodetrack_callback_t callback, gpointer user_data) +{ + nodetrack_t* ret = MALLOCT(nodetrack_t); + if (!mbr_inityet) { + init_global_membership(); + } + if (!ret) { + return ret; + } + nodetrack_t_count++; + ret->refcount = 0; + if (!create_new_hashtables(&ret->nt)) { + free(ret); + ret = NULL; + } + ret->user_data = user_data; + ret->callback = callback; + ret->extra_callback = NULL; + ret->ext_data = NULL; + return ret; +} +void +nodetrack_del(nodetrack_t * np) +{ + if (np->refcount) { + cl_log(LOG_ERR + , "%s: reply tracking reference count is %d" + , __FUNCTION__, np->refcount); + } + nodetrack_t_count--; + destroy_map_hashtable(np->nt.namemap); + np->nt.namemap=NULL; + destroy_map_hashtable(np->nt.uuidmap); + np->nt.uuidmap=NULL; + memset(np, 0, sizeof(*np)); + free(np); +} + +gboolean +nodetrack_ismember(nodetrack_t* mbr, const char * name, cl_uuid_t u) +{ + if (cl_uuid_is_null(&u)) { + return(g_hash_table_lookup(mbr->nt.namemap, name) != NULL); + } + return (g_hash_table_lookup(mbr->nt.uuidmap, &u) != NULL); +} + +struct nodetrack_iterator_data { + nodetrack_t* rlist; + nodetrack_iterator_t f; + int count; + gpointer user_data; +}; +static void /* g_hash_table user-level iteration helper */ +nodetrack_iterator_helper(gpointer key_unused, gpointer entry +, gpointer user_data) +{ + struct nodetrack_iterator_data* ri = user_data; + struct rt_node_info* ni = entry; + if (ri && ri->rlist) { + ++ri->count; + if (ri->f) { + ri->f(ri->rlist, ri->user_data + , ni->nodename, ni->nodeid); + } + } +} + +int /* iterate through the outstanding expected replies */ +nodetrack_iterate(nodetrack_t* rl +, nodetrack_iterator_t i, gpointer user_data) +{ + struct nodetrack_iterator_data id; + id.rlist = rl; + id.f = i; + id.count = 0; + id.user_data = user_data; + g_hash_table_foreach(rl->nt.namemap, nodetrack_iterator_helper + , &id); + g_hash_table_foreach(rl->nt.uuidmap, nodetrack_iterator_helper + , &id); + if (id.count != (rl->nt.namecount + rl->nt.uuidcount)) { + cl_log(LOG_ERR + , "%s: iteration count %d disagrees with" + " (namecount %d+uuidcount %d)" + , __FUNCTION__, id.count + , rl->nt.namecount,rl->nt.uuidcount); + } + return id.count; +} +static void +intersection_callback +( nodetrack_t * mbr +, const char * node +, cl_uuid_t u +, nodetrack_change_t reason +, gpointer user_data) +{ + nodetrack_intersection_t* it = user_data; + int j; + gboolean allfound = TRUE; + + if (reason == NODET_DOWN) { + if (nodetrack_ismember(it->intersection, node, u)) { + nodetrack_nodedown(it->intersection,node,u); + } + return; + } + for (j=0; j < it->ntables && allfound; ++j) { + if (nodetrack_ismember(it->tables[j], node, u)) { + allfound = FALSE; + } + } + if (allfound) { + nodetrack_nodeup(it->intersection, node, u); + } +} + +struct li_helper { + nodetrack_intersection_t* i; + gboolean result; +}; + +static void +intersection_init_iterator(nodetrack_t* nt +, gpointer ghelp +, const char* node +, cl_uuid_t uuid) +{ + struct li_helper* help = ghelp; + gboolean allfound = TRUE; + int j; + + for (j=1; allfound && j < help->i->ntables; ++j) { + if (!nodetrack_ismember(help->i->tables[j] + , node, uuid)) { + allfound = FALSE; + } + } + if (allfound) { + nodetrack_nodeup(help->i->intersection, node, uuid); + } +} + +nodetrack_intersection_t* +nodetrack_intersection_new(nodetrack_t** tables, int ntables +, nodetrack_callback_t callback, gpointer user_data) +{ + nodetrack_intersection_t* ret; + int j; + ret = MALLOCT(nodetrack_intersection_t); + if (!ret) { + return ret; + } + ret->intersection = nodetrack_new(callback, user_data); + if (!ret->intersection) { + free(ret); + ret = NULL; + return ret; + } + ret->tables = tables; + ret->ntables = ntables; + ret->callback = callback; + ret->user_data = user_data; + for (j=0; j < ntables; ++j) { + tables[j]->refcount ++; + tables[j]->ext_data = ret; + tables[j]->extra_callback = intersection_callback; + } + /* Initialize the intersection membership list */ + nodetrack_iterate(tables[0], intersection_init_iterator, ret); + replytrack_intersection_t_count++; + return ret; +} +void +nodetrack_intersection_del(nodetrack_intersection_t* p) +{ + int j; + + for (j=0; j < p->ntables; ++j) { + p->tables[j]->refcount ++; + } + nodetrack_del(p->intersection); + p->intersection = NULL; + memset(p, 0, sizeof(*p)); + free(p); + p = NULL; + replytrack_intersection_t_count--; +} + +nodetrack_t* +nodetrack_intersection_table(nodetrack_intersection_t*p) +{ + return p->intersection; +} diff --git a/lib/clplumbing/setproctitle.c b/lib/clplumbing/setproctitle.c new file mode 100644 index 0000000..ffc5481 --- /dev/null +++ b/lib/clplumbing/setproctitle.c @@ -0,0 +1,235 @@ +/* + * setproctitle.c + * + * The code in this file, setproctitle.c is heavily based on code from + * proftpd, please see the licening information below. + * + * This file added to the heartbeat tree by Horms <horms@vergenet.net> + * + * Code to portably change the title of a programme as displayed + * by ps(1). + * + * heartbeat: Linux-HA heartbeat code + * + * Copyright (C) 1999,2000,2001 Alan Robertson <alanr@unix.sh> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * ProFTPD - FTP server daemon + * Copyright (c) 1997, 1998 Public Flood Software + * Copyright (C) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu + * and other respective copyright holders give permission to link this program + * with OpenSSL, and distribute the resulting executable, without including + * the source code for OpenSSL in the source distribution. + */ + +#include <lha_internal.h> + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <stdarg.h> + +#define PF_ARGV_NONE 0 +#define PF_ARGV_NEW 1 +#define PF_ARGV_WRITEABLE 2 +#define PF_ARGV_PSTAT 3 +#define PF_ARGV_PSSTRINGS 4 + +#if PF_ARGV_TYPE == PF_ARGV_PSTAT +# include <pstat.h> +#endif + +#include <clplumbing/setproctitle.h> + +#if PF_ARGV_TYPE != PF_ARGV_NONE +static char **Argv = NULL; +static char *LastArgv = NULL; +#endif /* PF_ARGV_TYPE != PF_ARGV_NONE */ + +extern char **environ; + +#ifdef HAVE___PROGNAME +extern char *__progname; +extern char *__progname_full; +#endif /* HAVE___PROGNAME */ + +int +init_set_proc_title(int argc, char *argv[], char *envp[]) +{ +#if PF_ARGV_TYPE == PF_ARGV_NONE + return 0; +#else + int i; + int envpsize; + char **p; + + /* Move the environment so setproctitle can use the space. + */ + for(i = envpsize = 0; envp[i] != NULL; i++) { + envpsize += strlen(envp[i]) + 1; + } + + p = (char **) malloc((i + 1) * sizeof(char *)); + if (p == NULL) { + return -1; + } + + environ = p; + + for(i = 0; envp[i] != NULL; i++) { + environ[i] = strdup(envp[i]); + if(environ[i] == NULL) { + goto error_environ; + } + } + environ[i] = NULL; + + Argv = argv; + + for(i = 0; i < argc; i++) { + if(!i || (LastArgv + 1 == argv[i])) + LastArgv = argv[i] + strlen(argv[i]); + } + + for(i = 0; envp[i] != NULL; i++) { + if((LastArgv + 1) == envp[i]) { + LastArgv = envp[i] + strlen(envp[i]); + } + } + +#ifdef HAVE___PROGNAME + /* Set the __progname and __progname_full variables so glibc and + * company don't go nuts. - MacGyver + */ + + __progname = strdup("heartbeat"); + if (__progname == NULL) { + goto error_environ; + } + __progname_full = strdup(argv[0]); + if (__progname_full == NULL) { + goto error_environ; + } +#endif /* HAVE___PROGNAME */ + + return 0; + +error_environ: + for(i = 0; environ[i] != NULL; i++) { + free(environ[i]); + } + free(environ); + return -1; +#endif /* PF_ARGV_TYPE == PF_ARGV_NONE */ +} + +void set_proc_title(const char *fmt,...) +{ +#if PF_ARGV_TYPE != PF_ARGV_NONE + va_list msg; + static char statbuf[BUFSIZ]; + +#ifndef HAVE_SETPROCTITLE +#if PF_ARGV_TYPE == PF_ARGV_PSTAT + union pstun pst; +#endif /* PF_ARGV_PSTAT */ + int i,maxlen = (LastArgv - Argv[0]) - 2; + char *p; +#endif /* HAVE_SETPROCTITLE */ + + va_start(msg,fmt); + + memset(statbuf, 0, sizeof(statbuf)); + + +#ifdef HAVE_SETPROCTITLE +# if __FreeBSD__ >= 4 && !defined(FREEBSD4_0) && !defined(FREEBSD4_1) + /* FreeBSD's setproctitle() automatically prepends the process name. */ + vsnprintf(statbuf, sizeof(statbuf), fmt, msg); + +# else /* FREEBSD4 */ + /* Manually append the process name for non-FreeBSD platforms. */ + vsnprintf(statbuf + strlen(statbuf), sizeof(statbuf) - strlen(statbuf), + fmt, msg); + +# endif /* FREEBSD4 */ + setproctitle("%s", statbuf); + +#else /* HAVE_SETPROCTITLE */ + /* Manually append the process name for non-setproctitle() platforms. */ + vsnprintf(statbuf + strlen(statbuf), sizeof(statbuf) - strlen(statbuf), + fmt, msg); + +#endif /* HAVE_SETPROCTITLE */ + + va_end(msg); + +#ifdef HAVE_SETPROCTITLE + return; +#else + i = strlen(statbuf); + +#if PF_ARGV_TYPE == PF_ARGV_NEW + /* We can just replace argv[] arguments. Nice and easy. + */ + Argv[0] = statbuf; + Argv[1] = NULL; +#endif /* PF_ARGV_NEW */ + +#if PF_ARGV_TYPE == PF_ARGV_WRITEABLE + /* We can overwrite individual argv[] arguments. Semi-nice. + */ + snprintf(Argv[0], maxlen, "%s", statbuf); + p = &Argv[0][i]; + + while(p < LastArgv) + *p++ = '\0'; + Argv[1] = NULL; +#endif /* PF_ARGV_WRITEABLE */ + +#if PF_ARGV_TYPE == PF_ARGV_PSTAT + pst.pst_command = statbuf; + pstat(PSTAT_SETCMD, pst, i, 0, 0); +#endif /* PF_ARGV_PSTAT */ + +#if PF_ARGV_TYPE == PF_ARGV_PSSTRINGS + PS_STRINGS->ps_nargvstr = 1; + PS_STRINGS->ps_argvstr = statbuf; +#endif /* PF_ARGV_PSSTRINGS */ + +#endif /* HAVE_SETPROCTITLE */ + +#endif /* PF_ARGV_TYPE != PF_ARGV_NONE */ +} diff --git a/lib/clplumbing/timers.c b/lib/clplumbing/timers.c new file mode 100644 index 0000000..c3e99da --- /dev/null +++ b/lib/clplumbing/timers.c @@ -0,0 +1,119 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <sys/time.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <clplumbing/timers.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/cl_signal.h> +#include <clplumbing/longclock.h> + +int +setmsrepeattimer(long ms) +{ + long secs = ms / 1000L; + long usecs = (ms % 1000L)*1000L; + struct itimerval nexttime = + { {secs, usecs} /* Repeat Interval */ + , {secs, usecs} /* Timer Value */ + }; + +#if 0 + cl_log(LOG_DEBUG, "Setting repeating timer for %ld ms" + , ms); +#endif + + + /* Is this right??? */ + CL_IGNORE_SIG(SIGALRM); + return setitimer(ITIMER_REAL, &nexttime, NULL); +} + +int +setmsalarm(long ms) +{ + long secs = ms / 1000L; + long usecs = (ms % 1000L)*1000L; + struct itimerval nexttime = + { {0L, 0L} /* Repeat Interval */ + , {secs, usecs} /* Timer Value */ + }; + + return setitimer(ITIMER_REAL, &nexttime, NULL); +} + +int +cancelmstimer(void) +{ + struct itimerval nexttime = + { {0L, 0L} /* Repeat Interval */ + , {0L, 0L} /* Timer Value */ + }; + return setitimer(ITIMER_REAL, &nexttime, NULL); +} + + +static int alarmpopped = 0; + +static void +st_timer_handler(int nsig) +{ + ++alarmpopped; +} + +/* + * Pretty simple: + * 1) Set up SIGALRM signal handler + * 2) set alarmpopped to FALSE; + * 2) Record current time + * 3) Call setmsalarm(ms) + * 4) Call pause(2) + * 5) Call cancelmstimer() + * 6) Reset signal handler + * 7) See if SIGALRM happened + * if so: return zero + * if not: get current time, and compute milliseconds left 'til signal + * should arrive, and return that... + */ +long +mssleep(long ms) +{ + struct sigaction saveaction; + longclock_t start; + longclock_t finish; + unsigned long elapsedms; + + memset(&saveaction, 0, sizeof(saveaction)); + + cl_signal_set_simple_handler(SIGALRM, st_timer_handler, &saveaction); + alarmpopped = 0; + start = time_longclock(); + setmsalarm(ms); + pause(); + cancelmstimer(); + cl_signal_set_simple_handler(SIGALRM, saveaction.sa_handler, &saveaction); + if (alarmpopped) { + return 0; + } + + finish = time_longclock(); + elapsedms = longclockto_ms(sub_longclock(finish, start)); + return ms - elapsedms; +} diff --git a/lib/clplumbing/transient-test.sh b/lib/clplumbing/transient-test.sh new file mode 100755 index 0000000..7da88bf --- /dev/null +++ b/lib/clplumbing/transient-test.sh @@ -0,0 +1,120 @@ +#!/bin/sh +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# +####FIXME +# Known problems within this testing: +# 1. Doesn't reflect "someone else's message" problems into error count. +# 2. Path to "ipctransient{client,server}" not flexible enough. + +no_logs=0 +exit_on_error=1 +num_servers=10 +num_clients=10 +client_time=2 +commpath_args="" + +cmd=`basename $0` +USAGE="Usage: $cmd [-c clients] [-s servers] [-t timetowait] [-C commpath]" + +while getopts c:s:C:t: c +do + case $c in + c) + num_clients=$OPTARG + ;; + s) + num_servers=$OPTARG + ;; + t) + client_time=$OPTARG + ;; + C) + commpath_args="-$c $OPTARG" + ;; + \?) + echo $USAGE + exit 2 + ;; + esac +done +shift `expr $OPTIND - 1` + +total_failed=0 + +server_failed=0 +server_loop_cnt=0 +while [ $server_loop_cnt != $num_servers ]; do + echo "############ DEBUG: Starting server iter $server_loop_cnt" + if [ $no_logs = 1 ]; then + ./ipctransientserver $commpath_args > /dev/null 2>&1 & + else + ./ipctransientserver $commpath_args & + fi + server_pid=$! + + sleep 5 + + client_failed=0 + client_loop_cnt=0 + while [ $client_loop_cnt != $num_clients ]; do + sleep 5 + echo "############ DEBUG: Starting client iter $client_loop_cnt" + if [ $no_logs = 1 ]; then + ./ipctransientclient $commpath_args > /dev/null 2>&1 & + else + ./ipctransientclient $commpath_args & + fi + client_pid=$! + sleep $client_time + if [ $exit_on_error = 1 ];then + kill -0 $client_pid > /dev/null 2>&1 + else + kill -9 $client_pid > /dev/null 2>&1 + fi + rc=$? + if [ $rc = 0 ]; then + echo "############ ERROR: Iter $client_loop_cnt failed to receive all messages" + client_failed=`expr $client_failed + 1` + if [ $exit_on_error = 1 ];then + echo "terminating after first error..." + exit 0 + fi + else + echo "############ INFO: Iter $client_loop_cnt passed" + fi + + client_loop_cnt=`expr $client_loop_cnt + 1`; + done + server_loop_cnt=`expr $server_loop_cnt + 1`; + total_failed=`expr $total_failed + $client_failed` + kill -9 $server_pid > /dev/null 2>&1 + rc=$? + if [ $rc = 0 ]; then + echo "############ ERROR: Server was already dead" + server_failed=`expr $server_failed + 1` + fi +done + +total_failed=`expr $total_failed + $server_failed` + +if [ $total_failed = 0 ]; then + echo "INFO: All tests passed" +else + echo "ERROR: $total_failed tests failed" +fi + +exit $total_failed diff --git a/lib/clplumbing/uids.c b/lib/clplumbing/uids.c new file mode 100644 index 0000000..0727e1d --- /dev/null +++ b/lib/clplumbing/uids.c @@ -0,0 +1,140 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> +#include <pwd.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> +#include <clplumbing/uids.h> +#include <clplumbing/coredumps.h> +#define NOBODY "nobody" + +#if defined(HAVE_SETEUID) && defined(HAVE_SETEGID) && \ + defined(_POSIX_SAVED_IDS) +# define CAN_DROP_PRIVS 1 + +#endif + + + + +#ifndef CAN_DROP_PRIVS + int drop_privs(uid_t uid, gid_t gid) { return 0; } + int return_to_orig_privs(void) { return 0; } + int return_to_dropped_privs(void) { return 0; } + int cl_have_full_privs(void) { return 0; } +#else + +static int anysaveduid = 0; +static uid_t nobodyuid=-1; +static gid_t nobodygid=-1; +static uid_t poweruid=-1; +static gid_t powergid=-1; +static int privileged_state = 1; + +/* WARNING: uids are unsigned! */ +#define WANT_NOBODY(uid) ((uid) == 0) + +int /* Become nobody - and remember our original privileges */ +drop_privs(uid_t uid, gid_t gid) +{ + int rc; + gid_t curgid = getgid(); + + if (!anysaveduid) { + poweruid=getuid(); + powergid=curgid; + } + + if (WANT_NOBODY(uid)) { + struct passwd* p; + + p = getpwnam(NOBODY); + + if (p == NULL) { + return -1; + } + uid = p->pw_uid; + gid = p->pw_gid; + } + if (setegid(gid) < 0) { + return -1; + } + rc = seteuid(uid); + + if (rc >= 0) { + anysaveduid = 1; + nobodyuid=uid; + nobodygid=gid; + privileged_state = 0; + }else{ + /* Attempt to recover original privileges */ + int err = errno; + setegid(curgid); + errno = err; + } + cl_untaint_coredumps(); + return rc; +} + +int /* Return to our original privileges (if any) */ +return_to_orig_privs(void) +{ + int rc; + if (!anysaveduid) { + return 0; + } + if (seteuid(poweruid) < 0) { + return -1; + } + privileged_state = 1; + rc = setegid(powergid); + /* + * Sad but true, for security reasons we can't call + * cl_untaint_coredumps() here - because it might cause an + * leak of confidential information for some applications. + * So, the applications need to use either cl_untaint_coredumps() + * when they change privileges, or they need to call + * cl_set_all_coredump_signal_handlers() to handle core dump + * signals and set their privileges to maximum before core + * dumping. See the comments in coredumps.c for more details. + */ + return rc; +} + +int /* Return to "nobody" level of privs (if any) */ +return_to_dropped_privs(void) +{ + int rc; + + if (!anysaveduid) { + return 0; + } + setegid(nobodygid); + privileged_state = 0; + rc = seteuid(nobodyuid); + /* See note above about dumping core */ + return rc; +} + +/* Return TRUE if we have full privileges at the moment */ +int +cl_have_full_privs(void) +{ + return privileged_state != 0; +} +#endif diff --git a/lib/lrm/Makefile.am b/lib/lrm/Makefile.am new file mode 100644 index 0000000..815f92f --- /dev/null +++ b/lib/lrm/Makefile.am @@ -0,0 +1,36 @@ +# +# Author: Sun Jiang Dong <sunjd@cn.ibm.com> +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + +lrmdir = $(localstatedir)/lib/heartbeat/lrm +COMMONLIBS = $(top_builddir)/lib/clplumbing/libplumb.la \ + $(GLIBLIB) + +lib_LTLIBRARIES = liblrm.la +liblrm_la_SOURCES = lrm_msg.c clientlib.c racommon.c +liblrm_la_LDFLAGS = -version-info 2:0:0 $(COMMONLIBS) +liblrm_la_CFLAGS = $(INCLUDES) + +install-exec-local: + $(mkinstalldirs) $(DESTDIR)$(lrmdir) + -chgrp $(GLUE_DAEMON_GROUP) $(DESTDIR)/$(lrmdir) + chmod 770 $(DESTDIR)/$(lrmdir) diff --git a/lib/lrm/clientlib.c b/lib/lrm/clientlib.c new file mode 100644 index 0000000..78dcdc8 --- /dev/null +++ b/lib/lrm/clientlib.c @@ -0,0 +1,1612 @@ +/* + * Client Library for Local Resource Manager API. + * + * Author: Huang Zhen <zhenh@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <lha_internal.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> + +#include <glib.h> +#include <clplumbing/ipc.h> +#include <ha_msg.h> +#include <lrm/lrm_api.h> + +#include <lrm/lrm_msg.h> + +/* FIXME: Notice: this define should be replaced when merge to the whole pkg*/ +#define LRM_MAXPIDLEN 256 +#define LRM_ID "lrm" + +#define LOG_FAIL_create_lrm_msg(msg_type) \ + cl_log(LOG_ERR, "%s(%d): failed to create a %s message with " \ + "function create_lrm_msg." \ + , __FUNCTION__, __LINE__, msg_type) + +#define LOG_FAIL_create_lrm_rsc_msg(msg_type) \ + cl_log(LOG_ERR, "%s(%d): failed to create a %s message with " \ + "function create_lrm_rsc_msg." \ + , __FUNCTION__, __LINE__, msg_type) + +#define LOG_FAIL_receive_reply(msg_type) \ + cl_log(LOG_ERR, "%s(%d): failed to receive a reply message of %s." \ + , __FUNCTION__, __LINE__, msg_type) + +#define LOG_FAIL_SEND_MSG(msg_type, chan_name) \ + cl_log(LOG_ERR, "%s(%d): failed to send a %s message to lrmd " \ + "via %s channel." \ + , __FUNCTION__, __LINE__, msg_type, chan_name) + +#define LOG_GOT_FAIL_RET(priority, msg_type) \ + cl_log(priority, "%s(%d): got a return code HA_FAIL from " \ + "a reply message of %s with function get_ret_from_msg." \ + , __FUNCTION__, __LINE__, msg_type) + +#define LOG_BASIC_ERROR(apiname) \ + cl_log(LOG_ERR, "%s(%d): %s failed." \ + , __FUNCTION__, __LINE__, apiname) + +#define LOG_FAIL_GET_MSG_FIELD(priority, field_name, msg) \ + {cl_log(priority, "%s(%d): failed to get the value " \ + "of field %s from a ha_msg" \ + , __FUNCTION__, __LINE__, field_name); \ + cl_log(LOG_INFO, "%s: Message follows:", __FUNCTION__); \ + cl_log_message(LOG_INFO, (msg)); \ + } + +/* declare the functions used by the lrm_ops structure*/ +static int lrm_signon (ll_lrm_t* lrm, const char * app_name); +static int lrm_signoff (ll_lrm_t*); +static int lrm_delete (ll_lrm_t*); +static int lrm_set_lrm_callback (ll_lrm_t* lrm, + lrm_op_done_callback_t op_done_callback_func); +static GList* lrm_get_rsc_class_supported (ll_lrm_t* lrm); +static GList* lrm_get_rsc_type_supported (ll_lrm_t* lrm, const char* class); +static GList* lrm_get_rsc_provider_supported (ll_lrm_t* lrm + ,const char* class, const char* type); +static char* lrm_get_rsc_type_metadata(ll_lrm_t* lrm, const char* class + ,const char* type, const char* provider); +static GHashTable* lrm_get_all_type_metadata(ll_lrm_t*, const char* class); +static GList* lrm_get_all_rscs (ll_lrm_t* lrm); +static lrm_rsc_t* lrm_get_rsc (ll_lrm_t* lrm, const char* rsc_id); +static int lrm_add_rsc (ll_lrm_t*, const char* id, const char* class + ,const char* type, const char* provider + ,GHashTable* parameter); +static int lrm_delete_rsc (ll_lrm_t*, const char* id); +static int lrm_fail_rsc (ll_lrm_t* lrm, const char* rsc_id, const int fail_rc + ,const char* fail_reason); +static int lrm_set_lrmd_param (ll_lrm_t* lrm, const char* name, const char *value); +static char* lrm_get_lrmd_param (ll_lrm_t* lrm, const char* name); +static IPC_Channel* lrm_ipcchan (ll_lrm_t*); +static int lrm_msgready (ll_lrm_t*); +static int lrm_rcvmsg (ll_lrm_t*, int blocking); +static struct lrm_ops lrm_ops_instance = +{ + lrm_signon, + lrm_signoff, + lrm_delete, + lrm_set_lrm_callback, + lrm_set_lrmd_param, + lrm_get_lrmd_param, + lrm_get_rsc_class_supported, + lrm_get_rsc_type_supported, + lrm_get_rsc_provider_supported, + lrm_get_rsc_type_metadata, + lrm_get_all_type_metadata, + lrm_get_all_rscs, + lrm_get_rsc, + lrm_add_rsc, + lrm_delete_rsc, + lrm_fail_rsc, + lrm_ipcchan, + lrm_msgready, + lrm_rcvmsg +}; +/* declare the functions used by the lrm_rsc_ops structure*/ +static int rsc_perform_op (lrm_rsc_t*, lrm_op_t* op); +static int rsc_cancel_op (lrm_rsc_t*, int call_id); +static int rsc_flush_ops (lrm_rsc_t*); +static GList* rsc_get_cur_state (lrm_rsc_t*, state_flag_t* cur_state); +static lrm_op_t* rsc_get_last_result (lrm_rsc_t*, const char* op_type); +static gint compare_call_id(gconstpointer a, gconstpointer b); + +static struct rsc_ops rsc_ops_instance = +{ + rsc_perform_op, + rsc_cancel_op, + rsc_flush_ops, + rsc_get_cur_state, + rsc_get_last_result +}; + + +/* define the internal data used by the client library*/ +static int is_signed_on = FALSE; +static IPC_Channel* ch_cmd = NULL; +static IPC_Channel* ch_cbk = NULL; +static lrm_op_done_callback_t op_done_callback = NULL; + +/* define some utility functions*/ +static int get_ret_from_ch(IPC_Channel* ch); +static int get_ret_from_msg(struct ha_msg* msg); +static struct ha_msg* op_to_msg (lrm_op_t* op); +static lrm_op_t* msg_to_op(struct ha_msg* msg); +static void free_op (lrm_op_t* op); + +/* define of the api functions*/ +ll_lrm_t* +ll_lrm_new (const char * llctype) +{ + ll_lrm_t* lrm; + + /* check the parameter*/ + if (0 != STRNCMP_CONST(llctype, LRM_ID)) { + cl_log(LOG_ERR, "ll_lrm_new: wrong parameter"); + return NULL; + } + + /* alloc memory for lrm*/ + if (NULL == (lrm = (ll_lrm_t*) g_new(ll_lrm_t,1))) { + cl_log(LOG_ERR, "ll_lrm_new: can not allocate memory"); + return NULL; + } + /* assign the ops*/ + lrm->lrm_ops = &lrm_ops_instance; + + return lrm; +} + +static int +lrm_signon (ll_lrm_t* lrm, const char * app_name) +{ + + GHashTable* ch_cmd_attrs; + GHashTable* ch_cbk_attrs; + + struct ha_msg* msg; + + char path[] = IPC_PATH_ATTR; + char cmd_path[] = LRM_CMDPATH; + char callback_path[] = LRM_CALLBACKPATH; + + /* check parameters*/ + if (NULL == lrm || NULL == app_name) { + cl_log(LOG_ERR, "lrm_signon: wrong parameter"); + return HA_FAIL; + } + + /* if already signed on, sign off first*/ + if (is_signed_on) { + cl_log(LOG_WARNING, + "lrm_signon: the client is alreay signed on, re-sign"); + lrm_signoff(lrm); + } + + /* create the command ipc channel to lrmd*/ + ch_cmd_attrs = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(ch_cmd_attrs, path, cmd_path); + ch_cmd = ipc_channel_constructor(IPC_ANYTYPE, ch_cmd_attrs); + g_hash_table_destroy(ch_cmd_attrs); + + if (NULL == ch_cmd){ + lrm_signoff(lrm); + cl_log(LOG_WARNING, + "lrm_signon: can not connect to lrmd for cmd channel"); + return HA_FAIL; + } + + if (IPC_OK != ch_cmd->ops->initiate_connection(ch_cmd)) { + lrm_signoff(lrm); + cl_log(LOG_WARNING, + "lrm_signon: can not initiate connection"); + return HA_FAIL; + } + + /* construct the reg msg*/ + if (NULL == (msg = create_lrm_reg_msg(app_name))) { + lrm_signoff(lrm); + cl_log(LOG_ERR,"lrm_signon: failed to create a register message"); + return HA_FAIL; + } + + /* send the msg*/ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + lrm_signoff(lrm); + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(REGISTER, "ch_cmd"); + return HA_FAIL; + } + /* parse the return msg*/ + if (HA_OK != get_ret_from_ch(ch_cmd)) { + ha_msg_del(msg); + lrm_signoff(lrm); + LOG_FAIL_receive_reply(REGISTER); + return HA_FAIL; + } + + /* create the callback ipc channel to lrmd*/ + ch_cbk_attrs = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(ch_cbk_attrs, path, callback_path); + ch_cbk = ipc_channel_constructor(IPC_ANYTYPE,ch_cbk_attrs); + g_hash_table_destroy(ch_cbk_attrs); + + if (NULL == ch_cbk) { + ha_msg_del(msg); + lrm_signoff(lrm); + cl_log(LOG_ERR, "lrm_signon: failed to construct a callback " + "channel to lrmd"); + return HA_FAIL; + } + + if (IPC_OK != ch_cbk->ops->initiate_connection(ch_cbk)) { + ha_msg_del(msg); + lrm_signoff(lrm); + cl_log(LOG_ERR, + "lrm_signon: failed to initiate the callback channel."); + return HA_FAIL; + } + /* send the msg*/ + if (HA_OK != msg2ipcchan(msg,ch_cbk)) { + lrm_signoff(lrm); + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(REGISTER, "ch_cbk"); + return HA_FAIL; + } + ha_msg_del(msg); + /* parse the return msg*/ + if (HA_OK != get_ret_from_ch(ch_cbk)) { + lrm_signoff(lrm); + LOG_FAIL_receive_reply(REGISTER); + return HA_FAIL; + } + /* ok, we sign on sucessfully now*/ + is_signed_on = TRUE; + return HA_OK; +} + +static int +lrm_signoff (ll_lrm_t* lrm) +{ + /* close channels */ + if (NULL != ch_cmd) { + if (IPC_ISWCONN(ch_cmd)) { + ch_cmd->ops->destroy(ch_cmd); + } + ch_cmd = NULL; + } + if (NULL != ch_cbk) { + if (IPC_ISWCONN(ch_cbk)) { + ch_cbk->ops->destroy(ch_cbk); + } + ch_cbk = NULL; + } + is_signed_on = FALSE; + + return HA_OK; +} + +static int +lrm_delete (ll_lrm_t* lrm) +{ + /* check the parameter */ + if (NULL == lrm) { + cl_log(LOG_ERR,"lrm_delete: the parameter is a null pointer."); + return HA_FAIL; + } + g_free(lrm); + + return HA_OK; +} + +static int +lrm_set_lrm_callback (ll_lrm_t* lrm, + lrm_op_done_callback_t op_done_callback_func) + +{ + op_done_callback = op_done_callback_func; + + return HA_OK; +} + +static GList* +lrm_get_rsc_class_supported (ll_lrm_t* lrm) +{ + struct ha_msg* msg; + struct ha_msg* ret; + GList* class_list = NULL; + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) + { + cl_log(LOG_ERR, + "lrm_get_rsc_class_supported: ch_cmd is a null pointer."); + return NULL; + } + /* create the get ra type message */ + msg = create_lrm_msg(GETRSCCLASSES); + if ( NULL == msg) { + LOG_FAIL_create_lrm_msg(GETRSCCLASSES); + return NULL; + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETRSCCLASSES, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return message */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETRSCCLASSES); + return NULL; + } + /* get the return code of the message */ + if (HA_OK != get_ret_from_msg(ret)) { + LOG_GOT_FAIL_RET(LOG_WARNING, GETRSCCLASSES); + ha_msg_del(ret); + return NULL; + } + /* get the ra type list from message */ + class_list = ha_msg_value_str_list(ret,F_LRM_RCLASS); + + ha_msg_del(ret); + + return class_list; +} +static GList* +lrm_get_rsc_type_supported (ll_lrm_t* lrm, const char* rclass) +{ + struct ha_msg* msg; + struct ha_msg* ret; + GList* type_list = NULL; + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) + { + cl_log(LOG_ERR, "%s(%d): ch_cmd is null." + , __FUNCTION__, __LINE__); + + return NULL; + } + /* create the get ra type message */ + msg = create_lrm_msg(GETRSCTYPES); + if ( NULL == msg) { + LOG_FAIL_create_lrm_msg(GETRSCTYPES); + return NULL; + } + if ( HA_OK != ha_msg_add(msg, F_LRM_RCLASS, rclass)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETRSCTYPES, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return message */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETRSCTYPES); + return NULL; + } + /* get the return code of the message */ + if (HA_OK != get_ret_from_msg(ret)) { + LOG_GOT_FAIL_RET(LOG_ERR, GETRSCTYPES); + ha_msg_del(ret); + return NULL; + } + /* get the ra type list from message */ + type_list = ha_msg_value_str_list(ret,F_LRM_RTYPES); + + ha_msg_del(ret); + + return type_list; +} +static GList* +lrm_get_rsc_provider_supported (ll_lrm_t* lrm, const char* class, const char* type) +{ + struct ha_msg* msg; + struct ha_msg* ret; + GList* provider_list = NULL; + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) + { + cl_log(LOG_ERR, + "lrm_get_rsc_provider_supported: ch_mod is null."); + return NULL; + } + /* create the get ra providers message */ + msg = create_lrm_msg(GETPROVIDERS); + if ( NULL == msg) { + LOG_FAIL_create_lrm_msg(GETPROVIDERS); + return NULL; + } + if (HA_OK != ha_msg_add(msg, F_LRM_RCLASS, class) + || HA_OK != ha_msg_add(msg, F_LRM_RTYPE, type)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETPROVIDERS, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return message */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETPROVIDERS); + return NULL; + } + /* get the return code of the message */ + if (HA_OK != get_ret_from_msg(ret)) { + LOG_GOT_FAIL_RET(LOG_ERR, GETPROVIDERS); + ha_msg_del(ret); + return NULL; + } + /* get the ra provider list from message */ + provider_list = ha_msg_value_str_list(ret,F_LRM_RPROVIDERS); + + ha_msg_del(ret); + + return provider_list; +} + +/* + * lrm_get_all_type_metadatas(): + * The key of the hash table is in the format "type:provider" + * The value of the hash table is the metadata. + */ +static GHashTable* +lrm_get_all_type_metadata (ll_lrm_t* lrm, const char* rclass) +{ + GHashTable* metas = g_hash_table_new_full(g_str_hash, g_str_equal + , g_free, g_free); + GList* types = lrm_get_rsc_type_supported (lrm, rclass); + GList* providers = NULL; + GList* cur_type = NULL; + GList* cur_provider = NULL; + + cur_type = g_list_first(types); + while (cur_type != NULL) + { + const char* type; + char key[MAXLENGTH]; + type = (const char*) cur_type->data; + providers = lrm_get_rsc_provider_supported(lrm, rclass, type); + cur_provider = g_list_first(providers); + while (cur_provider != NULL) { + const char* meta; + const char* provider; + provider = (const char*) cur_provider->data; + meta = lrm_get_rsc_type_metadata(lrm,rclass,type,provider); + if (NULL == meta) { + cur_provider = g_list_next(cur_provider); + continue; + } + snprintf(key,MAXLENGTH, "%s:%s",type,provider); + key[MAXLENGTH-1]='\0'; + g_hash_table_insert(metas,g_strdup(key),g_strdup(meta)); + cur_provider = g_list_next(cur_provider); + } + lrm_free_str_list(providers); + cur_type=g_list_next(cur_type); + } + lrm_free_str_list(types); + return metas; +} + +static char* +lrm_get_rsc_type_metadata (ll_lrm_t* lrm, const char* rclass, const char* rtype, + const char* provider) +{ + struct ha_msg* msg; + struct ha_msg* ret; + const char* tmp = NULL; + char* metadata = NULL; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) + { + cl_log(LOG_ERR, + "lrm_get_rsc_type_metadata: ch_mod is null."); + return NULL; + } + /* create the get ra type message */ + msg = create_lrm_msg(GETRSCMETA); + if (NULL == msg ) { + LOG_FAIL_create_lrm_msg(GETRSCMETA); + return NULL; + } + + if (HA_OK != ha_msg_add(msg, F_LRM_RCLASS, rclass) + || HA_OK != ha_msg_add(msg, F_LRM_RTYPE, rtype)){ + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + if( provider ) { + if (HA_OK != ha_msg_add(msg, F_LRM_RPROVIDER, provider)) { + LOG_BASIC_ERROR("ha_msg_add"); + ha_msg_del(msg); + return NULL; + } + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETRSCMETA, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return message */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETRSCMETA); + return NULL; + } + /* get the return code of the message */ + if (HA_OK != get_ret_from_msg(ret)) { + LOG_GOT_FAIL_RET(LOG_ERR, GETRSCMETA); + ha_msg_del(ret); + return NULL; + } + + /* get the metadata from message */ + tmp = cl_get_string(ret, F_LRM_METADATA); + if (NULL!=tmp) { + metadata = g_strdup(tmp); + } + ha_msg_del(ret); + + return metadata; +} + +static GList* +lrm_get_all_rscs (ll_lrm_t* lrm) +{ + struct ha_msg* msg = NULL; + struct ha_msg* ret = NULL; + GList* rid_list = NULL; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "lrm_get_all_rscs: ch_mod is null."); + return NULL; + } + /* create the msg of get all resource */ + msg = create_lrm_msg(GETALLRCSES); + if ( NULL == msg) { + LOG_FAIL_create_lrm_msg(GETALLRCSES); + return NULL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETALLRCSES, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return msg */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETALLRCSES); + return NULL; + } + /* get the return code of msg */ + if (HA_OK != get_ret_from_msg(ret)) { + LOG_GOT_FAIL_RET(LOG_ERR, GETALLRCSES); + ha_msg_del(ret); + return NULL; + } + /* get the rsc_id list from msg */ + rid_list = ha_msg_value_str_list(ret,F_LRM_RID); + + ha_msg_del(ret); + /* return the id list */ + return rid_list; + +} + +static lrm_rsc_t* +lrm_get_rsc (ll_lrm_t* lrm, const char* rsc_id) +{ + struct ha_msg* msg = NULL; + struct ha_msg* ret = NULL; + lrm_rsc_t* rsc = NULL; + + /* check whether the rsc_id is available */ + if (strlen(rsc_id) >= RID_LEN) { + cl_log(LOG_ERR, "lrm_get_rsc: rsc_id is too long."); + return NULL; + } + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "lrm_get_rsc: ch_mod is null."); + return NULL; + } + /* create the msg of get resource */ + msg = create_lrm_rsc_msg(rsc_id, GETRSC); + if ( NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(GETRSC); + return NULL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETRSC, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return msg from lrmd */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETRSC); + return NULL; + } + /* get the return code of return message */ + if (HA_OK != get_ret_from_msg(ret)) { + ha_msg_del(ret); + return NULL; + } + /* create a new resource structure */ + rsc = g_new(lrm_rsc_t, 1); + + /* fill the field of resource with the data from msg */ + rsc->id = g_strdup(ha_msg_value(ret, F_LRM_RID)); + rsc->type = g_strdup(ha_msg_value(ret, F_LRM_RTYPE)); + rsc->class = g_strdup(ha_msg_value(ret, F_LRM_RCLASS)); + rsc->provider = g_strdup(ha_msg_value(ret, F_LRM_RPROVIDER)); + rsc->params = ha_msg_value_str_table(ret,F_LRM_PARAM); + + rsc->ops = &rsc_ops_instance; + ha_msg_del(ret); + /* return the new resource */ + return rsc; +} + +static int +lrm_fail_rsc (ll_lrm_t* lrm, const char* rsc_id, const int fail_rc +, const char* fail_reason) +{ + struct ha_msg* msg; + + /* check whether the rsc_id is available */ + if (NULL == rsc_id || RID_LEN <= strlen(rsc_id)) { + cl_log(LOG_ERR, "%s: wrong parameter rsc_id.", __FUNCTION__); + return HA_FAIL; + } + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "%s: ch_mod is null.", __FUNCTION__); + return HA_FAIL; + } + + /* create the message */ + msg = create_lrm_rsc_msg(rsc_id,FAILRSC); + if (NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(FAILRSC); + return HA_FAIL; + } + if ((fail_reason && HA_OK != ha_msg_add(msg,F_LRM_FAIL_REASON,fail_reason)) + || HA_OK != ha_msg_add_int(msg, F_LRM_ASYNCMON_RC, fail_rc) + ) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return HA_FAIL; + } + /* send to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(FAILRSC, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + /* check the result */ + if (HA_OK != get_ret_from_ch(ch_cmd)) { + LOG_GOT_FAIL_RET(LOG_ERR, FAILRSC); + return HA_FAIL; + } + + return HA_OK; +} + +static int +lrm_set_lrmd_param(ll_lrm_t* lrm, const char* name, const char *value) +{ + struct ha_msg* msg; + + if (!name || !value) { + cl_log(LOG_ERR, "%s: no parameter name or value", __FUNCTION__); + return HA_FAIL; + } + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "%s: ch_mod is null.", __FUNCTION__); + return HA_FAIL; + } + + /* create the message */ + msg = create_lrm_msg(SETLRMDPARAM); + if (NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(SETLRMDPARAM); + return HA_FAIL; + } + if (HA_OK != ha_msg_add(msg,F_LRM_LRMD_PARAM_NAME,name) + || HA_OK != ha_msg_add(msg,F_LRM_LRMD_PARAM_VAL,value)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return HA_FAIL; + } + /* send to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(FAILRSC, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + /* check the result */ + if (HA_OK != get_ret_from_ch(ch_cmd)) { + LOG_GOT_FAIL_RET(LOG_ERR, FAILRSC); + return HA_FAIL; + } + + return HA_OK; +} + +static char* +lrm_get_lrmd_param (ll_lrm_t* lrm, const char *name) +{ + struct ha_msg* msg = NULL; + struct ha_msg* ret = NULL; + const char* value = NULL; + char* v2; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "lrm_get_rsc: ch_mod is null."); + return NULL; + } + /* create the msg of get resource */ + msg = create_lrm_msg(GETLRMDPARAM); + if ( NULL == msg) { + LOG_FAIL_create_lrm_msg(GETLRMDPARAM); + return NULL; + } + if (HA_OK != ha_msg_add(msg,F_LRM_LRMD_PARAM_NAME,name)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETLRMDPARAM, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return msg from lrmd */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETLRMDPARAM); + return NULL; + } + /* get the return code of return message */ + if (HA_OK != get_ret_from_msg(ret)) { + ha_msg_del(ret); + return NULL; + } + value = ha_msg_value(ret,F_LRM_LRMD_PARAM_VAL); + if (!value) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_LRMD_PARAM_VAL, ret); + ha_msg_del(ret); + return NULL; + } + v2 = g_strdup(value); + ha_msg_del(ret); + return v2; +} + +static int +lrm_add_rsc (ll_lrm_t* lrm, const char* rsc_id, const char* class +, const char* type, const char* provider, GHashTable* parameter) +{ + struct ha_msg* msg; + + /* check whether the rsc_id is available */ + if (NULL == rsc_id || RID_LEN <= strlen(rsc_id)) { + cl_log(LOG_ERR, "lrm_add_rsc: wrong parameter rsc_id."); + return HA_FAIL; + } + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "lrm_add_rsc: ch_mod is null."); + return HA_FAIL; + } + + /* create the message of add resource */ + msg = create_lrm_addrsc_msg(rsc_id, class, type, provider, parameter); + if ( NULL == msg) { + cl_log(LOG_ERR, "%s(%d): failed to create a ADDSRC message " + "with function create_lrm_addrsc_msg" + , __FUNCTION__, __LINE__); + return HA_FAIL; + } + /* send to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(ADDRSC, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + /* check the result */ + if (HA_OK != get_ret_from_ch(ch_cmd)) { + LOG_GOT_FAIL_RET(LOG_ERR, ADDRSC); + return HA_FAIL; + } + + return HA_OK; +} + +static int +lrm_delete_rsc (ll_lrm_t* lrm, const char* rsc_id) +{ + struct ha_msg* msg = NULL; + int rc; + + /* check whether the rsc_id is available */ + if (NULL == rsc_id || RID_LEN <= strlen(rsc_id)) { + cl_log(LOG_ERR, "lrm_delete_rsc: wrong parameter rsc_id."); + return HA_FAIL; + } + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "lrm_delete_rsc: ch_mod is null."); + return HA_FAIL; + } + + /* create the msg of del resource */ + msg = create_lrm_rsc_msg(rsc_id, DELRSC); + if ( NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(DELRSC); + return HA_FAIL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(DELRSC, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + /* check the response of the msg */ + rc = get_ret_from_ch(ch_cmd); + if (rc != HA_OK && rc != HA_RSCBUSY) { + LOG_GOT_FAIL_RET(LOG_ERR, DELRSC); + return HA_FAIL; + } + + return rc; +} + +static IPC_Channel* +lrm_ipcchan (ll_lrm_t* lrm) +{ + if (NULL == ch_cbk) { + cl_log(LOG_ERR, + "lrm_inputfd: callback channel is null."); + return NULL; + } + + return ch_cbk; +} + +static gboolean +lrm_msgready (ll_lrm_t* lrm) +{ + if (NULL == ch_cbk) { + cl_log(LOG_ERR, + "lrm_msgready: callback channel is null."); + return FALSE; + } + return ch_cbk->ops->is_message_pending(ch_cbk); +} + +static int +lrm_rcvmsg (ll_lrm_t* lrm, int blocking) +{ + struct ha_msg* msg = NULL; + lrm_op_t* op = NULL; + int msg_count = 0; + + /* if it is not blocking mode and no message in the channel, return */ + if ((!lrm_msgready(lrm)) && (!blocking)) { + cl_log(LOG_DEBUG, + "lrm_rcvmsg: no message and non-block."); + return msg_count; + } + /* wait until message ready */ + if (!lrm_msgready(lrm)) { + ch_cbk->ops->waitin(ch_cbk); + } + while (lrm_msgready(lrm)) { + if (ch_cbk->ch_status == IPC_DISCONNECT) { + return msg_count; + } + /* get the message */ + msg = msgfromIPC(ch_cbk, MSG_ALLOWINTR); + if (msg == NULL) { + cl_log(LOG_WARNING, + "%s(%d): receive a null message with msgfromIPC." + , __FUNCTION__, __LINE__); + return msg_count; + } + msg_count++; + + op = msg_to_op(msg); + if (NULL!=op && NULL!=op_done_callback) { + (*op_done_callback)(op); + } + free_op(op); + ha_msg_del(msg); + } + + return msg_count; +} + +/* following are the functions for rsc_ops */ +static int +rsc_perform_op (lrm_rsc_t* rsc, lrm_op_t* op) +{ + int rc = 0; + struct ha_msg* msg = NULL; + char* rsc_id; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd + || NULL == rsc + || NULL == rsc->id + || NULL == op + || NULL == op->op_type) { + cl_log(LOG_ERR, + "rsc_perform_op: wrong parameters."); + return HA_FAIL; + } + /* create the msg of perform op */ + rsc_id = op->rsc_id; + op->rsc_id = rsc->id; + msg = op_to_msg(op); + op->rsc_id = rsc_id; + if ( NULL == msg) { + cl_log(LOG_ERR, "rsc_perform_op: failed to create a message " + "with function op_to_msg"); + return HA_FAIL; + } + /* send it to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(PERFORMOP, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + + /* check return code, the return code is the call_id of the op */ + rc = get_ret_from_ch(ch_cmd); + return rc; +} + +static int +rsc_cancel_op (lrm_rsc_t* rsc, int call_id) +{ + int rc; + struct ha_msg* msg = NULL; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "rsc_cancel_op: ch_mod is null."); + return HA_FAIL; + } + /* check parameter */ + if (NULL == rsc) { + cl_log(LOG_ERR, "rsc_cancel_op: parameter rsc is null."); + return HA_FAIL; + } + /* create the msg of flush ops */ + msg = create_lrm_rsc_msg(rsc->id,CANCELOP); + if (NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(CANCELOP); + return HA_FAIL; + } + if (HA_OK != ha_msg_add_int(msg, F_LRM_CALLID, call_id)) { + LOG_BASIC_ERROR("ha_msg_add_int"); + ha_msg_del(msg); + return HA_FAIL; + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(CANCELOP, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + + rc = get_ret_from_ch(ch_cmd); + + return rc; +} + +static int +rsc_flush_ops (lrm_rsc_t* rsc) +{ + int rc; + struct ha_msg* msg = NULL; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "rsc_flush_ops: ch_mod is null."); + return HA_FAIL; + } + /* check parameter */ + if (NULL == rsc) { + cl_log(LOG_ERR, "rsc_flush_ops: parameter rsc is null."); + return HA_FAIL; + } + /* create the msg of flush ops */ + msg = create_lrm_rsc_msg(rsc->id,FLUSHOPS); + if ( NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(CANCELOP); + return HA_FAIL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(FLUSHOPS, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + + rc = get_ret_from_ch(ch_cmd); + + return rc>0?rc:HA_FAIL; +} +static gint +compare_call_id(gconstpointer a, gconstpointer b) +{ + const lrm_op_t* opa = (const lrm_op_t*)a; + const lrm_op_t* opb = (const lrm_op_t*)b; + return opa->call_id - opb->call_id; +} +static GList* +rsc_get_cur_state (lrm_rsc_t* rsc, state_flag_t* cur_state) +{ + GList* op_list = NULL, * tmplist = NULL; + struct ha_msg* msg = NULL; + struct ha_msg* ret = NULL; + struct ha_msg* op_msg = NULL; + lrm_op_t* op = NULL; + int state; + int op_count, i; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "rsc_get_cur_state: ch_mod is null."); + return NULL; + } + /* check paramter */ + if (NULL == rsc) { + cl_log(LOG_ERR, "rsc_get_cur_state: parameter rsc is null."); + return NULL; + } + /* create the msg of get current state of resource */ + msg = create_lrm_rsc_msg(rsc->id,GETRSCSTATE); + if ( NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(GETRSCSTATE); + return NULL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETRSCSTATE, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + + /* get the return msg */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETRSCSTATE); + return NULL; + } + + /* get the state of the resource from the message */ + if (HA_OK != ha_msg_value_int(ret, F_LRM_STATE, &state)) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_STATE, ret); + ha_msg_del(ret); + return NULL; + } + *cur_state = (state_flag_t)state; + /* the first msg includes the count of pending ops. */ + if (HA_OK != ha_msg_value_int(ret, F_LRM_OPCNT, &op_count)) { + LOG_FAIL_GET_MSG_FIELD(LOG_WARNING, F_LRM_OPCNT, ret); + ha_msg_del(ret); + return NULL; + } + ha_msg_del(ret); + for (i = 0; i < op_count; i++) { + /* one msg for one op */ + op_msg = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + + if (NULL == op_msg) { + cl_log(LOG_WARNING, "%s(%d): failed to receive a " + "(pending operation) message from lrmd." + , __FUNCTION__, __LINE__); + continue; + } + op = msg_to_op(op_msg); + /* add msg to the return list */ + + if (NULL != op) { + op_list = g_list_append(op_list, op); + } + else { + cl_log(LOG_WARNING, "%s(%d): failed to make a operation " + "from a message with function msg_to_op" + , __FUNCTION__, __LINE__); + } + ha_msg_del(op_msg); + } + op_list = g_list_sort(op_list, compare_call_id); + + /* Delete the duplicate op for call_id */ +#if 0 + cl_log(LOG_WARNING, "Before uniquing"); + tmplist = g_list_first(op_list); + while (tmplist != NULL) { + cl_log(LOG_WARNING, "call_id=%d", ((lrm_op_t*)(tmplist->data))->call_id); + tmplist = g_list_next(tmplist); + } +#endif + + tmplist = g_list_first(op_list); + while (tmplist != NULL) { + if (NULL != g_list_previous(tmplist)) { + if (((lrm_op_t*)(g_list_previous(tmplist)->data))->call_id + == ((lrm_op_t*)(tmplist->data))->call_id) { + op_list = g_list_remove_link (op_list, tmplist); + free_op((lrm_op_t *)tmplist->data); + g_list_free_1(tmplist); + tmplist = g_list_first(op_list); + } + } + tmplist = g_list_next(tmplist); + } + +#if 0 + cl_log(LOG_WARNING, "After uniquing"); + while (tmplist != NULL) { + cl_log(LOG_WARNING, "call_id=%d", ((lrm_op_t*)(tmplist->data))->call_id); + tmplist = g_list_next(tmplist); + } +#endif + + return op_list; +} + +static lrm_op_t* +rsc_get_last_result (lrm_rsc_t* rsc, const char* op_type) +{ + struct ha_msg* msg = NULL; + struct ha_msg* ret = NULL; + lrm_op_t* op = NULL; + int opcount = 0; + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "rsc_get_last_result: ch_mod is null."); + return NULL; + } + /* check parameter */ + if (NULL == rsc) { + cl_log(LOG_ERR, "rsc_get_last_result: parameter rsc is null."); + return NULL; + } + /* create the msg of get last op */ + msg = create_lrm_rsc_msg(rsc->id,GETLASTOP); + if (NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(GETLASTOP); + return NULL; + } + if (HA_OK != ha_msg_add(msg, F_LRM_RID, rsc->id)) { + LOG_BASIC_ERROR("ha_msg_add"); + ha_msg_del(msg); + return NULL; + } + if (HA_OK != ha_msg_add(msg, F_LRM_OP, op_type)) { + LOG_BASIC_ERROR("ha_msg_add"); + ha_msg_del(msg); + return NULL; + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETLASTOP, "ch_cmd"); + return NULL; + } + + /* get the return msg */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETLASTOP); + ha_msg_del(msg); + return NULL; + } + if (HA_OK != ha_msg_value_int(ret,F_LRM_OPCNT, &opcount)) { + op = NULL; + } + else if ( 1 == opcount ) { + op = msg_to_op(ret); + } + ha_msg_del(msg); + ha_msg_del(ret); + return op; +} +/* + * following are the implements of the utility functions + */ +lrm_op_t* +lrm_op_new(void) +{ + lrm_op_t* op; + + op = g_new0(lrm_op_t, 1); + op->op_status = LRM_OP_PENDING; + return op; +} + +static lrm_op_t* +msg_to_op(struct ha_msg* msg) +{ + lrm_op_t* op; + const char* op_type; + const char* app_name; + const char* rsc_id; + const char* fail_reason; + const char* output; + const void* user_data; + + op = lrm_op_new(); + + /* op->timeout, op->interval, op->target_rc, op->call_id*/ + if (HA_OK != ha_msg_value_int(msg,F_LRM_TIMEOUT, &op->timeout) + || HA_OK != ha_msg_value_int(msg,F_LRM_INTERVAL, &op->interval) + || HA_OK != ha_msg_value_int(msg,F_LRM_TARGETRC, &op->target_rc) + || HA_OK != ha_msg_value_int(msg,F_LRM_DELAY, &op->start_delay) + || HA_OK != ha_msg_value_int(msg,F_LRM_CALLID, &op->call_id)) { + LOG_BASIC_ERROR("ha_msg_value_int"); + free_op(op); + return NULL; + } + + /* op->op_status */ + if (HA_OK != + ha_msg_value_int(msg, F_LRM_OPSTATUS, (int*)&op->op_status)) { + LOG_FAIL_GET_MSG_FIELD(LOG_WARNING, F_LRM_OPSTATUS, msg); + op->op_status = LRM_OP_PENDING; + } + + /* if it finished successfully */ + if (LRM_OP_DONE == op->op_status ) { + /* op->rc */ + if (HA_OK != ha_msg_value_int(msg, F_LRM_RC, &op->rc)) { + free_op(op); + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_RC, msg); + return NULL; + } + /* op->output */ + output = cl_get_string(msg, F_LRM_DATA); + if (NULL != output){ + op->output = g_strdup(output); + } + else { + op->output = NULL; + } + } else if(op->op_status == LRM_OP_PENDING) { + op->rc = EXECRA_STATUS_UNKNOWN; + + } else { + op->rc = EXECRA_EXEC_UNKNOWN_ERROR; + } + + + /* op->app_name */ + app_name = ha_msg_value(msg, F_LRM_APP); + if (NULL == app_name) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_APP, msg); + free_op(op); + return NULL; + } + op->app_name = g_strdup(app_name); + + + /* op->op_type */ + op_type = ha_msg_value(msg, F_LRM_OP); + if (NULL == op_type) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_OP, msg); + free_op(op); + return NULL; + } + op->op_type = g_strdup(op_type); + + /* op->rsc_id */ + rsc_id = ha_msg_value(msg, F_LRM_RID); + if (NULL == rsc_id) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_RID, msg); + free_op(op); + return NULL; + } + op->rsc_id = g_strdup(rsc_id); + + /* op->fail_reason present only on async failures */ + fail_reason = ha_msg_value(msg, F_LRM_FAIL_REASON); + if (fail_reason) { + op->fail_reason = g_strdup(fail_reason); + } + + /* op->user_data */ + user_data = cl_get_string(msg, F_LRM_USERDATA); + + if (NULL != user_data) { + op->user_data = g_strdup(user_data); + } + + /* time_stamps */ + if (ha_msg_value_ul(msg, F_LRM_T_RUN, &op->t_run) != HA_OK + || ha_msg_value_ul(msg, F_LRM_T_RCCHANGE, &op->t_rcchange) != HA_OK + || ha_msg_value_ul(msg, F_LRM_EXEC_TIME, &op->exec_time) != HA_OK + || ha_msg_value_ul(msg, F_LRM_QUEUE_TIME, &op->queue_time) != HA_OK) { + /* cl_log(LOG_WARNING + , "%s:%d: failed to get the timing information" + , __FUNCTION__, __LINE__); + */ + } + + /* op->params */ + op->params = ha_msg_value_str_table(msg, F_LRM_PARAM); + + ha_msg_value_int(msg, F_LRM_RSCDELETED, &op->rsc_deleted); + + return op; +} + +static struct ha_msg* +op_to_msg (lrm_op_t* op) +{ + struct ha_msg* msg = ha_msg_new(15); + if (!msg) { + LOG_BASIC_ERROR("ha_msg_new"); + return NULL; + } + + if (HA_OK != ha_msg_add(msg, F_LRM_TYPE, PERFORMOP) + || HA_OK != ha_msg_add(msg, F_LRM_RID, op->rsc_id) + || HA_OK != ha_msg_add(msg, F_LRM_OP, op->op_type) + || HA_OK != ha_msg_add_int(msg, F_LRM_TIMEOUT, op->timeout) + || HA_OK != ha_msg_add_int(msg, F_LRM_INTERVAL, op->interval) + || HA_OK != ha_msg_add_int(msg, F_LRM_DELAY, op->start_delay) + || HA_OK != ha_msg_add_int(msg, F_LRM_COPYPARAMS, op->copyparams) + || HA_OK != ha_msg_add_ul(msg, F_LRM_T_RUN,op->t_run) + || HA_OK != ha_msg_add_ul(msg, F_LRM_T_RCCHANGE, op->t_rcchange) + || HA_OK != ha_msg_add_ul(msg, F_LRM_EXEC_TIME, op->exec_time) + || HA_OK != ha_msg_add_ul(msg, F_LRM_QUEUE_TIME, op->queue_time) + || HA_OK != ha_msg_add_int(msg, F_LRM_TARGETRC, op->target_rc) + || ( op->app_name && (HA_OK != ha_msg_add(msg, F_LRM_APP, op->app_name))) + || ( op->user_data && (HA_OK != ha_msg_add(msg,F_LRM_USERDATA,op->user_data))) + || ( op->params && (HA_OK != ha_msg_add_str_table(msg,F_LRM_PARAM,op->params)))) { + LOG_BASIC_ERROR("op_to_msg conversion failed"); + ha_msg_del(msg); + return NULL; + } + + return msg; +} + +static int +get_ret_from_ch(IPC_Channel* ch) +{ + int ret; + struct ha_msg* msg; + + msg = msgfromIPC(ch, MSG_ALLOWINTR); + + if (NULL == msg) { + cl_log(LOG_ERR + , "%s(%d): failed to receive message with function msgfromIPC" + , __FUNCTION__, __LINE__); + return HA_FAIL; + } + if (HA_OK != ha_msg_value_int(msg, F_LRM_RET, &ret)) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_RET, msg); + ha_msg_del(msg); + return HA_FAIL; + } + ha_msg_del(msg); + return ret; +} + +static int +get_ret_from_msg(struct ha_msg* msg) +{ + int ret; + + if (NULL == msg) { + cl_log(LOG_ERR, "%s(%d): the parameter is a NULL pointer." + , __FUNCTION__, __LINE__); + return HA_FAIL; + } + if (HA_OK != ha_msg_value_int(msg, F_LRM_RET, &ret)) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_RET, msg); + return HA_FAIL; + } + return ret; +} +static void +free_op (lrm_op_t* op) +{ + if (NULL == op) { + return; + } + if (NULL != op->op_type) { + g_free(op->op_type); + } + if (NULL != op->output) { + g_free(op->output); + } + if (NULL != op->rsc_id) { + g_free(op->rsc_id); + } + if (NULL != op->app_name) { + g_free(op->app_name); + } + if (NULL != op->user_data) { + g_free(op->user_data); + } + if (NULL != op->params) { + free_str_table(op->params); + } + g_free(op); +} + +void lrm_free_op(lrm_op_t* op) { + free_op(op); +} +void lrm_free_rsc(lrm_rsc_t* rsc) { + if (NULL == rsc) { + return; + } + if (NULL != rsc->id) { + g_free(rsc->id); + } + if (NULL != rsc->type) { + g_free(rsc->type); + } + if (NULL != rsc->class) { + g_free(rsc->class); + } + if (NULL != rsc->provider) { + g_free(rsc->provider); + } + if (NULL != rsc->params) { + free_str_table(rsc->params); + } + g_free(rsc); +} +void lrm_free_str_list(GList* list) { + GList* item; + if (NULL == list) { + return; + } + item = g_list_first(list); + while (NULL != item) { + if (NULL != item->data) { + g_free(item->data); + } + list = g_list_delete_link(list, item); + item = g_list_first(list); + } +} +void lrm_free_op_list(GList* list) { + GList* item; + if (NULL == list) { + return; + } + item = g_list_first(list); + while (NULL != item) { + if (NULL != item->data) { + free_op((lrm_op_t*)item->data); + } + list = g_list_delete_link(list, item); + item = g_list_first(list); + } +} +void lrm_free_str_table(GHashTable* table) { + if (NULL != table) { + free_str_table(table); + } +} + +const char * +execra_code2string(uniform_ret_execra_t code) +{ + switch(code) { + case EXECRA_EXEC_UNKNOWN_ERROR: + return "unknown exec error"; + case EXECRA_NO_RA: + return "no RA"; + case EXECRA_OK: + return "ok"; + case EXECRA_UNKNOWN_ERROR: + return "unknown error"; + case EXECRA_INVALID_PARAM: + return "invalid parameter"; + case EXECRA_UNIMPLEMENT_FEATURE: + return "unimplemented feature"; + case EXECRA_INSUFFICIENT_PRIV: + return "insufficient privileges"; + case EXECRA_NOT_INSTALLED: + return "not installed"; + case EXECRA_NOT_CONFIGURED: + return "not configured"; + case EXECRA_NOT_RUNNING: + return "not running"; + /* For status command only */ + case EXECRA_RUNNING_MASTER: + return "master"; + case EXECRA_FAILED_MASTER: + return "master (failed)"; + case EXECRA_RA_DEAMON_DEAD1: + return "status: daemon dead"; + case EXECRA_RA_DEAMON_DEAD2: + return "status: daemon dead"; + case EXECRA_RA_DEAMON_STOPPED: + return "status: daemon stopped"; + case EXECRA_STATUS_UNKNOWN: + return "status: unknown"; + default: + break; + } + + return "<unknown>"; +} diff --git a/lib/lrm/lrm_msg.c b/lib/lrm/lrm_msg.c new file mode 100644 index 0000000..fdd3b3f --- /dev/null +++ b/lib/lrm/lrm_msg.c @@ -0,0 +1,212 @@ +/* + * Message Functions For Local Resource Manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * By Huang Zhen <zhenh@cn.ibm.com> 2004/2/13 + * + */ +#include <lha_internal.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <clplumbing/cl_log.h> +#include <ha_msg.h> +#include <lrm/lrm_api.h> +#include <lrm/lrm_msg.h> +#define LOG_BASIC_ERROR(apiname) \ + cl_log(LOG_ERR, "%s(%d): %s failed.", __FUNCTION__, __LINE__, apiname) + +const lrm_op_t lrm_zero_op; /* Default initialized to zeros */ + +static void +copy_pair(gpointer key, gpointer value, gpointer user_data) +{ + GHashTable* taget_table = (GHashTable*)user_data; + g_hash_table_insert(taget_table, g_strdup(key), g_strdup(value)); +} + +GHashTable* +copy_str_table(GHashTable* src_table) +{ + GHashTable* target_table = NULL; + + if ( NULL == src_table) { + return NULL; + } + target_table = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_foreach(src_table, copy_pair, target_table); + return target_table; +} + +static void +merge_pair(gpointer key, gpointer value, gpointer user_data) +{ + GHashTable *merged = (GHashTable*)user_data; + + if (g_hash_table_lookup(merged, key)) { + return; + } + + g_hash_table_insert(merged, g_strdup(key), g_strdup(value)); +} + +GHashTable* +merge_str_tables(GHashTable* old, GHashTable* new) +{ + GHashTable* merged = NULL; + if ( NULL == old ) { + return copy_str_table(new); + } + if ( NULL == new ) { + return copy_str_table(old); + } + merged = copy_str_table(new); + g_hash_table_foreach(old, merge_pair, merged); + return merged; +} + +static gboolean +free_pair(gpointer key, gpointer value, gpointer user_data) +{ + g_free(key); + g_free(value); + return TRUE; +} + +void +free_str_table(GHashTable* hash_table) +{ + g_hash_table_foreach_remove(hash_table, free_pair, NULL); + g_hash_table_destroy(hash_table); +} + + + +struct ha_msg* +create_lrm_msg (const char* msg) +{ + struct ha_msg* ret; + if ((NULL == msg) || (0 == strlen(msg))) { + return NULL; + } + + ret = ha_msg_new(1); + if (HA_OK != ha_msg_add(ret, F_LRM_TYPE, msg)) { + ha_msg_del(ret); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + return ret; +} + +struct ha_msg* +create_lrm_reg_msg(const char* app_name) +{ + struct ha_msg* ret; + if ((NULL == app_name) || (0 == strlen(app_name))) { + return NULL; + } + + ret = ha_msg_new(5); + + if(HA_OK != ha_msg_add(ret, F_LRM_TYPE, REGISTER) + || HA_OK != ha_msg_add(ret, F_LRM_APP, app_name) + || HA_OK != ha_msg_add_int(ret, F_LRM_PID, getpid()) + || HA_OK != ha_msg_add_int(ret, F_LRM_GID, getegid()) + || HA_OK != ha_msg_add_int(ret, F_LRM_UID, getuid())) { + ha_msg_del(ret); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + return ret; +} + +struct ha_msg* +create_lrm_addrsc_msg(const char* rid, const char* class, const char* type, + const char* provider, GHashTable* params) +{ + struct ha_msg* msg; + if (NULL==rid||NULL==class||NULL==type) { + return NULL; + } + + msg = ha_msg_new(5); + if(HA_OK != ha_msg_add(msg, F_LRM_TYPE, ADDRSC) + || HA_OK != ha_msg_add(msg, F_LRM_RID, rid) + || HA_OK != ha_msg_add(msg, F_LRM_RCLASS, class) + || HA_OK != ha_msg_add(msg, F_LRM_RTYPE, type)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + if( provider ) { + if (HA_OK != ha_msg_add(msg, F_LRM_RPROVIDER, provider)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + } + + if ( params ) { + if (HA_OK != ha_msg_add_str_table(msg,F_LRM_PARAM,params)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + } + return msg; +} + + +struct ha_msg* +create_lrm_rsc_msg(const char* rid, const char* msg) +{ + struct ha_msg* ret; + if ((NULL == rid) ||(NULL == msg) || (0 == strlen(msg))) { + return NULL; + } + + ret = ha_msg_new(2); + if(HA_OK != ha_msg_add(ret, F_LRM_TYPE, msg) + || HA_OK != ha_msg_add(ret, F_LRM_RID, rid)) { + ha_msg_del(ret); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + return ret; +} + + + +struct ha_msg* +create_lrm_ret(int ret, int fields) +{ + struct ha_msg* msg = ha_msg_new(fields); + if(HA_OK != ha_msg_add(msg, F_LRM_TYPE, RETURN) + || HA_OK != ha_msg_add_int(msg, F_LRM_RET, ret)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + return msg; +} + diff --git a/lib/lrm/racommon.c b/lib/lrm/racommon.c new file mode 100644 index 0000000..2670f05 --- /dev/null +++ b/lib/lrm/racommon.c @@ -0,0 +1,178 @@ +/* + * Common functions for LRM interface to resource agents + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * File: racommon.c + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + */ + + +#include <lha_internal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <dirent.h> +#include <libgen.h> /* Add it for compiling on OSX */ +#include <glib.h> +#include <sys/stat.h> +#include <clplumbing/cl_log.h> +#include <lrm/raexec.h> +#include <lrm/racommon.h> + +void +get_ra_pathname(const char* class_path, const char* type, const char* provider, + char pathname[]) +{ + char* type_dup; + char* base_name; + + type_dup = g_strndup(type, RA_MAX_NAME_LENGTH); + if (type_dup == NULL) { + cl_log(LOG_ERR, "No enough memory to allocate."); + pathname[0] = '\0'; + return; + } + + base_name = basename(type_dup); + + if ( strncmp(type, base_name, RA_MAX_NAME_LENGTH) == 0 ) { + /*the type does not include path*/ + if (provider) { + snprintf(pathname, RA_MAX_NAME_LENGTH, "%s/%s/%s", + class_path, provider, type); + }else{ + snprintf(pathname, RA_MAX_NAME_LENGTH, "%s/%s", + class_path,type); + } + }else{ + /*the type includes path, just copy it to pathname*/ + if ( *type == '/' ) { + g_strlcpy(pathname, type, RA_MAX_NAME_LENGTH); + } else { + *pathname = '\0'; + cl_log(LOG_ERR, "%s: relative paths not allowed: %s", + __FUNCTION__, type); + } + } + + g_free(type_dup); +} + +/* + * Description: Filter a file. + * Return Value: + * TRUE: the file is qualified. + * FALSE: the file is unqualified. + * Notes: A qualifed file is a regular file with execute bits + * which does not start with '.' + */ +gboolean +filtered(char * file_name) +{ + struct stat buf; + char *s; + + if ( stat(file_name, &buf) != 0 ) { + return FALSE; + } + if ( ((s = strrchr(file_name,'/')) && *(s+1) == '.') + || *file_name == '.' ) { + return FALSE; + } + + if ( S_ISREG(buf.st_mode) + && ( ( buf.st_mode & S_IXUSR ) || ( buf.st_mode & S_IXGRP ) + || ( buf.st_mode & S_IXOTH ) ) ) { + return TRUE; + } + return FALSE; +} + +int +get_runnable_list(const char* class_path, GList ** rsc_info) +{ + struct dirent **namelist; + int file_num; + + if ( rsc_info == NULL ) { + cl_log(LOG_ERR, "Parameter error: get_resource_list"); + return -2; + } + + if ( *rsc_info != NULL ) { + cl_log(LOG_ERR, "Parameter error: get_resource_list."\ + "will cause memory leak."); + *rsc_info = NULL; + } + + file_num = scandir(class_path, &namelist, NULL, alphasort); + if (file_num < 0) { + cl_log(LOG_ERR, "scandir failed in RA plugin"); + return -2; + } else{ + while (file_num--) { + char tmp_buffer[FILENAME_MAX+1]; + + tmp_buffer[0] = '\0'; + tmp_buffer[FILENAME_MAX] = '\0'; + snprintf(tmp_buffer, FILENAME_MAX, "%s/%s", + class_path, namelist[file_num]->d_name ); + if ( filtered(tmp_buffer) == TRUE ) { + *rsc_info = g_list_append(*rsc_info, + g_strdup(namelist[file_num]->d_name)); + } + free(namelist[file_num]); + } + free(namelist); + } + return g_list_length(*rsc_info); +} + +int +get_failed_exec_rc(void) +{ + int rc; + + switch (errno) { /* see execve(2) */ + case ENOENT: /* No such file or directory */ + case EISDIR: /* Is a directory */ + rc = EXECRA_NOT_INSTALLED; + break; + case EACCES: /* permission denied (various errors) */ + rc = EXECRA_INSUFFICIENT_PRIV; + break; + default: + rc = EXECRA_EXEC_UNKNOWN_ERROR; + break; + } + return rc; +} + +void +closefiles(void) +{ + int fd; + + /* close all descriptors except stdin/out/err and channels to logd */ + for (fd = getdtablesize() - 1; fd > STDERR_FILENO; fd--) { + /*if (!cl_log_is_logd_fd(fd))*/ + close(fd); + } +} diff --git a/lib/pils/Makefile.am b/lib/pils/Makefile.am new file mode 100644 index 0000000..d47c6c7 --- /dev/null +++ b/lib/pils/Makefile.am @@ -0,0 +1,57 @@ +# +# pils: Linux-HA heartbeat code +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + + +AM_CFLAGS = @CFLAGS@ + +## include files +#pkginclude_HEADERS = $(top_srcdir)/include/pils/plugin.h \ +# $(top_srcdir)/include/pils/interface.h + +## binaries +#sbin_PROGRAMS = main + + +#main_SOURCES = main.c + +#main_LDADD = libpils.la @LIBLTDL@ \ +# $(GLIBLIB) \ +# $(top_builddir)/replace/libreplace.la +#main_LDFLAGS = @LIBADD_DL@ @LIBLTDL@ -export-dynamic @DLOPEN_FORCE_FLAGS@ + + +## libraries + +lib_LTLIBRARIES = libpils.la + +plugindir = $(libdir)/@HB_PKG@/plugins/test +plugin_LTLIBRARIES = test.la + +libpils_la_SOURCES = pils.c +libpils_la_LDFLAGS = -version-info 2:0:0 +libpils_la_LIBADD = $(top_builddir)/replace/libreplace.la \ + @LIBLTDL@ $(GLIBLIB) +test_la_SOURCES = test.c +test_la_LDFLAGS = -export-dynamic -module -avoid-version diff --git a/lib/pils/main.c b/lib/pils/main.c new file mode 100644 index 0000000..32faceb --- /dev/null +++ b/lib/pils/main.c @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2001 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <lha_internal.h> +#include <stdio.h> +#include <pils/generic.h> + +#define MOD "/home/alanr/modules" + +GHashTable* test1functions = NULL; + +long one = 1; +long two = 2; +long three = 3; +long four = 4; + +static int TestCallBack +( GenericPILCallbackType t +, PILPluginUniv* univ +, const char * iftype +, const char * ifname +, void* userptr +); + +static PILGenericIfMgmtRqst RegRqsts [] = + { {"test", &test1functions, &one, TestCallBack, &two}, + {NULL, NULL, NULL, NULL, NULL} +}; + +int +main(int argc, char ** argv) +{ + PILPluginUniv * u; + PIL_rc rc; + int j; + + + u = NewPILPluginUniv(MOD); + /* PILSetDebugLevel(u, NULL, NULL, 0); */ + PILLogMemStats(); + + + if ((rc = PILLoadPlugin(u, "InterfaceMgr", "generic", &RegRqsts)) + != PIL_OK) { + fprintf(stderr, "generic plugin load Error = [%s]\n" + , lt_dlerror()); + /*exit(1);*/ + } + /* PILSetDebugLevel(u, NULL, NULL, 0); */ + + for (j=0; j < 10; ++j) { + PILLogMemStats(); + fprintf(stderr, "****Loading plugin test/test\n"); + if ((rc = PILLoadPlugin(u, "test", "test", NULL)) != PIL_OK) { + printf("ERROR: test plugin load error = [%d/%s]\n" + , rc, lt_dlerror()); + } + PILLogMemStats(); + fprintf(stderr, "****UN-loading plugin test/test\n"); + if ((rc = PILIncrIFRefCount(u, "test", "test", -1))!= PIL_OK){ + printf("ERROR: test plugin UNload error = [%d/%s]\n" + , rc, lt_dlerror()); + } + } + PILLogMemStats(); + DelPILPluginUniv(u); u = NULL; + PILLogMemStats(); + + return 0; +} + + +static int +TestCallBack +( GenericPILCallbackType t +, PILPluginUniv* univ +, const char * iftype +, const char * ifname +, void* userptr) +{ + char cbbuf[32]; + + switch(t) { + case PIL_REGISTER: + snprintf(cbbuf, sizeof(cbbuf), "PIL_REGISTER"); + break; + + case PIL_UNREGISTER: + snprintf(cbbuf, sizeof(cbbuf), "PIL_UNREGISTER"); + break; + + default: + snprintf(cbbuf, sizeof(cbbuf), "type [%d?]", t); + break; + } + + fprintf(stderr, "Callback: (%s, univ: 0x%lx, module: %s/%s, user ptr: 0x%lx (%ld))\n" + , cbbuf + , (unsigned long) univ + , iftype, ifname + , (unsigned long)userptr + , (*((long *)userptr))); + return PIL_OK; +} + diff --git a/lib/pils/pils.c b/lib/pils/pils.c new file mode 100644 index 0000000..4243b22 --- /dev/null +++ b/lib/pils/pils.c @@ -0,0 +1,2152 @@ +/* + * Copyright (C) 2001 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <lha_internal.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <errno.h> +#include <stdarg.h> +#include <sys/stat.h> + +/* Dumbness... */ +#define time FooTimeParameter +#define index FooIndexParameter +# include <glib.h> +#undef time +#undef index + + +#define ENABLE_PIL_DEFS_PRIVATE +#define ENABLE_PLUGIN_MANAGER_PRIVATE + +#ifndef STRLEN_CONST +# define STRLEN_CONST(con) (sizeof(con)-1) +#endif + +#include <pils/interface.h> + +#define NEW(type) (g_new(type,1)) +#define ZAP(obj) memset(obj, 0, sizeof(*obj)) +#define DELETE(obj) {g_free(obj); obj = NULL;} + +#ifdef LTDL_SHLIB_EXT +# define PLUGINSUFFIX LTDL_SHLIB_EXT +#else +# define PLUGINSUFFIX ".so" +#endif + +static int PluginDebugLevel = 0; + +#define DEBUGPLUGIN (PluginDebugLevel > 0) + + + +static PIL_rc InterfaceManager_plugin_init(PILPluginUniv* univ); + +static char** PILPluginTypeListPlugins(PILPluginType* pitype, int* picount); +static PILInterface* FindIF(PILPluginUniv* universe, const char *iftype +, const char * ifname); +static PIL_rc PluginExists(const char * PluginPath); +static char * PILPluginPath(PILPluginUniv* universe, const char * plugintype +, const char * pluginname); + + +void DelPILPluginUniv(PILPluginUniv*); +/* + * These RmA* functions primarily called from hash_table_foreach, + * functions, so they have gpointer arguments. When calling + * them by hand, take special care to pass the right argument types. + * + * They all follow the same calling sequence though. It is: + * String name"*" type object + * "*" type object with the name given by 1st argument + * NULL + * + * For example: + * RmAPILPluginType takes + * string name + * PILPluginType* object with the given name. + */ +static gboolean RmAPILPluginType +( gpointer pitname /* Name of this plugin type */ +, gpointer pitype /* PILPluginType* */ +, gpointer notused +); +static void RemoveAPILPluginType(PILPluginType*); + +static PILPluginType* NewPILPluginType +( PILPluginUniv* pluginuniv +, const char * plugintype +); +static void DelPILPluginType(PILPluginType*); +/* + * These RmA* functions primarily called from hash_table_foreach, + * so they have gpointer arguments. When calling + * them by hand, take special care to pass the right argument types. + */ +static gboolean RmAPILPlugin +( gpointer piname /* Name of this plugin */ +, gpointer plugin /* PILPlugin* */ +, gpointer notused +); +static void RemoveAPILPlugin(PILPlugin*); + + +static PILPlugin* NewPILPlugin(PILPluginType* pitype + , const char * plugin_name + , lt_dlhandle dlhand + , PILPluginInitFun PluginSym); +static void DelPILPlugin(PILPlugin*); + +struct MemStat { + unsigned long news; + unsigned long frees; +}; + +static struct PluginStats { + struct MemStat plugin; + struct MemStat pitype; + struct MemStat piuniv; + struct MemStat interface; + struct MemStat interfacetype; + struct MemStat interfaceuniv; +}PILstats; + +#define STATNEW(t) {PILstats.t.news ++; } +#define STATFREE(t) {PILstats.t.frees ++; } + + + +static PILInterfaceUniv* NewPILInterfaceUniv(PILPluginUniv*); +static void DelPILInterfaceUniv(PILInterfaceUniv*); +/* + * These RmA* functions primarily called from hash_table_foreach, but + * not necessarily, so they have gpointer arguments. When calling + * them by hand, take special care to pass the right argument types. + */ +static gboolean RmAPILInterfaceType +( gpointer iftypename /* Name of this interface type */ +, gpointer iftype /* PILInterfaceType* */ +, gpointer notused +); +static void RemoveAPILInterfaceType(PILInterfaceType*, PILInterfaceType*); + +static PILInterfaceType* NewPILInterfaceType +( PILInterfaceUniv* +, const char * typename +, void* ifexports, void* user_data +); +static void DelPILInterfaceType(PILInterfaceType*); +/* + * These RmA* functions are designed to be called from + * hash_table_foreach, so they have gpointer arguments. When calling + * them by hand, take special care to pass the right argument types. + * They can be called from other places safely also. + */ +static gboolean RmAPILInterface +( gpointer ifname /* Name of this interface */ +, gpointer plugin /* PILInterface* */ +, gpointer notused +); +static PIL_rc RemoveAPILInterface(PILInterface*); +static void DelPILPluginType(PILPluginType*); + +static PILInterface* NewPILInterface +( PILInterfaceType* interfacetype +, const char* interfacename +, void * exports +, PILInterfaceFun closefun +, void* ud_interface +, PILPlugin* loading_plugin /* The plugin that loaded us */ +); +static void DelPILInterface(PILInterface*); +static PIL_rc close_ifmgr_interface(PILInterface*, void*); + + + + +/* + * For consistency, we show up as a plugin in our our system. + * + * Here are our exports as a plugin. + * + */ +static const char * PIL_PILPluginVersion(void); +static void PIL_PILPluginClose (PILPlugin*); +void PILpisysSetDebugLevel (int level); +int PILpisysGetDebugLevel(void); +static const char * PIL_PILPluginLicense (void); +static const char * PIL_PILPluginLicenseUrl (void); + +static const PILPluginOps PluginExports = +{ PIL_PILPluginVersion +, PILpisysGetDebugLevel +, PILpisysSetDebugLevel +, PIL_PILPluginLicense +, PIL_PILPluginLicenseUrl +, PIL_PILPluginClose +}; + +/* Prototypes for the functions that we export to every plugin */ +static PIL_rc PILregister_plugin(PILPlugin* piinfo, const PILPluginOps* mops); +static PIL_rc PILunregister_plugin(PILPlugin* piinfo); +static PIL_rc +PILRegisterInterface +( PILPlugin* piinfo +, const char * interfacetype /* Type of interface */ +, const char * interfacename /* Name of interface */ +, void* Ops /* Ops exported by this interface */ +, PILInterfaceFun closefunc /* Ops exported by this interface */ +, PILInterface** interfaceid /* Interface id (OP) */ +, void** Imports /* Functions imported by */ + /* this interface (OP) */ +, void* ud_interface /* interface user data */ +); +static PIL_rc PILunregister_interface(PILInterface* interfaceid); +static void PILLog(PILLogLevel priority, const char * fmt, ...) + G_GNUC_PRINTF(2,3); + + +/* + * This is the set of functions that we export to every plugin + * + * That also makes it the set of functions that every plugin imports. + * + */ + +static PILPluginImports PILPluginImportSet = +{ PILregister_plugin /* register_plugin */ +, PILunregister_plugin /* unregister_plugin */ +, PILRegisterInterface /* register_interface */ +, RemoveAPILInterface /* unregister_interface */ +, PILLoadPlugin /* load_plugin */ +, PILLog /* Logging function */ +, g_malloc /* Malloc function */ +, g_realloc /* realloc function */ +, g_free /* Free function */ +, g_strdup /* Strdup function */ +}; + +static PIL_rc ifmgr_register_interface(PILInterface* newif + , void** imports); +static PIL_rc ifmgr_unregister_interface(PILInterface* interface); + +/* + * For consistency, the master interface manager is a interface in the + * system. Below is our set of exported Interface functions. + * + * Food for thought: This is the interface manager whose name is + * interface. This makes it the Interface Interface interface ;-) + * (or the Interface/Interface interface if you prefer) + */ + +static PILInterfaceOps IfExports = +{ ifmgr_register_interface +, ifmgr_unregister_interface +}; + + + +/* + * Below is the set of functions we export to every interface manager. + */ + +static int IfRefCount(PILInterface * ifh); +static int IfIncrRefCount(PILInterface*eifinfo,int plusminus); +static int PluginIncrRefCount(PILPlugin*eifinfo,int plusminus); +#if 0 +static int PluginRefCount(PILPlugin * ifh); +#endif +static void IfForceUnregister(PILInterface *eifinfo); +static void IfForEachClientRemove(PILInterface* manangerif + , gboolean(*f)(PILInterface* clientif, void * other) + , void* other); + +static PILInterfaceImports IFManagerImports = +{ IfRefCount +, IfIncrRefCount +, IfForceUnregister +, IfForEachClientRemove +}; +static void PILValidatePlugin(gpointer key, gpointer plugin, gpointer pitype); +static void PILValidatePluginType(gpointer key, gpointer pitype, gpointer piuniv); +static void PILValidatePluginUniv(gpointer key, gpointer pitype, gpointer); +static void PILValidateInterface(gpointer key, gpointer interface, gpointer iftype); +static void PILValidateInterfaceType(gpointer key, gpointer iftype, gpointer ifuniv); +static void PILValidateInterfaceUniv(gpointer key, gpointer puniv, gpointer); + +/***************************************************************************** + * + * This code is for managing plugins, and interacting with them... + * + ****************************************************************************/ + +static PILPlugin* +NewPILPlugin( PILPluginType* pitype + , const char * plugin_name + , lt_dlhandle dlhand + , PILPluginInitFun PluginSym) +{ + PILPlugin* ret = NEW(PILPlugin); + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILPlugin(0x%lx)", (unsigned long)ret); + } + + STATNEW(plugin); + ret->MagicNum = PIL_MAGIC_PLUGIN; + ret->plugin_name = g_strdup(plugin_name); + ret->plugintype = pitype; + ret->refcnt = 0; + ret->dlhandle = dlhand; + ret->dlinitfun = PluginSym; + PILValidatePlugin(ret->plugin_name, ret, pitype); + return ret; +} +static void +DelPILPlugin(PILPlugin*pi) +{ + + if (pi->refcnt > 0) { + PILLog(PIL_INFO, "DelPILPlugin: Non-zero refcnt"); + } + + if (pi->dlhandle) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Closing dlhandle for (%s/%s)" + , pi->plugintype->plugintype, pi->plugin_name); + } + lt_dlclose(pi->dlhandle); + }else if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NO dlhandle for (%s/%s)!" + , pi->plugintype->plugintype, pi->plugin_name); + } + DELETE(pi->plugin_name); + ZAP(pi); + DELETE(pi); + STATFREE(plugin); +} + + +static PILPluginType dummymlpitype = +{ PIL_MAGIC_PLUGINTYPE +, NULL /*plugintype*/ +, NULL /*piuniv*/ +, NULL /*Plugins*/ +, PILPluginTypeListPlugins /* listplugins */ +}; + +static PILPluginType* +NewPILPluginType(PILPluginUniv* pluginuniv + , const char * plugintype +) +{ + PILPluginType* ret = NEW(PILPluginType); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILPlugintype(0x%lx)", (unsigned long)ret); + } + STATNEW(pitype); + + *ret = dummymlpitype; + + ret->plugintype = g_strdup(plugintype); + ret->piuniv = pluginuniv; + ret->Plugins = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(pluginuniv->PluginTypes + , g_strdup(ret->plugintype), ret); + PILValidatePluginType(ret->plugintype, ret, pluginuniv); + return ret; +} +static void +DelPILPluginType(PILPluginType*pitype) +{ + PILValidatePluginType(NULL, pitype, NULL); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILPluginType(%s)", pitype->plugintype); + } + + STATFREE(pitype); + g_hash_table_foreach_remove(pitype->Plugins, RmAPILPlugin, NULL); + g_hash_table_destroy(pitype->Plugins); + DELETE(pitype->plugintype); + ZAP(pitype); + DELETE(pitype); +} +/* + * These RmA* functions primarily called from hash_table_foreach, + * so they have gpointer arguments. This *not necessarily* clause + * is why they do the g_hash_table_lookup_extended call instead of + * just deleting the key. When called from outside, the key * + * may not be pointing at the key to actually free, but a copy + * of the key. + */ +static gboolean +RmAPILPlugin /* IsA GHFunc: required for g_hash_table_foreach_remove() */ +( gpointer piname /* Name of this plugin */ +, gpointer plugin /* PILPlugin* */ +, gpointer notused +) +{ + PILPlugin* Plugin = plugin; + PILPluginType* Pitype = Plugin->plugintype; + + PILValidatePlugin(piname, plugin, NULL); + PILValidatePluginType(NULL, Pitype, NULL); + g_assert(IS_PILPLUGIN(Plugin)); + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILPlugin(%s/%s)", Pitype->plugintype + , Plugin->plugin_name); + } + /* Normally called from g_hash_table_foreachremove or equivalent */ + + DelPILPlugin(plugin); + DELETE(piname); + return TRUE; +} + +static void +RemoveAPILPlugin(PILPlugin*Plugin) +{ + PILPluginType* Pitype = Plugin->plugintype; + gpointer key; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RemoveAPILPlugin(%s/%s)" + , Pitype->plugintype + , Plugin->plugin_name); + } + if (g_hash_table_lookup_extended(Pitype->Plugins + , Plugin->plugin_name, &key, (void*)&Plugin)) { + + g_hash_table_remove(Pitype->Plugins, key); + RmAPILPlugin(key, Plugin, NULL); + key = NULL; + Plugin = NULL; + + }else{ + g_assert_not_reached(); + } + if (g_hash_table_size(Pitype->Plugins) == 0) { + RemoveAPILPluginType(Pitype); + /* Pitype is now invalid */ + Pitype = NULL; + } +} + +PILPluginUniv* +NewPILPluginUniv(const char * basepluginpath) +{ + PILPluginUniv* ret = NEW(PILPluginUniv); + + /* The delimiter separating search path components */ + const char* path_delim = G_SEARCHPATH_SEPARATOR_S; + char * fullpath; + + STATNEW(piuniv); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILPluginUniv(0x%lx)" + , (unsigned long)ret); + } + if (!g_path_is_absolute(basepluginpath)) { + DELETE(ret); + return(ret); + } + ret->MagicNum = PIL_MAGIC_PLUGINUNIV; + fullpath = g_strdup_printf("%s%s%s", basepluginpath + , path_delim, PILS_BASE_PLUGINDIR); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "PILS: Plugin path = %s", fullpath); + } + + /* Separate the root directory PATH into components */ + ret->rootdirlist = g_strsplit(fullpath, path_delim, 100); + g_free(fullpath); + + ret->PluginTypes = g_hash_table_new(g_str_hash, g_str_equal); + ret->imports = &PILPluginImportSet; + ret->ifuniv = NewPILInterfaceUniv(ret); + PILValidatePluginUniv(NULL, ret, NULL); + return ret; +} + +/* Change memory allocation functions immediately after creating universe */ +void +PilPluginUnivSetMemalloc(PILPluginUniv* u +, gpointer (*allocfun)(glib_size_t size) +, gpointer (*reallocfun)(gpointer ptr, glib_size_t size) +, void (*freefun)(void* space) +, char* (*strdupfun)(const char *s)) +{ + u->imports->alloc = allocfun; + u->imports->mrealloc = reallocfun; + u->imports->mfree = freefun; + u->imports->mstrdup = strdupfun; +} + + +/* Change logging functions - preferably right after creating universe */ +void +PilPluginUnivSetLog(PILPluginUniv* u +, void (*logfun) (PILLogLevel priority, const char * fmt, ...)) +{ + u->imports->log = logfun; +} + +void +DelPILPluginUniv(PILPluginUniv* piuniv) +{ + + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILPluginUniv(0x%lx)" + , (unsigned long)piuniv); + } + STATFREE(piuniv); + PILValidatePluginUniv(NULL, piuniv, NULL); + DelPILInterfaceUniv(piuniv->ifuniv); + piuniv->ifuniv = NULL; + g_hash_table_foreach_remove(piuniv->PluginTypes + , RmAPILPluginType, NULL); + g_hash_table_destroy(piuniv->PluginTypes); + g_strfreev(piuniv->rootdirlist); + ZAP(piuniv); + DELETE(piuniv); +} + +/* + * These RmA* functions primarily called from hash_table_foreach, + * so they have gpointer arguments. This *not necessarily* clause + * is why they do the g_hash_table_lookup_extended call instead of + * just deleting the key. When called from outside, the key * + * may not be pointing at the key to actually free, but a copy + * of the key. + */ +static gboolean /* IsA GHFunc: required for g_hash_table_foreach_remove() */ +RmAPILPluginType +( gpointer pitname /* Name of this plugin type "real" key */ +, gpointer pitype /* PILPluginType* */ +, gpointer notused +) +{ + PILPluginType* Plugintype = pitype; + + g_assert(IS_PILPLUGINTYPE(Plugintype)); + PILValidatePluginType(pitname, pitype, NULL); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILPluginType(%s)" + , Plugintype->plugintype); + } + /* + * This function is usually but not always called by + * g_hash_table_foreach_remove() + */ + + DelPILPluginType(pitype); + DELETE(pitname); + return TRUE; +} +static void +RemoveAPILPluginType(PILPluginType*Plugintype) +{ + PILPluginUniv* Pluginuniv = Plugintype->piuniv; + gpointer key; + if (g_hash_table_lookup_extended(Pluginuniv->PluginTypes + , Plugintype->plugintype, &key, (void*)&Plugintype)) { + + g_hash_table_remove(Pluginuniv->PluginTypes, key); + RmAPILPluginType(key, Plugintype, NULL); + }else{ + g_assert_not_reached(); + } +} + +/* + * InterfaceManager_plugin_init: Initialize the handling of + * "Interface Manager" interfaces. + * + * There are a few potential bootstrapping problems here ;-) + * + */ +static PIL_rc +InterfaceManager_plugin_init(PILPluginUniv* univ) +{ + PILPluginImports* imports = univ->imports; + PILPluginType* pitype; + PILInterface* ifinfo; + PILInterfaceType* iftype; + void* dontcare; + PILPlugin* ifmgr_plugin; + PIL_rc rc; + + + iftype = NewPILInterfaceType(univ->ifuniv, PI_IFMANAGER, &IfExports + , NULL); + + g_hash_table_insert(univ->ifuniv->iftypes + , g_strdup(PI_IFMANAGER), iftype); + + pitype = NewPILPluginType(univ, PI_IFMANAGER); + + g_hash_table_insert(univ->PluginTypes + , g_strdup(PI_IFMANAGER), pitype); + + ifmgr_plugin= NewPILPlugin(pitype, PI_IFMANAGER, NULL, NULL); + + g_hash_table_insert(pitype->Plugins + , g_strdup(PI_IFMANAGER), ifmgr_plugin); + + /* We can call register_plugin, since it doesn't depend on us... */ + rc = imports->register_plugin(ifmgr_plugin, &PluginExports); + if (rc != PIL_OK) { + PILLog(PIL_CRIT, "register_plugin() failed in init: %s" + , PIL_strerror(rc)); + return(rc); + } + /* + * Now, we're registering interfaces, and are into some deep + * Catch-22 if do it the "easy" way, since our code is + * needed in order to support interface loading for the type of + * interface we are (a Interface interface). + * + * So, instead of calling imports->register_interface(), we have to + * do the work ourselves here... + * + * Since no one should yet be registered to handle Interface + * interfaces, we need to bypass the hash table handler lookup + * that register_interface would do and call the function that + * register_interface would call... + * + */ + + /* The first argument is the PILInterfaceType* */ + ifinfo = NewPILInterface(iftype, PI_IFMANAGER, &IfExports + , close_ifmgr_interface, NULL, NULL); + ifinfo->ifmanager = iftype->ifmgr_ref = ifinfo; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "InterfaceManager_plugin_init(0x%lx/%s)" + , (unsigned long)ifinfo, ifinfo->interfacename); + } + PILValidatePluginUniv(NULL, univ, NULL); + ifmgr_register_interface(ifinfo, &dontcare); + PILValidatePluginUniv(NULL, univ, NULL); + + return(PIL_OK); +}/*InterfaceManager_plugin_init*/ + + +/* Return current IfIf "plugin" version (not very interesting for us) */ +static const char * +PIL_PILPluginVersion(void) +{ + return("1.0"); +} + +/* Return current IfIf debug level */ +int +PILpisysGetDebugLevel(void) +{ + return(PluginDebugLevel); +} + +/* Set current IfIf debug level */ +void +PILpisysSetDebugLevel (int level) +{ + PluginDebugLevel = level; +} +struct set_debug_helper { + const char * pitype; + const char * piname; + int level; +}; + +static void +PILSetDebugLeveltoPlugin(gpointer key, gpointer plugin, gpointer Helper) +{ + PILPlugin* p = plugin; + struct set_debug_helper* helper = Helper; + + p->pluginops->setdebuglevel(helper->level); +} + +static void +PILSetDebugLevelbyType(const void * key, gpointer plugintype, gpointer Helper) +{ + struct set_debug_helper* helper = Helper; + + + PILPluginType* t = plugintype; + + if (helper->piname == NULL) { + g_hash_table_foreach(t->Plugins, PILSetDebugLeveltoPlugin + , helper); + }else{ + PILPlugin* p = g_hash_table_lookup(t->Plugins + , helper->piname); + if (p != NULL) { + p->pluginops->setdebuglevel(helper->level); + } + } +} + +void +PILSetDebugLevel(PILPluginUniv* u, const char * pitype, const char * piname +, int level) +{ + struct set_debug_helper helper = {pitype, piname, level}; + + if (u == NULL) { + return; + } + + if (pitype == NULL) { + g_hash_table_foreach(u->PluginTypes + /* + * Reason for this next cast: + * SetDebugLevelbyType takes const gpointer + * arguments, unlike a GHFunc which doesn't. + */ + , (GHFunc)PILSetDebugLevelbyType + , &helper); + }else{ + PILPluginType* t = g_hash_table_lookup(u->PluginTypes + , pitype); + if (t != NULL) { + PILSetDebugLevelbyType(pitype, t, &helper); + } + } +} + + +int +PILGetDebugLevel(PILPluginUniv* u, const char * pitype, const char * piname) +{ + PILPluginType* t; + PILPlugin* p; + if ( u == NULL + || pitype == NULL + || (t = g_hash_table_lookup(u->PluginTypes, pitype)) == NULL + || (p = g_hash_table_lookup(t->Plugins, piname)) == NULL) { + return -1; + } + return p->pluginops->getdebuglevel(); +} + +/* Close/shutdown our PILPlugin (the interface manager interface plugin) */ +/* All our interfaces will have already been shut down and unregistered */ +static void +PIL_PILPluginClose (PILPlugin* plugin) +{ +} +static const char * +PIL_PILPluginLicense (void) +{ + return LICENSE_LGPL; +} +static const char * +PIL_PILPluginLicenseUrl (void) +{ + return URL_LGPL; +} + +/***************************************************************************** + * + * This code is for managing interfaces, and interacting with them... + * + ****************************************************************************/ + + +static PILInterface* +NewPILInterface(PILInterfaceType* interfacetype + , const char* interfacename + , void * exports + , PILInterfaceFun closefun + , void* ud_interface + , PILPlugin* loading_plugin) +{ + PILInterface* ret = NULL; + PILInterface* look = NULL; + + + if ((look = g_hash_table_lookup(interfacetype->interfaces + , interfacename)) != NULL) { + PILLog(PIL_DEBUG, "Deleting PILInterface!"); + DelPILInterface(look); + } + ret = NEW(PILInterface); + STATNEW(interface); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILInterface(0x%lx)", (unsigned long)ret); + } + + if (ret) { + ret->MagicNum = PIL_MAGIC_INTERFACE; + ret->interfacetype = interfacetype; + ret->exports = exports; + ret->ud_interface = ud_interface; + ret->interfacename = g_strdup(interfacename); + ret->ifmanager = interfacetype->ifmgr_ref; + ret->loadingpi = loading_plugin; + g_hash_table_insert(interfacetype->interfaces + , g_strdup(ret->interfacename), ret); + + ret->if_close = closefun; + ret->refcnt = 1; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILInterface(0x%lx:%s/%s)*** user_data: 0x%p *******" + , (unsigned long)ret + , interfacetype->typename + , ret->interfacename + , ud_interface); + } + } + return ret; +} +static void +DelPILInterface(PILInterface* intf) +{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILInterface(0x%lx/%s)" + , (unsigned long)intf, intf->interfacename); + } + STATFREE(interface); + DELETE(intf->interfacename); + ZAP(intf); + DELETE(intf); +} + +static PILInterfaceType* +NewPILInterfaceType(PILInterfaceUniv*univ, const char * typename +, void* ifeports, void* user_data) +{ + PILInterfaceType* ifmgr_types; + PILInterface* ifmgr_ref; + PILInterfaceType* ret = NEW(PILInterfaceType); + + + STATNEW(interfacetype); + ret->MagicNum = PIL_MAGIC_INTERFACETYPE; + ret->typename = g_strdup(typename); + ret->interfaces = g_hash_table_new(g_str_hash, g_str_equal); + ret->ud_if_type = user_data; + ret->universe = univ; + ret->ifmgr_ref = NULL; + + /* Now find the pointer to our if type in the Interface Universe */ + if ((ifmgr_types = g_hash_table_lookup(univ->iftypes, PI_IFMANAGER)) + != NULL) { + if ((ifmgr_ref=g_hash_table_lookup(ifmgr_types->interfaces + , typename)) != NULL) { + ret->ifmgr_ref = ifmgr_ref; + }else { + g_assert(strcmp(typename, PI_IFMANAGER) == 0); + } + }else { + g_assert(strcmp(typename, PI_IFMANAGER) == 0); + } + + /* Insert ourselves into our parent's table */ + g_hash_table_insert(univ->iftypes, g_strdup(typename), ret); + return ret; +} +static void +DelPILInterfaceType(PILInterfaceType*ift) +{ + PILInterfaceUniv* u = ift->universe; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILInterfaceType(%s)" + , ift->typename); + } + STATFREE(interfacetype); + + PILValidateInterfaceUniv(NULL, u, NULL); + + /* + * RmAPILInterface refuses to remove the interface for the + * Interface manager, because it must be removed last. + * + * Otherwise we won't be able to unregister interfaces + * for other types of objects, and we'll be very confused. + */ + + g_hash_table_foreach_remove(ift->interfaces, RmAPILInterface, NULL); + + PILValidateInterfaceUniv(NULL, u, NULL); + + if (g_hash_table_size(ift->interfaces) > 0) { + gpointer key, iftype; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "DelPILInterfaceType(%s): table size (%d)" + , ift->typename, g_hash_table_size(ift->interfaces)); + } + if (g_hash_table_lookup_extended(ift->interfaces + , PI_IFMANAGER, &key, &iftype)) { + DelPILInterface((PILInterface*)iftype); + DELETE(key); + } + } + DELETE(ift->typename); + g_hash_table_destroy(ift->interfaces); + ZAP(ift); + DELETE(ift); +} + +/* + * These RmA* functions primarily called from hash_table_foreach, + * so they have gpointer arguments. This *not necessarily* clause + * is why they do the g_hash_table_lookup_extended call instead of + * just deleting the key. When called from outside, the key * + * may not be pointing at the key to actually free, but a copy + * of the key. + */ +static gboolean /* IsAGHFunc: required for g_hash_table_foreach_remove() */ +RmAPILInterface +( gpointer ifname /* Name of this interface */ +, gpointer intf /* PILInterface* */ +, gpointer notused +) +{ + PILInterface* If = intf; + PILInterfaceType* Iftype = If->interfacetype; + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILInterface(0x%lx/%s)" + , (unsigned long)If, If->interfacename); + } + g_assert(IS_PILINTERFACE(If)); + + /* + * Don't remove the master interface manager this way, or + * Somebody will have a cow... + */ + if (If == If->ifmanager) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILInterface: skipping (%s)" + , If->interfacename); + } + return FALSE; + } + PILValidateInterface(ifname, If, Iftype); + PILValidateInterfaceType(NULL, Iftype, NULL); + + /* + * This function is usually but not always called by + * g_hash_table_foreach_remove() + */ + + PILunregister_interface(If); + PILValidateInterface(ifname, If, Iftype); + PILValidateInterfaceType(NULL, Iftype, NULL); + DELETE(ifname); + DelPILInterface(If); + return TRUE; +} +static PIL_rc +RemoveAPILInterface(PILInterface* pif) +{ + PILInterfaceType* Iftype = pif->interfacetype; + gpointer key; + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RemoveAPILInterface(0x%lx/%s)" + , (unsigned long)pif, pif->interfacename); + } + if (g_hash_table_lookup_extended(Iftype->interfaces + , pif->interfacename, &key, (void*)&pif)) { + g_assert(IS_PILINTERFACE(pif)); + g_hash_table_remove(Iftype->interfaces, key); + RmAPILInterface(key, pif, NULL); + }else{ + g_assert_not_reached(); + } + + if (g_hash_table_size(Iftype->interfaces) == 0) { + /* The generic plugin handler doesn't want us to + * delete it's types... + */ + if (Iftype->ifmgr_ref->refcnt <= 1) { + RemoveAPILInterfaceType(Iftype, NULL); + } + } + return PIL_OK; +} + + +/* Register a Interface Interface (Interface manager) */ +static PIL_rc +ifmgr_register_interface(PILInterface* intf +, void** imports) +{ + PILInterfaceType* ift = intf->interfacetype; + PILInterfaceUniv* ifuniv = ift->universe; + PILInterfaceOps* ifops; /* Ops vector for InterfaceManager */ + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "Registering Implementation manager for" + " Interface type '%s'" + , intf->interfacename); + } + + ifops = intf->exports; + if (ifops->RegisterInterface == NULL + || ifops->UnRegisterInterface == NULL) { + PILLog(PIL_DEBUG, "ifmgr_register_interface(%s)" + ": NULL exported function pointer" + , intf->interfacename); + return PIL_INVAL; + } + + *imports = &IFManagerImports; + + if(g_hash_table_lookup(ifuniv->iftypes, intf->interfacename) == NULL){ + /* It registers itself into ifuniv automatically */ + NewPILInterfaceType(ifuniv,intf->interfacename, &IfExports + , NULL); + } + return PIL_OK; +} + +static gboolean +RemoveAllClients(PILInterface*interface, void * managerif) +{ + /* + * Careful! We can't remove ourselves this way... + * This gets taken care of as a special case in DelPILInterfaceUniv... + */ + if (managerif == interface) { + return FALSE; + } + PILunregister_interface(interface); + return TRUE; +} + +/* Unconditionally unregister a interface manager (InterfaceMgr Interface) */ +static PIL_rc +ifmgr_unregister_interface(PILInterface* interface) +{ + /* + * We need to unregister every interface we manage + */ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "ifmgr_unregister_interface(%s)" + , interface->interfacename); + } + + IfForEachClientRemove(interface, RemoveAllClients, interface); + return PIL_OK; +} + +/* Called to close the Interface manager for type Interface */ +static PIL_rc +close_ifmgr_interface(PILInterface* us, void* ud_interface) +{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "close_ifmgr_interface(%s)" + , us->interfacename); + } + /* Nothing much to do */ + return PIL_OK; +} + +/* Return the reference count for this interface */ +static int +IfRefCount(PILInterface * eifinfo) +{ + return eifinfo->refcnt; +} + +/* Modify the reference count for this interface */ +static int +IfIncrRefCount(PILInterface*eifinfo, int plusminus) +{ + if(DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "IfIncrRefCount(%d + %d )" + , eifinfo->refcnt, plusminus); + } + eifinfo->refcnt += plusminus; + if (eifinfo->refcnt <= 0) { + eifinfo->refcnt = 0; + /* Unregister this interface. */ + RemoveAPILInterface(eifinfo); + return 0; + } + return eifinfo->refcnt; +} + +#if 0 +static int +PluginRefCount(PILPlugin * pi) +{ + return pi->refcnt; +} +#endif + +static int +PluginIncrRefCount(PILPlugin*pi, int plusminus) +{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PluginIncrRefCount(%d + %d )" + , pi->refcnt, plusminus); + } + pi->refcnt += plusminus; + if (pi->refcnt <= 0) { + pi->refcnt = 0; + RemoveAPILPlugin(pi); + return 0; + } + return pi->refcnt; +} + +static PILInterface* +FindIF(PILPluginUniv* universe, const char *iftype, const char * ifname) +{ + PILInterfaceUniv* puniv; + PILInterfaceType* ptype; + + if (universe == NULL || (puniv = universe->ifuniv) == NULL + || (ptype=g_hash_table_lookup(puniv->iftypes, iftype))==NULL){ + return NULL; + } + return g_hash_table_lookup(ptype->interfaces, ifname); +} + +PIL_rc +PILIncrIFRefCount(PILPluginUniv* mu +, const char * interfacetype +, const char * interfacename +, int plusminus) +{ + PILInterface* intf = FindIF(mu, interfacetype, interfacename); + + if (intf) { + g_assert(IS_PILINTERFACE(intf)); + IfIncrRefCount(intf, plusminus); + return PIL_OK; + } + return PIL_NOPLUGIN; +} + +int +PILGetIFRefCount(PILPluginUniv* mu +, const char * interfacetype +, const char * interfacename) +{ + PILInterface* intf = FindIF(mu, interfacetype, interfacename); + + return IfRefCount(intf); +} + +static void +IfForceUnregister(PILInterface *id) +{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "IfForceUnRegister(%s)" + , id->interfacename); + } + RemoveAPILInterface(id); +} + +struct f_e_c_helper { + gboolean(*fun)(PILInterface* clientif, void * passalong); + void* passalong; +}; + +static gboolean IfForEachClientHelper(gpointer key +, gpointer iftype, gpointer helper_v); + +static gboolean +IfForEachClientHelper(gpointer unused, gpointer iftype, gpointer v) +{ + struct f_e_c_helper* s = (struct f_e_c_helper*)v; + + g_assert(IS_PILINTERFACE((PILInterface*)iftype)); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "IfForEachClientHelper(%s)" + , ((PILInterface*)iftype)->interfacename); + } + + return s->fun((PILInterface*)iftype, s->passalong); +} + + +static void +IfForEachClientRemove +( PILInterface* mgrif +, gboolean(*f)(PILInterface* clientif, void * passalong) +, void* passalong /* usually PILInterface* */ +) +{ + PILInterfaceType* mgrt; + PILInterfaceUniv* u; + const char * ifname; + PILInterfaceType* clientt; + + struct f_e_c_helper h = {f, passalong}; + + + if (mgrif == NULL || (mgrt = mgrif->interfacetype) == NULL + || (u = mgrt->universe) == NULL + || (ifname = mgrif->interfacename) == NULL) { + PILLog(PIL_WARN, "bad parameters to IfForEachClientRemove"); + return; + } + + if ((clientt = g_hash_table_lookup(u->iftypes, ifname)) == NULL) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "Interface manager [%s/%s] has no clients" + , PI_IFMANAGER, ifname); + } + return; + }; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "IfForEachClientRemove(%s:%s)" + , mgrt->typename, clientt->typename); + } + if (clientt->ifmgr_ref != mgrif) { + PILLog(PIL_WARN, "Bad ifmgr_ref ptr in PILInterfaceType"); + return; + } + + g_hash_table_foreach_remove(clientt->interfaces, IfForEachClientHelper + , &h); +} + +static PIL_rc +PILregister_plugin(PILPlugin* piinfo, const PILPluginOps* commonops) +{ + piinfo->pluginops = commonops; + + return PIL_OK; +} + +static PIL_rc +PILunregister_plugin(PILPlugin* piinfo) +{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PILunregister_plugin(%s)" + , piinfo->plugin_name); + } + RemoveAPILPlugin(piinfo); + return PIL_OK; +} + +/* General logging function (not really UPPILS-specific) */ +static void +PILLog(PILLogLevel priority, const char * format, ...) +{ + va_list args; + GLogLevelFlags flags; + + switch(priority) { + case PIL_FATAL: flags = G_LOG_LEVEL_ERROR; + break; + case PIL_CRIT: flags = G_LOG_LEVEL_CRITICAL; + break; + + default: /* FALL THROUGH... */ + case PIL_WARN: flags = G_LOG_LEVEL_WARNING; + break; + + case PIL_INFO: flags = G_LOG_LEVEL_INFO; + break; + case PIL_DEBUG: flags = G_LOG_LEVEL_DEBUG; + break; + }; + va_start (args, format); + g_logv (G_LOG_DOMAIN, flags, format, args); + va_end (args); +} + +static const char * PIL_strerrmsgs [] = +{ "Success" +, "Invalid Parameters" +, "Bad plugin/interface type" +, "Duplicate entry (plugin/interface name/type)" +, "Oops happens" +, "No such plugin/interface/interface type" +}; + +const char * +PIL_strerror(PIL_rc rc) +{ + int irc = (int) rc; + static char buf[128]; + + if (irc < 0 || irc >= DIMOF(PIL_strerrmsgs)) { + snprintf(buf, sizeof(buf), "return code %d (?)", irc); + return buf; + } + return PIL_strerrmsgs[irc]; +} + +/* + * Returns the PATHname of the file containing the requested plugin + * This file handles PATH-like semantics from the rootdirlist. + * It is also might be the right place to put alias handing in the future... + */ +static char * +PILPluginPath(PILPluginUniv* universe, const char * plugintype +, const char * pluginname) +{ + char * PluginPath = NULL; + char ** spath_component; + + for (spath_component = universe->rootdirlist; *spath_component + ; ++ spath_component) { + + if (PluginPath) { + g_free(PluginPath); PluginPath=NULL; + } + + PluginPath = g_strdup_printf("%s%s%s%s%s%s" + , *spath_component + , G_DIR_SEPARATOR_S + , plugintype + , G_DIR_SEPARATOR_S + , pluginname + , PLUGINSUFFIX); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "PILS: Looking for %s/%s => [%s]" + , plugintype, pluginname, PluginPath); + } + + if (PluginExists(PluginPath) == PIL_OK) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "Plugin path for %s/%s => [%s]" + , plugintype, pluginname, PluginPath); + } + return PluginPath; + } + /* FIXME: Put alias file processing here... */ + } + + /* Can't find 'em all... */ + return PluginPath; +} + +static PIL_rc +PluginExists(const char * PluginPath) +{ + /* Make sure we can read and execute the plugin file */ + /* This test is nice, because dlopen reasons aren't return codes */ + + if (access(PluginPath, R_OK) != 0) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Plugin file %s does not exist" + , PluginPath); + } + return PIL_NOPLUGIN; + } + return PIL_OK; +} + +/* Return PIL_OK if the given plugin exists */ +PIL_rc +PILPluginExists(PILPluginUniv* piuniv +, const char * plugintype +, const char * pluginname) +{ + PIL_rc rc; + char * path = PILPluginPath(piuniv, plugintype, pluginname); + + if (path == NULL) { + return PIL_INVAL; + } + rc = PluginExists(path); + DELETE(path); + return rc; +} + +/* + * PILLoadPlugin() - loads a plugin into memory and calls the + * initial() entry point in the plugin. + * + * + * Method: + * + * Construct file name of plugin. + * See if plugin exists. If not, fail with PIL_NOPLUGIN. + * + * Search Universe for plugin type + * If found, search plugin type for pluginname + * if found, fail with PIL_EXIST. + * Otherwise, + * Create new Plugin type structure + * Use lt_dlopen() on plugin to get lt_dlhandle for it. + * + * Construct the symbol name of the initialization function. + * + * Use lt_dlsym() to find the pointer to the init function. + * + * Call the initialization function. + */ +PIL_rc +PILLoadPlugin(PILPluginUniv* universe, const char * plugintype +, const char * pluginname +, void* plugin_user_data) +{ + PIL_rc rc; + char * PluginPath; + char * PluginSym; + PILPluginType* pitype; + PILPlugin* piinfo; + lt_dlhandle dlhand; + PILPluginInitFun initfun; + + PluginPath = PILPluginPath(universe, plugintype, pluginname); + + if ((rc=PluginExists(PluginPath)) != PIL_OK) { + DELETE(PluginPath); + return rc; + } + + if((pitype=g_hash_table_lookup(universe->PluginTypes, plugintype)) + != NULL) { + if ((piinfo = g_hash_table_lookup + ( pitype->Plugins, pluginname)) != NULL) { + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Plugin %s already loaded" + , PluginPath); + } + DELETE(PluginPath); + return PIL_EXIST; + } + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PluginType %s already present" + , plugintype); + } + }else{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Creating PluginType for %s" + , plugintype); + } + /* Create a new PILPluginType object */ + pitype = NewPILPluginType(universe, plugintype); + } + + g_assert(pitype != NULL); + + /* + * At this point, we have a PILPluginType object and our + * plugin name is not listed in it. + */ + + dlhand = lt_dlopen(PluginPath); + + if (!dlhand) { + PILLog(PIL_WARN + , "lt_dlopen() failure on plugin %s/%s [%s]." + " Reason: [%s]" + , plugintype, pluginname + , PluginPath + , lt_dlerror()); + DELETE(PluginPath); + return PIL_NOPLUGIN; + } + DELETE(PluginPath); + /* Construct the magic init function symbol name */ + PluginSym = g_strdup_printf(PIL_FUNC_FMT + , plugintype, pluginname); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Plugin %s/%s init function: %s" + , plugintype, pluginname + , PluginSym); + } + + initfun = lt_dlsym(dlhand, PluginSym); + + if (initfun == NULL) { + PILLog(PIL_WARN + , "Plugin %s/%s init function (%s) not found" + , plugintype, pluginname, PluginSym); + DELETE(PluginSym); + lt_dlclose(dlhand); dlhand=NULL; + DelPILPluginType(pitype); + return PIL_NOPLUGIN; + } + DELETE(PluginSym); + /* + * Construct the new PILPlugin object + */ + piinfo = NewPILPlugin(pitype, pluginname, dlhand, initfun); + g_assert(piinfo != NULL); + g_hash_table_insert(pitype->Plugins, g_strdup(piinfo->plugin_name), piinfo); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Plugin %s/%s loaded and constructed." + , plugintype, pluginname); + } + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Calling init function in plugin %s/%s." + , plugintype, pluginname); + } + /* Save away the user_data for later */ + piinfo->ud_plugin = plugin_user_data; + /* initfun is allowed to change ud_plugin if they want */ + initfun(piinfo, universe->imports, plugin_user_data); + + return PIL_OK; +}/*PILLoadPlugin*/ + + + +#define REPORTERR(msg) PILLog(PIL_CRIT, "%s", msg) + +/* + * Register an interface. + * + * This function is exported to plugins for their use. + */ +static PIL_rc +PILRegisterInterface(PILPlugin* piinfo +, const char * interfacetype /* Type of interface */ +, const char * interfacename /* Name of interface */ +, void* Ops /* Info (functions) exported + by this interface */ +, PILInterfaceFun close_func /* Close function for interface */ +, PILInterface** interfaceid /* Interface id (OP) */ +, void** Imports /* Functions imported by + this interface (OP) */ +, void* ud_interface /* Optional user_data */ +) +{ + PILPluginUniv* piuniv; /* Universe this plugin is in */ + PILPluginType* pitype; /* Type of this plugin */ + PILInterfaceUniv* ifuniv; /* Universe this interface is in */ + PILInterfaceType*iftype; /* Type of this interface */ + PILInterface* ifinfo; /* Info about this Interface */ + + PILInterfaceType*ifmgrtype; /* PILInterfaceType for PI_IFMANAGER */ + PILInterface* ifmgrinfo; /* Interf info for "interfacetype" */ + const PILInterfaceOps* ifops; /* Ops vector for InterfaceManager */ + /* of type "interfacetype" */ + PIL_rc rc; + + if ( piinfo == NULL + || (pitype = piinfo->plugintype) == NULL + || (piuniv = pitype->piuniv) == NULL + || (ifuniv = piuniv->ifuniv) == NULL + || ifuniv->iftypes == NULL + ) { + REPORTERR("bad parameters to PILRegisterInterface"); + return PIL_INVAL; + } + + /* Now we have lots of info, but not quite enough... */ + + if ((iftype = g_hash_table_lookup(ifuniv->iftypes, interfacetype)) + == NULL) { + + /* Try to autoload the needed interface handler */ + rc = PILLoadPlugin(piuniv, PI_IFMANAGER, interfacetype, NULL); + + /* See if the interface handler loaded like we expect */ + if ((iftype = g_hash_table_lookup(ifuniv->iftypes + , interfacetype)) == NULL) { + return PIL_BADTYPE; + } + } + if ((ifinfo = g_hash_table_lookup(iftype->interfaces, interfacename)) + != NULL) { + g_warning("Attempt to register duplicate interface: %s/%s" + , interfacetype, interfacename); + return PIL_EXIST; + } + /* + * OK... Now we know it is valid, and isn't registered... + * Let's locate the InterfaceManager registrar for this type + */ + if ((ifmgrtype = g_hash_table_lookup(ifuniv->iftypes, PI_IFMANAGER)) + == NULL) { + REPORTERR("No " PI_IFMANAGER " type!"); + return PIL_OOPS; + } + if ((ifmgrinfo = g_hash_table_lookup(ifmgrtype->interfaces + , interfacetype)) == NULL) { + PILLog(PIL_CRIT + , "No interface manager for given type (%s) !" + , interfacetype); + return PIL_BADTYPE; + } + + ifops = ifmgrinfo->exports; + + /* Now we have all the information anyone could possibly want ;-) */ + + ifinfo = NewPILInterface(iftype, interfacename, Ops + , close_func, ud_interface, piinfo); + + g_assert(ifmgrinfo == ifinfo->ifmanager); + *interfaceid = ifinfo; + + /* Call the registration function for our interface type */ + rc = ifops->RegisterInterface(ifinfo, Imports); + + + /* Increment reference count of interface manager */ + IfIncrRefCount(ifmgrinfo, 1); + + /* Increment the ref count of the plugin that loaded us */ + PluginIncrRefCount(piinfo, 1); + + if (rc != PIL_OK) { + RemoveAPILInterface(ifinfo); + } + return rc; +} + +/* + * Method: + * + * Verify interface is valid. + * + * Call interface close function. + * + * Call interface manager unregister function + * + * Call RmAPILInterface to remove from InterfaceType table, and + * free interface object. + * + */ + +static PIL_rc +PILunregister_interface(PILInterface* id) +{ + PILInterfaceType* t; + PILInterfaceUniv* u; + PIL_rc rc; + PILInterface* ifmgr_info; /* Pointer to our interface handler */ + const PILInterfaceOps* exports; /* InterfaceManager operations for + * the type of interface we are + */ + + if ( id == NULL + || (t = id->interfacetype) == NULL + || (u = t->universe) == NULL + || id->interfacename == NULL) { + PILLog(PIL_WARN, "PILunregister_interface: bad interfaceid"); + return PIL_INVAL; + } + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PILunregister_interface(%s/%s)" + , t->typename, id->interfacename); + } + PILValidateInterface(NULL, id, t); + PILValidateInterfaceType(NULL, t, u); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Calling InterfaceClose on %s/%s" + , t->typename, id->interfacename); + } + + /* Call the close function supplied by the interface */ + + if ((id->if_close != NULL) + && ((rc=id->if_close(id, id->ud_interface)) != PIL_OK)) { + PILLog(PIL_WARN, "InterfaceClose on %s/%s returned %s" + , t->typename, id->interfacename + , PIL_strerror(rc)); + } else { + rc = PIL_OK; + } + + /* Find the InterfaceManager that manages us */ + ifmgr_info = t->ifmgr_ref; + + g_assert(ifmgr_info != NULL); + + /* Find the exported functions from that IFIF */ + exports = ifmgr_info->exports; + + g_assert(exports != NULL && exports->UnRegisterInterface != NULL); + + /* Call the interface manager unregister function */ + exports->UnRegisterInterface(id); + + /* Decrement reference count of interface manager */ + IfIncrRefCount(ifmgr_info, -1); + /* This may make ifmgr_info invalid */ + ifmgr_info = NULL; + + /* Decrement the reference count of the plugin that loaded us */ + PluginIncrRefCount(id->loadingpi, -1); + + return rc; +} + +static PILInterfaceUniv* +NewPILInterfaceUniv(PILPluginUniv* piuniv) +{ + PILInterfaceUniv* ret = NEW(PILInterfaceUniv); + static int ltinityet = 0; + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILInterfaceUniv(0x%lx)" + , (unsigned long)ret); + } + if (!ltinityet) { + ltinityet=1; + lt_dlinit(); + } + STATNEW(interfaceuniv); + ret->MagicNum = PIL_MAGIC_INTERFACEUNIV; + /* Make the two universes point at each other */ + ret->piuniv = piuniv; + piuniv->ifuniv = ret; + + ret->iftypes = g_hash_table_new(g_str_hash, g_str_equal); + + InterfaceManager_plugin_init(piuniv); + return ret; +} + +static void +DelPILInterfaceUniv(PILInterfaceUniv* ifuniv) +{ + PILInterfaceType* ifmgrtype; + g_assert(ifuniv!= NULL && ifuniv->iftypes != NULL); + PILValidateInterfaceUniv(NULL, ifuniv, NULL); + + STATFREE(interfaceuniv); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILInterfaceUniv(0x%lx)" + , (unsigned long) ifuniv); + } + g_hash_table_foreach_remove(ifuniv->iftypes, RmAPILInterfaceType, NULL); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILInterfaceUniv: final cleanup"); + } + ifmgrtype = g_hash_table_lookup(ifuniv->iftypes, PI_IFMANAGER); + RemoveAPILInterfaceType(ifmgrtype, ifmgrtype); + /* + * FIXME! need to delete the interface for PI_IFMANAGER last + * Right now, it seems to happen last, but I think that's + * coincidence... + */ + g_hash_table_destroy(ifuniv->iftypes); + ZAP(ifuniv); + DELETE(ifuniv); +} + +/* + * These RmA* functions primarily called from hash_table_foreach, + * so they have gpointer arguments. This *not necessarily* clause + * is why they do the g_hash_table_lookup_extended call instead of + * just deleting the key. When called from outside, the key + * may not be pointing at the key to actually free, but a copy + * of the key. + */ +static gboolean /* IsA GHFunc: required for g_hash_table_foreach_remove() */ +RmAPILInterfaceType +( gpointer typename /* Name of this interface type */ +, gpointer iftype /* PILInterfaceType* */ +, gpointer notused +) +{ + PILInterfaceType* Iftype = iftype; + PILInterfaceUniv* Ifuniv = Iftype->universe; + + /* + * We are not always called by g_hash_table_foreach_remove() + */ + + g_assert(IS_PILINTERFACETYPE(Iftype)); + PILValidateInterfaceUniv(NULL, Ifuniv, NULL); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILInterfaceType(%s)" + , (char*)typename); + } + if (iftype != notused + && strcmp(Iftype->typename, PI_IFMANAGER) == 0) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILInterfaceType: skipping (%s)" + , (char*)typename); + } + return FALSE; + } + + DelPILInterfaceType(iftype); + DELETE(typename); + + return TRUE; +} + +static void +RemoveAPILInterfaceType(PILInterfaceType*Iftype, PILInterfaceType* t2) +{ + PILInterfaceUniv* Ifuniv = Iftype->universe; + gpointer key; + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RemoveAPILInterfaceType(%s)" + , Iftype->typename); + } + if (t2 != Iftype + && strcmp(Iftype->typename, PI_IFMANAGER) == 0) { + PILLog(PIL_DEBUG, "RemoveAPILInterfaceType: skipping (%s)" + , Iftype->typename); + return; + } + if (g_hash_table_lookup_extended(Ifuniv->iftypes + , Iftype->typename, &key, (gpointer)&Iftype)) { + + g_hash_table_remove(Ifuniv->iftypes, key); + RmAPILInterfaceType(key, Iftype, t2); + }else{ + g_assert_not_reached(); + } +} + +/* + * We need to write more functions: These include... + * + * Plugin functions: + * + * PILPluginPath() - returns path name for a given plugin + * + * PILPluginTypeList() - returns list of plugins of a given type + * + */ +static void free_dirlist(struct dirent** dlist, int n); + +static int qsort_string_cmp(const void *a, const void *b); + + +static void +free_dirlist(struct dirent** dlist, int n) +{ + int j; + for (j=0; j < n; ++j) { + if (dlist[j]) { + free(dlist[j]); + dlist[j] = NULL; + } + } + free(dlist); +} + +static int +qsort_string_cmp(const void *a, const void *b) +{ + return(strcmp(*(const char * const *)a, *(const char * const *)b)); +} + +#define FREE_DIRLIST(dlist, n) {free_dirlist(dlist, n); dlist = NULL;} + +static int +so_select (const struct dirent *dire) +{ + + const char obj_end [] = PLUGINSUFFIX; + const char *end = &dire->d_name[strlen(dire->d_name) + - (STRLEN_CONST(obj_end))]; + + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "In so_select: %s.", dire->d_name); + } + if (end < dire->d_name) { + return 0; + } + if (strcmp(end, obj_end) == 0) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "FILE %s looks like a plugin name." + , dire->d_name); + } + return 1; + } + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "FILE %s Doesn't look like a plugin name [%s] " + "%zd %zd %s." + , dire->d_name, end + , sizeof(obj_end), strlen(dire->d_name) + , &dire->d_name[strlen(dire->d_name) + - (STRLEN_CONST(obj_end))]); + } + + return 0; +} + +/* Return (sorted) list of available plugin names */ +static char** +PILPluginTypeListPlugins(PILPluginType* pitype +, int * picount /* Can be NULL ... */) +{ + const char * piclass = pitype->plugintype; + unsigned plugincount = 0; + char ** result = NULL; + int initoff = 0; + char ** pelem; + + /* Return all the plugins in all the directories in the PATH */ + + for (pelem=pitype->piuniv->rootdirlist; *pelem; ++pelem) { + int j; + GString* path; + int dircount; + struct dirent** files; + + + path = g_string_new(*pelem); + g_assert(piclass != NULL); + if (piclass) { + if (g_string_append_c(path, G_DIR_SEPARATOR) == NULL + || g_string_append(path, piclass) == NULL) { + g_string_free(path, 1); path = NULL; + return(NULL); + } + } + + files = NULL; + errno = 0; + dircount = scandir(path->str, &files + , SCANSEL_CAST so_select, NULL); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PILS: Examining directory [%s]" + ": [%d] files matching [%s] suffix found." + , path->str, dircount, PLUGINSUFFIX); + } + g_string_free(path, 1); path=NULL; + + if (dircount <= 0) { + if (files != NULL) { + FREE_DIRLIST(files, dircount); + files = NULL; + } + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "PILS: skipping empty directory" + " in PILPluginTypeListPlugins()"); + } + continue; + } + + initoff = plugincount; + plugincount += dircount; + if (result == NULL) { + result = (char **) g_malloc((plugincount+1)*sizeof(char *)); + }else{ + result = (char **) g_realloc(result + , (plugincount+1)*sizeof(char *)); + } + + for (j=0; j < dircount; ++j) { + char* s; + unsigned slen = strlen(files[j]->d_name) + - STRLEN_CONST(PLUGINSUFFIX); + + s = g_malloc(slen+1); + strncpy(s, files[j]->d_name, slen); + s[slen] = EOS; + result[initoff+j] = s; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PILS: plugin [%s] found" + , s); + } + } + FREE_DIRLIST(files, dircount); + files = NULL; + } + + if (picount != NULL) { + *picount = plugincount; + } + if (result) { + result[plugincount] = NULL; + /* Return them in sorted order... */ + qsort(result, plugincount, sizeof(char *), qsort_string_cmp); + }else{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PILS: NULL return" + " from PILPluginTypeListPlugins()"); + } + } + + + return result; +} +/* Return (sorted) list of available plugin names */ +char** +PILListPlugins(PILPluginUniv* u, const char * pitype +, int * picount /* Can be NULL ... */) +{ + PILPluginType* t; + if ((t = g_hash_table_lookup(u->PluginTypes, pitype)) == NULL) { + if (picount) { + *picount = 0; + } + t = NewPILPluginType(u, pitype); + if (!t) { + return NULL; + } + } + return PILPluginTypeListPlugins(t, picount); +} + +void +PILFreePluginList(char ** pluginlist) +{ + char ** ml = pluginlist; + + if (!ml) { + return; + } + + while (*ml != NULL) { + DELETE(*ml); + } + DELETE(pluginlist); +} + + +static void +PILValidatePlugin(gpointer key, gpointer plugin, gpointer pitype) +{ + const char * Key = key; + const PILPlugin * Plugin = plugin; + + g_assert(IS_PILPLUGIN(Plugin)); + + g_assert(Key == NULL || strcmp(Key, Plugin->plugin_name) == 0); + + g_assert (Plugin->refcnt >= 0 ); + + /* g_assert (Plugin->pluginops != NULL ); */ + g_assert (strcmp(Key, PI_IFMANAGER) == 0 || Plugin->dlinitfun != NULL ); + g_assert (strcmp(Plugin->plugin_name, PI_IFMANAGER) == 0 + || Plugin->dlhandle != NULL); + g_assert(Plugin->plugintype != NULL); + g_assert(IS_PILPLUGINTYPE(Plugin->plugintype)); + g_assert(pitype == NULL || pitype == Plugin->plugintype); +} + +static void +PILValidatePluginType(gpointer key, gpointer pitype, gpointer piuniv) +{ + char * Key = key; + PILPluginType * Pitype = pitype; + PILPluginUniv * Muniv = piuniv; + + g_assert(IS_PILPLUGINTYPE(Pitype)); + g_assert(Muniv == NULL || IS_PILPLUGINUNIV(Muniv)); + g_assert(Key == NULL || strcmp(Key, Pitype->plugintype) == 0); + g_assert(IS_PILPLUGINUNIV(Pitype->piuniv)); + g_assert(piuniv == NULL || piuniv == Pitype->piuniv); + g_assert(Pitype->Plugins != NULL); + g_hash_table_foreach(Pitype->Plugins, PILValidatePlugin, Pitype); +} +static void +PILValidatePluginUniv(gpointer key, gpointer piuniv, gpointer dummy) +{ + PILPluginUniv * Muniv = piuniv; + + g_assert(IS_PILPLUGINUNIV(Muniv)); + g_assert(Muniv->rootdirlist != NULL); + g_assert(Muniv->imports != NULL); + g_hash_table_foreach(Muniv->PluginTypes, PILValidatePluginType, piuniv); + PILValidateInterfaceUniv(NULL, Muniv->ifuniv, piuniv); +} +static void +PILValidateInterface(gpointer key, gpointer interface, gpointer iftype) +{ + char * Key = key; + PILInterface* Interface = interface; + g_assert(IS_PILINTERFACE(Interface)); + g_assert(Key == NULL || strcmp(Key, Interface->interfacename) == 0); + g_assert(IS_PILINTERFACETYPE(Interface->interfacetype)); + g_assert(iftype == NULL || iftype == Interface->interfacetype); + g_assert(Interface->ifmanager!= NULL); + g_assert(IS_PILINTERFACE(Interface->ifmanager)); + g_assert(strcmp(Interface->interfacetype->typename + , Interface->ifmanager->interfacename)== 0); + g_assert(Interface->exports != NULL); +} +static void +PILValidateInterfaceType(gpointer key, gpointer iftype, gpointer ifuniv) +{ + char * Key = key; + PILInterfaceType* Iftype = iftype; + g_assert(IS_PILINTERFACETYPE(Iftype)); + g_assert(Key == NULL || strcmp(Key, Iftype->typename) == 0); + g_assert(ifuniv == NULL || Iftype->universe == ifuniv); + g_assert(Iftype->interfaces != NULL); + g_assert(Iftype->ifmgr_ref != NULL); + g_assert(IS_PILINTERFACE(Iftype->ifmgr_ref)); + g_assert(Key == NULL || strcmp(Key, Iftype->ifmgr_ref->interfacename) == 0); + + g_hash_table_foreach(Iftype->interfaces, PILValidateInterface, iftype); +} +static void +PILValidateInterfaceUniv(gpointer key, gpointer ifuniv, gpointer piuniv) +{ + PILInterfaceUniv* Ifuniv = ifuniv; + PILPluginUniv* Pluginuniv = piuniv; + g_assert(IS_PILINTERFACEUNIV(Ifuniv)); + g_assert(Pluginuniv == NULL || IS_PILPLUGINUNIV(Pluginuniv)); + g_assert(piuniv == NULL || piuniv == Ifuniv->piuniv); + g_hash_table_foreach(Ifuniv->iftypes, PILValidateInterfaceType, ifuniv); +} + +#define PRSTAT(type) { \ + PILLog(PIL_INFO, "Plugin system objects (" #type "): " \ + "\tnew %ld free \%ld current %ld" \ + , PILstats.type.news \ + , PILstats.type.frees \ + , PILstats.type.news - PILstats.type.frees); \ +} +void +PILLogMemStats(void) +{ + PRSTAT(plugin); + PRSTAT(pitype); + PRSTAT(piuniv); + PRSTAT(interface); + PRSTAT(interfacetype); + PRSTAT(interfaceuniv); +} + +/* + * Function for logging with the given logging function + * The reason why it's here is so we can get printf arg checking + * You can't get that when you call a function pointer directly. + */ +void +PILCallLog(PILLogFun logfun, PILLogLevel priority, const char * fmt, ...) +{ + va_list args; + char * str; + int err = errno; + + va_start (args, fmt); + str = g_strdup_vprintf(fmt, args); + va_end (args); + logfun(priority, "%s", str); + g_free(str); + errno = err; +} diff --git a/lib/pils/test.c b/lib/pils/test.c new file mode 100644 index 0000000..c2cdb26 --- /dev/null +++ b/lib/pils/test.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2001 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * Sample Interface manager. + */ +#define PIL_PLUGINTYPE test +#define PIL_PLUGINTYPENAME "test" +#define PIL_PLUGIN test +#define PIL_PLUGINNAME "test" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +/* We are a interface manager... */ +#define ENABLE_PLUGIN_MANAGER_PRIVATE + +#include <pils/interface.h> + +PIL_PLUGIN_BOILERPLATE("1.0", DebugFlag, Ourclose) + +/* + * Places to store information gotten during registration. + */ +static const PILPluginImports* OurPIImports; /* Imported plugin funs */ +static PILPlugin* OurPlugin; /* Our plugin info */ +static PILInterfaceImports* OurIfImports; /* Interface imported funs */ +static PILInterface* OurIf; /* Pointer to interface info */ + +static void +Ourclose (PILPlugin* us) +{ +} + +/* + * Our Interface Manager interfaces - exported to the universe! + * + * (or at least the interface management universe ;-). + * + */ +static PILInterfaceOps OurIfOps = { + /* FIXME -- put some in here !! */ +}; + +PIL_rc PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void*); + +static PIL_rc +IfClose(PILInterface*intf, void* ud_interface) +{ + OurPIImports->log(PIL_INFO, "In Ifclose (test plugin)"); + return PIL_OK; +} + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void *user_ptr) +{ + PIL_rc ret; + /* + * Force compiler to check our parameters... + */ + PILPluginInitFun fun = &PIL_PLUGIN_INIT; (void)fun; + + + OurPIImports = imports; + OurPlugin = us; + + imports->log(PIL_INFO, "Plugin %s: user_ptr = %lx" + , PIL_PLUGINNAME, (unsigned long)user_ptr); + + imports->log(PIL_INFO, "Registering ourselves as a plugin"); + + /* Register as a plugin */ + imports->register_plugin(us, &OurPIExports); + + imports->log(PIL_INFO, "Registering our interfaces"); + + /* Register our interfaces */ + ret = imports->register_interface + ( us + , PIL_PLUGINTYPENAME + , PIL_PLUGINNAME + , &OurIfOps /* Exported interface operations */ + , IfClose /* Interface Close function */ + , &OurIf + , (void*)&OurIfImports + , NULL); + imports->log(PIL_INFO, "test init function: returning %d" + , ret); + + return ret; +} diff --git a/lib/plugins/InterfaceMgr/HBauth.c b/lib/plugins/InterfaceMgr/HBauth.c new file mode 100644 index 0000000..eae22cf --- /dev/null +++ b/lib/plugins/InterfaceMgr/HBauth.c @@ -0,0 +1,171 @@ +/* + * Heartbeat authentication interface manager + * + * Copyright 2001 Alan Robertson <alanr@unix.sh> + * Licensed under the GNU Lesser General Public License + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + */ +#define PIL_PLUGINTYPE InterfaceMgr +#define PIL_PLUGIN HBauth + +#define PIN(f) #f +#define PIN2(f) PIN(f) +#define PIN3 PIN2(PIL_PLUGIN) +#define PIT PIN2(PIL_PLUGINTYPE) + +/* We are a interface manager... */ +#define ENABLE_PLUGIN_MANAGER_PRIVATE + +#include <lha_internal.h> +#include <pils/interface.h> +#include <stdio.h> + +PIL_PLUGIN_BOILERPLATE2("1.0", AuthDebugFlag) + + +/* + * Places to store information gotten during registration. + */ +static const PILPluginImports* AuthPIImports; /* Imported plugin fcns */ +static PILPlugin* AuthPlugin; /* Our plugin info */ +static PILInterfaceImports* AuthIfImports; /* Interface imported fcns */ +static PILInterface* AuthIf; /* Our Auth Interface info */ + +/* Our exported auth interface management functions */ +static PIL_rc RegisterAuthIF(PILInterface* ifenv, void** imports); + +static PIL_rc UnregisterAuthIF(PILInterface*iifinfo); + +/* + * Our Interface Manager interfaces - exported to the universe! + * + * (or at least to the interface management universe ;-). + * + * These are the interfaces which are used to manage our + * client authentication interfaces + * + */ +static PILInterfaceOps AuthIfOps = +{ RegisterAuthIF +, UnregisterAuthIF +}; + + +PIL_rc PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void*); + +/* + * Our user_ptr is presumed to point at a GHashTable for us + * to put plugin into when they show up, and drop from when + * they disappear. + * + * We need to think more carefully about the way for us to get + * the user_ptr from the global environment. + * + * We need to think more carefully about how interface registration + * etc. interact with plugin loading, reference counts, etc. and how + * the application that uses us (i.e., heartbeat) interacts with us. + * + * Issues include: + * - freeing all memory, + * - making sure things are all cleaned up correctly + * - Thread-safety? + * + * I think the global system should handle thread-safety. + */ + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void *user_ptr) +{ + PIL_rc ret; + /* + * Force compiler to check our parameters... + */ + PILPluginInitFun fun = &PIL_PLUGIN_INIT; (void)fun; + + + if (user_ptr == NULL) { + imports->log(PIL_CRIT + , "Interface Manager %s requires non-NULL " + " user pointer (to GHashTable) at initialization" + , PIN3); + return PIL_INVAL; + } + + AuthPIImports = imports; + AuthPlugin = us; + + /* Register as a plugin */ + imports->register_plugin(us, &OurPIExports); + + + /* Register our interfaces */ + ret = imports->register_interface(us + , PIT + , PIN3 + , &AuthIfOps + , NULL + , &AuthIf /* Our interface object pointer */ + , (void**)&AuthIfImports /* Interface-imported functions */ + , user_ptr); + return ret; +} + +/* + * We get called for every authentication interface that gets registered. + * + * It's our job to make the authentication interface that's + * registering with us available to the system. + * + * We do that by adding it to a g_hash_table of authentication + * plugin. The rest of the system takes it from there... + * The key is the authentication method, and the data + * is a pointer to the functions the method exports. + * It's a piece of cake ;-) + */ +static PIL_rc +RegisterAuthIF(PILInterface* intf, void** imports) +{ + GHashTable* authtbl = intf->ifmanager->ud_interface; + + g_assert(authtbl != NULL); + + /* Reference count should now be one */ + g_assert(intf->refcnt == 1); + g_hash_table_insert(authtbl, intf->interfacename, intf->exports); + + return PIL_OK; +} + +/* Unregister a client authentication interface - + * We get called from the interface mgmt sys when someone requests that + * a interface be unregistered. + */ +static PIL_rc +UnregisterAuthIF(PILInterface*intf) +{ + GHashTable* authtbl = intf->ifmanager->ud_interface; + g_assert(authtbl != NULL); + + intf->refcnt--; + g_assert(intf->refcnt >= 0); + if (intf->refcnt <= 0) { + g_hash_table_remove(authtbl, intf->interfacetype); + } + return PIL_OK; +} + diff --git a/lib/plugins/InterfaceMgr/Makefile.am b/lib/plugins/InterfaceMgr/Makefile.am new file mode 100644 index 0000000..86b88d1 --- /dev/null +++ b/lib/plugins/InterfaceMgr/Makefile.am @@ -0,0 +1,33 @@ +# +# InterfaceMgr: Interface manager plugins for Linux-HA +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl \ + -I$(top_builddir)/lib/upmls -I$(top_srcdir)/lib/upmls + +## libraries + +plugindir = $(libdir)/@HB_PKG@/plugins/InterfaceMgr +plugin_LTLIBRARIES = generic.la + +generic_la_SOURCES = generic.c +generic_la_LDFLAGS = -export-dynamic -module -avoid-version diff --git a/lib/plugins/InterfaceMgr/generic.c b/lib/plugins/InterfaceMgr/generic.c new file mode 100644 index 0000000..6ddad3b --- /dev/null +++ b/lib/plugins/InterfaceMgr/generic.c @@ -0,0 +1,452 @@ +/* + * + * Generic interface (implementation) manager + * + * Copyright 2001 Alan Robertson <alanr@unix.sh> + * Licensed under the GNU Lesser General Public License + * + * This manager will manage any number of types of interfaces. + * + * This means that when any implementations of our client interfaces register + * or unregister, it is us that makes their interfaces show up in the outside + * world. + * + * And, of course, we have to do this in a very generic way, since we have + * no idea about the client programs or interface types, or anything else. + * + * We do that by getting a parameter passed to us which tell us the names + * of the interface types we want to manage, and the address of a GHashTable + * for each type that we put the implementation in when they register + * themselves. + * + * So, each type of interface that we manage gets its own private + * GHashTable of the implementations of that type that are currently + * registered. + * + * For example, if we manage communication modules, their exported + * interfaces will be registered in a hash table. If we manage + * authentication modules, they'll have their (separate) hash table that + * their exported interfaces are registered in. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define PIL_PLUGINTYPE InterfaceMgr +#define PIL_PLUGINTYPE_S "InterfaceMgr" +#define PIL_PLUGIN generic +#define PIL_PLUGIN_S "generic" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +/* We are an interface manager... */ +#define ENABLE_PLUGIN_MANAGER_PRIVATE +#define ENABLE_PIL_DEFS_PRIVATE + +#include <lha_internal.h> +#include <pils/generic.h> + +#include <stdio.h> + +PIL_PLUGIN_BOILERPLATE("1.0", GenDebugFlag, CloseGeneralPluginManager) + +/* + * Key is interface type, value is a PILGenericIfMgmtRqst. + * The key is g_strdup()ed, but the struct is not copied. + */ + +static gboolean FreeAKey(gpointer key, gpointer value, gpointer data); + +/* + * Places to store information gotten during registration. + */ +static const PILPluginImports* GenPIImports; /* Imported plugin fcns */ +static PILPlugin* GenPlugin; /* Our plugin info */ +static PILInterfaceImports* GenIfImports; /* Interface imported fcns */ + +/* Our exported generic interface management functions */ +static PIL_rc RegisterGenIF(PILInterface* ifenv, void** imports); + +static PIL_rc UnregisterGenIF(PILInterface*iifinfo); + +static PIL_rc CloseGenInterfaceManager(PILInterface*, void* info); + +/* + * Our Interface Manager interfaces - exported to the universe! + * + * (or at least to the interface management universe ;-). + * + * These are the interfaces which are used to manage our + * client implementations + */ +static PILInterfaceOps GenIfOps = +{ RegisterGenIF +, UnregisterGenIF +}; + + +PIL_rc PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void*); + +/* + * Our user_ptr is presumed to point to NULL-terminated array of + * PILGenericIfMgmtRqst structs. + * + * These requests have pointers to GHashTables for us + * to put plugins into when they show up, and drop from when + * they disappear. + * + * Issues include: + * - freeing all memory, + * - making sure things are all cleaned up correctly + * - Thread-safety? + * + * IMHO the global system should handle thread-safety. + */ +static PIL_rc AddAnInterfaceType(PILPlugin*us, GHashTable* MasterTable, PILGenericIfMgmtRqst* req); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void *user_ptr) +{ + PIL_rc ret; + PILGenericIfMgmtRqst* user_req; + PILGenericIfMgmtRqst* curreq; + GHashTable* MasterTable = NULL; + /* + * Force the compiler to check our parameters... + */ + PILPluginInitFun fun = &PIL_PLUGIN_INIT; (void)fun; + + + GenPIImports = imports; + + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "IF manager %s: initializing.", PIL_PLUGIN_S); + } + + if (user_ptr == NULL) { + PILCallLog(GenPIImports->log, PIL_CRIT + , "%s Interface Manager requires non-NULL " + " PILGenericIfMgmtRqst user pointer at initialization." + , PIL_PLUGIN_S); + return PIL_INVAL; + } + + GenPlugin = us; + + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "IF manager %s: registering as a plugin." + , PIL_PLUGIN_S); + } + + user_req = user_ptr; + MasterTable = g_hash_table_new(g_str_hash, g_str_equal); + us->ud_plugin = MasterTable; /* Override passed value */ + + /* Register ourselves as a plugin */ + + if ((ret = imports->register_plugin(us, &OurPIExports)) != PIL_OK) { + PILCallLog(imports->log, PIL_CRIT + , "IF manager %s unable to register as plugin (%s)" + , PIL_PLUGIN_S, PIL_strerror(ret)); + + return ret; + } + + /* + * Register to manage implementations + * for all the interface types we've been asked to manage. + */ + + for(curreq = user_req; curreq->iftype != NULL; ++curreq) { + PIL_rc newret; + + newret = AddAnInterfaceType(us, MasterTable, curreq); + + if (newret != PIL_OK) { + ret = newret; + } + } + + /* + * Our plugin and all our registered plugin types + * have ud_plugin pointing at MasterTable. + */ + + return ret; +} + +static PIL_rc +AddAnInterfaceType(PILPlugin*us, GHashTable* MasterTable, PILGenericIfMgmtRqst* req) +{ + PIL_rc rc; + PILInterface* GenIf; /* Our Generic Interface info*/ + + g_assert(MasterTable != NULL); + g_hash_table_insert(MasterTable, g_strdup(req->iftype), req); + + if (req->ifmap == NULL) { + PILCallLog(GenPIImports->log, PIL_CRIT + , "IF manager %s: iftype %s has NULL" + " ifmap pointer address." + , PIL_PLUGIN_S, req->iftype); + return PIL_INVAL; + } + if ((*req->ifmap) != NULL) { + PILCallLog(GenPIImports->log, PIL_CRIT + , "IF manager %s: iftype %s GHashTable pointer" + " was not initialized to NULL" + , PIL_PLUGIN_S, req->iftype); + return PIL_INVAL; + } + + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "IF manager %s: registering ourselves" + " to manage interface type %s" + , PIL_PLUGIN_S, req->iftype); + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: ifmap: 0x%lx callback: 0x%lx" + " imports: 0x%lx" + , PIL_PLUGIN_S + , (unsigned long)req->ifmap + , (unsigned long)req->callback + , (unsigned long)req->importfuns); + } + + /* Create the hash table to communicate with this client */ + *(req->ifmap) = g_hash_table_new(g_str_hash, g_str_equal); + + rc = GenPIImports->register_interface(us + , PIL_PLUGINTYPE_S + , req->iftype /* the iftype we're managing here */ + , &GenIfOps + , CloseGenInterfaceManager + , &GenIf + , (void*)&GenIfImports + , MasterTable); /* Point ud_interface to MasterTable */ + + /* We don't ever want to be unloaded... */ + GenIfImports->ModRefCount(GenIf, +100); + + if (rc != PIL_OK) { + PILCallLog(GenPIImports->log, PIL_CRIT + , "Generic interface manager %s: unable to register" + " to manage interface type %s: %s" + , PIL_PLUGIN_S, req->iftype + , PIL_strerror(rc)); + } + return rc; +} + +static void +CloseGeneralPluginManager(PILPlugin* us) +{ + + GHashTable* MasterTable = us->ud_plugin; + int count; + + g_assert(MasterTable != NULL); + + /* + * All our clients have already been shut down automatically + * This is the final shutdown for us... + */ + + + /* There *shouldn't* be any keys in there ;-) */ + + if ((count=g_hash_table_size(MasterTable)) > 0) { + + /* But just in case there are... */ + g_hash_table_foreach_remove(MasterTable, FreeAKey, NULL); + } + g_hash_table_destroy(MasterTable); + us->ud_plugin = NULL; + return; +} + +/* + * We get called for every time an implementation registers itself as + * implementing one of the kinds of interfaces we manage. + * + * It's our job to make the implementation that's + * registering with us available to the system. + * + * We do that by adding it to a GHashTable for its interface type + * Our users in the rest of the system takes it from there... + * + * The key to the GHashTable is the implementation name, and the data is + * a pointer to the information the implementation exports. + * + * It's a piece of cake ;-) + */ +static PIL_rc +RegisterGenIF(PILInterface* intf, void** imports) +{ + PILGenericIfMgmtRqst* ifinfo; + GHashTable* MasterTable = intf->ifmanager->ud_interface; + + g_assert(MasterTable != NULL); + + /* Reference count should now be one */ + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: interface %s/%s registering." + , PIL_PLUGIN_S, intf->interfacetype->typename + , intf->interfacename); + } + g_assert(intf->refcnt == 1); + /* + * We need to add it to the table that goes with this particular + * type of interface. + */ + if ((ifinfo = g_hash_table_lookup(MasterTable + , intf->interfacetype->typename)) != NULL) { + GHashTable* ifmap = *(ifinfo->ifmap); + + g_hash_table_insert(ifmap, intf->interfacename,intf->exports); + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: Inserted interface [%s] in hash" + " table @ 0x%08lx" + , PIL_PLUGIN_S, intf->interfacename + , (unsigned long)ifmap); + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: Exports are here: 0x%08x" + , PIL_PLUGIN_S + , GPOINTER_TO_UINT(intf->exports)); + } + + if (ifinfo->callback != NULL) { + PILInterfaceType* t = intf->interfacetype; + + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: callback 0x%lx" + , PIL_PLUGIN_S + , (unsigned long)ifinfo->callback); + } + ifinfo->callback(PIL_REGISTER + , t->universe->piuniv, intf->interfacename + , t->typename, ifinfo->userptr); + } + + *imports = ifinfo->importfuns; + + return PIL_OK; + + }else{ + PILCallLog(GenPIImports->log, PIL_WARN + , "RegisterGenIF: interface type %s not found" + , intf->interfacename); + } + return PIL_INVAL; +} + +/* Unregister an implementation - + * We get called from the interface management system when someone + * has requested that an implementation of a client interface be + * unregistered. + */ +static PIL_rc +UnregisterGenIF(PILInterface*intf) +{ + GHashTable* MasterTable = intf->ifmanager->ud_interface; + PILGenericIfMgmtRqst* ifinfo; + + g_assert(MasterTable != NULL); + g_assert(intf->refcnt >= 0); + /* + * Go through the "master table" and find client table, + * notify client we're about to remove this entry, then + * then remove this entry from it. + */ + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: unregistering interface %s/%s." + , PIL_PLUGIN_S, intf->interfacetype->typename + , intf->interfacename); + } + if ((ifinfo = g_hash_table_lookup(MasterTable + , intf->interfacetype->typename)) != NULL) { + + GHashTable* ifmap = *(ifinfo->ifmap); + + if (ifinfo->callback != NULL) { + PILInterfaceType* t = intf->interfacetype; + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: callback 0x%lx" + , PIL_PLUGIN_S + , (unsigned long)ifinfo->callback); + } + ifinfo->callback(PIL_UNREGISTER + , t->universe->piuniv, intf->interfacename + , t->typename, ifinfo->userptr); + } + + /* Remove the client entry from master table */ + g_hash_table_remove(ifmap, intf->interfacename); + + }else{ + PILCallLog(GenPIImports->log, PIL_WARN + , "UnregisterGenIF: interface type %s not found" + , intf->interfacename); + return PIL_INVAL; + } + return PIL_OK; +} + +/* + * Close down the generic interface manager. + */ +static PIL_rc +CloseGenInterfaceManager(PILInterface*intf, void* info) +{ + void* key; + void* data; + GHashTable* MasterTable = intf->ud_interface; + + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_INFO + , "In CloseGenInterFaceManager on %s/%s (MasterTable: 0x%08lx)" + , intf->interfacetype->typename, intf->interfacename + , (unsigned long)MasterTable); + } + + + g_assert(MasterTable != NULL); + if (g_hash_table_lookup_extended(MasterTable + , intf->interfacename, &key, &data)) { + PILGenericIfMgmtRqst* ifinfo = data; + g_hash_table_destroy(*(ifinfo->ifmap)); + *(ifinfo->ifmap) = NULL; + g_hash_table_remove(MasterTable, key); + g_free(key); + }else{ + g_assert_not_reached(); + } + return PIL_OK; +} + +static gboolean +FreeAKey(gpointer key, gpointer value, gpointer data) +{ + g_free(key); + return TRUE; +} diff --git a/lib/plugins/Makefile.am b/lib/plugins/Makefile.am new file mode 100644 index 0000000..21827cd --- /dev/null +++ b/lib/plugins/Makefile.am @@ -0,0 +1,20 @@ +# +# Copyright (C) 2008 Andrew Beekhof +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +MAINTAINERCLEANFILES = Makefile.in +SUBDIRS = InterfaceMgr stonith lrm compress diff --git a/lib/plugins/compress/Makefile.am b/lib/plugins/compress/Makefile.am new file mode 100644 index 0000000..3a3193a --- /dev/null +++ b/lib/plugins/compress/Makefile.am @@ -0,0 +1,52 @@ +# +# InterfaceMgr: Interface manager plugins for Linux-HA +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +if BUILD_ZLIB_COMPRESS_MODULE +zlibmodule = zlib.la +endif + +if BUILD_BZ2_COMPRESS_MODULE +bz2module = bz2.la +endif + +SUBDIRS = + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl \ + -I$(top_builddir)/lib/upmls -I$(top_srcdir)/lib/upmls + +AM_CFLAGS = @CFLAGS@ + +## libraries + +halibdir = $(libdir)/@HB_PKG@ +plugindir = $(halibdir)/plugins/compress +plugin_LTLIBRARIES = $(zlibmodule) $(bz2module) + +zlib_la_SOURCES = zlib.c +zlib_la_LDFLAGS = -export-dynamic -module -avoid-version -lz +zlib_la_LIBADD = $(top_builddir)/replace/libreplace.la + +bz2_la_SOURCES = bz2.c +bz2_la_LDFLAGS = -export-dynamic -module -avoid-version -lbz2 +bz2_la_LIBADD = $(top_builddir)/replace/libreplace.la + diff --git a/lib/plugins/compress/bz2.c b/lib/plugins/compress/bz2.c new file mode 100644 index 0000000..2eab116 --- /dev/null +++ b/lib/plugins/compress/bz2.c @@ -0,0 +1,142 @@ + /* bz2.c: compression module using bz2 for heartbeat. + * + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * SECURITY NOTE: It would be very easy for someone to masquerade as the + * device that you're pinging. If they don't know the password, all they can + * do is echo back the packets that you're sending out, or send out old ones. + * This does mean that if you're using such an approach, that someone could + * make you think you have quorum when you don't during a cluster partition. + * The danger in that seems small, but you never know ;-) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + + +#define PIL_PLUGINTYPE HB_COMPRESS_TYPE +#define PIL_PLUGINTYPE_S HB_COMPRESS_TYPE_S +#define PIL_PLUGIN bz2 +#define PIL_PLUGIN_S "bz2" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <lha_internal.h> +#include <stdio.h> +#include <pils/plugin.h> +#include <compress.h> +#include <bzlib.h> +#include <clplumbing/cl_log.h> +#include <string.h> + + +static struct hb_compress_fns bz2Ops; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static struct hb_media_imports* OurImports; +static void* interfprivate; + +#define LOG PluginImports->log +#define MALLOC PluginImports->alloc +#define STRDUP PluginImports->mstrdup +#define FREE PluginImports->mfree + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &bz2Ops + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , interfprivate); +} + +static int +bz2_compress(char* dest, size_t* destlen, + const char* _src, size_t srclen) +{ + int ret; + char* src; + unsigned int tmpdestlen; + + memcpy(&src, &_src, sizeof(char*)); + + tmpdestlen = *destlen; + ret = BZ2_bzBuffToBuffCompress(dest, &tmpdestlen, src, srclen, 1, 0, 30); + if (ret != BZ_OK){ + cl_log(LOG_ERR, "%s: compression failed", + __FUNCTION__); + return HA_FAIL; + } + + *destlen = tmpdestlen; + + return HA_OK; +} + +static int +bz2_decompress(char* dest, size_t* destlen, + const char* _src, size_t srclen) +{ + + int ret; + char* src; + unsigned int tmpdestlen; + + memcpy(&src, &_src, sizeof(char*)); + + tmpdestlen = *destlen; + ret = BZ2_bzBuffToBuffDecompress(dest, &tmpdestlen, src, srclen, 1, 0); + if (ret != BZ_OK){ + cl_log(LOG_ERR, "%s: decompression failed", + __FUNCTION__); + return HA_FAIL; + } + + *destlen = tmpdestlen; + + return HA_OK; +} + +static const char* +bz2_getname(void) +{ + return "bz2"; +} + +static struct hb_compress_fns bz2Ops ={ + bz2_compress, + bz2_decompress, + bz2_getname, +}; diff --git a/lib/plugins/compress/zlib.c b/lib/plugins/compress/zlib.c new file mode 100644 index 0000000..5958966 --- /dev/null +++ b/lib/plugins/compress/zlib.c @@ -0,0 +1,135 @@ + /* zlib.c: compression module using zlib for heartbeat. + * + * Copyright (C) 2005 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * SECURITY NOTE: It would be very easy for someone to masquerade as the + * device that you're pinging. If they don't know the password, all they can + * do is echo back the packets that you're sending out, or send out old ones. + * This does mean that if you're using such an approach, that someone could + * make you think you have quorum when you don't during a cluster partition. + * The danger in that seems small, but you never know ;-) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + + +#define PIL_PLUGINTYPE HB_COMPRESS_TYPE +#define PIL_PLUGINTYPE_S HB_COMPRESS_TYPE_S +#define PIL_PLUGIN zlib +#define PIL_PLUGIN_S "zlib" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <lha_internal.h> +#include <pils/plugin.h> +#include <compress.h> +#include <zlib.h> +#include <clplumbing/cl_log.h> +#include <string.h> + + +static struct hb_compress_fns zlibOps; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static struct hb_media_imports* OurImports; +static void* interfprivate; + +#define LOG PluginImports->log +#define MALLOC PluginImports->alloc +#define STRDUP PluginImports->mstrdup +#define FREE PluginImports->mfree + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &zlibOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , interfprivate); +} + +static int +zlib_compress(char* dest, size_t* _destlen, + const char* src, size_t _srclen) +{ + int ret; + uLongf destlen = *_destlen; + uLongf srclen = _srclen; + + ret = compress((Bytef *)dest, &destlen, (const Bytef *)src, srclen); + if (ret != Z_OK){ + cl_log(LOG_ERR, "%s: compression failed", + __FUNCTION__); + return HA_FAIL; + } + + *_destlen = destlen; + return HA_OK; + +} + +static int +zlib_decompress(char* dest, size_t* _destlen, + const char* src, size_t _srclen) +{ + + int ret; + uLongf destlen = *_destlen; + uLongf srclen = _srclen; + + ret = uncompress((Bytef *)dest, &destlen, (const Bytef *)src, srclen); + if (ret != Z_OK){ + cl_log(LOG_ERR, "%s: decompression failed", + __FUNCTION__); + return HA_FAIL; + } + + *_destlen = destlen; + + return HA_OK; +} + +static const char* +zlib_getname(void) +{ + return "zlib"; +} + +static struct hb_compress_fns zlibOps ={ + zlib_compress, + zlib_decompress, + zlib_getname, +}; diff --git a/lib/plugins/lrm/Makefile.am b/lib/plugins/lrm/Makefile.am new file mode 100644 index 0000000..fd24579 --- /dev/null +++ b/lib/plugins/lrm/Makefile.am @@ -0,0 +1,58 @@ +# +# Author: Sun Jiang Dong <sunjd@cn.ibm.com> +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in +if UPSTART +SUBDIRS = dbus +endif + +LRM_DIR = lrm +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl +if UPSTART +INCLUDES += $(DBUS_CFLAGS) +endif + +halibdir = $(libdir)/@HB_PKG@ +havarlibdir = $(localstatedir)/lib/@HB_PKG@ +COMMONLIBS = $(top_builddir)/lib/clplumbing/libplumb.la \ + $(top_builddir)/lib/lrm/liblrm.la \ + $(GLIBLIB) + +plugindir = $(halibdir)/plugins/RAExec + +plugin_LTLIBRARIES = lsb.la ocf.la heartbeat.la +if UPSTART +plugin_LTLIBRARIES += upstart.la +endif + +lsb_la_SOURCES = raexeclsb.c +lsb_la_LDFLAGS = -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version + +ocf_la_SOURCES = raexecocf.c +ocf_la_LDFLAGS = -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version + +heartbeat_la_SOURCES = raexechb.c +heartbeat_la_LDFLAGS = -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version + +if UPSTART +upstart_la_SOURCES = raexecupstart.c upstart-dbus.c +upstart_la_LDFLAGS = -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version \ + $(DBUS_LIBS) +endif diff --git a/lib/plugins/lrm/dbus/Makefile.am b/lib/plugins/lrm/dbus/Makefile.am new file mode 100644 index 0000000..ec93436 --- /dev/null +++ b/lib/plugins/lrm/dbus/Makefile.am @@ -0,0 +1,16 @@ +if UPSTART +BINDINGS=Upstart_Instance.h \ + Upstart_Job.h \ + Upstart.h + +all-local: + for header in $(BINDINGS); do \ + input=com.ubuntu.`echo $$header | sed 's/\.h//' | tr _ .`.xml; \ + $(DBUS_BINDING_TOOL) --mode=glib-client $$input > $$header; \ + done + +clean-local: + rm -f $(BINDINGS) + +EXTRA_DIST = *.xml +endif diff --git a/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Instance.xml b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Instance.xml new file mode 100644 index 0000000..d4f7ab2 --- /dev/null +++ b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Instance.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- upstart + + com.ubuntu.Upstart.Instance.xml - interface definition for interface + objects + + Copyright © 2009 Canonical Ltd. + Author: Scott James Remnant <scott@netsplit.com>. + + This file is free software; Canonical Ltd gives unlimited permission + to copy and/or distribute it, with or without modifications, as long + as this notice is preserved. + + Communication and interaction with Upstart through this interface is + permitted without restriction. + --> + +<!DOCTYPE node PUBLIC + "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> + +<node> + <interface name="com.ubuntu.Upstart0_6.Instance"> + <!-- Methods to directly control instances. Unlike the equivalent methods + for a Job, these are not permitted to pass or set environment. --> + <method name="Start"> + <annotation name="com.netsplit.Nih.Method.Async" value="true" /> + <arg name="wait" type="b" direction="in" /> + </method> + <method name="Stop"> + <annotation name="com.netsplit.Nih.Method.Async" value="true" /> + <arg name="wait" type="b" direction="in" /> + </method> + <method name="Restart"> + <annotation name="com.netsplit.Nih.Method.Async" value="true" /> + <arg name="wait" type="b" direction="in" /> + </method> + + <!-- Basic information about an Instance --> + <property name="name" type="s" access="read" /> + <property name="goal" type="s" access="read" /> + <property name="state" type="s" access="read" /> + <property name="processes" type="a(si)" access="read" /> + </interface> +</node> diff --git a/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Job.xml b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Job.xml new file mode 100644 index 0000000..27f47a1 --- /dev/null +++ b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Job.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- upstart + + com.ubuntu.Upstart.Job.xml - interface definition for job objects + + Copyright © 2009 Canonical Ltd. + Author: Scott James Remnant <scott@netsplit.com>. + + This file is free software; Canonical Ltd gives unlimited permission + to copy and/or distribute it, with or without modifications, as long + as this notice is preserved. + + Communication and interaction with Upstart through this interface is + permitted without restriction. + --> + +<!DOCTYPE node PUBLIC + "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> + +<node> + <interface name="com.ubuntu.Upstart0_6.Job"> + <!-- Get object paths for instances, while you can figure these out too, + it's still better form to use these --> + <method name="GetInstance"> + <arg name="env" type="as" direction="in" /> + <arg name="instance" type="o" direction="out" /> + </method> + <method name="GetInstanceByName"> + <arg name="name" type="s" direction="in" /> + <arg name="instance" type="o" direction="out" /> + </method> + <method name="GetAllInstances"> + <arg name="instances" type="ao" direction="out" /> + </method> + + <!-- Signals for changes to the instance list for a job --> + <signal name="InstanceAdded"> + <arg name="instance" type="o" /> + </signal> + <signal name="InstanceRemoved"> + <arg name="instance" type="o" /> + </signal> + + <!-- Job control; the environment arguments are used for both instance + selection and for passing environment to the processes of the job. --> + <method name="Start"> + <annotation name="com.netsplit.Nih.Method.Async" value="true" /> + <arg name="env" type="as" direction="in" /> + <arg name="wait" type="b" direction="in" /> + <arg name="instance" type="o" direction="out" /> + </method> + <method name="Stop"> + <annotation name="com.netsplit.Nih.Method.Async" value="true" /> + <arg name="env" type="as" direction="in" /> + <arg name="wait" type="b" direction="in" /> + </method> + <method name="Restart"> + <annotation name="com.netsplit.Nih.Method.Async" value="true" /> + <arg name="env" type="as" direction="in" /> + <arg name="wait" type="b" direction="in" /> + <arg name="instance" type="o" direction="out" /> + </method> + + <!-- Basic information about a Job --> + <property name="name" type="s" access="read" /> + <property name="description" type="s" access="read" /> + <property name="author" type="s" access="read" /> + <property name="version" type="s" access="read" /> + </interface> +</node> diff --git a/lib/plugins/lrm/dbus/com.ubuntu.Upstart.xml b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.xml new file mode 100644 index 0000000..a4331cd --- /dev/null +++ b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- upstart + + com.ubuntu.Upstart.xml - interface definition for manager object + + Copyright © 2009 Canonical Ltd. + Author: Scott James Remnant <scott@netsplit.com>. + + This file is free software; Canonical Ltd gives unlimited permission + to copy and/or distribute it, with or without modifications, as long + as this notice is preserved. + + Communication and interaction with Upstart through this interface is + permitted without restriction. + --> + +<!DOCTYPE node PUBLIC + "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> + +<node name="/com/ubuntu/Upstart"> + <interface name="com.ubuntu.Upstart0_6"> + <!-- Reload all configuration sources --> + <method name="ReloadConfiguration"> + </method> + + <!-- Get object paths for jobs, while you can figure them out, it's + better form to use these --> + <method name="GetJobByName"> + <arg name="name" type="s" direction="in" /> + <arg name="job" type="o" direction="out" /> + </method> + <method name="GetAllJobs"> + <arg name="jobs" type="ao" direction="out" /> + </method> + + <!-- Signals for changes to the job list --> + <signal name="JobAdded"> + <arg name="job" type="o" /> + </signal> + <signal name="JobRemoved"> + <arg name="job" type="o" /> + </signal> + + <!-- Event emission --> + <method name="EmitEvent"> + <annotation name="com.netsplit.Nih.Method.Async" value="true" /> + <arg name="name" type="s" direction="in" /> + <arg name="env" type="as" direction="in" /> + <arg name="wait" type="b" direction="in" /> + </method> + + <!-- Basic information about Upstart --> + <property name="version" type="s" access="read" /> + <property name="log_priority" type="s" access="readwrite" /> + </interface> +</node> diff --git a/lib/plugins/lrm/raexechb.c b/lib/plugins/lrm/raexechb.c new file mode 100644 index 0000000..f9f1eb9 --- /dev/null +++ b/lib/plugins/lrm/raexechb.c @@ -0,0 +1,416 @@ +/* + * 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.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * File: raexechb.c + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * This code implements the Resource Agent Plugin Module for LSB style. + * It's a part of Local Resource Manager. Currently it's used by lrmd only. + */ + +#include <lha_internal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <errno.h> +#include <dirent.h> +#include <libgen.h> +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <pils/plugin.h> +#include <lrm/raexec.h> + +#define PIL_PLUGINTYPE RA_EXEC_TYPE +#define PIL_PLUGIN heartbeat +#define PIL_PLUGINTYPE_S "RAExec" +#define PIL_PLUGIN_S "heartbeat" +#define PIL_PLUGINLICENSE LICENSE_PUBDOM +#define PIL_PLUGINLICENSEURL URL_PUBDOM + +static const char * RA_PATH = HB_RA_DIR; + +static const char meta_data_template[] = +"<?xml version=\"1.0\"?>\n" +"<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n" +"<resource-agent name=\"%s\">\n" +"<version>1.0</version>\n" +"<longdesc lang=\"en\">\n" +"%s" +"</longdesc>\n" +"<shortdesc lang=\"en\">%s</shortdesc>\n" +"<parameters>\n" +"<parameter name=\"1\" unique=\"1\" required=\"0\">\n" +"<longdesc lang=\"en\">\n" +"This argument will be passed as the first argument to the " +"heartbeat resource agent (assuming it supports one)\n" +"</longdesc>\n" +"<shortdesc lang=\"en\">argv[1]</shortdesc>\n" +"<content type=\"string\" default=\" \" />\n" +"</parameter>\n" +"<parameter name=\"2\" unique=\"1\" required=\"0\">\n" +"<longdesc lang=\"en\">\n" +"This argument will be passed as the second argument to the " +"heartbeat resource agent (assuming it supports one)\n" +"</longdesc>\n" +"<shortdesc lang=\"en\">argv[2]</shortdesc>\n" +"<content type=\"string\" default=\" \" />\n" +"</parameter>\n" +"<parameter name=\"3\" unique=\"1\" required=\"0\">\n" +"<longdesc lang=\"en\">\n" +"This argument will be passed as the third argument to the " +"heartbeat resource agent (assuming it supports one)\n" +"</longdesc>\n" +"<shortdesc lang=\"en\">argv[3]</shortdesc>\n" +"<content type=\"string\" default=\" \" />\n" +"</parameter>\n" +"<parameter name=\"4\" unique=\"1\" required=\"0\">\n" +"<longdesc lang=\"en\">\n" +"This argument will be passed as the fourth argument to the " +"heartbeat resource agent (assuming it supports one)\n" +"</longdesc>\n" +"<shortdesc lang=\"en\">argv[4]</shortdesc>\n" +"<content type=\"string\" default=\" \" />\n" +"</parameter>\n" +"<parameter name=\"5\" unique=\"1\" required=\"0\">\n" +"<longdesc lang=\"en\">\n" +"This argument will be passed as the fifth argument to the " +"heartbeat resource agent (assuming it supports one)\n" +"</longdesc>\n" +"<shortdesc lang=\"en\">argv[5]</shortdesc>\n" +"<content type=\"string\" default=\" \" />\n" +"</parameter>\n" +"</parameters>\n" +"<actions>\n" +"<action name=\"start\" timeout=\"15\" />\n" +"<action name=\"stop\" timeout=\"15\" />\n" +"<action name=\"status\" timeout=\"15\" />\n" +"<action name=\"monitor\" timeout=\"15\" interval=\"15\" start-delay=\"15\" />\n" +"<action name=\"meta-data\" timeout=\"5\" />\n" +"</actions>\n" +"<special tag=\"heartbeart\">\n" +"</special>\n" +"</resource-agent>\n"; + +/* The begin of exported function list */ +static int execra(const char * rsc_id, + const char * rsc_type, + const char * provider, + const char * op_type, + const int timeout, + GHashTable * params); + +static uniform_ret_execra_t map_ra_retvalue(int ret_execra + , const char * op_type, const char * std_output); +static int get_resource_list(GList ** rsc_info); +static char* get_resource_meta(const char* rsc_type, const char* provider); +static int get_provider_list(const char* ra_type, GList ** providers); + +/* The end of exported function list */ + +/* The begin of internal used function & data list */ +#define MAX_PARAMETER_NUM 40 +typedef char * RA_ARGV[MAX_PARAMETER_NUM]; + +static const int MAX_LENGTH_OF_RSCNAME = 40, + MAX_LENGTH_OF_OPNAME = 40; + +static int prepare_cmd_parameters(const char * rsc_type, const char * op_type, + GHashTable * params, RA_ARGV params_argv); +/* The end of internal function & data list */ + +/* Rource agent execution plugin operations */ +static struct RAExecOps raops = +{ execra, + map_ra_retvalue, + get_resource_list, + get_provider_list, + get_resource_meta +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static void* OurImports; +static void* interfprivate; +static int idebuglevel = 0; + +/* + * Our plugin initialization and registration function + * It gets called when the plugin gets loaded. + */ +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + if (getenv(HADEBUGVAL) != NULL && atoi(getenv(HADEBUGVAL)) > 0 ) { + idebuglevel = atoi(getenv(HADEBUGVAL)); + cl_log(LOG_DEBUG, "LRM debug level set to %d", idebuglevel); + } + + /* Register our interfaces */ + return imports->register_interface(us, PIL_PLUGINTYPE_S, PIL_PLUGIN_S, + &raops, NULL, &OurInterface, &OurImports, + interfprivate); +} + +/* + * Real work starts here ;-) + */ + +static int +execra( const char * rsc_id, const char * rsc_type, const char * provider, + const char * op_type, const int timeout, GHashTable * params) +{ + RA_ARGV params_argv; + char ra_pathname[RA_MAX_NAME_LENGTH]; + uniform_ret_execra_t exit_value; + GString * debug_info; + char * optype_tmp = NULL; + int index_tmp = 0; + + /* How to generate the meta-data? There is nearly no value + * information in meta-data build up in current way. + * Should directly add meta-data to the script itself? + */ + if ( 0 == STRNCMP_CONST(op_type, "meta-data") ) { + printf("%s", get_resource_meta(rsc_type, provider)); + exit(0); + } + + /* To simulate the 'monitor' operation with 'status'. + * Now suppose there is no 'monitor' operation for heartbeat scripts. + */ + if ( 0 == STRNCMP_CONST(op_type, "monitor") ) { + optype_tmp = g_strdup("status"); + } else { + optype_tmp = g_strdup(op_type); + } + + /* Prepare the call parameter */ + if (0 > prepare_cmd_parameters(rsc_type, optype_tmp, params, params_argv)) { + cl_log(LOG_ERR, "HB RA: Error of preparing parameters"); + g_free(optype_tmp); + return -1; + } + g_free(optype_tmp); + + get_ra_pathname(RA_PATH, rsc_type, NULL, ra_pathname); + + /* let this log show only high loglevel. */ + if (idebuglevel > 1) { + debug_info = g_string_new(""); + do { + g_string_append(debug_info, params_argv[index_tmp]); + g_string_append(debug_info, " "); + } while (params_argv[++index_tmp] != NULL); + debug_info->str[debug_info->len-1] = '\0'; + + cl_log(LOG_DEBUG, "RA instance %s executing: heartbeat::%s" + , rsc_id, debug_info->str); + + g_string_free(debug_info, TRUE); + } + + closefiles(); /* don't leak open files */ + execv(ra_pathname, params_argv); + cl_perror("(%s:%s:%d) execv failed for %s" + , __FILE__, __FUNCTION__, __LINE__, ra_pathname); + + switch (errno) { + case ENOENT: /* No such file or directory */ + case EISDIR: /* Is a directory */ + exit_value = EXECRA_NOT_INSTALLED; + break; + default: + exit_value = EXECRA_EXEC_UNKNOWN_ERROR; + } + exit(exit_value); +} + +static int +prepare_cmd_parameters(const char * rsc_type, const char * op_type, + GHashTable * params_ht, RA_ARGV params_argv) +{ + int tmp_len, index; + int ht_size = 0; + int param_num = 0; + char buf_tmp[20]; + void * value_tmp; + + if (params_ht) { + ht_size = g_hash_table_size(params_ht); + } + if ( ht_size+3 > MAX_PARAMETER_NUM ) { + cl_log(LOG_ERR, "Too many parameters"); + return -1; + } + + /* Now suppose the parameter format stored in Hashtabe is as like as + * key="1", value="-Wl,soname=test" + * Moreover, the key is supposed as a string transfered from an integer. + * It may be changed in the future. + */ + /* Notice: if ht_size==0, no actual arguments except op_type */ + for (index = 1; index <= ht_size; index++ ) { + snprintf(buf_tmp, sizeof(buf_tmp), "%d", index); + value_tmp = g_hash_table_lookup(params_ht, buf_tmp); + /* suppose the key is consecutive */ + if ( value_tmp == NULL ) { +/* cl_log(LOG_WARNING, "Parameter ordering error in"\ + "prepare_cmd_parameters, raexeclsb.c"); + cl_log(LOG_WARNING, "search key=%s.", buf_tmp); +*/ continue; + } + param_num ++; + params_argv[param_num] = g_strdup((char *)value_tmp); + } + + tmp_len = strnlen(rsc_type, MAX_LENGTH_OF_RSCNAME); + params_argv[0] = g_strndup(rsc_type, tmp_len); + /* Add operation code as the last argument */ + tmp_len = strnlen(op_type, MAX_LENGTH_OF_OPNAME); + params_argv[param_num+1] = g_strndup(op_type, tmp_len); + /* Add the teminating NULL pointer */ + params_argv[param_num+2] = NULL; + return 0; +} + +static uniform_ret_execra_t +map_ra_retvalue(int ret_execra, const char * op_type, const char * std_output) +{ + + /* Now there is no formal related specification for Heartbeat RA + * scripts. Temporarily deal as LSB init script. + */ + /* Except op_type equals 'status', the UNIFORM_RET_EXECRA is compatible + with LSB standard. + */ + const char * stop_pattern1 = "*stopped*", + * stop_pattern2 = "*not*running*", + * running_pattern1 = "*running*", + * running_pattern2 = "*OK*"; + char * lower_std_output = NULL; + + if(ret_execra == EXECRA_NOT_INSTALLED) { + return ret_execra; + } + + if ( 0 == STRNCMP_CONST(op_type, "status") + || 0 == STRNCMP_CONST(op_type, "monitor")) { + if (std_output == NULL ) { + cl_log(LOG_WARNING, "No status output from the (hb) resource agent."); + return EXECRA_NOT_RUNNING; + } + + if (idebuglevel) { + cl_log(LOG_DEBUG, "RA output was: [%s]", std_output); + } + + lower_std_output = g_ascii_strdown(std_output, -1); + + if ( TRUE == g_pattern_match_simple(stop_pattern1 + , lower_std_output) || TRUE == + g_pattern_match_simple(stop_pattern2 + , lower_std_output) ) { + if (idebuglevel) { + cl_log(LOG_DEBUG + , "RA output [%s] matched stopped pattern" + " [%s] or [%s]" + , std_output + , stop_pattern1 + , stop_pattern2); + } + ret_execra = EXECRA_NOT_RUNNING; /* stopped */ + } else if ( TRUE == g_pattern_match_simple(running_pattern1 + , lower_std_output) || TRUE == + g_pattern_match_simple(running_pattern2 + , std_output) ) { + if (idebuglevel) { + cl_log(LOG_DEBUG + , "RA output [%s] matched running" + " pattern [%s] or [%s]" + , std_output, running_pattern1 + , running_pattern2); + } + ret_execra = EXECRA_OK; /* running */ + } else { + /* It didn't say it was running - must be stopped */ + cl_log(LOG_DEBUG, "RA output [%s] didn't match any pattern" + , std_output); + ret_execra = EXECRA_NOT_RUNNING; /* stopped */ + } + g_free(lower_std_output); + } + /* For non-status operation return code */ + if (ret_execra < 0) { + ret_execra = EXECRA_UNKNOWN_ERROR; + } + return ret_execra; +} + +static int +get_resource_list(GList ** rsc_info) +{ + return get_runnable_list(RA_PATH, rsc_info); +} + +static char* +get_resource_meta(const char* rsc_type, const char* provider) +{ + GString * meta_data; + + meta_data = g_string_new(""); + g_string_sprintf( meta_data, meta_data_template, rsc_type + , rsc_type, rsc_type); + return meta_data->str; +} +static int +get_provider_list(const char* ra_type, GList ** providers) +{ + if ( providers == NULL ) { + cl_log(LOG_ERR, "%s:%d: Parameter error: providers==NULL" + , __FUNCTION__, __LINE__); + return -2; + } + + if ( *providers != NULL ) { + cl_log(LOG_ERR, "%s:%d: Parameter error: *providers==NULL." + "This will cause memory leak." + , __FUNCTION__, __LINE__); + } + + /* Now temporarily make it fixed */ + *providers = g_list_append(*providers, g_strdup("heartbeat")); + + return g_list_length(*providers); +} diff --git a/lib/plugins/lrm/raexeclsb.c b/lib/plugins/lrm/raexeclsb.c new file mode 100644 index 0000000..46d7546 --- /dev/null +++ b/lib/plugins/lrm/raexeclsb.c @@ -0,0 +1,609 @@ +/* + * 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.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * File: raexeclsb.c + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * This code implements the Resource Agent Plugin Module for LSB style. + * It's a part of Local Resource Manager. Currently it's used by lrmd only. + */ +/* + * Todo + * 1) Use flex&bison to make the analysis functions for lsb compliant comment? + * 2) Support multiple paths which contain lsb compliant RAs. + * 3) Optional and additional actions analysis? + */ + +#include <lha_internal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <errno.h> +#include <dirent.h> +#include <libgen.h> /* Add it for compiling on OSX */ +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <pils/plugin.h> +#include <lrm/raexec.h> +#include <libgen.h> + +#include <libxml/entities.h> + +#define PIL_PLUGINTYPE RA_EXEC_TYPE +#define PIL_PLUGIN lsb +#define PIL_PLUGINTYPE_S "RAExec" +#define PIL_PLUGIN_S "lsb" +#define PIL_PLUGINLICENSE LICENSE_PUBDOM +#define PIL_PLUGINLICENSEURL URL_PUBDOM + + +/* meta-data template for lsb scripts */ +/* Note: As for optional actions -- extracted from lsb standard. + * The reload and the try-restart options are optional. Other init script + * actions may be defined by the init script. + */ +#define meta_data_template \ +"<?xml version=\"1.0\"?>\n"\ +"<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n"\ +"<resource-agent name=\"%s\" version=\"0.1\">\n"\ +" <version>1.0</version>\n"\ +" <longdesc lang=\"en\">\n"\ +" %s"\ +" </longdesc>\n"\ +" <shortdesc lang=\"en\">%s</shortdesc>\n"\ +" <parameters>\n"\ +" </parameters>\n"\ +" <actions>\n"\ +" <action name=\"start\" timeout=\"15\" />\n"\ +" <action name=\"stop\" timeout=\"15\" />\n"\ +" <action name=\"status\" timeout=\"15\" />\n"\ +" <action name=\"restart\" timeout=\"15\" />\n"\ +" <action name=\"force-reload\" timeout=\"15\" />\n"\ +" <action name=\"monitor\" timeout=\"15\" interval=\"15\" />\n"\ +" <action name=\"meta-data\" timeout=\"5\" />\n"\ +" </actions>\n"\ +" <special tag=\"LSB\">\n"\ +" <Provides>%s</Provides>\n"\ +" <Required-Start>%s</Required-Start>\n"\ +" <Required-Stop>%s</Required-Stop>\n"\ +" <Should-Start>%s</Should-Start>\n"\ +" <Should-Stop>%s</Should-Stop>\n"\ +" <Default-Start>%s</Default-Start>\n"\ +" <Default-Stop>%s</Default-Stop>\n"\ +" </special>\n"\ +"</resource-agent>\n" + +/* The keywords for lsb-compliant comment */ +#define LSB_INITSCRIPT_INFOBEGIN_TAG "### BEGIN INIT INFO" +#define LSB_INITSCRIPT_INFOEND_TAG "### END INIT INFO" +#define PROVIDES "# Provides:" +#define REQ_START "# Required-Start:" +#define REQ_STOP "# Required-Stop:" +#define SHLD_START "# Should-Start:" +#define SHLD_STOP "# Should-Stop:" +#define DFLT_START "# Default-Start:" +#define DFLT_STOP "# Default-Stop:" +#define SHORT_DSCR "# Short-Description:" +#define DESCRIPTION "# Description:" + +#define ZAPXMLOBJ(m) \ + if ( (m) != NULL ) { \ + xmlFree(m); \ + (m) = NULL; \ + } + +#define RALSB_GET_VALUE(ptr, keyword) \ + if ( (ptr == NULL) & (0 == strncasecmp(buffer, keyword, strlen(keyword))) ) { \ + (ptr) = (char *)xmlEncodeEntitiesReentrant(NULL,BAD_CAST buffer+strlen(keyword)); \ + continue; \ + } +/* + * Are there multiple paths? Now according to LSB init scripts, the answer + * is 'no', but should be 'yes' for lsb none-init scripts? + */ +static const char * RA_PATH = LSB_RA_DIR; +/* Map to the return code of the 'monitor' operation defined in the OCF RA + * specification. + */ +static const int status_op_exitcode_map[] = { + EXECRA_OK, /* LSB_STATUS_OK */ + EXECRA_NOT_RUNNING, /* LSB_STATUS_VAR_PID */ + EXECRA_NOT_RUNNING, /* LSB_STATUS_VAR_LOCK */ + EXECRA_NOT_RUNNING, /* LSB_STATUS_STOPPED */ + EXECRA_UNKNOWN_ERROR /* LSB_STATUS_UNKNOWN */ +}; + +/* The begin of exported function list */ +static int execra(const char * rsc_id, + const char * rsc_type, + const char * provider, + const char * op_type, + const int timeout, + GHashTable * params); + +static uniform_ret_execra_t map_ra_retvalue(int ret_execra + , const char * op_type, const char * std_output); +static char* get_resource_meta(const char* rsc_type, const char* provider); +static int get_resource_list(GList ** rsc_info); +static int get_provider_list(const char* ra_type, GList ** providers); + +/* The end of exported function list */ + +/* The begin of internal used function & data list */ +#define MAX_PARAMETER_NUM 40 + +const int MAX_LENGTH_OF_RSCNAME = 40, + MAX_LENGTH_OF_OPNAME = 40; + +typedef char * RA_ARGV[MAX_PARAMETER_NUM]; + +static int prepare_cmd_parameters(const char * rsc_type, const char * op_type, + GHashTable * params, RA_ARGV params_argv); +/* The end of internal function & data list */ + +/* Rource agent execution plugin operations */ +static struct RAExecOps raops = +{ execra, + map_ra_retvalue, + get_resource_list, + get_provider_list, + get_resource_meta +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static void* OurImports; +static void* interfprivate; + +/* + * Our plugin initialization and registration function + * It gets called when the plugin gets loaded. + */ +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interfaces */ + return imports->register_interface(us, PIL_PLUGINTYPE_S, PIL_PLUGIN_S, + &raops, NULL, &OurInterface, &OurImports, + interfprivate); +} + +/* + * Real work starts here ;-) + */ + +static int +execra( const char * rsc_id, const char * rsc_type, const char * provider, + const char * op_type, const int timeout, GHashTable * params) +{ + RA_ARGV params_argv; + char ra_pathname[RA_MAX_NAME_LENGTH]; + GString * debug_info; + char * inherit_debuglevel = NULL; + char * optype_tmp = NULL; + int index_tmp = 0; + int save_errno; + + /* Specially handle the operation "metameta-data". To build up its + * output from templet, dummy data and its comment head. + */ + if ( 0 == STRNCMP_CONST(op_type, "meta-data")) { + printf("%s", get_resource_meta(rsc_type, provider)); + exit(0); + } + + /* To simulate the 'monitor' operation with 'status'. + * Now suppose there is no 'monitor' operation for LSB scripts. + */ + if (0 == STRNCMP_CONST(op_type, "monitor")) { + optype_tmp = g_strdup("status"); + } else { + optype_tmp = g_strdup(op_type); + } + + /* Prepare the call parameter */ + if ( prepare_cmd_parameters(rsc_type, optype_tmp, params, params_argv) + != 0) { + cl_log(LOG_ERR, "lsb RA: Error of preparing parameters"); + g_free(optype_tmp); + return -1; + } + g_free(optype_tmp); + get_ra_pathname(RA_PATH, rsc_type, NULL, ra_pathname); + + /* let this log show only high loglevel. */ + inherit_debuglevel = getenv(HADEBUGVAL); + if ((inherit_debuglevel != NULL) && (atoi(inherit_debuglevel) > 1)) { + debug_info = g_string_new(""); + do { + g_string_append(debug_info, params_argv[index_tmp]); + g_string_append(debug_info, " "); + } while (params_argv[++index_tmp] != NULL); + debug_info->str[debug_info->len-1] = '\0'; + + cl_log(LOG_DEBUG, "RA instance %s executing: lsb::%s" + , rsc_id, debug_info->str); + + g_string_free(debug_info, TRUE); + } + + closefiles(); /* don't leak open files */ + execv(ra_pathname, params_argv); + /* oops, exec failed */ + save_errno = errno; /* cl_perror may change errno */ + cl_perror("(%s:%s:%d) execv failed for %s" + , __FILE__, __FUNCTION__, __LINE__, ra_pathname); + errno = save_errno; + exit(get_failed_exec_rc()); +} + +static uniform_ret_execra_t +map_ra_retvalue(int ret_execra, const char * op_type, const char * std_output) +{ + /* Except op_type equals 'status', the UNIFORM_RET_EXECRA is compatible + * with the LSB standard. + */ + if (ret_execra < 0) { + return EXECRA_UNKNOWN_ERROR; + } + + if(ret_execra == EXECRA_NOT_INSTALLED) { + return ret_execra; + } + + if ( 0 == STRNCMP_CONST(op_type, "status") + || 0 == STRNCMP_CONST(op_type, "monitor")) { + if (ret_execra < DIMOF(status_op_exitcode_map)) { + ret_execra = status_op_exitcode_map[ret_execra]; + } + } + return ret_execra; +} + +static int +get_resource_list(GList ** rsc_info) +{ + char ra_pathname[RA_MAX_NAME_LENGTH]; + FILE * fp; + gboolean next_continue, found_begin_tag, is_lsb_script; + int rc = 0; + GList *cur, *tmp; + const size_t BUFLEN = 80; + char buffer[BUFLEN]; + + if ((rc = get_runnable_list(RA_PATH, rsc_info)) <= 0) { + return rc; + } + + /* Use the following comment line as the filter patterns to choose + * the real LSB-compliant scripts. + * "### BEGIN INIT INFO" and "### END INIT INFO" + */ + cur = g_list_first(*rsc_info); + while ( cur != NULL ) { + get_ra_pathname(RA_PATH, cur->data, NULL, ra_pathname); + if ( (fp = fopen(ra_pathname, "r")) == NULL ) { + tmp = g_list_next(cur); + *rsc_info = g_list_remove(*rsc_info, cur->data); + if (cur->data) + g_free(cur->data); + cur = tmp; + continue; + } + is_lsb_script = FALSE; + next_continue = FALSE; + found_begin_tag = FALSE; + while (NULL != fgets(buffer, BUFLEN, fp)) { + /* Handle the lines over BUFLEN(80) columns, only + * the first part is compared. + */ + if ( next_continue == TRUE ) { + continue; + } + if (strlen(buffer) == BUFLEN ) { + next_continue = TRUE; + } else { + next_continue = FALSE; + } + /* Shorten the search time */ + if (buffer[0] != '#' && buffer[0] != ' ' + && buffer[0] != '\n') { + break; /* donnot find */ + } + + if (found_begin_tag == TRUE && 0 == strncasecmp(buffer + , LSB_INITSCRIPT_INFOEND_TAG + , strlen(LSB_INITSCRIPT_INFOEND_TAG)) ) { + is_lsb_script = TRUE; + break; + } + if (found_begin_tag == FALSE && 0 == strncasecmp(buffer + , LSB_INITSCRIPT_INFOBEGIN_TAG + , strlen(LSB_INITSCRIPT_INFOBEGIN_TAG)) ) { + found_begin_tag = TRUE; + } + } + fclose(fp); + tmp = g_list_next(cur); + +/* + * Temporarily remove the filter to the initscript, or many initscripts on + * many distros, such as RHEL4 and fedora5, cannot be used by management GUI. + * Please refer to the bug + * http://www.osdl.org/developer_bugzilla/show_bug.cgi?id=1250 + */ + +#if 0 + if ( is_lsb_script != TRUE ) { + *rsc_info = g_list_remove(*rsc_info, cur->data); + g_free(cur->data); + } +#else + (void) is_lsb_script; +#endif + cur = tmp; + } + + return g_list_length(*rsc_info); +} + +static int +prepare_cmd_parameters(const char * rsc_type, const char * op_type, + GHashTable * params_ht, RA_ARGV params_argv) +{ + int tmp_len; + int ht_size = 0; +#if 0 + /* Reserve it for possible furture use */ + int index; + void * value_tmp = NULL; + char buf_tmp[20]; +#endif + + if (params_ht) { + ht_size = g_hash_table_size(params_ht); + } + + /* Need 3 additonal spaces for accomodating: + * argv[0] = RA_file_name(RA_TYPE) + * argv[1] = operation + * a terminal NULL + */ + if ( ht_size+3 > MAX_PARAMETER_NUM ) { + cl_log(LOG_ERR, "Too many parameters"); + return -1; + } + + tmp_len = strnlen(rsc_type, MAX_LENGTH_OF_RSCNAME); + params_argv[0] = g_strndup(rsc_type, tmp_len); + /* Add operation code as the first argument */ + tmp_len = strnlen(op_type, MAX_LENGTH_OF_OPNAME); + params_argv[1] = g_strndup(op_type, tmp_len); + + /* + * No actual arguments needed except op_type. + * Add the teminating NULL pointer. + */ + params_argv[2] = NULL; + if ( (ht_size != 0) && (0 != STRNCMP_CONST(op_type, "status")) ) { + cl_log(LOG_WARNING, "For LSB init script, no additional " + "parameters are needed."); + } + +/* Actually comment the following code, but I still think it may be used + * in the future for LSB none-initial scripts, so reserver it. + */ +#if 0 + /* Now suppose the parameter formate stored in Hashtabe is like + * key="1", value="-Wl,soname=test" + * Moreover, the key is supposed as a string transfered from an integer. + * It may be changed in the future. + */ + for (index = 1; index <= ht_size; index++ ) { + snprintf(buf_tmp, sizeof(buf_tmp), "%d", index); + value_tmp = g_hash_table_lookup(params_ht, buf_tmp); + /* suppose the key is consecutive */ + if ( value_tmp == NULL ) { + cl_log(LOG_ERR, "Parameter ordering error in"\ + "prepare_cmd_parameters, raexeclsb.c"); + return -1; + } + params_argv[index+1] = g_strdup((char *)value_tmp); + } +#endif + + return 0; +} + +static char* +get_resource_meta(const char* rsc_type, const char* provider) +{ + char ra_pathname[RA_MAX_NAME_LENGTH]; + FILE * fp; + gboolean next_continue; + GString * meta_data; + const size_t BUFLEN = 132; + char buffer[BUFLEN]; + char * provides = NULL, + * req_start = NULL, + * req_stop = NULL, + * shld_start = NULL, + * shld_stop = NULL, + * dflt_start = NULL, + * dflt_stop = NULL, + * s_dscrpt = NULL, + * xml_l_dscrpt = NULL; + GString * l_dscrpt = NULL; + + /* + * Use the following tags to find the LSb-compliant comment block. + * "### BEGIN INIT INFO" and "### END INIT INFO" + */ + get_ra_pathname(RA_PATH, rsc_type, NULL, ra_pathname); + if ( (fp = fopen(ra_pathname, "r")) == NULL ) { + cl_log(LOG_ERR, "Failed to open lsb RA %s. No meta-data gotten." + , rsc_type); + return NULL; + } + meta_data = g_string_new(""); + + next_continue = FALSE; + +/* + * Is not stick to the rule that the description should be located in the + * comment block between "### BEGIN INIT INFO" and "### END INIT INFO". + * Please refer to the bug + * http://www.osdl.org/developer_bugzilla/show_bug.cgi?id=1250 + */ +#if 0 + while (NULL != fgets(buffer, BUFLEN, fp)) { + /* Handle the lines over BUFLEN(80) columns, only + * the first part is compared. + */ + if ( next_continue == TRUE ) { + continue; + } + if (strlen(buffer) == BUFLEN ) { + next_continue = TRUE; + } else { + next_continue = FALSE; + } + + if ( 0 == strncasecmp(buffer , LSB_INITSCRIPT_INFOBEGIN_TAG + , strlen(LSB_INITSCRIPT_INFOBEGIN_TAG)) ) { + break; + } + } +#else + (void) next_continue; +#endif + + /* Enter into the lsb-compliant comment block */ + while ( NULL != fgets(buffer, BUFLEN, fp) ) { + /* Now suppose each of the following eight arguments contain + * only one line + */ + RALSB_GET_VALUE(provides, PROVIDES) + RALSB_GET_VALUE(req_start, REQ_START) + RALSB_GET_VALUE(req_stop, REQ_STOP) + RALSB_GET_VALUE(shld_start, SHLD_START) + RALSB_GET_VALUE(shld_stop, SHLD_STOP) + RALSB_GET_VALUE(dflt_start, DFLT_START) + RALSB_GET_VALUE(dflt_stop, DFLT_STOP) + RALSB_GET_VALUE(s_dscrpt, SHORT_DSCR) + + /* Long description may cross multiple lines */ + if ( (l_dscrpt == NULL) && (0 == strncasecmp(buffer, DESCRIPTION + , strlen(DESCRIPTION))) ) { + l_dscrpt = g_string_new(buffer+strlen(DESCRIPTION)); + /* Between # and keyword, more than one space, or a tab + * character, indicates the continuation line. + * Extracted from LSB init script standard + */ + while ( NULL != fgets(buffer, BUFLEN, fp) ) { + if ( (0 == strncmp(buffer, "# ", 3)) + || (0 == strncmp(buffer, "#\t", 2)) ) { + buffer[0] = ' '; + l_dscrpt = g_string_append(l_dscrpt + , buffer); + } else { + fputs(buffer, fp); + break; /* Long description ends */ + } + } + continue; + } + if( l_dscrpt ) + xml_l_dscrpt = (char *)xmlEncodeEntitiesReentrant(NULL, + BAD_CAST (l_dscrpt->str)); + + if ( 0 == strncasecmp(buffer, LSB_INITSCRIPT_INFOEND_TAG + , strlen(LSB_INITSCRIPT_INFOEND_TAG)) ) { + /* Get to the out border of LSB comment block */ + break; + } + + if ( buffer[0] != '#' ) { + break; /* Out of comment block in the beginning */ + } + } + fclose(fp); + + g_string_sprintf( meta_data, meta_data_template, rsc_type + , (xml_l_dscrpt==NULL)? rsc_type : xml_l_dscrpt + , (s_dscrpt==NULL)? rsc_type : s_dscrpt + , (provides==NULL)? "" : provides + , (req_start==NULL)? "" : req_start + , (req_stop==NULL)? "" : req_stop + , (shld_start==NULL)? "" : shld_start + , (shld_stop==NULL)? "" : shld_stop + , (dflt_start==NULL)? "" : dflt_start + , (dflt_stop==NULL)? "" : dflt_stop ); + + ZAPXMLOBJ(xml_l_dscrpt); + ZAPXMLOBJ(s_dscrpt); + ZAPXMLOBJ(provides); + ZAPXMLOBJ(req_start); + ZAPXMLOBJ(req_stop); + ZAPXMLOBJ(shld_start); + ZAPXMLOBJ(shld_stop); + ZAPXMLOBJ(dflt_start); + ZAPXMLOBJ(dflt_stop); + + if( l_dscrpt ) + g_string_free(l_dscrpt, TRUE); + return meta_data->str; +} + +static int +get_provider_list(const char* ra_type, GList ** providers) +{ + if ( providers == NULL ) { + cl_log(LOG_ERR, "%s:%d: Parameter error: providers==NULL" + , __FUNCTION__, __LINE__); + return -2; + } + + if ( *providers != NULL ) { + cl_log(LOG_ERR, "%s:%d: Parameter error: *providers==NULL." + "This will cause memory leak." + , __FUNCTION__, __LINE__); + } + + /* Now temporarily make it fixed */ + *providers = g_list_append(*providers, g_strdup("heartbeat")); + + return g_list_length(*providers); +} diff --git a/lib/plugins/lrm/raexecocf.c b/lib/plugins/lrm/raexecocf.c new file mode 100644 index 0000000..f7cd7ed --- /dev/null +++ b/lib/plugins/lrm/raexecocf.c @@ -0,0 +1,496 @@ +/* + * 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.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * File: raexecocf.c + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * This code implements the Resource Agent Plugin Module for LSB style. + * It's a part of Local Resource Manager. Currently it's used by lrmd only. + */ + +#include <lha_internal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <libgen.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <errno.h> +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/realtime.h> +#include <pils/plugin.h> +#include <dirent.h> +#include <libgen.h> /* Add it for compiling on OSX */ +#ifdef HAVE_TIME_H +#include <time.h> +#endif + +#include <lrm/raexec.h> + +# define PIL_PLUGINTYPE RA_EXEC_TYPE +# define PIL_PLUGINTYPE_S "RAExec" +# define PIL_PLUGINLICENSE LICENSE_PUBDOM +# define PIL_PLUGINLICENSEURL URL_PUBDOM + +# define PIL_PLUGIN ocf +# define PIL_PLUGIN_S "ocf" +/* + * Are there multiple paths? Now according to OCF spec, the answer is 'no'. + * But actually or for future? + */ +static const char * RA_PATH = OCF_RA_DIR; + +/* The begin of exported function list */ +static int execra(const char * rsc_id, + const char * rsc_type, + const char * provider, + const char * op_type, + const int timeout, + GHashTable * params); +static uniform_ret_execra_t map_ra_retvalue(int ret_execra, + const char * op_type, const char * std_output); +static int get_resource_list(GList ** rsc_info); +static char* get_resource_meta(const char* rsc_type, const char* provider); +static int get_provider_list(const char* ra_type, GList ** providers); + +/* The end of exported function list */ + +/* The begin of internal used function & data list */ +static void add_OCF_prefix(GHashTable * params, GHashTable * new_params); +static void add_OCF_env_vars(GHashTable * env, const char * rsc_id, + const char * rsc_type, const char * provider); +static void add_prefix_foreach(gpointer key, gpointer value, + gpointer user_data); + +static void hash_to_str(GHashTable * , GString *); +static void hash_to_str_foreach(gpointer key, gpointer value, + gpointer user_data); + +static int raexec_setenv(GHashTable * env_params); +static void set_env(gpointer key, gpointer value, gpointer user_data); + +static gboolean let_remove_eachitem(gpointer key, gpointer value, + gpointer user_data); +static int get_providers(const char* class_path, const char* op_type, + GList ** providers); +static void merge_string_list(GList** old, GList* new); +static gint compare_str(gconstpointer a, gconstpointer b); + +/* The end of internal function & data list */ + +/* Rource agent execution plugin operations */ +static struct RAExecOps raops = +{ execra, + map_ra_retvalue, + get_resource_list, + get_provider_list, + get_resource_meta +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static void* OurImports; +static void* interfprivate; + +/* + * Our plugin initialization and registration function + * It gets called when the plugin gets loaded. + */ +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interfaces */ + return imports->register_interface(us, PIL_PLUGINTYPE_S, PIL_PLUGIN_S, + &raops, NULL, &OurInterface, &OurImports, + interfprivate); +} + +/* + * The function to execute a RA. + */ +static int +execra(const char * rsc_id, const char * rsc_type, const char * provider, + const char * op_type, const int timeout, GHashTable * params) +{ + char ra_pathname[RA_MAX_NAME_LENGTH]; + GHashTable * tmp_for_setenv; + GString * params_gstring; + char * inherit_debuglevel = NULL; + int save_errno; + + get_ra_pathname(RA_PATH, rsc_type, provider, ra_pathname); + + /* Setup environment correctly */ + tmp_for_setenv = g_hash_table_new(g_str_hash, g_str_equal); + add_OCF_prefix(params, tmp_for_setenv); + add_OCF_env_vars(tmp_for_setenv, rsc_id, rsc_type, provider); + raexec_setenv(tmp_for_setenv); + g_hash_table_foreach_remove(tmp_for_setenv, let_remove_eachitem, NULL); + g_hash_table_destroy(tmp_for_setenv); + + /* let this log show only high loglevel. */ + inherit_debuglevel = getenv(HADEBUGVAL); + if ((inherit_debuglevel != NULL) && (atoi(inherit_debuglevel) > 1)) { + params_gstring = g_string_new(""); + hash_to_str(params, params_gstring); + cl_log(LOG_DEBUG, "RA instance %s executing: OCF::%s %s. Parameters: " + "{%s}", rsc_id, rsc_type, op_type, params_gstring->str); + g_string_free(params_gstring, TRUE); + } + + closefiles(); /* don't leak open files */ + /* execute the RA */ + execl(ra_pathname, ra_pathname, op_type, (const char *)NULL); + /* oops, exec failed */ + save_errno = errno; /* cl_perror may change errno */ + cl_perror("(%s:%s:%d) execl failed for %s" + , __FILE__, __FUNCTION__, __LINE__, ra_pathname); + errno = save_errno; + exit(get_failed_exec_rc()); +} + +static uniform_ret_execra_t +map_ra_retvalue(int ret_execra, const char * op_type, const char * std_output) +{ + /* Because the UNIFORM_RET_EXECRA is compatible with OCF standard, + * no actual mapping except validating, which ensure the return code + * will be in the range 0 to 7. Too strict? + */ + if (ret_execra < 0 || ret_execra > 9) { + cl_log(LOG_WARNING, "mapped the invalid return code %d." + , ret_execra); + ret_execra = EXECRA_UNKNOWN_ERROR; + } + return ret_execra; +} + +static gint +compare_str(gconstpointer a, gconstpointer b) +{ + return strncmp(a,b,RA_MAX_NAME_LENGTH); +} + +static int +get_resource_list(GList ** rsc_info) +{ + struct dirent **namelist; + GList* item; + int file_num; + char subdir[FILENAME_MAX+1]; + + if ( rsc_info == NULL ) { + cl_log(LOG_ERR, "Parameter error: get_resource_list"); + return -2; + } + + if ( *rsc_info != NULL ) { + cl_log(LOG_ERR, "Parameter error: get_resource_list."\ + "will cause memory leak."); + *rsc_info = NULL; + } + file_num = scandir(RA_PATH, &namelist, NULL, alphasort); + if (file_num < 0) { + return -2; + } + while (file_num--) { + GList* ra_subdir = NULL; + struct stat prop; + if ('.' == namelist[file_num]->d_name[0]) { + free(namelist[file_num]); + continue; + } + + snprintf(subdir,FILENAME_MAX,"%s/%s", + RA_PATH, namelist[file_num]->d_name); + + if (stat(subdir, &prop) == -1) { + cl_perror("%s:%s:%d: stat failed for %s" + , __FILE__, __FUNCTION__, __LINE__, subdir); + free(namelist[file_num]); + continue; + } else if (!S_ISDIR(prop.st_mode)) { + free(namelist[file_num]); + continue; + } + + get_runnable_list(subdir,&ra_subdir); + + merge_string_list(rsc_info,ra_subdir); + + while (NULL != (item = g_list_first(ra_subdir))) { + ra_subdir = g_list_remove_link(ra_subdir, item); + g_free(item->data); + g_list_free_1(item); + } + + free(namelist[file_num]); + } + free(namelist); + + return 0; +} + +static void +merge_string_list(GList** old, GList* new) +{ + GList* item = NULL; + char* newitem; + for( item=g_list_first(new); NULL!=item; item=g_list_next(item)){ + if (!g_list_find_custom(*old, item->data,compare_str)){ + newitem = g_strndup(item->data,RA_MAX_NAME_LENGTH); + *old = g_list_append(*old, newitem); + } + } +} + +static int +get_provider_list(const char* ra_type, GList ** providers) +{ + int ret; + ret = get_providers(RA_PATH, ra_type, providers); + if (0>ret) { + cl_log(LOG_ERR, "scandir failed in OCF RA plugin"); + } + return ret; +} + +static char* +get_resource_meta(const char* rsc_type, const char* provider) +{ + const int BUFF_LEN=4096; + int read_len = 0; + char buff[BUFF_LEN]; + char* data = NULL; + GString* g_str_tmp = NULL; + char ra_pathname[RA_MAX_NAME_LENGTH]; + FILE* file = NULL; + GHashTable * tmp_for_setenv; + struct timespec short_sleep = {0,200000000L}; /*20ms*/ + + get_ra_pathname(RA_PATH, rsc_type, provider, ra_pathname); + + strncat(ra_pathname, " meta-data",RA_MAX_NAME_LENGTH-strlen(ra_pathname)-1); + tmp_for_setenv = g_hash_table_new(g_str_hash, g_str_equal); + add_OCF_env_vars(tmp_for_setenv, "DUMMY_INSTANCE", rsc_type, provider); + raexec_setenv(tmp_for_setenv); + g_hash_table_foreach_remove(tmp_for_setenv, let_remove_eachitem, NULL); + g_hash_table_destroy(tmp_for_setenv); + + file = popen(ra_pathname, "r"); + if (NULL==file) { + cl_log(LOG_ERR, "%s: popen failed: %s", __FUNCTION__, strerror(errno)); + return NULL; + } + + g_str_tmp = g_string_new(""); + while(!feof(file)) { + read_len = fread(buff, 1, BUFF_LEN - 1, file); + if (0<read_len) { + *(buff+read_len) = '\0'; + g_string_append(g_str_tmp, buff); + } + else { + nanosleep(&short_sleep,NULL); + } + } + if( pclose(file) ) { + cl_log(LOG_ERR, "%s: pclose failed: %s", __FUNCTION__, strerror(errno)); + } + if (0 == g_str_tmp->len) { + g_string_free(g_str_tmp, TRUE); + return NULL; + } + data = (char*)g_new(char, g_str_tmp->len+1); + data[0] = data[g_str_tmp->len] = 0; + strncpy(data, g_str_tmp->str, g_str_tmp->len); + + g_string_free(g_str_tmp, TRUE); + + return data; +} + +static void +add_OCF_prefix(GHashTable * env_params, GHashTable * new_env_params) +{ + if (env_params) { + g_hash_table_foreach(env_params, add_prefix_foreach, + new_env_params); + } +} + +static void +add_prefix_foreach(gpointer key, gpointer value, gpointer user_data) +{ + const int MAX_LENGTH_OF_ENV = 128; + int prefix = STRLEN_CONST("OCF_RESKEY_"); + GHashTable * new_hashtable = (GHashTable *) user_data; + char * newkey; + int keylen = strnlen((char*)key, MAX_LENGTH_OF_ENV-prefix)+prefix+1; + + newkey = g_new(gchar, keylen); + strncpy(newkey, "OCF_RESKEY_", keylen); + strncat(newkey, key, keylen-strlen(newkey)-1); + g_hash_table_insert(new_hashtable, (gpointer)newkey, g_strdup(value)); +} + +static void +hash_to_str(GHashTable * params , GString * str) +{ + if (params) { + g_hash_table_foreach(params, hash_to_str_foreach, str); + } +} + +static void +hash_to_str_foreach(gpointer key, gpointer value, gpointer user_data) +{ + char buffer_tmp[60]; + GString * str = (GString *)user_data; + + snprintf(buffer_tmp, 60, "%s=%s ", (char *)key, (char *)value); + str = g_string_append(str, buffer_tmp); +} + +static gboolean +let_remove_eachitem(gpointer key, gpointer value, gpointer user_data) +{ + g_free(key); + g_free(value); + return TRUE; +} + +static int +raexec_setenv(GHashTable * env_params) +{ + if (env_params) { + g_hash_table_foreach(env_params, set_env, NULL); + } + return 0; +} + +static void +set_env(gpointer key, gpointer value, gpointer user_data) +{ + if (setenv(key, value, 1) != 0) { + cl_log(LOG_ERR, "setenv failed in raexecocf."); + } +} + +static int +get_providers(const char* class_path, const char* ra_type, GList ** providers) +{ + struct dirent **namelist; + int file_num; + + if ( providers == NULL ) { + cl_log(LOG_ERR, "Parameter error: get_providers"); + return -2; + } + + if ( *providers != NULL ) { + cl_log(LOG_ERR, "Parameter error: get_providers."\ + "will cause memory leak."); + *providers = NULL; + } + + file_num = scandir(class_path, &namelist, 0, alphasort); + if (file_num < 0) { + return -2; + }else{ + char tmp_buffer[FILENAME_MAX+1]; + struct stat prop; + + while (file_num--) { + if ('.' == namelist[file_num]->d_name[0]) { + free(namelist[file_num]); + continue; + } + snprintf(tmp_buffer,FILENAME_MAX,"%s/%s", + class_path, namelist[file_num]->d_name); + stat(tmp_buffer, &prop); + if (!S_ISDIR(prop.st_mode)) { + free(namelist[file_num]); + continue; + } + + snprintf(tmp_buffer,FILENAME_MAX,"%s/%s/%s", + class_path, namelist[file_num]->d_name, ra_type); + + if ( filtered(tmp_buffer) == TRUE ) { + *providers = g_list_append(*providers, + g_strdup(namelist[file_num]->d_name)); + } + free(namelist[file_num]); + } + free(namelist); + } + return g_list_length(*providers); +} + +static void +add_OCF_env_vars(GHashTable * env, const char * rsc_id, + const char * rsc_type, const char * provider) +{ + if ( env == NULL ) { + cl_log(LOG_WARNING, "env should not be a NULL pointer."); + return; + } + + g_hash_table_insert(env, g_strdup("OCF_RA_VERSION_MAJOR"), + g_strdup("1")); + g_hash_table_insert(env, g_strdup("OCF_RA_VERSION_MINOR"), + g_strdup("0")); + g_hash_table_insert(env, g_strdup("OCF_ROOT"), + g_strdup(OCF_ROOT_DIR)); + + if ( rsc_id != NULL ) { + g_hash_table_insert(env, g_strdup("OCF_RESOURCE_INSTANCE"), + g_strdup(rsc_id)); + } + + /* Currently the rsc_type=="the filename of the RA script/executable", + * It seems always correct even in the furture. ;-) + */ + if ( rsc_type != NULL ) { + g_hash_table_insert(env, g_strdup("OCF_RESOURCE_TYPE"), + g_strdup(rsc_type)); + } + + /* Notes: this is not added to specification yet. Sept 10,2004 */ + if ( provider != NULL ) { + g_hash_table_insert(env, g_strdup("OCF_RESOURCE_PROVIDER"), + g_strdup(provider)); + } +} + diff --git a/lib/plugins/lrm/raexecupstart.c b/lib/plugins/lrm/raexecupstart.c new file mode 100644 index 0000000..baa0278 --- /dev/null +++ b/lib/plugins/lrm/raexecupstart.c @@ -0,0 +1,222 @@ +/* + * 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.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * File: raexecupstart.c + * Copyright (C) 2010 Senko Rasic <senko.rasic@dobarkod.hr> + * Copyright (c) 2010 Ante Karamatic <ivoks@init.hr> + * + * Heavily based on raexeclsb.c and raexechb.c: + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * This code implements the Resource Agent Plugin Module for Upstart. + * It's a part of Local Resource Manager. Currently it's used by lrmd only. + */ + +#define PIL_PLUGINTYPE RA_EXEC_TYPE +#define PIL_PLUGIN upstart +#define PIL_PLUGINTYPE_S "RAExec" +#define PIL_PLUGIN_S "upstart" +#define PIL_PLUGINLICENSE LICENSE_PUBDOM +#define PIL_PLUGINLICENSEURL URL_PUBDOM + +#include <lha_internal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <errno.h> +#include <dirent.h> +#include <libgen.h> /* Add it for compiling on OSX */ +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <pils/plugin.h> +#include <lrm/raexec.h> +#include <libgen.h> + +#include <glib-object.h> + +#include <libxml/entities.h> + +#include "upstart-dbus.h" + +#define meta_data_template \ +"<?xml version=\"1.0\"?>\n"\ +"<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n"\ +"<resource-agent name=\"%s\" version=\"0.1\">\n"\ +" <version>1.0</version>\n"\ +" <longdesc lang=\"en\">\n"\ +" %s"\ +" </longdesc>\n"\ +" <shortdesc lang=\"en\">%s</shortdesc>\n"\ +" <parameters>\n"\ +" </parameters>\n"\ +" <actions>\n"\ +" <action name=\"start\" timeout=\"15\" />\n"\ +" <action name=\"stop\" timeout=\"15\" />\n"\ +" <action name=\"status\" timeout=\"15\" />\n"\ +" <action name=\"restart\" timeout=\"15\" />\n"\ +" <action name=\"monitor\" timeout=\"15\" interval=\"15\" start-delay=\"15\" />\n"\ +" <action name=\"meta-data\" timeout=\"5\" />\n"\ +" </actions>\n"\ +" <special tag=\"upstart\">\n"\ +" </special>\n"\ +"</resource-agent>\n" + +/* The begin of exported function list */ +static int execra(const char * rsc_id, + const char * rsc_type, + const char * provider, + const char * op_type, + const int timeout, + GHashTable * params); + +static uniform_ret_execra_t map_ra_retvalue(int ret_execra + , const char * op_type, const char * std_output); +static char* get_resource_meta(const char* rsc_type, const char* provider); +static int get_resource_list(GList ** rsc_info); +static int get_provider_list(const char* ra_type, GList ** providers); + +/* The end of exported function list */ + +/* The begin of internal used function & data list */ +#define MAX_PARAMETER_NUM 40 + +const int MAX_LENGTH_OF_RSCNAME = 40, + MAX_LENGTH_OF_OPNAME = 40; + +typedef char * RA_ARGV[MAX_PARAMETER_NUM]; + +/* The end of internal function & data list */ + +/* Rource agent execution plugin operations */ +static struct RAExecOps raops = +{ execra, + map_ra_retvalue, + get_resource_list, + get_provider_list, + get_resource_meta +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static void* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports) +{ + PluginImports = imports; + OurPlugin = us; + + imports->register_plugin(us, &OurPIExports); + + g_type_init (); + + return imports->register_interface(us, PIL_PLUGINTYPE_S, PIL_PLUGIN_S, + &raops, NULL, &OurInterface, &OurImports, + interfprivate); +} + +static int +execra( const char * rsc_id, const char * rsc_type, const char * provider, + const char * op_type, const int timeout, GHashTable * params) +{ + UpstartJobCommand cmd; + + if (!g_strcmp0(op_type, "meta-data")) { + printf("%s", get_resource_meta(rsc_type, provider)); + exit(EXECRA_OK); + } else if (!g_strcmp0(op_type, "monitor") || !g_strcmp0(op_type, "status")) { + gboolean running = upstart_job_is_running (rsc_type); + printf("%s", running ? "running" : "stopped"); + + if (running) + exit(EXECRA_OK); + else + exit(EXECRA_NOT_RUNNING); + } else if (!g_strcmp0(op_type, "start")) { + cmd = UPSTART_JOB_START; + } else if (!g_strcmp0(op_type, "stop")) { + cmd = UPSTART_JOB_STOP; + } else if (!g_strcmp0(op_type, "restart")) { + cmd = UPSTART_JOB_RESTART; + } else { + exit(EXECRA_UNIMPLEMENT_FEATURE); + } + + /* It'd be better if it returned GError, so we can distinguish + * between failure modes (can't contact upstart, no such job, + * or failure to do action. */ + if (upstart_job_do(rsc_type, cmd, timeout)) { + exit(EXECRA_OK); + } else { + exit(EXECRA_NO_RA); + } +} + +static uniform_ret_execra_t +map_ra_retvalue(int ret_execra, const char * op_type, const char * std_output) +{ + /* no need to map anything, execra() returns correct exit code */ + return ret_execra; +} + +static int +get_resource_list(GList ** rsc_info) +{ + gchar **jobs; + gint i; + *rsc_info = NULL; + + jobs = upstart_get_all_jobs(); + + if (!jobs) + return 0; + + for (i = 0; jobs[i] != NULL; i++) { + *rsc_info = g_list_prepend(*rsc_info, jobs[i]); + } + + /* free the array, but not the strings */ + g_free(jobs); + + *rsc_info = g_list_reverse(*rsc_info); + return g_list_length(*rsc_info); +} + +static char * +get_resource_meta (const gchar *rsc_type, const gchar *provider) +{ + return g_strdup_printf(meta_data_template, rsc_type, + rsc_type, rsc_type); +} + +static int +get_provider_list (const gchar *ra_type, GList **providers) +{ + *providers = g_list_prepend(*providers, g_strdup("upstart")); + return g_list_length(*providers); +} + diff --git a/lib/plugins/lrm/upstart-dbus.c b/lib/plugins/lrm/upstart-dbus.c new file mode 100644 index 0000000..b994d87 --- /dev/null +++ b/lib/plugins/lrm/upstart-dbus.c @@ -0,0 +1,406 @@ +/* + * 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.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * File: upstart-dbus.c + * Copyright (C) 2010 Senko Rasic <senko.rasic@dobarkod.hr> + * Copyright (c) 2010 Ante Karamatic <ivoks@init.hr> + * + * + * Each exported function is standalone, and creates a new connection to + * the upstart daemon. This is because lrmd plugins fork off for exec, + * and if we try and share the connection, the whole thing blocks + * indefinitely. + */ + +#include "upstart-dbus.h" + +#include <glib.h> +#include <dbus/dbus-glib.h> + +#include <dbus/dbus.h> + +#include "dbus/Upstart.h" +#include "dbus/Upstart_Job.h" +#include "dbus/Upstart_Instance.h" + +#include <stdio.h> + +#define SYSTEM_BUS_ADDRESS "unix:path=/var/run/dbus/system_bus_socket" +#define UPSTART_BUS_ADDRESS "unix:abstract=/com/ubuntu/upstart" +#define UPSTART_SERVICE_NAME "com.ubuntu.Upstart" +#define UPSTART_MANAGER_PATH "/com/ubuntu/Upstart" +#define UPSTART_IFACE "com.ubuntu.Upstart0_6" +#define UPSTART_JOB_IFACE UPSTART_IFACE ".Job" +#define UPSTART_INSTANCE_IFACE UPSTART_IFACE ".Instance" +#define UPSTART_ERROR_ALREADY_STARTED UPSTART_IFACE ".Error.AlreadyStarted" +#define UPSTART_ERROR_UNKNOWN_INSTANCE UPSTART_IFACE ".Error.UnknownInstance" + +static DBusGConnection * +get_connection(void) +{ + GError *error = NULL; + DBusGConnection *conn; + + conn = dbus_g_bus_get_private(DBUS_BUS_SYSTEM, NULL, &error); + + if (error) + { + g_error_free(error); + error = NULL; + + conn = dbus_g_connection_open("unix:abstract=/com/ubuntu/upstart", + &error); + + if (error) + { + g_warning("Can't connect to either system or Upstart " + "DBus bus."); + g_error_free(error); + + return NULL; + } + } + + return conn; +} + +static DBusGProxy * +new_proxy(DBusGConnection *conn, const gchar *object_path, + const gchar *iface) +{ + return dbus_g_proxy_new_for_name(conn, + UPSTART_SERVICE_NAME, + object_path, + iface); +} + +static GHashTable * +get_object_properties(DBusGProxy *obj, const gchar *iface) +{ + GError *error = NULL; + DBusGProxy *proxy; + GHashTable *asv; + GHashTable *retval; + GHashTableIter iter; + gpointer k, v; + + proxy = dbus_g_proxy_new_from_proxy(obj, + DBUS_INTERFACE_PROPERTIES, NULL); + + dbus_g_proxy_call(proxy, "GetAll", &error, G_TYPE_STRING, + iface, G_TYPE_INVALID, + dbus_g_type_get_map("GHashTable", + G_TYPE_STRING, + G_TYPE_VALUE), + &asv, G_TYPE_INVALID); + + if (error) { + g_warning("Error getting %s properties: %s", iface, error->message); + g_error_free(error); + g_object_unref(proxy); + return NULL; + } + + retval = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + + g_hash_table_iter_init(&iter, asv); + while (g_hash_table_iter_next(&iter, &k, &v)) { + gchar *key = k; + GValue *val = v; + + /* all known properties are strings */ + if (G_VALUE_TYPE(val) == G_TYPE_STRING) { + g_hash_table_insert(retval, g_strdup(key), + g_value_dup_string(val)); + } + } + + g_hash_table_destroy(asv); + + return retval; +} + +gchar ** +upstart_get_all_jobs(void) +{ + DBusGConnection *conn; + DBusGProxy *manager; + GError *error = NULL; + GPtrArray *array; + gchar **retval = NULL; + gint i, j; + + conn = get_connection(); + if (!conn) + return NULL; + + manager = new_proxy(conn, UPSTART_MANAGER_PATH, UPSTART_IFACE); + + dbus_g_proxy_call(manager, "GetAllJobs", &error, G_TYPE_INVALID, + dbus_g_type_get_collection("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), + &array, G_TYPE_INVALID); + + if (error) + { + g_warning("Can't call GetAllJobs: %s", error->message); + g_error_free(error); + g_object_unref(manager); + dbus_g_connection_unref(conn); + return NULL; + } + + retval = g_new0(gchar *, array->len + 1); + + for (i = 0, j = 0; i < array->len; i++) + { + DBusGProxy *job; + + job = new_proxy(conn, g_ptr_array_index(array, i), + UPSTART_JOB_IFACE); + + if (job) { + GHashTable *props = get_object_properties(job, + UPSTART_JOB_IFACE); + + if (props) { + gchar *name = g_hash_table_lookup(props, + "name"); + + if (name) + retval[j++] = g_strdup(name); + + g_hash_table_destroy(props); + } + + g_object_unref(job); + } + } + + g_ptr_array_free(array, TRUE); + + g_object_unref(manager); + dbus_g_connection_unref(conn); + + return retval; +} + +static DBusGProxy * +upstart_get_job_by_name(DBusGConnection *conn, DBusGProxy *manager, + const gchar *name) +{ + GError *error = NULL; + gchar *object_path; + DBusGProxy *retval; + + dbus_g_proxy_call(manager, "GetJobByName", &error, G_TYPE_STRING, + name, G_TYPE_INVALID, DBUS_TYPE_G_OBJECT_PATH, &object_path, + G_TYPE_INVALID); + + if (error) + { + g_warning("Error calling GetJobByName: %s", error->message); + g_error_free(error); + return NULL; + } + + retval = new_proxy(conn, object_path, UPSTART_JOB_IFACE); + + g_free(object_path); + + return retval; +} + +static gchar ** +get_job_instances(DBusGProxy *job) +{ + GError *error = NULL; + GPtrArray *array; + gchar **retval; + gint i; + + dbus_g_proxy_call(job, "GetAllInstances", &error, G_TYPE_INVALID, + dbus_g_type_get_collection("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), + &array, G_TYPE_INVALID); + + if (error) + { + g_warning("Can't call GetAllInstances: %s", error->message); + g_error_free(error); + return NULL; + } + + retval = g_new0(gchar *, array->len + 1); + + for (i = 0; i < array->len; i++) + { + retval[i] = g_ptr_array_index(array, i); + } + + g_ptr_array_free(array, TRUE); + + return retval; +} + +static DBusGProxy * +get_first_instance(DBusGConnection *conn, DBusGProxy *job) +{ + gchar **instances; + DBusGProxy *instance = NULL; + + instances = get_job_instances(job); + + if (!instances) + return NULL; + + if (*instances) + { + instance = new_proxy(conn, instances[0], + UPSTART_INSTANCE_IFACE); + } + + g_strfreev(instances); + return instance; +} + +gboolean +upstart_job_is_running(const gchar *name) +{ + DBusGConnection *conn; + DBusGProxy *manager; + DBusGProxy *job; + gboolean retval = FALSE; + + conn = get_connection(); + if (!conn) + return FALSE; + + manager = new_proxy(conn, UPSTART_MANAGER_PATH, UPSTART_IFACE); + + job = upstart_get_job_by_name(conn, manager, name); + if (job) { + DBusGProxy *instance = get_first_instance(conn, job); + + if (instance) { + GHashTable *props = get_object_properties(instance, + UPSTART_INSTANCE_IFACE); + + if (props) { + const gchar *state = g_hash_table_lookup(props, + "state"); + + retval = !g_strcmp0(state, "running"); + + g_hash_table_destroy(props); + } + + g_object_unref(instance); + } + + g_object_unref(job); + } + + g_object_unref(manager); + dbus_g_connection_unref(conn); + + return retval; +} + +gboolean +upstart_job_do(const gchar *name, UpstartJobCommand cmd, const int timeout) +{ + DBusGConnection *conn; + DBusGProxy *manager; + DBusGProxy *job; + gboolean retval; + + conn = get_connection(); + if (!conn) + return FALSE; + + manager = new_proxy(conn, UPSTART_MANAGER_PATH, UPSTART_IFACE); + + job = upstart_get_job_by_name(conn, manager, name); + if (job) { + GError *error = NULL; + const gchar *cmd_name = NULL; + gchar *instance_path = NULL; + gchar *no_args[] = { NULL }; + + switch (cmd) { + case UPSTART_JOB_START: + cmd_name = "Start"; + dbus_g_proxy_call_with_timeout (job, cmd_name, + timeout, &error, + G_TYPE_STRV, no_args, + G_TYPE_BOOLEAN, TRUE, + G_TYPE_INVALID, + DBUS_TYPE_G_OBJECT_PATH, &instance_path, + G_TYPE_INVALID); + g_free (instance_path); + break; + case UPSTART_JOB_STOP: + cmd_name = "Stop"; + dbus_g_proxy_call_with_timeout(job, cmd_name, + timeout, &error, + G_TYPE_STRV, no_args, + G_TYPE_BOOLEAN, TRUE, + G_TYPE_INVALID, + G_TYPE_INVALID); + break; + case UPSTART_JOB_RESTART: + cmd_name = "Restart"; + dbus_g_proxy_call_with_timeout (job, cmd_name, + timeout, &error, + G_TYPE_STRV, no_args, + G_TYPE_BOOLEAN, TRUE, + G_TYPE_INVALID, + DBUS_TYPE_G_OBJECT_PATH, &instance_path, + G_TYPE_INVALID); + g_free (instance_path); + break; + default: + g_assert_not_reached(); + } + + if (error) { + g_warning("Could not issue %s: %s", cmd_name, + error->message); + + /* ignore "already started" or "not running" errors */ + if (dbus_g_error_has_name(error, + UPSTART_ERROR_ALREADY_STARTED) || + dbus_g_error_has_name(error, + UPSTART_ERROR_UNKNOWN_INSTANCE)) { + retval = TRUE; + } else { + retval = FALSE; + } + g_error_free(error); + } else { + retval = TRUE; + } + + g_object_unref(job); + } else { + retval = FALSE; + } + + g_object_unref(manager); + dbus_g_connection_unref(conn); + return retval; +} + + diff --git a/lib/plugins/lrm/upstart-dbus.h b/lib/plugins/lrm/upstart-dbus.h new file mode 100644 index 0000000..bc72c95 --- /dev/null +++ b/lib/plugins/lrm/upstart-dbus.h @@ -0,0 +1,36 @@ +/* + * 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.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * File: upstart-dbus.c + * Copyright (C) 2010 Senko Rasic <senko.rasic@dobarkod.hr> + * Copyright (c) 2010 Ante Karamatic <ivoks@init.hr> + */ +#ifndef _UPSTART_DBUS_H_ +#define _UPSTART_DBUS_H_ + +#include <glib.h> + +typedef enum { + UPSTART_JOB_START, + UPSTART_JOB_STOP, + UPSTART_JOB_RESTART +} UpstartJobCommand; + +G_GNUC_INTERNAL gchar **upstart_get_all_jobs(void); +G_GNUC_INTERNAL gboolean upstart_job_do(const gchar *name, UpstartJobCommand cmd, const int timeout); +G_GNUC_INTERNAL gboolean upstart_job_is_running (const gchar *name); + +#endif /* _UPSTART_DBUS_H_ */ + diff --git a/lib/plugins/stonith/Makefile.am b/lib/plugins/stonith/Makefile.am new file mode 100644 index 0000000..01f2f4a --- /dev/null +++ b/lib/plugins/stonith/Makefile.am @@ -0,0 +1,216 @@ +# +# stonith: STONITH plugins for Linux-HA +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +SUBDIRS = external + +INCFILES = stonith_expect_helpers.h \ + stonith_plugin_common.h \ + stonith_signal.h \ + stonith_config_xml.h + +idir=$(includedir)/stonith + +i_HEADERS = $(INCFILES) + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + + +AM_CFLAGS = @NON_FATAL_CFLAGS@ + +# +# We need "vacmclient_api.h" and -lvacmclient to make the VACM +# plugin work. +# +if USE_VACM +vacm_LIB = vacm.la +else +vacm_LIB = +endif + +# +# We need <ucd-snmp/asn1.h>, <ucd-snmp/snmp_api.h>, <ucd-snmp/snmp.h> +# <ucd-snmp/snmp_client.h>, <ucd-snmp/mib.h>, -lsnmp and -lcrypto +# for the apcmastersnmp plugin to work +# + +if USE_APC_SNMP +apcmastersnmp_LIB = apcmastersnmp.la wti_mpc.la +else +apcmastersnmp_LIB = +endif +if IPMILAN_BUILD +OPENIPMI_LIB = -lOpenIPMI -lOpenIPMIposix -lOpenIPMIutils +libipmilan_LIB = libipmilan.la +ipmilan_LIB = ipmilan.la +ipmilan_TEST = ipmilantest +else +OPENIPMI_LIB = +libipmilan_LIB = +ipmilan_LIB = +endif +# +# We need <curl/curl.h>, <libxml/xmlmemory.h>, +# <libxml/parser.h>, <libxml/xpath.h>, +# -lcurl and -lxml2 for the drac3 plugin to work +# +if USE_DRAC3 +drac3_LIB = drac3.la +else +drac3_LIB = +endif + +# +# We need OpenHPI to make the IBM BladeCenter plugin work. +# +if USE_OPENHPI +bladehpi_LIB = bladehpi.la +else +bladehpi_LIB = +endif + +noinst_PROGRAMS = $(ipmilan_TEST) +ipmilantest_SOURCES = ipmilan_test.c +ipmilantest_LDADD = $(libipmilan_LIB) \ + $(top_builddir)/lib/pils/libpils.la \ + $(top_builddir)/lib/stonith/libstonith.la \ + $(OPENIPMI_LIB) + +## libraries + +plugindir = $(stonith_plugindir)/stonith2 + +plugin_LTLIBRARIES = apcmaster.la \ + $(apcmastersnmp_LIB) \ + apcsmart.la \ + baytech.la \ + $(bladehpi_LIB) \ + cyclades.la \ + $(drac3_LIB) \ + external.la \ + rhcs.la \ + ibmhmc.la \ + $(ipmilan_LIB) \ + meatware.la \ + null.la \ + nw_rpc100s.la \ + rcd_serial.la \ + rps10.la \ + ssh.la \ + suicide.la \ + $(vacm_LIB) \ + wti_nps.la +noinst_LTLIBRARIES = $(libipmilan_LIB) + +apcmaster_la_SOURCES = apcmaster.c $(INCFILES) +apcmaster_la_LDFLAGS = -export-dynamic -module -avoid-version +apcmaster_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +apcmastersnmp_la_SOURCES= apcmastersnmp.c $(INCFILES) +apcmastersnmp_la_LDFLAGS= -export-dynamic -module -avoid-version @SNMPLIB@ \ + @CRYPTOLIB@ +apcmastersnmp_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +apcsmart_la_SOURCES = apcsmart.c $(INCFILES) +apcsmart_la_LDFLAGS = -export-dynamic -module -avoid-version +apcsmart_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +baytech_la_SOURCES = baytech.c $(INCFILES) +baytech_la_LDFLAGS = -export-dynamic -module -avoid-version +baytech_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +bladehpi_la_SOURCES = bladehpi.c $(INCFILES) +bladehpi_la_LDFLAGS = -export-dynamic -module -avoid-version +bladehpi_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) -lopenhpi + +cyclades_la_SOURCES = cyclades.c $(INCFILES) +cyclades_la_LDFLAGS = -export-dynamic -module -avoid-version +cyclades_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +drac3_la_SOURCES = drac3.c drac3_command.c drac3_command.h drac3_hash.c drac3_hash.h $(INCFILES) +drac3_la_LDFLAGS = -export-dynamic -module -avoid-version +drac3_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la -lcurl -lxml2 $(GLIBLIB) + +external_la_SOURCES = external.c $(INCFILES) +external_la_LDFLAGS = -export-dynamic -module -avoid-version +external_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la + +rhcs_la_SOURCES = rhcs.c $(INCFILES) +rhcs_la_LDFLAGS = -export-dynamic -module -avoid-version +rhcs_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la + +ibmhmc_la_SOURCES = ibmhmc.c $(INCFILES) +ibmhmc_la_LDFLAGS = -export-dynamic -module -avoid-version +ibmhmc_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) + +ipmilan_la_SOURCES = ipmilan.c ipmilan.h ipmilan_command.c $(INCFILES) +ipmilan_la_LDFLAGS = -export-dynamic -module -avoid-version +ipmilan_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(OPENIPMI_LIB) $(GLIBLIB) + +libipmilan_la_SOURCES = ipmilan.c ipmilan.h ipmilan_command.c $(INCFILES) +libipmilan_la_LDFLAGS = -version-info 1:0:0 +libipmilan_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(OPENIPMI_LIB) $(GLIBLIB) + +meatware_la_SOURCES = meatware.c $(INCFILES) +meatware_la_LDFLAGS = -export-dynamic -module -avoid-version +meatware_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +null_la_SOURCES = null.c $(INCFILES) +null_la_LDFLAGS = -export-dynamic -module -avoid-version +null_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +nw_rpc100s_la_SOURCES = nw_rpc100s.c $(INCFILES) +nw_rpc100s_la_LDFLAGS = -export-dynamic -module -avoid-version +nw_rpc100s_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) + +rcd_serial_la_SOURCES = rcd_serial.c $(INCFILES) +rcd_serial_la_LDFLAGS = -export-dynamic -module -avoid-version +rcd_serial_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +rps10_la_SOURCES = rps10.c $(INCFILES) +rps10_la_LDFLAGS = -export-dynamic -module -avoid-version +rps10_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +ssh_la_SOURCES = ssh.c $(INCFILES) +ssh_la_LDFLAGS = -export-dynamic -module -avoid-version +ssh_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la + +vacm_la_SOURCES = vacm.c $(INCFILES) +vacm_la_LDFLAGS = -export-dynamic -module -avoid-version +vacm_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la + +wti_nps_la_SOURCES = wti_nps.c $(INCFILES) +wti_nps_la_LDFLAGS = -export-dynamic -module -avoid-version +wti_nps_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) + +wti_mpc_la_SOURCES= wti_mpc.c $(INCFILES) +wti_mpc_la_LDFLAGS= -export-dynamic -module -avoid-version @SNMPLIB@ \ + @CRYPTOLIB@ +wti_mpc_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +suicide_la_SOURCES = suicide.c $(INCFILES) +suicide_la_LDFLAGS = -export-dynamic -module -avoid-version +suicide_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la + +stonithscriptdir = $(stonith_plugindir)/stonith2 + +stonithscript_SCRIPTS = ribcl.py diff --git a/lib/plugins/stonith/apcmaster.c b/lib/plugins/stonith/apcmaster.c new file mode 100644 index 0000000..09a56d3 --- /dev/null +++ b/lib/plugins/stonith/apcmaster.c @@ -0,0 +1,822 @@ +/* +* +* Copyright 2001 Mission Critical Linux, Inc. +* +* All Rights Reserved. +*/ +/* + * Stonith module for APC Master Switch (AP9211) + * + * Copyright (c) 2001 Mission Critical Linux, Inc. + * author: mike ledoux <mwl@mclinux.com> + * author: Todd Wheeling <wheeling@mclinux.com> + * mangled by Sun Jiang Dong, <sunjd@cn.ibm.com>, IBM, 2005 + * + * Based strongly on original code from baytech.c by Alan Robertson. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* Observations/Notes + * + * 1. The APC MasterSwitch, unlike the BayTech network power switch, + * accepts only one (telnet) connection/session at a time. When one + * session is active, any subsequent attempt to connect to the MasterSwitch + * will result in a connection refused/closed failure. In a cluster + * environment or other environment utilizing polling/monitoring of the + * MasterSwitch (from multiple nodes), this can clearly cause problems. + * Obviously the more nodes and the shorter the polling interval, the more + * frequently such errors/collisions may occur. + * + * 2. We observed that on busy networks where there may be high occurances + * of broadcasts, the MasterSwitch became unresponsive. In some + * configurations this necessitated placing the power switch onto a + * private subnet. + */ + +#include <lha_internal.h> + +#define DEVICE "APC MasterSwitch" + +#define DOESNT_USE_STONITHKILLCOMM 1 + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN apcmaster +#define PIL_PLUGIN_S "apcmaster" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include "stonith_signal.h" + +static StonithPlugin * apcmaster_new(const char *); +static void apcmaster_destroy(StonithPlugin *); +static const char * const * apcmaster_get_confignames(StonithPlugin *); +static int apcmaster_set_config(StonithPlugin *, StonithNVpair *); +static const char * apcmaster_getinfo(StonithPlugin * s, int InfoType); +static int apcmaster_status(StonithPlugin * ); +static int apcmaster_reset_req(StonithPlugin * s, int request, const char * host); +static char ** apcmaster_hostlist(StonithPlugin *); + +static struct stonith_ops apcmasterOps ={ + apcmaster_new, /* Create new STONITH object */ + apcmaster_destroy, /* Destroy STONITH object */ + apcmaster_getinfo, /* Return STONITH info string */ + apcmaster_get_confignames, /* Get configuration parameters */ + apcmaster_set_config, /* Set configuration */ + apcmaster_status, /* Return STONITH device status */ + apcmaster_reset_req, /* Request a reset */ + apcmaster_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +#include "stonith_expect_helpers.h" + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &apcmasterOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * I have an AP9211. This code has been tested with this switch. + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + pid_t pid; + int rdfd; + int wrfd; + char * device; + char * user; + char * passwd; +}; + +static const char * pluginid = "APCMS-Stonith"; +static const char * NOTpluginID = "APCMS device has been destroyed"; + +/* + * Different expect strings that we get from the APC MasterSwitch + */ + +#define APCMSSTR "American Power Conversion" + +static struct Etoken EscapeChar[] = { {"Escape character is '^]'.", 0, 0} + , {NULL,0,0}}; +static struct Etoken login[] = { {"User Name :", 0, 0}, {NULL,0,0}}; +static struct Etoken password[] = { {"Password :", 0, 0} ,{NULL,0,0}}; +static struct Etoken Prompt[] = { {"> ", 0, 0} ,{NULL,0,0}}; +static struct Etoken LoginOK[] = { {APCMSSTR, 0, 0} + , {"User Name :", 1, 0} ,{NULL,0,0}}; +static struct Etoken Separator[] = { {"-----", 0, 0} ,{NULL,0,0}}; + +/* We may get a notice about rebooting, or a request for confirmation */ +static struct Etoken Processing[] = { {"Press <ENTER> to continue", 0, 0} + , {"Enter 'YES' to continue", 1, 0} + , {NULL,0,0}}; + +#include "stonith_config_xml.h" + +static const char *apcmasterXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +static int MS_connect_device(struct pluginDevice * ms); +static int MSLogin(struct pluginDevice * ms); +static int MSRobustLogin(struct pluginDevice * ms); +static int MSNametoOutlet(struct pluginDevice*, const char * name); +static int MSReset(struct pluginDevice*, int outletNum, const char * host); +static int MSLogout(struct pluginDevice * ms); + +#if defined(ST_POWERON) && defined(ST_POWEROFF) +static int apcmaster_onoff(struct pluginDevice*, int outletnum, const char * unitid +, int request); +#endif + +/* Login to the APC Master Switch */ + +static int +MSLogin(struct pluginDevice * ms) +{ + EXPECT(ms->rdfd, EscapeChar, 10); + + /* + * We should be looking at something like this: + * User Name : + */ + EXPECT(ms->rdfd, login, 10); + SEND(ms->wrfd, ms->user); + SEND(ms->wrfd, "\r"); + + /* Expect "Password :" */ + EXPECT(ms->rdfd, password, 10); + SEND(ms->wrfd, ms->passwd); + SEND(ms->wrfd, "\r"); + + switch (StonithLookFor(ms->rdfd, LoginOK, 30)) { + + case 0: /* Good! */ + LOG(PIL_INFO, "Successful login to %s.", ms->idinfo); + break; + + case 1: /* Uh-oh - bad password */ + LOG(PIL_CRIT, "Invalid password for %s.", ms->idinfo); + return(S_ACCESS); + + default: + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + + return(S_OK); +} + +/* Attempt to login up to 20 times... */ + +static int +MSRobustLogin(struct pluginDevice * ms) +{ + int rc = S_OOPS; + int j = 0; + + for ( ; ; ) { + if (MS_connect_device(ms) == S_OK) { + rc = MSLogin(ms); + if( rc == S_OK ) { + break; + } + } + if ((++j) == 20) { + break; + } else { + sleep(1); + } + } + + return rc; +} + +/* Log out of the APC Master Switch */ + +static +int MSLogout(struct pluginDevice* ms) +{ + int rc; + + /* Make sure we're in the right menu... */ + /*SEND(ms->wrfd, "\033\033\033\033\033\033\033"); */ + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + + /* Expect "> " */ + rc = StonithLookFor(ms->rdfd, Prompt, 5); + + /* "4" is logout */ + SEND(ms->wrfd, "4\r"); + + close(ms->wrfd); + close(ms->rdfd); + ms->wrfd = ms->rdfd = -1; + + return(rc >= 0 ? S_OK : (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS)); +} +/* Reset (power-cycle) the given outlets */ +static int +MSReset(struct pluginDevice* ms, int outletNum, const char *host) +{ + char unum[32]; + + /* Make sure we're in the top level menu */ + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + + /* Expect ">" */ + EXPECT(ms->rdfd, Prompt, 5); + + /* Request menu 1 (Device Control) */ + SEND(ms->wrfd, "1\r"); + + /* Select requested outlet */ + EXPECT(ms->rdfd, Prompt, 5); + snprintf(unum, sizeof(unum), "%i\r", outletNum); + SEND(ms->wrfd, unum); + + /* Select menu 1 (Control Outlet) */ + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "1\r"); + + /* Select menu 3 (Immediate Reboot) */ + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "3\r"); + + /* Expect "Press <ENTER> " or "Enter 'YES'" (if confirmation turned on) */ + retry: + switch (StonithLookFor(ms->rdfd, Processing, 5)) { + case 0: /* Got "Press <ENTER>" Do so */ + SEND(ms->wrfd, "\r"); + break; + + case 1: /* Got that annoying command confirmation :-( */ + SEND(ms->wrfd, "YES\r"); + goto retry; + + default: + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + + + LOG(PIL_INFO, "Host being rebooted: %s", host); + + /* Expect ">" */ + if (StonithLookFor(ms->rdfd, Prompt, 10) < 0) { + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + + /* All Right! Power is back on. Life is Good! */ + + LOG(PIL_INFO, "Power restored to host: %s", host); + + /* Return to top level menu */ + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + + return(S_OK); +} + +#if defined(ST_POWERON) && defined(ST_POWEROFF) +static int +apcmaster_onoff(struct pluginDevice* ms, int outletNum, const char * unitid, int req) +{ + char unum[32]; + + const char * onoff = (req == ST_POWERON ? "1\r" : "2\r"); + int rc; + + if ((rc = MSRobustLogin(ms) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo); + return(rc); + } + + /* Make sure we're in the top level menu */ + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + + /* Expect ">" */ + EXPECT(ms->rdfd, Prompt, 5); + + /* Request menu 1 (Device Control) */ + SEND(ms->wrfd, "1\r"); + + /* Select requested outlet */ + snprintf(unum, sizeof(unum), "%d\r", outletNum); + SEND(ms->wrfd, unum); + + /* Select menu 1 (Control Outlet) */ + SEND(ms->wrfd, "1\r"); + + /* Send ON/OFF command for given outlet */ + SEND(ms->wrfd, onoff); + + /* Expect "Press <ENTER> " or "Enter 'YES'" (if confirmation turned on) */ + retry: + switch (StonithLookFor(ms->rdfd, Processing, 5)) { + case 0: /* Got "Press <ENTER>" Do so */ + SEND(ms->wrfd, "\r"); + break; + + case 1: /* Got that annoying command confirmation :-( */ + SEND(ms->wrfd, "YES\r"); + goto retry; + + default: + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + + EXPECT(ms->rdfd, Prompt, 10); + + /* All Right! Command done. Life is Good! */ + LOG(PIL_INFO, "Power to MS outlet(s) %d turned %s", outletNum, onoff); + /* Pop back to main menu */ + SEND(ms->wrfd, "\033\033\033\033\033\033\033\r"); + return(S_OK); +} +#endif /* defined(ST_POWERON) && defined(ST_POWEROFF) */ + +/* + * Map the given host name into an (AC) Outlet number on the power strip + */ + +static int +MSNametoOutlet(struct pluginDevice* ms, const char * name) +{ + char NameMapping[128]; + int sockno; + char sockname[32]; + int times = 0; + int ret = -1; + + /* Verify that we're in the top-level menu */ + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + + /* Expect ">" */ + EXPECT(ms->rdfd, Prompt, 5); + + /* Request menu 1 (Device Control) */ + SEND(ms->wrfd, "1\r"); + + /* Expect: "-----" so we can skip over it... */ + EXPECT(ms->rdfd, Separator, 5); + EXPECT(ms->rdfd, CRNL, 5); + EXPECT(ms->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + + do { + times++; + NameMapping[0] = EOS; + SNARF(ms->rdfd, NameMapping, 5); + if (sscanf(NameMapping + , "%d- %23c",&sockno, sockname) == 2) { + + char * last = sockname+23; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (strcasecmp(name, sockname) == 0) { + ret = sockno; + } + } + } while (strlen(NameMapping) > 2 && times < 8); + + /* Pop back out to the top level menu */ + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + return(ret); +} + +static int +apcmaster_status(StonithPlugin *s) +{ + struct pluginDevice* ms; + int rc; + + ERRIFNOTCONFIGED(s,S_OOPS); + + ms = (struct pluginDevice*) s; + + if ((rc = MSRobustLogin(ms) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo); + return(rc); + } + + /* Expect ">" */ + SEND(ms->wrfd, "\033\r"); + EXPECT(ms->rdfd, Prompt, 5); + + return(MSLogout(ms)); +} + +/* + * Return the list of hosts (outlet names) for the devices on this MS unit + */ + +static char ** +apcmaster_hostlist(StonithPlugin *s) +{ + char NameMapping[128]; + char* NameList[64]; + unsigned int numnames = 0; + char ** ret = NULL; + struct pluginDevice* ms; + unsigned int i; + + ERRIFNOTCONFIGED(s,NULL); + + ms = (struct pluginDevice*) s; + + if (MSRobustLogin(ms) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo); + return(NULL); + } + + /* Expect ">" */ + NULLEXPECT(ms->rdfd, Prompt, 10); + + /* Request menu 1 (Device Control) */ + SEND(ms->wrfd, "1\r"); + + /* Expect: "-----" so we can skip over it... */ + NULLEXPECT(ms->rdfd, Separator, 5); + NULLEXPECT(ms->rdfd, CRNL, 5); + NULLEXPECT(ms->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + do { + int sockno; + char sockname[64]; + NameMapping[0] = EOS; + NULLSNARF(ms->rdfd, NameMapping, 5); + if (sscanf(NameMapping + , "%d- %23c",&sockno, sockname) == 2) { + + char * last = sockname+23; + char * nm; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (numnames >= DIMOF(NameList)-1) { + break; + } + if ((nm = (char*)STRDUP(sockname)) == NULL) { + goto out_of_memory; + } + strdown(nm); + NameList[numnames] = nm; + ++numnames; + NameList[numnames] = NULL; + } + } while (strlen(NameMapping) > 2); + + /* Pop back out to the top level menu */ + SEND(ms->wrfd, "\033"); + NULLEXPECT(ms->rdfd, Prompt, 10); + SEND(ms->wrfd, "\033"); + NULLEXPECT(ms->rdfd, Prompt, 10); + SEND(ms->wrfd, "\033"); + NULLEXPECT(ms->rdfd, Prompt, 10); + SEND(ms->wrfd, "\033"); + NULLEXPECT(ms->rdfd, Prompt, 10); + + + if (numnames >= 1) { + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + goto out_of_memory; + }else{ + memcpy(ret, NameList, (numnames+1)*sizeof(char*)); + } + } + (void)MSLogout(ms); + return(ret); + +out_of_memory: + LOG(PIL_CRIT, "out of memory"); + for (i=0; i<numnames; i++) { + FREE(NameList[i]); + } + return(NULL); +} + +/* + * Connect to the given MS device. We should add serial support here + * eventually... + */ +static int +MS_connect_device(struct pluginDevice * ms) +{ + int fd = OurImports->OpenStreamSocket(ms->device + , TELNET_PORT, TELNET_SERVICE); + + if (fd < 0) { + return(S_OOPS); + } + ms->rdfd = ms->wrfd = fd; + return(S_OK); +} + +/* + * Reset the given host on this StonithPlugin device. + */ +static int +apcmaster_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = 0; + int lorc = 0; + struct pluginDevice* ms; + + ERRIFNOTCONFIGED(s,S_OOPS); + + ms = (struct pluginDevice*) s; + + if ((rc = MSRobustLogin(ms)) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo); + return(rc); + }else{ + int noutlet; + noutlet = MSNametoOutlet(ms, host); + if (noutlet < 1) { + LOG(PIL_WARN, "%s doesn't control host [%s]" + , ms->device, host); + return(S_BADHOST); + } + switch(request) { + +#if defined(ST_POWERON) && defined(ST_POWEROFF) + case ST_POWERON: + rc = apcmaster_onoff(ms, noutlet, host, request); + break; + case ST_POWEROFF: + rc = apcmaster_onoff(ms, noutlet, host, request); + break; +#endif + case ST_GENERIC_RESET: + rc = MSReset(ms, noutlet, host); + break; + default: + rc = S_INVAL; + break; + } + } + + lorc = MSLogout(ms); + return(rc != S_OK ? rc : lorc); +} + +/* + * Get the configuration parameters names + */ +static const char * const * +apcmaster_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_LOGIN, ST_PASSWD, NULL}; + return ret; +} + +/* + * Set the configuration parameters + */ +static int +apcmaster_set_config(StonithPlugin * s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s, S_OOPS); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->device = namestocopy[0].s_value; + sd->user = namestocopy[1].s_value; + sd->passwd = namestocopy[2].s_value; + + return(S_OK); +} + +static const char * +apcmaster_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* ms; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + ms = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ms->idinfo; + break; + + case ST_DEVICENAME: /* Which particular device? */ + ret = ms->device; + break; + + case ST_DEVICEDESCR: + ret = "APC MasterSwitch (via telnet)\n" + "NOTE: The APC MasterSwitch accepts only one (telnet)\n" + "connection/session a time. When one session is active,\n" + "subsequent attempts to connect to the MasterSwitch" + " will fail."; + break; + + case ST_DEVICEURL: + ret = "http://www.apc.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = apcmasterXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * APC MasterSwitch StonithPlugin destructor... + */ +static void +apcmaster_destroy(StonithPlugin *s) +{ + struct pluginDevice* ms; + + VOIDERRIFWRONGDEV(s); + + ms = (struct pluginDevice *)s; + + ms->pluginid = NOTpluginID; + if (ms->rdfd >= 0) { + close(ms->rdfd); + ms->rdfd = -1; + } + if (ms->wrfd >= 0) { + close(ms->wrfd); + ms->wrfd = -1; + } + if (ms->device != NULL) { + FREE(ms->device); + ms->device = NULL; + } + if (ms->user != NULL) { + FREE(ms->user); + ms->user = NULL; + } + if (ms->passwd != NULL) { + FREE(ms->passwd); + ms->passwd = NULL; + } + FREE(ms); +} + +/* Create a new APC Master Switch StonithPlugin device. */ + +static StonithPlugin * +apcmaster_new(const char *subplugin) +{ + struct pluginDevice* ms = ST_MALLOCT(struct pluginDevice); + + if (ms == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(ms, 0, sizeof(*ms)); + ms->pluginid = pluginid; + ms->pid = -1; + ms->rdfd = -1; + ms->wrfd = -1; + ms->user = NULL; + ms->device = NULL; + ms->passwd = NULL; + ms->idinfo = DEVICE; + ms->sp.s_ops = &apcmasterOps; + + return(&(ms->sp)); +} diff --git a/lib/plugins/stonith/apcmastersnmp.c b/lib/plugins/stonith/apcmastersnmp.c new file mode 100644 index 0000000..a9eeaeb --- /dev/null +++ b/lib/plugins/stonith/apcmastersnmp.c @@ -0,0 +1,890 @@ +/* + * Stonith module for APC Masterswitch (SNMP) + * Copyright (c) 2001 Andreas Piesk <a.piesk@gmx.net> + * Mangled by Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version.* + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> + +/* device ID */ +#define DEVICE "APC MasterSwitch (SNMP)" + +#include "stonith_plugin_common.h" +#undef FREE /* defined by snmp stuff */ + +#ifdef PACKAGE_BUGREPORT +#undef PACKAGE_BUGREPORT +#endif +#ifdef PACKAGE_NAME +#undef PACKAGE_NAME +#endif +#ifdef PACKAGE_STRING +#undef PACKAGE_STRING +#endif +#ifdef PACKAGE_TARNAME +#undef PACKAGE_TARNAME +#endif +#ifdef PACKAGE_VERSION +#undef PACKAGE_VERSION +#endif + +#ifdef HAVE_NET_SNMP_NET_SNMP_CONFIG_H +# include <net-snmp/net-snmp-config.h> +# include <net-snmp/net-snmp-includes.h> +# include <net-snmp/agent/net-snmp-agent-includes.h> +# define INIT_AGENT() init_master_agent() +#else +# include <ucd-snmp/ucd-snmp-config.h> +# include <ucd-snmp/ucd-snmp-includes.h> +# include <ucd-snmp/ucd-snmp-agent-includes.h> +# ifndef NETSNMP_DS_APPLICATION_ID +# define NETSNMP_DS_APPLICATION_ID DS_APPLICATION_ID +# endif +# ifndef NETSNMP_DS_AGENT_ROLE +# define NETSNMP_DS_AGENT_ROLE DS_AGENT_ROLE +# endif +# define netsnmp_ds_set_boolean ds_set_boolean +# define INIT_AGENT() init_master_agent(161, NULL, NULL) +#endif + +#define PIL_PLUGIN apcmastersnmp +#define PIL_PLUGIN_S "apcmastersnmp" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#define DEBUGCALL \ + if (Debug) { \ + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); \ + } + +static StonithPlugin * apcmastersnmp_new(const char *); +static void apcmastersnmp_destroy(StonithPlugin *); +static const char * const * apcmastersnmp_get_confignames(StonithPlugin *); +static int apcmastersnmp_set_config(StonithPlugin *, StonithNVpair *); +static const char * apcmastersnmp_getinfo(StonithPlugin * s, int InfoType); +static int apcmastersnmp_status(StonithPlugin * ); +static int apcmastersnmp_reset_req(StonithPlugin * s, int request, const char * host); +static char ** apcmastersnmp_hostlist(StonithPlugin *); + +static struct stonith_ops apcmastersnmpOps ={ + apcmastersnmp_new, /* Create new STONITH object */ + apcmastersnmp_destroy, /* Destroy STONITH object */ + apcmastersnmp_getinfo, /* Return STONITH info string */ + apcmastersnmp_get_confignames, /* Get configuration parameters */ + apcmastersnmp_set_config, /* Set configuration */ + apcmastersnmp_status, /* Return STONITH device status */ + apcmastersnmp_reset_req, /* Request a reset */ + apcmastersnmp_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + DEBUGCALL; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &apcmastersnmpOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * APCMaster tested with APC Masterswitch 9212 + */ + +/* outlet commands / status codes */ +#define OUTLET_ON 1 +#define OUTLET_OFF 2 +#define OUTLET_REBOOT 3 +#define OUTLET_NO_CMD_PEND 2 + +/* oids */ +#define OID_IDENT ".1.3.6.1.4.1.318.1.1.12.1.5.0" +#define OID_NUM_OUTLETS ".1.3.6.1.4.1.318.1.1.12.1.8.0" +#define OID_OUTLET_NAMES ".1.3.6.1.4.1.318.1.1.12.3.4.1.1.2.%i" +#define OID_OUTLET_STATE ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.%i" +#define OID_OUTLET_COMMAND_PENDING ".1.3.6.1.4.1.318.1.1.12.3.5.1.1.5.%i" +#define OID_OUTLET_REBOOT_DURATION ".1.3.6.1.4.1.318.1.1.12.3.4.1.1.6.%i" + +/* + snmpset -c private -v1 172.16.0.32:161 + ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.1" i 1 + The last octet in the OID is the plug number. The value can + be 1 thru 8 because there are 8 power plugs on this device. + The integer that can be set is as follows: 1=on, 2=off, and + 3=reset +*/ + +/* own defines */ +#define MAX_STRING 128 +#define ST_PORT "port" + +/* structur of stonith object */ +struct pluginDevice { + StonithPlugin sp; /* StonithPlugin object */ + const char* pluginid; /* id of object */ + const char* idinfo; /* type of device */ + struct snmp_session* sptr; /* != NULL->session created */ + char * hostname; /* masterswitch's hostname */ + /* or ip addr */ + int port; /* snmp port */ + char * community; /* snmp community (r/w) */ + int num_outlets; /* number of outlets */ +}; + +/* for checking hardware (issue a warning if mismatch) */ +static const char* APC_tested_ident[] = {"AP9606","AP7920","AP7921","AP7900","AP_other_well_tested"}; + +/* constant strings */ +static const char *pluginid = "APCMS-SNMP-Stonith"; +static const char *NOTpluginID = "APCMS SNMP device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_PORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PORT \ + XML_PARM_SHORTDESC_END + +#define XML_PORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The port number on which the SNMP server is running on the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_PORT_PARM \ + XML_PARAMETER_BEGIN(ST_PORT, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +static const char *apcmastersnmpXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_PORT_PARM + XML_COMMUNITY_PARM + XML_PARAMETERS_END; + +/* + * own prototypes + */ + +static void APC_error(struct snmp_session *sptr, const char *fn +, const char *msg); +static struct snmp_session *APC_open(char *hostname, int port +, char *community); +static void *APC_read(struct snmp_session *sptr, const char *objname +, int type); +static int APC_write(struct snmp_session *sptr, const char *objname +, char type, char *value); + +static void +APC_error(struct snmp_session *sptr, const char *fn, const char *msg) +{ + int snmperr = 0; + int cliberr = 0; + char *errstr; + + snmp_error(sptr, &cliberr, &snmperr, &errstr); + LOG(PIL_CRIT + , "%s: %s (cliberr: %i / snmperr: %i / error: %s)." + , fn, msg, cliberr, snmperr, errstr); + free(errstr); +} + + +/* + * creates a snmp session + */ +static struct snmp_session * +APC_open(char *hostname, int port, char *community) +{ + static struct snmp_session session; + struct snmp_session *sptr; + + DEBUGCALL; + + /* create session */ + snmp_sess_init(&session); + + /* fill session */ + session.peername = hostname; + session.version = SNMP_VERSION_1; + session.remote_port = port; + session.community = (u_char *)community; + session.community_len = strlen(community); + session.retries = 5; + session.timeout = 1000000; + + /* open session */ + sptr = snmp_open(&session); + + if (sptr == NULL) { + APC_error(&session, __FUNCTION__, "cannot open snmp session"); + } + + /* return pointer to opened session */ + return (sptr); +} + +/* + * parse config + */ + +/* + * read value of given oid and return it as string + */ +static void * +APC_read(struct snmp_session *sptr, const char *objname, int type) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct variable_list *vars; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + static char response_str[MAX_STRING]; + static int response_int; + + DEBUGCALL; + + /* convert objname into oid; return NULL if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (NULL); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_GET)) != NULL) { + + /* get-request have no values */ + snmp_add_null_var(pdu, name, namelen); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == SNMPERR_SUCCESS) { + + /* request succeed, got valid response ? */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* go through the returned vars */ + for (vars = resp->variables; vars; + vars = vars->next_variable) { + + /* return response as string */ + if ((vars->type == type) && (type == ASN_OCTET_STR)) { + memset(response_str, 0, MAX_STRING); + strncpy(response_str, (char *)vars->val.string, + MIN(vars->val_len, MAX_STRING)); + snmp_free_pdu(resp); + return ((void *) response_str); + } + /* return response as integer */ + if ((vars->type == type) && (type == ASN_INTEGER)) { + response_int = *vars->val.integer; + snmp_free_pdu(resp); + return ((void *) &response_int); + } + } + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + APC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free repsonse pdu (necessary?) */ + snmp_free_pdu(resp); + }else{ + APC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error: return nothing */ + return (NULL); +} + +/* + * write value of given oid + */ +static int +APC_write(struct snmp_session *sptr, const char *objname, char type, + char *value) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + + DEBUGCALL; + + /* convert objname into oid; return FALSE if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (FALSE); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_SET)) != NULL) { + + /* add to be written value to pdu */ + snmp_add_var(pdu, name, namelen, type, value); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == STAT_SUCCESS) { + + /* go through the returned vars */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* request successful done */ + snmp_free_pdu(resp); + return (TRUE); + + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + APC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free pdu (again: necessary?) */ + snmp_free_pdu(resp); + }else{ + APC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error */ + return (FALSE); +} + +/* + * return the status for this device + */ + +static int +apcmastersnmp_status(StonithPlugin * s) +{ + struct pluginDevice *ad; + char *ident; + int i; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + if ((ident = APC_read(ad->sptr, OID_IDENT, ASN_OCTET_STR)) == NULL) { + LOG(PIL_CRIT, "%s: cannot read ident.", __FUNCTION__); + return (S_ACCESS); + } + + /* issue a warning if ident mismatches */ + for(i=DIMOF(APC_tested_ident) -1; i >=0 ; i--) { + if (strcmp(ident, APC_tested_ident[i]) == 0) { + break; + } + } + + if (i<0) { + LOG(PIL_WARN + , "%s: module not tested with this hardware '%s'." + , __FUNCTION__, ident); + } + /* status ok */ + return (S_OK); +} + +/* + * return the list of hosts configured for this device + */ + +static char ** +apcmastersnmp_hostlist(StonithPlugin * s) +{ + char **hl; + struct pluginDevice *ad; + int j, h, num_outlets; + char *outlet_name; + char objname[MAX_STRING]; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, NULL); + + ad = (struct pluginDevice *) s; + + /* allocate memory for array of up to NUM_OUTLETS strings */ + if ((hl = (char **)MALLOC((ad->num_outlets+1) * sizeof(char *))) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + /* clear hostlist array */ + memset(hl, 0, (ad->num_outlets + 1) * sizeof(char *)); + num_outlets = 0; + + /* read NUM_OUTLETS values and put them into hostlist array */ + for (j = 0; j < ad->num_outlets; ++j) { + + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_NAMES, j + 1); + + /* read outlet name */ + if ((outlet_name = APC_read(ad->sptr, objname, ASN_OCTET_STR)) == + NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, j+1); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + + /* Check whether the host is already listed */ + for (h = 0; h < num_outlets; ++h) { + if (strcasecmp(hl[h],outlet_name) == 0) + break; + } + + if (h >= num_outlets) { + /* put outletname in hostlist */ + if (Debug) { + LOG(PIL_DEBUG, "%s: added %s to hostlist." + , __FUNCTION__, outlet_name); + } + + if ((hl[num_outlets] = STRDUP(outlet_name)) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + strdown(hl[num_outlets]); + num_outlets++; + } + } + + + if (Debug) { + LOG(PIL_DEBUG, "%s: %d unique hosts connected to %d outlets." + , __FUNCTION__, num_outlets, j); + } + /* return list */ + return (hl); +} + +/* + * reset the host + */ + +static int +apcmastersnmp_reset_req(StonithPlugin * s, int request, const char *host) +{ + struct pluginDevice *ad; + char objname[MAX_STRING]; + char value[MAX_STRING]; + char *outlet_name; + int req_oid = OUTLET_REBOOT; + int expect_state = OUTLET_ON; + int i, h, num_outlets, outlet, reboot_duration, *state, bad_outlets; + int outlets[8]; /* Assume that one node is connected to a + maximum of 8 outlets */ + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + num_outlets = 0; + reboot_duration = 0; + bad_outlets = 0; + + /* read max. as->num_outlets values */ + for (outlet = 1; outlet <= ad->num_outlets; outlet++) { + + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_NAMES, outlet); + + /* read outlet name */ + if ((outlet_name = APC_read(ad->sptr, objname, ASN_OCTET_STR)) + == NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + if (Debug) { + LOG(PIL_DEBUG, "%s: found outlet: %s.", __FUNCTION__, outlet_name); + } + + /* found one */ + if (strcasecmp(outlet_name, host) == 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: found %s at outlet %d." + , __FUNCTION__, host, outlet); + } + /* Check that the outlet is not administratively down */ + + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_STATE, outlet); + + /* get outlet's state */ + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) == NULL) { + LOG(PIL_CRIT + , "%s: cannot read state for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + + /* prepare oid */ + snprintf(objname, MAX_STRING, OID_OUTLET_REBOOT_DURATION + , outlet); + + /* read reboot duration of the port */ + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) + == NULL) { + LOG(PIL_CRIT + , "%s: cannot read reboot duration for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + if (num_outlets == 0) { + /* save the inital value of the first port */ + reboot_duration = *state; + } else if (reboot_duration != *state) { + LOG(PIL_WARN, "%s: outlet %d has a different reboot duration!" + , __FUNCTION__, outlet); + if (reboot_duration < *state) + reboot_duration = *state; + } + + /* Ok, add it to the list of outlets to control */ + outlets[num_outlets]=outlet; + num_outlets++; + } + } + if (Debug) { + LOG(PIL_DEBUG, "%s: outlet: %i.", __FUNCTION__, outlet); + } + + /* host not found in outlet names */ + if (num_outlets < 1) { + LOG(PIL_CRIT, "%s: no active outlet for '%s'.", __FUNCTION__, host); + return (S_BADHOST); + } + + + /* choose the OID for the stonith request */ + switch (request) { + case ST_POWERON: + req_oid = OUTLET_ON; + expect_state = OUTLET_ON; + break; + case ST_POWEROFF: + req_oid = OUTLET_OFF; + expect_state = OUTLET_OFF; + break; + case ST_GENERIC_RESET: + req_oid = OUTLET_REBOOT; + expect_state = OUTLET_ON; + break; + default: break; + } + + /* Turn them all off */ + + for (outlet=outlets[0], i=0 ; i < num_outlets; i++, outlet = outlets[i]) { + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_COMMAND_PENDING, outlet); + + /* are there pending commands ? */ + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) == NULL) { + LOG(PIL_CRIT, "%s: cannot read pending commands for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + + if (*state != OUTLET_NO_CMD_PEND) { + LOG(PIL_CRIT, "%s: command pending.", __FUNCTION__); + return (S_RESETFAIL); + } + + /* prepare objnames */ + snprintf(objname, MAX_STRING, OID_OUTLET_STATE, outlet); + snprintf(value, MAX_STRING, "%i", req_oid); + + /* send reboot cmd */ + if (!APC_write(ad->sptr, objname, 'i', value)) { + LOG(PIL_CRIT + , "%s: cannot send reboot command for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + } + + /* wait max. 2*reboot_duration for all outlets to go back on */ + for (i = 0; i < reboot_duration << 1; i++) { + + sleep(1); + + bad_outlets = 0; + for (outlet=outlets[0], h=0 ; h < num_outlets; h++, + outlet = outlets[h]) { + + /* prepare objname of the first outlet */ + snprintf(objname, MAX_STRING, OID_OUTLET_STATE, outlet); + /* get outlet's state */ + + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) + == NULL) { + LOG(PIL_CRIT + , "%s: cannot read state for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + + if (*state != expect_state) + bad_outlets++; + } + + if (bad_outlets == 0) + return (S_OK); + } + + if (bad_outlets == num_outlets) { + /* reset failed */ + LOG(PIL_CRIT, "%s: stonith operation for '%s' failed." + , __FUNCTION__, host); + return (S_RESETFAIL); + } else { + /* Not all outlets back on, but at least one; implies node was */ + /* rebooted correctly */ + LOG(PIL_WARN,"%s: Not all outlets in the expected state!" + , __FUNCTION__); + return (S_OK); + } +} + +/* + * Get the configuration parameter names. + */ + +static const char * const * +apcmastersnmp_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_PORT, ST_COMMUNITY, NULL}; + return ret; +} + +/* + * Set the configuration parameters. + */ + +static int +apcmastersnmp_set_config(StonithPlugin * s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + int * i; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_PORT, NULL} + , {ST_COMMUNITY, NULL} + , {NULL, NULL} + }; + + DEBUGCALL; + ERRIFWRONGDEV(s,S_INVAL); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->hostname = namestocopy[0].s_value; + sd->port = atoi(namestocopy[1].s_value); + PluginImports->mfree(namestocopy[1].s_value); + sd->community = namestocopy[2].s_value; + + /* try to resolve the hostname/ip-address */ + if (gethostbyname(sd->hostname) != NULL) { + /* init snmp library */ + init_snmp("apcmastersnmp"); + + /* now try to get a snmp session */ + if ((sd->sptr = APC_open(sd->hostname, sd->port, sd->community)) != NULL) { + + /* ok, get the number of outlets from the masterswitch */ + if ((i = APC_read(sd->sptr, OID_NUM_OUTLETS, ASN_INTEGER)) + == NULL) { + LOG(PIL_CRIT + , "%s: cannot read number of outlets." + , __FUNCTION__); + return (S_ACCESS); + } + /* store the number of outlets */ + sd->num_outlets = *i; + if (Debug) { + LOG(PIL_DEBUG, "%s: number of outlets: %i." + , __FUNCTION__, sd->num_outlets ); + } + + /* Everything went well */ + return (S_OK); + }else{ + LOG(PIL_CRIT, "%s: cannot create snmp session." + , __FUNCTION__); + } + }else{ + LOG(PIL_CRIT, "%s: cannot resolve hostname '%s', h_errno %d." + , __FUNCTION__, sd->hostname, h_errno); + } + + /* not a valid config */ + return (S_BADCONFIG); +} + +/* + * get info about the stonith device + */ + +static const char * +apcmastersnmp_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *ad; + const char *ret = NULL; + + DEBUGCALL; + + ERRIFWRONGDEV(s, NULL); + + ad = (struct pluginDevice *) s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ad->idinfo; + break; + + case ST_DEVICENAME: + ret = ad->hostname; + break; + + case ST_DEVICEDESCR: + ret = "APC MasterSwitch (via SNMP)\n" + "The APC MasterSwitch can accept multiple simultaneous SNMP clients"; + break; + + case ST_DEVICEURL: + ret = "http://www.apc.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = apcmastersnmpXML; + break; + + } + return ret; +} + + +/* + * APC StonithPlugin destructor... + */ + +static void +apcmastersnmp_destroy(StonithPlugin * s) +{ + struct pluginDevice *ad; + + DEBUGCALL; + + VOIDERRIFWRONGDEV(s); + + ad = (struct pluginDevice *) s; + + ad->pluginid = NOTpluginID; + + /* release snmp session */ + if (ad->sptr != NULL) { + snmp_close(ad->sptr); + ad->sptr = NULL; + } + + /* reset defaults */ + if (ad->hostname != NULL) { + PluginImports->mfree(ad->hostname); + ad->hostname = NULL; + } + if (ad->community != NULL) { + PluginImports->mfree(ad->community); + ad->community = NULL; + } + ad->num_outlets = 0; + + PluginImports->mfree(ad); +} + +/* + * Create a new APC StonithPlugin device. Too bad this function can't be + * static + */ + +static StonithPlugin * +apcmastersnmp_new(const char *subplugin) +{ + struct pluginDevice *ad = ST_MALLOCT(struct pluginDevice); + + DEBUGCALL; + + /* no memory for stonith-object */ + if (ad == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + + /* clear stonith-object */ + memset(ad, 0, sizeof(*ad)); + + /* set defaults */ + ad->pluginid = pluginid; + ad->sptr = NULL; + ad->hostname = NULL; + ad->community = NULL; + ad->idinfo = DEVICE; + ad->sp.s_ops = &apcmastersnmpOps; + + /* return the object */ + return (&(ad->sp)); +} diff --git a/lib/plugins/stonith/apcmastersnmp.cfg.example b/lib/plugins/stonith/apcmastersnmp.cfg.example new file mode 100644 index 0000000..76fea08 --- /dev/null +++ b/lib/plugins/stonith/apcmastersnmp.cfg.example @@ -0,0 +1,39 @@ +# +# this is an example config for the stonith module apcmastersnmp +# +# 1. what does the fields on the line mean ? +# +# all parameters must be given on a single line. blank lines and lines +# starting with '#' are ignored. only the first not ignored line will +# be processed. all subsequent lines will be ignored. the different +# fields must be seperated by white-spaces (blanks and/or tabs). +# +# the first field is the either the hostname or the ip address. the +# hostname must be resolvable. the second fields specifies the snmp port +# the masterswitch is listening. for snmp the default is 161. the last +# field contains the so called 'community' string. this must be the same +# as the one in the masterswitch configuration. +# +# +# 2. how must the masterswitch be configured ? +# +# as said above, the community string must be set to the same value entered +# in this config. the different outlets must be named after the connected +# hosts. that means, the outlet names must be the same as the node names +# in /etc/ha.d/ha.cf. the reset values should be set to reasonable values. +# +# the module DON'T configure the module in any way! +# +# +# 3. how does the module work ? +# +# in case of a stonith the module receives the nodename of the host, which +# should be reset. the module looks up this nodename in the list of outlet +# names. that's why the names must be identical (see 2.). if it finds the +# name, it'll reset the appropriate outlet using the configured values +# (eg. delay, duration). then the module waits for the outlet to coming +# up. if it comes up, a successful stonith will be reported back. otherwise +# the stonith failed and a failure code will be returned. +# + +192.168.1.110 161 private diff --git a/lib/plugins/stonith/apcsmart.c b/lib/plugins/stonith/apcsmart.c new file mode 100644 index 0000000..18d1612 --- /dev/null +++ b/lib/plugins/stonith/apcsmart.c @@ -0,0 +1,1028 @@ +/* + * Stonith module for APCSmart Stonith device + * Copyright (c) 2000 Andreas Piesk <a.piesk@gmx.net> + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version.* + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Original version of this UPS code was taken from: + * 'Network UPS Tools' by Russell Kroll <rkroll@exploits.org> + * homepage: http://www.networkupstools.org/ + * + * Significantly mangled by Alan Robertson <alanr@unix.sh> + */ + +#include <lha_internal.h> + +#define DEVICE "APCSmart" + +#include "stonith_plugin_common.h" + +/* + * APCSmart (tested with old 900XLI, APC SmartUPS 700 and SmartUPS-1000) + * + * The reset is a combined reset: "S" and "@000" + * The "S" command tells the ups that if it is on-battery, it should + * remain offline until the power is back. + * If that command is not accepted, the "@000" command will be sent + * to tell the ups to turn off and back on right away. + * In both cases, if the UPS supports a 20 second shutdown grace + * period (such as on the 900XLI), the shutdown will delay that long, + * otherwise the shutdown will happen immediately (the code searches + * for the smallest possible delay). + */ + +#define CFG_FILE "/etc/ha.d/apcsmart.cfg" + +#define MAX_DEVICES 1 + +#define SERIAL_TIMEOUT 3 /* timeout in sec */ +#define SEND_DELAY 50000 /* in microseconds */ +#define ENDCHAR 10 /* use LF */ +#define MAX_STRING 512 +#define MAX_DELAY_STRING 16 +#define SWITCH_TO_NEXT_VAL "-" /* APC cmd for cycling through + * the values + */ + +#define CMD_SMART_MODE "Y" +#define RSP_SMART_MODE "SM" +#define CMD_GET_STATUS "Q" +#define RSP_GET_STATUS NULL +#define CMD_RESET "S" /* turn off & stay off if on battery */ +#define CMD_RESET2 "@000" /* turn off & immediately turn on */ +#define RSP_RESET "*" /* RESET response from older models */ +#define RSP_RESET2 "OK" /* RESET response from newer models */ +#define RSP_NA "NA" +#define CMD_READREG1 "~" +#define CMD_OFF "Z" +#define CMD_ON "\016" /* (control-n) */ +#define CMD_SHUTDOWN_DELAY "p" +#define CMD_WAKEUP_DELAY "r" + +#define CR 13 + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; /* of object */ + const char * idinfo; /* type of device */ + char ** hostlist; /* served by the device (only 1) */ + int hostcount;/* of hosts (1) */ + char * upsdev; /* */ + int upsfd; /* for serial port */ + int retries; + char shutdown_delay[MAX_DELAY_STRING]; + char old_shutdown_delay[MAX_DELAY_STRING]; + char wakeup_delay[MAX_DELAY_STRING]; + char old_wakeup_delay[MAX_DELAY_STRING]; +}; + +/* saving old settings */ +/* FIXME! These should be part of pluginDevice struct above */ +static struct termios old_tio; + +static int f_serialtimeout; /* flag for timeout */ +static const char *pluginid = "APCSmart-Stonith"; +static const char *NOTpluginID = "APCSmart device has been destroyed"; + +/* + * stonith prototypes + */ + +#define PIL_PLUGIN apcsmart +#define PIL_PLUGIN_S "apcsmart" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include "stonith_signal.h" + +static StonithPlugin * apcsmart_new(const char *); +static void apcsmart_destroy(StonithPlugin *); +static const char * const * apcsmart_get_confignames(StonithPlugin*); +static int apcsmart_set_config(StonithPlugin *, StonithNVpair*); +static const char * apcsmart_get_info(StonithPlugin * s, int InfoType); +static int apcsmart_status(StonithPlugin * ); +static int apcsmart_reset_req(StonithPlugin * s, int request, const char * host); +static char ** apcsmart_hostlist(StonithPlugin *); + +static struct stonith_ops apcsmartOps ={ + apcsmart_new, /* Create new STONITH object */ + apcsmart_destroy, /* Destroy STONITH object */ + apcsmart_get_info, /* Return STONITH info string */ + apcsmart_get_confignames, /* Return STONITH info string */ + apcsmart_set_config, /* Get configuration from NVpairs */ + apcsmart_status, /* Return STONITH device status */ + apcsmart_reset_req, /* Request a reset */ + apcsmart_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &apcsmartOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#include "stonith_config_xml.h" + +static const char *apcsmartXML = + XML_PARAMETERS_BEGIN + XML_TTYDEV_PARM + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +/* + * own prototypes + */ + +int APC_open_serialport(const char *port, speed_t speed); +void APC_close_serialport(const char *port, int upsfd); +void APC_sh_serial_timeout(int sig); +int APC_send_cmd(int upsfd, const char *cmd); +int APC_recv_rsp(int upsfd, char *rsp); +int APC_enter_smartmode(int upsfd); +int APC_set_ups_var(int upsfd, const char *cmd, char *newval); +int APC_get_smallest_delay(int upsfd, const char *cmd, char *smdelay); +int APC_init( struct pluginDevice *ad ); +void APC_deinit( struct pluginDevice *ad ); + +/* + * Signal handler for serial port timeouts + */ + +void +APC_sh_serial_timeout(int sig) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + STONITH_IGNORE_SIG(SIGALRM); + + if (Debug) { + LOG(PIL_DEBUG, "%s: serial port timed out.", __FUNCTION__); + } + + f_serialtimeout = TRUE; + + return; +} + +/* + * Open serial port and set it to b2400 + */ + +int +APC_open_serialport(const char *port, speed_t speed) +{ + struct termios tio; + int fd; + int rc; + int errno_save; + int fflags; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if ((rc = OurImports->TtyLock(port)) < 0) { + LOG(PIL_CRIT, "%s: Could not lock tty %s [rc=%d]." + , __FUNCTION__, port, rc); + return -1; + } + + STONITH_SIGNAL(SIGALRM, APC_sh_serial_timeout); + alarm(SERIAL_TIMEOUT); + f_serialtimeout = FALSE; + + fd = open(port, O_RDWR | O_NOCTTY | O_NONBLOCK | O_EXCL); + errno_save = errno; + + alarm(0); + STONITH_IGNORE_SIG(SIGALRM); + + if (fd < 0) { + LOG(PIL_CRIT, "%s: Open of %s %s [%s].", __FUNCTION__ + , port + , f_serialtimeout ? "timed out" : "failed" + , strerror(errno_save)); + OurImports->TtyUnlock(port); + return -1; + } + + if ((fflags = fcntl(fd, F_GETFL)) < 0 + || fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) { + LOG(PIL_CRIT, "%s: Setting flags on %s failed [%s]." + , __FUNCTION__ + , port + , strerror(errno_save)); + close(fd); + OurImports->TtyUnlock(port); + return -1; + } + + if (tcgetattr(fd, &old_tio) < 0) { + LOG(PIL_CRIT, "%s: tcgetattr of %s failed [%s].", __FUNCTION__ + , port + , strerror(errno)); + close(fd); + OurImports->TtyUnlock(port); + return -1; + } + + memcpy(&tio, &old_tio, sizeof(struct termios)); + tio.c_cflag = CS8 | CLOCAL | CREAD; + tio.c_iflag = IGNPAR; + tio.c_oflag = 0; + tio.c_lflag = 0; + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + + cfsetispeed(&tio, speed); + cfsetospeed(&tio, speed); + + tcflush(fd, TCIOFLUSH); + tcsetattr(fd, TCSANOW, &tio); + + return (fd); +} + +/* + * Close serial port and restore old settings + */ + +void +APC_close_serialport(const char *port, int upsfd) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + if (upsfd < 0) { + return; + } + + tcflush(upsfd, TCIFLUSH); + tcsetattr(upsfd, TCSANOW, &old_tio); + close(upsfd); + if (port != NULL) { + OurImports->TtyUnlock(port); + } +} + +/* + * Send a command to the ups + */ + +int +APC_send_cmd(int upsfd, const char *cmd) +{ + int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s(\"%s\")", __FUNCTION__, cmd); + } + + tcflush(upsfd, TCIFLUSH); + for (i = strlen(cmd); i > 0; i--) { + if (write(upsfd, cmd++, 1) != 1) { + return (S_ACCESS); + } + + usleep(SEND_DELAY); + } + return (S_OK); +} + +/* + * Get the response from the ups + */ + +int +APC_recv_rsp(int upsfd, char *rsp) +{ + char *p = rsp; + char inp; + int num = 0; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + *p = '\0'; + + STONITH_SIGNAL(SIGALRM, APC_sh_serial_timeout); + + alarm(SERIAL_TIMEOUT); + + while (num < MAX_STRING) { + + if (read(upsfd, &inp, 1) == 1) { + + /* shutdown sends only a '*' without LF */ + if ((inp == '*') && (num == 0)) { + *p++ = inp; + num++; + inp = ENDCHAR; + } + + if (inp == ENDCHAR) { + alarm(0); + STONITH_IGNORE_SIG(SIGALRM); + + *p = '\0'; + if (Debug) { + LOG(PIL_DEBUG, "return(\"%s\")/*%s*/;" + , rsp, __FUNCTION__); + } + return (S_OK); + } + + if (inp != CR) { + *p++ = inp; + num++; + } + }else{ + alarm(0); + STONITH_IGNORE_SIG(SIGALRM); + *p = '\0'; + LOG(PIL_DEBUG, "%s: %s.", __FUNCTION__, + f_serialtimeout ? "timeout" : + "can't access device" ); + return (f_serialtimeout ? S_TIMEOUT : S_ACCESS); + } + } + return (S_ACCESS); +} + +/* + * Enter smart mode + */ + +int +APC_enter_smartmode(int upsfd) +{ + int rc; + char resp[MAX_STRING]; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + strcpy(resp, RSP_SMART_MODE); + + if (((rc = APC_send_cmd(upsfd, CMD_SMART_MODE)) == S_OK) + && ((rc = APC_recv_rsp(upsfd, resp)) == S_OK) + && (strcmp(RSP_SMART_MODE, resp) == 0)) { + return (S_OK); + } + + return (S_ACCESS); +} + +/* + * Set a value in the hardware using the <cmdchar> '-' (repeat) approach + */ + +int +APC_set_ups_var(int upsfd, const char *cmd, char *newval) +{ + char resp[MAX_STRING]; + char orig[MAX_STRING]; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, orig)) != S_OK)) { + return (rc); + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: var '%s' original val %s" + , __FUNCTION__, cmd, orig); + } + + if (strcmp(orig, newval) == 0) { + return (S_OK); /* already set */ + } + + *resp = '\0'; + + while (strcmp(resp, orig) != 0) { + if (((rc = APC_send_cmd(upsfd, SWITCH_TO_NEXT_VAL)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if (strcmp(resp, newval) == 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: var '%s' set to %s" + , __FUNCTION__, cmd, newval); + } + + strcpy(newval, orig); /* return the old value */ + return (S_OK); /* got it */ + } + } + + LOG(PIL_CRIT, "%s(): Could not set variable '%s' to %s!" + , __FUNCTION__, cmd, newval); + LOG(PIL_CRIT, "%s(): This UPS may not support STONITH :-(" + , __FUNCTION__); + + return (S_OOPS); +} + +/* + * Query the smallest delay supported by the hardware using the + * <cmdchar> '-' (repeat) approach and looping through all possible values, + * saving the smallest + */ + +int +APC_get_smallest_delay(int upsfd, const char *cmd, char *smdelay) +{ + char resp[MAX_DELAY_STRING]; + char orig[MAX_DELAY_STRING]; + int delay, smallest; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, orig)) != S_OK)) { + return (rc); + } + + smallest = atoi(orig); + strcpy(smdelay, orig); + + *resp = '\0'; + + /* search for smallest delay; need to loop through all possible + * values so that we leave delay the way we found it */ + while (strcmp(resp, orig) != 0) { + if (((rc = APC_send_cmd(upsfd, SWITCH_TO_NEXT_VAL)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if ((delay = atoi(resp)) < smallest) { + smallest = delay; + strcpy(smdelay, resp); + } + } + + return (S_OK); +} + +/* + * Initialize the ups + */ + +int +APC_init(struct pluginDevice *ad) +{ + int upsfd; + char value[MAX_DELAY_STRING]; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + /* if ad->upsfd != -1 device has already been configured. */ + /* Just enter smart mode again because otherwise a SmartUPS-1000 */ + /* has been observed to sometimes not respond. */ + if(ad->upsfd >= 0) { + if(APC_enter_smartmode(ad->upsfd) != S_OK) { + return(S_OOPS); + } + return S_OK; + } + + /* open serial port and store the fd in ad->upsfd */ + if ((upsfd = APC_open_serialport(ad->upsdev, B2400)) == -1) { + return S_OOPS; + } + + /* switch into smart mode */ + if (APC_enter_smartmode(upsfd) != S_OK) { + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + + /* get the smallest possible delays for this particular hardware */ + if (APC_get_smallest_delay(upsfd, CMD_SHUTDOWN_DELAY + , ad->shutdown_delay) != S_OK + || APC_get_smallest_delay(upsfd, CMD_WAKEUP_DELAY + , ad->wakeup_delay) != S_OK) { + LOG(PIL_CRIT, "%s: couldn't retrieve smallest delay from UPS" + , __FUNCTION__); + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + + /* get the old settings and store them */ + strcpy(value, ad->shutdown_delay); + if (APC_set_ups_var(upsfd, CMD_SHUTDOWN_DELAY, value) != S_OK) { + LOG(PIL_CRIT, "%s: couldn't set shutdown delay to %s" + , __FUNCTION__, ad->shutdown_delay); + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + strcpy(ad->old_shutdown_delay, value); + strcpy(value, ad->wakeup_delay); + if (APC_set_ups_var(upsfd, CMD_WAKEUP_DELAY, value) != S_OK) { + LOG(PIL_CRIT, "%s: couldn't set wakeup delay to %s" + , __FUNCTION__, ad->wakeup_delay); + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + strcpy(ad->old_wakeup_delay, value); + + ad->upsfd = upsfd; + return S_OK; +} + +/* + * Restore original settings and close the port + */ + +void +APC_deinit(struct pluginDevice *ad) +{ + APC_enter_smartmode( ad->upsfd ); + + APC_set_ups_var(ad->upsfd, CMD_SHUTDOWN_DELAY, ad->old_shutdown_delay); + APC_set_ups_var(ad->upsfd, CMD_WAKEUP_DELAY, ad->old_wakeup_delay); + + /* close serial port */ + if (ad->upsfd >= 0) { + APC_close_serialport(ad->upsdev, ad->upsfd); + ad->upsfd = -1; + } +} +static const char * const * +apcsmart_get_confignames(StonithPlugin* sp) +{ + static const char * names[] = {ST_TTYDEV, ST_HOSTLIST, NULL}; + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + return names; +} + +/* + * Stash away the config info we've been given... + */ + +static int +apcsmart_set_config(StonithPlugin * s, StonithNVpair* list) +{ + struct pluginDevice * ad = (struct pluginDevice*)s; + StonithNamesToGet namestocopy [] = + { {ST_TTYDEV, NULL} + , {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + ERRIFWRONGDEV(s, S_OOPS); + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + ad->upsdev = namestocopy[0].s_value; + ad->hostlist = OurImports->StringToHostList(namestocopy[1].s_value); + FREE(namestocopy[1].s_value); + + if (ad->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (ad->hostcount = 0; ad->hostlist[ad->hostcount] + ; ad->hostcount++) { + strdown(ad->hostlist[ad->hostcount]); + } + if (access(ad->upsdev, R_OK|W_OK|F_OK) < 0) { + LOG(PIL_CRIT,"Cannot access tty [%s]", ad->upsdev); + return S_BADCONFIG; + } + + return ad->hostcount ? S_OK : S_BADCONFIG; +} + +/* + * return the status for this device + */ + +static int +apcsmart_status(StonithPlugin * s) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + char resp[MAX_STRING]; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + + /* get status */ + if (((rc = APC_init( ad )) == S_OK) + && ((rc = APC_send_cmd(ad->upsfd, CMD_GET_STATUS)) == S_OK) + && ((rc = APC_recv_rsp(ad->upsfd, resp)) == S_OK)) { + return (S_OK); /* everything ok. */ + } + if (Debug) { + LOG(PIL_DEBUG, "%s: failed, rc=%d.", __FUNCTION__, rc); + } + return (rc); +} + + +/* + * return the list of hosts configured for this device + */ + +static char ** +apcsmart_hostlist(StonithPlugin * s) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + ERRIFNOTCONFIGED(s,NULL); + + return OurImports->CopyHostList((const char **)(void*)ad->hostlist); +} + +static gboolean +apcsmart_RegisterBitsSet(struct pluginDevice * ad, int nreg, unsigned bits +, gboolean* waserr) +{ + const char* reqregs[4] = {"?", "~", "'", "8"}; + unsigned regval; + char resp[MAX_STRING]; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + + if (APC_enter_smartmode(ad->upsfd) != S_OK + || APC_send_cmd(ad->upsfd, reqregs[nreg]) != S_OK + || APC_recv_rsp(ad->upsfd, resp) != S_OK + || (sscanf(resp, "%02x", ®val) != 1)) { + if (waserr){ + *waserr = TRUE; + } + return FALSE; + } + if (waserr){ + *waserr = FALSE; + } + return ((regval & bits) == bits); +} + +#define apcsmart_IsPoweredOff(ad, err) apcsmart_RegisterBitsSet(ad,1,0x40,err) +#define apcsmart_ResetHappening(ad,err) apcsmart_RegisterBitsSet(ad,3,0x08,err) + + +static int +apcsmart_ReqOnOff(struct pluginDevice * ad, int request) +{ + const char * cmdstr; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + cmdstr = (request == ST_POWEROFF ? CMD_OFF : CMD_ON); + /* enter smartmode, send on/off command */ + if ((rc =APC_enter_smartmode(ad->upsfd)) != S_OK + || (rc = APC_send_cmd(ad->upsfd, cmdstr)) != S_OK) { + return rc; + } + sleep(2); + if ((rc = APC_send_cmd(ad->upsfd, cmdstr)) == S_OK) { + gboolean ison; + gboolean waserr; + sleep(1); + ison = !apcsmart_IsPoweredOff(ad, &waserr); + if (waserr) { + return S_RESETFAIL; + } + if (request == ST_POWEROFF) { + return ison ? S_RESETFAIL : S_OK; + }else{ + return ison ? S_OK : S_RESETFAIL; + } + } + return rc; +} + +/* + * reset the host + */ + +static int +apcsmart_ReqGenericReset(struct pluginDevice *ad) +{ + char resp[MAX_STRING]; + int rc = S_RESETFAIL; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + /* send reset command(s) */ + if (((rc = APC_init(ad)) == S_OK) + && ((rc = APC_send_cmd(ad->upsfd, CMD_RESET)) == S_OK)) { + if (((rc = APC_recv_rsp(ad->upsfd, resp)) == S_OK) + && (strcmp(resp, RSP_RESET) == 0 + || strcmp(resp, RSP_RESET2) == 0)) { + /* first kind of reset command was accepted */ + } else if (((rc = APC_send_cmd(ad->upsfd, CMD_RESET2)) == S_OK) + && ((rc = APC_recv_rsp(ad->upsfd, resp)) == S_OK) + && (strcmp(resp, RSP_RESET) == 0 + || strcmp(resp, RSP_RESET2) == 0)) { + /* second kind of command was accepted */ + } else { + if (Debug) { + LOG(PIL_DEBUG, "APC: neither reset command " + "was accepted"); + } + rc = S_RESETFAIL; + } + } + if (rc == S_OK) { + /* we wait grace period + up to 10 seconds after shutdown */ + int maxdelay = atoi(ad->shutdown_delay)+10; + int j; + + for (j=0; j < maxdelay; ++j) { + gboolean err; + if (apcsmart_ResetHappening(ad, &err)) { + return err ? S_RESETFAIL : S_OK; + } + sleep(1); + } + LOG(PIL_CRIT, "%s: timed out waiting for reset to end." + , __FUNCTION__); + return S_RESETFAIL; + + }else{ + if (strcmp(resp, RSP_NA) == 0){ + gboolean iserr; + /* This means it's currently powered off */ + /* or busy on a previous command... */ + if (apcsmart_IsPoweredOff(ad, &iserr)) { + if (iserr) { + LOG(PIL_DEBUG, "%s: power off " + "detection failed.", __FUNCTION__); + return S_RESETFAIL; + } + if (Debug) { + LOG(PIL_DEBUG, "APC: was powered off, " + "powering back on."); + } + return apcsmart_ReqOnOff(ad, ST_POWERON); + } + } + } + strcpy(resp, "?"); + + /* reset failed */ + + return S_RESETFAIL; +} + +static int +apcsmart_reset_req(StonithPlugin * s, int request, const char *host) +{ + char ** hl; + int b_found=FALSE; + struct pluginDevice * ad = (struct pluginDevice *) s; + int rc; + + ERRIFNOTCONFIGED(s, S_OOPS); + + if (host == NULL) { + LOG(PIL_CRIT, "%s: invalid hostname argument.", __FUNCTION__); + return (S_INVAL); + } + + /* look through the hostlist */ + hl = ad->hostlist; + + while (*hl && !b_found ) { + if( strcasecmp( *hl, host ) == 0 ) { + b_found = TRUE; + break; + }else{ + ++hl; + } + } + + /* host not found in hostlist */ + if( !b_found ) { + LOG(PIL_CRIT, "%s: host '%s' not in hostlist." + , __FUNCTION__, host); + return S_BADHOST; + } + if ((rc = APC_init(ad)) != S_OK) { + return rc; + } + + if (request == ST_POWERON || request == ST_POWEROFF) { + return apcsmart_ReqOnOff(ad, request); + } + return apcsmart_ReqGenericReset(ad); +} + + +/* + * get info about the stonith device + */ + +static const char * +apcsmart_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + const char *ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + + switch (reqtype) { + case ST_DEVICEID: + ret = ad->idinfo; + break; + + case ST_DEVICENAME: + ret = ad->upsdev; + break; + + case ST_DEVICEDESCR: + ret = "APC Smart UPS\n" + " (via serial port - NOT USB!). \n" + " Works with higher-end APC UPSes, like\n" + " Back-UPS Pro, Smart-UPS, Matrix-UPS, etc.\n" + " (Smart-UPS may have to be >= Smart-UPS 700?).\n" + " See http://www.networkupstools.org/protocols/apcsmart.html\n" + " for protocol compatibility details."; + break; + + case ST_DEVICEURL: + ret = "http://www.apc.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = apcsmartXML; + break; + + default: + ret = NULL; + break; + } + return (ret); +} + +/* + * APC Stonith destructor... + */ + +static void +apcsmart_destroy(StonithPlugin * s) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + VOIDERRIFWRONGDEV(s); + + if (ad->upsfd >= 0 && ad->upsdev) { + APC_deinit( ad ); + } + + ad->pluginid = NOTpluginID; + + if (ad->hostlist) { + stonith_free_hostlist(ad->hostlist); + ad->hostlist = NULL; + } + if (ad->upsdev != NULL) { + FREE(ad->upsdev); + ad->upsdev = NULL; + } + + ad->hostcount = -1; + ad->upsfd = -1; + + FREE(ad); + +} + +/* + * Create a new APC Stonith device. Too bad this function can't be + * static + */ + +static StonithPlugin * +apcsmart_new(const char *subplugin) +{ + struct pluginDevice *ad = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + if (ad == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + + memset(ad, 0, sizeof(*ad)); + + ad->pluginid = pluginid; + ad->hostlist = NULL; + ad->hostcount = -1; + ad->upsfd = -1; + ad->idinfo = DEVICE; + ad->sp.s_ops = &apcsmartOps; + + if (Debug) { + LOG(PIL_DEBUG, "%s: returning successfully.", __FUNCTION__); + } + return &(ad->sp); +} diff --git a/lib/plugins/stonith/apcsmart.cfg.example b/lib/plugins/stonith/apcsmart.cfg.example new file mode 100644 index 0000000..278f925 --- /dev/null +++ b/lib/plugins/stonith/apcsmart.cfg.example @@ -0,0 +1 @@ +/dev/ups hostname diff --git a/lib/plugins/stonith/baytech.c b/lib/plugins/stonith/baytech.c new file mode 100644 index 0000000..33093ad --- /dev/null +++ b/lib/plugins/stonith/baytech.c @@ -0,0 +1,924 @@ +/* + * Stonith module for BayTech Remote Power Controllers (RPC-x devices) + * + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> +#define DEVICE "BayTech power switch" + +#define DOESNT_USE_STONITHKILLCOMM 1 + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN baytech +#define PIL_PLUGIN_S "baytech" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include "stonith_signal.h" + +static StonithPlugin * baytech_new(const char *); +static void baytech_destroy(StonithPlugin *); +static int baytech_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * baytech_get_confignames(StonithPlugin * s); +static const char * baytech_get_info(StonithPlugin * s, int InfoType); +static int baytech_status(StonithPlugin *); +static int baytech_reset_req(StonithPlugin * s, int request, const char * host); +static char ** baytech_hostlist(StonithPlugin *); + +static struct stonith_ops baytechOps ={ + baytech_new, /* Create new STONITH object */ + baytech_destroy, /* Destroy STONITH object */ + baytech_get_info, /* Return STONITH info string */ + baytech_get_confignames, /* Return STONITH config vars */ + baytech_set_config, /* set configuration from vars */ + baytech_status, /* Return STONITH device status */ + baytech_reset_req, /* Request a reset */ + baytech_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +#include "stonith_expect_helpers.h" + +#define MAXOUTLET 32 + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &baytechOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * I have an RPC-5. This code has been tested with this switch. + * + * The BayTech switches are quite nice, but the dialogues are a bit of a + * pain for mechanical parsing. + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + char * idinfo; + char * unitid; + const struct BayTechModelInfo* modelinfo; + pid_t pid; + int rdfd; + int wrfd; + char * device; + char * user; + char * passwd; +}; + +struct BayTechModelInfo { + const char * type; /* Baytech model info */ + size_t socklen; /* Length of socket name string */ + struct Etoken * expect; /* Expect string before outlet list */ +}; + +static int parse_socket_line(struct pluginDevice*,const char * +, int *, char *); + +static const char * pluginid = "BayTech-Stonith"; +static const char * NOTpluginID = "BayTech device has been destroyed"; + +/* + * Different expect strings that we get from the Baytech + * Remote Power Controllers... + */ + +#define BAYTECHASSOC "Bay Technical Associates" + +static struct Etoken BayTechAssoc[] = { {BAYTECHASSOC, 0, 0}, {NULL,0,0}}; +static struct Etoken UnitId[] = { {"Unit ID: ", 0, 0}, {NULL,0,0}}; +static struct Etoken login[] = { {"username>", 0, 0} ,{NULL,0,0}}; +static struct Etoken password[] = { {"password>", 0, 0} + , {"username>", 0, 0} ,{NULL,0,0}}; +static struct Etoken Selection[] = { {"election>", 0, 0} ,{NULL,0,0}}; +static struct Etoken RPC[] = { {"RPC", 0, 0} ,{NULL,0,0}}; +static struct Etoken LoginOK[] = { {"RPC", 0, 0}, {"Invalid password", 1, 0} + , {NULL,0,0}}; +static struct Etoken GTSign[] = { {">", 0, 0} ,{NULL,0,0}}; +static struct Etoken Menu[] = { {"Menu:", 0, 0} ,{NULL,0,0}}; +static struct Etoken Temp[] = { {"emperature: ", 0, 0} + , {NULL,0,0}}; +static struct Etoken Break[] = { {"Status", 0, 0} + , {NULL,0,0}}; +static struct Etoken PowerApplied[] = { {"ower applied to outlet", 0, 0} + , {NULL,0,0}}; + +/* We may get a notice about rebooting, or a request for confirmation */ +static struct Etoken Rebooting[] = { {"ebooting selected outlet", 0, 0} + , {"(Y/N)>", 1, 0} + , {"already off.", 2, 0} + , {NULL,0,0}}; + +static struct Etoken TurningOnOff[] = { {"RPC", 0, 0} + , {"(Y/N)>", 1, 0} + , {"already ", 2, 0} + , {NULL,0,0}}; + + +static struct BayTechModelInfo ModelInfo [] = { + {"BayTech RPC-5", 18, Temp},/* This first model will be the default */ + {"BayTech RPC-3", 10, Break}, + {"BayTech RPC-3A", 10, Break}, + {NULL, 0, NULL}, +}; + +#include "stonith_config_xml.h" + +static const char *baytechXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +static int RPC_connect_device(struct pluginDevice * bt); +static int RPCLogin(struct pluginDevice * bt); +static int RPCRobustLogin(struct pluginDevice * bt); +static int RPCNametoOutletList(struct pluginDevice*, const char * name +, int outletlist[]); +static int RPCReset(struct pluginDevice*, int unitnum, const char * rebootid); +static int RPCLogout(struct pluginDevice * bt); + + +static int RPC_onoff(struct pluginDevice*, int unitnum, const char * unitid +, int request); + +/* Login to the Baytech Remote Power Controller (RPC) */ + +static int +RPCLogin(struct pluginDevice * bt) +{ + char IDinfo[128]; + static char IDbuf[128]; + char * idptr = IDinfo; + char * delim; + int j; + + EXPECT(bt->rdfd, RPC, 10); + + /* Look for the unit type info */ + if (EXPECT_TOK(bt->rdfd, BayTechAssoc, 2, IDinfo + , sizeof(IDinfo), Debug) < 0) { + LOG(PIL_CRIT, "No initial response from %s.", bt->idinfo); + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + idptr += strspn(idptr, WHITESPACE); + /* + * We should be looking at something like this: + * RPC-5 Telnet Host + * Revision F 4.22, (C) 1999 + * Bay Technical Associates + */ + + /* Truncate the result after the RPC-5 part */ + if ((delim = strchr(idptr, ' ')) != NULL) { + *delim = EOS; + } + snprintf(IDbuf, sizeof(IDbuf), "BayTech RPC%s", idptr); + REPLSTR(bt->idinfo, IDbuf); + if (bt->idinfo == NULL) { + return(S_OOPS); + } + + bt->modelinfo = &ModelInfo[0]; + + for (j=0; ModelInfo[j].type != NULL; ++j) { + /* + * TIMXXX - + * Look at device ID as this really describes the model. + */ + if (strcasecmp(ModelInfo[j].type, IDbuf) == 0) { + bt->modelinfo = &ModelInfo[j]; + break; + } + } + + /* Look for the unit id info */ + EXPECT(bt->rdfd, UnitId, 10); + SNARF(bt->rdfd, IDbuf, 2); + delim = IDbuf + strcspn(IDbuf, WHITESPACE); + *delim = EOS; + REPLSTR(bt->unitid, IDbuf); + if (bt->unitid == NULL) { + return(S_OOPS); + } + + /* Expect "username>" */ + EXPECT(bt->rdfd, login, 2); + + SEND(bt->wrfd, bt->user); + SEND(bt->wrfd, "\r"); + + /* Expect "password>" */ + + switch (StonithLookFor(bt->rdfd, password, 5)) { + case 0: /* Good! */ + break; + + case 1: /* OOPS! got another username prompt */ + LOG(PIL_CRIT, "Invalid username for %s.", bt->idinfo); + return(S_ACCESS); + + default: + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + + SEND(bt->wrfd, bt->passwd); + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + + switch (StonithLookFor(bt->rdfd, LoginOK, 5)) { + + case 0: /* Good! */ + break; + + case 1: /* Uh-oh - bad password */ + LOG(PIL_CRIT, "Invalid password for %s.", bt->idinfo); + return(S_ACCESS); + + default: + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + EXPECT(bt->rdfd, Menu, 2); + + return(S_OK); +} + +static int +RPCRobustLogin(struct pluginDevice * bt) +{ + int rc=S_OOPS; + int j; + + for (j=0; j < 20 && rc != S_OK; ++j) { + + + if (RPC_connect_device(bt) != S_OK) { + continue; + } + + rc = RPCLogin(bt); + } + return rc; +} + +/* Log out of the Baytech RPC */ + +static int +RPCLogout(struct pluginDevice* bt) +{ + int rc; + + /* Make sure we're in the right menu... */ + SEND(bt->wrfd, "\r"); + + /* Expect "Selection>" */ + rc = StonithLookFor(bt->rdfd, Selection, 5); + + /* Option 6 is Logout */ + SEND(bt->wrfd, "6\r"); + + close(bt->wrfd); + close(bt->rdfd); + bt->wrfd = bt->rdfd = -1; + return(rc >= 0 ? S_OK : (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS)); +} + +/* Reset (power-cycle) the given outlet number */ +static int +RPCReset(struct pluginDevice* bt, int unitnum, const char * rebootid) +{ + char unum[32]; + + + SEND(bt->wrfd, "\r"); + + /* Make sure we're in the top level menu */ + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, GTSign, 5); + + + /* Send REBOOT command for given outlet */ + snprintf(unum, sizeof(unum), "REBOOT %d\r", unitnum); + SEND(bt->wrfd, unum); + + /* Expect "ebooting "... or "(Y/N)" (if confirmation turned on) */ + + retry: + switch (StonithLookFor(bt->rdfd, Rebooting, 5)) { + case 0: /* Got "Rebooting" Do nothing */ + break; + + case 1: /* Got that annoying command confirmation :-( */ + SEND(bt->wrfd, "Y\r"); + goto retry; + + case 2: /* Outlet is turned off */ + LOG(PIL_CRIT, "Host is OFF: %s.", rebootid); + return(S_ISOFF); + + default: + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + LOG(PIL_INFO, "Host %s (outlet %d) being rebooted." + , rebootid, unitnum); + + /* Expect "ower applied to outlet" */ + if (StonithLookFor(bt->rdfd, PowerApplied, 30) < 0) { + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + + /* All Right! Power is back on. Life is Good! */ + + LOG(PIL_INFO, "Power restored to host %s (outlet %d)." + , rebootid, unitnum); + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC,5); + EXPECT(bt->rdfd, GTSign, 5); + + /* Pop back to main menu */ + SEND(bt->wrfd, "MENU\r"); + return(S_OK); +} + +static int +RPC_onoff(struct pluginDevice* bt, int unitnum, const char * unitid, int req) +{ + char unum[32]; + + const char * onoff = (req == ST_POWERON ? "on" : "off"); + int rc; + + + if ((rc = RPCRobustLogin(bt) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + return(rc); + } + SEND(bt->wrfd, "\r"); + + /* Make sure we're in the top level menu */ + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, GTSign, 5); + + + /* Send ON/OFF command for given outlet */ + snprintf(unum, sizeof(unum), "%s %d\r" + , onoff, unitnum); + SEND(bt->wrfd, unum); + + /* Expect "RPC->x "... or "(Y/N)" (if confirmation turned on) */ + + if (StonithLookFor(bt->rdfd, TurningOnOff, 10) == 1) { + /* They've turned on that annoying command confirmation :-( */ + SEND(bt->wrfd, "Y\r"); + EXPECT(bt->rdfd, TurningOnOff, 10); + } + + EXPECT(bt->rdfd, GTSign, 10); + + /* All Right! Command done. Life is Good! */ + LOG(PIL_INFO, "Power to host %s (outlet %d) turned %s." + , unitid, unitnum, onoff); + /* Pop back to main menu */ + SEND(bt->wrfd, "MENU\r"); + return(S_OK); +} + +/* + * Map the given host name into an (AC) Outlet number on the power strip + */ + +static int +RPCNametoOutletList(struct pluginDevice* bt, const char * name +, int outletlist[]) +{ + char NameMapping[128]; + int sockno; + char sockname[32]; + int maxfound = 0; + + + + /* Verify that we're in the top-level menu */ + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, GTSign, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(bt->wrfd, "STATUS\r"); + + /* Expect: "emperature:" so we can skip over it... */ + EXPECT(bt->rdfd, bt->modelinfo->expect, 5); + EXPECT(bt->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + + do { + char * last; + NameMapping[0] = EOS; + SNARF(bt->rdfd, NameMapping, 5); + + if (!parse_socket_line(bt, NameMapping, &sockno, sockname)) { + continue; + } + + last = sockname+bt->modelinfo->socklen; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (strcasecmp(name, sockname) == 0) { + outletlist[maxfound] = sockno; + ++maxfound; + } + } while (strlen(NameMapping) > 2 && maxfound < MAXOUTLET); + + /* Pop back out to the top level menu */ + SEND(bt->wrfd, "MENU\r"); + return(maxfound); +} + +static int +baytech_status(StonithPlugin *s) +{ + struct pluginDevice* bt; + int rc; + + ERRIFNOTCONFIGED(s,S_OOPS); + + bt = (struct pluginDevice*) s; + + if ((rc = RPCRobustLogin(bt) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + return(rc); + } + + /* Verify that we're in the top-level menu */ + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + return(RPCLogout(bt)); +} +/* + * Return the list of hosts (outlet names) for the devices on this BayTech unit + */ + +static char ** +baytech_hostlist(StonithPlugin *s) +{ + char NameMapping[128]; + char* NameList[64]; + unsigned int numnames = 0; + char ** ret = NULL; + struct pluginDevice* bt; + unsigned int i; + + ERRIFNOTCONFIGED(s,NULL); + + bt = (struct pluginDevice*) s; + + if (RPCRobustLogin(bt) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + return(NULL); + } + + /* Verify that we're in the top-level menu */ + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + NULLEXPECT(bt->rdfd, RPC, 5); + NULLEXPECT(bt->rdfd, Menu, 5); + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + NULLEXPECT(bt->rdfd, RPC, 5); + NULLEXPECT(bt->rdfd, GTSign, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(bt->wrfd, "STATUS\r"); + + /* Expect: "emperature:" so we can skip over it... */ + NULLEXPECT(bt->rdfd, bt->modelinfo->expect, 5); + NULLEXPECT(bt->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + + do { + int sockno; + char sockname[64]; + char * last; + char * nm; + + NameMapping[0] = EOS; + + NULLSNARF(bt->rdfd, NameMapping, 5); + + if (!parse_socket_line(bt, NameMapping, &sockno, sockname)) { + continue; + } + + last = sockname+bt->modelinfo->socklen; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (numnames >= DIMOF(NameList)-1) { + break; + } + if ((nm = (char*)STRDUP(sockname)) == NULL) { + goto out_of_memory; + } + strdown(nm); + NameList[numnames] = nm; + ++numnames; + NameList[numnames] = NULL; + } while (strlen(NameMapping) > 2); + + /* Pop back out to the top level menu */ + SEND(bt->wrfd, "MENU\r"); + if (numnames >= 1) { + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + goto out_of_memory; + }else{ + memcpy(ret, NameList, (numnames+1)*sizeof(char*)); + } + } + (void)RPCLogout(bt); + return(ret); + +out_of_memory: + LOG(PIL_CRIT, "out of memory"); + for (i=0; i<numnames; i++) { + FREE(NameList[i]); + } + return(NULL); +} + +/* + * Connect to the given BayTech device. + * We should add serial support here eventually... + */ +static int +RPC_connect_device(struct pluginDevice * bt) +{ + int fd = OurImports->OpenStreamSocket(bt->device + , TELNET_PORT, TELNET_SERVICE); + + if (fd < 0) { + return(S_OOPS); + } + bt->rdfd = bt->wrfd = fd; + return(S_OK); +} + +/* + * Reset the given host on this Stonith device. + */ +static int +baytech_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = S_OK; + int lorc = 0; + struct pluginDevice* bt; + + ERRIFNOTCONFIGED(s,S_OOPS); + + bt = (struct pluginDevice*) s; + + if ((rc = RPCRobustLogin(bt)) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + }else{ + int noutlets; + int outlets[MAXOUTLET]; + int j; + noutlets = RPCNametoOutletList(bt, host, outlets); + + if (noutlets < 1) { + LOG(PIL_CRIT, "%s %s doesn't control host [%s]" + , bt->idinfo, bt->unitid, host); + return(S_BADHOST); + } + switch(request) { + + case ST_POWERON: + case ST_POWEROFF: + for (j=0; rc == S_OK && j < noutlets;++j) { + rc = RPC_onoff(bt, outlets[j], host, request); + } + break; + case ST_GENERIC_RESET: + /* + * Our strategy here: + * 1. Power off all outlets except the last one + * 2. reset the last outlet + * 3. power the other outlets back on + */ + + for (j=0; rc == S_OK && j < noutlets-1; ++j) { + rc = RPC_onoff(bt,outlets[j],host + , ST_POWEROFF); + } + if (rc == S_OK) { + rc = RPCReset(bt, outlets[j], host); + } + for (j=0; rc == S_OK && j < noutlets-1; ++j) { + rc = RPC_onoff(bt, outlets[j], host + , ST_POWERON); + } + break; + default: + rc = S_INVAL; + break; + } + } + + lorc = RPCLogout(bt); + + return(rc != S_OK ? rc : lorc); +} + +static const char * const * +baytech_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_LOGIN, ST_PASSWD, NULL}; + return ret; +} + + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +baytech_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* bt = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s, S_OOPS); + if (bt->sp.isconfigured) { + return S_OOPS; + } + + if ((rc =OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + bt->device = namestocopy[0].s_value; + bt->user = namestocopy[1].s_value; + bt->passwd = namestocopy[2].s_value; + + return(S_OK); +} + +static const char * +baytech_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* bt; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + + bt = (struct pluginDevice *)s; + + switch (reqtype) { + + case ST_DEVICEID: /* What type of device? */ + ret = bt->idinfo; + break; + + case ST_DEVICENAME: /* Which particular device? */ + ret = bt->device; + break; + + case ST_DEVICEDESCR: /* Description of dev type */ + ret = "Bay Technical Associates (Baytech) RPC " + "series power switches (via telnet).\n" + "The RPC-5, RPC-3 and RPC-3A switches are well tested" + "."; + break; + + case ST_DEVICEURL: /* Manufacturer's web site */ + ret = "http://www.baytech.net/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = baytechXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Baytech Stonith destructor... + */ +static void +baytech_destroy(StonithPlugin *s) +{ + struct pluginDevice* bt; + + VOIDERRIFWRONGDEV(s); + + bt = (struct pluginDevice *)s; + + bt->pluginid = NOTpluginID; + if (bt->rdfd >= 0) { + close(bt->rdfd); + bt->rdfd = -1; + } + if (bt->wrfd >= 0) { + close(bt->wrfd); + bt->wrfd = -1; + } + if (bt->device != NULL) { + FREE(bt->device); + bt->device = NULL; + } + if (bt->user != NULL) { + FREE(bt->user); + bt->user = NULL; + } + if (bt->passwd != NULL) { + FREE(bt->passwd); + bt->passwd = NULL; + } + if (bt->idinfo != NULL) { + FREE(bt->idinfo); + bt->idinfo = NULL; + } + if (bt->unitid != NULL) { + FREE(bt->unitid); + bt->unitid = NULL; + } + FREE(bt); +} + +/* Create a new BayTech Stonith device. */ + +static StonithPlugin * +baytech_new(const char *subplugin) +{ + struct pluginDevice* bt = ST_MALLOCT(struct pluginDevice); + + if (bt == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(bt, 0, sizeof(*bt)); + bt->pluginid = pluginid; + bt->pid = -1; + bt->rdfd = -1; + bt->wrfd = -1; + REPLSTR(bt->idinfo, DEVICE); + if (bt->idinfo == NULL) { + FREE(bt); + return(NULL); + } + bt->modelinfo = &ModelInfo[0]; + bt->sp.s_ops = &baytechOps; + + return &(bt->sp); /* same as "bt" */ +} + +static int +parse_socket_line(struct pluginDevice * bt, const char *NameMapping +, int *sockno, char *sockname) +{ +#if 0 + char format[64]; + snprintf(format, sizeof(format), "%%7d %%%dc" + , bt->modelinfo->socklen); + /* 7 digits, 7 blanks, then 'socklen' characters */ + /* [0-6]: digits, NameMapping[13] begins the sockname */ + /* NameMapping strlen must be >= socklen + 14 */ + + if (sscanf(NameMapping, format, sockno, sockname) != 2) { + return FALSE; + } +#else +# define OFFSET 14 + + if (sscanf(NameMapping, "%7d", sockno) != 1 + || strlen(NameMapping) < OFFSET+bt->modelinfo->socklen) { + return FALSE; + } + strncpy(sockname, NameMapping+OFFSET, bt->modelinfo->socklen); + sockname[bt->modelinfo->socklen] = EOS; +#endif + return TRUE; +} diff --git a/lib/plugins/stonith/bladehpi.c b/lib/plugins/stonith/bladehpi.c new file mode 100644 index 0000000..ae9a4cf --- /dev/null +++ b/lib/plugins/stonith/bladehpi.c @@ -0,0 +1,1101 @@ +/* + * Stonith module for BladeCenter via OpenHPI, an implementation of Service + * Availability Forum's Hardware Platfrom Interface + * + * Author: Dave Blaschke <debltc@us.ibm.com> + * + * Copyright (c) 2005 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> + +#define DEVICE "IBM BladeCenter (OpenHPI)" + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN bladehpi +#define PIL_PLUGIN_S "bladehpi" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include <openhpi/SaHpi.h> + +/* Maximum number of seconds to wait for host to power off */ +#define MAX_POWEROFF_WAIT 60 + +/* entity_root, the one required plugin parameter */ +#define ST_ENTITYROOT "entity_root" + +/* String format of entity_root */ +#define SYSTEM_CHASSIS_FMT "{SYSTEM_CHASSIS,%d}" + +/* soft_reset, the one optional plugin parameter */ +#define ST_SOFTRESET "soft_reset" + +#define OPENHPIURL "http://www.openhpi.org/" + +/* OpenHPI resource types of interest to this plugin */ +#define OHRES_NONE 0 +#define OHRES_BLADECENT 1 +#define OHRES_MGMTMOD 2 +#define OHRES_BLADE 3 + +/* IBMBC_WAIT_FOR_OFF - This constant has to do with the problem that + saHpiResourcePowerStateSet can return before the desired state has been + achieved by the blade. In the SAHPI_POWER_OFF case this is not good, + as whoever calls this plugin assumes that the power is actually off + when the plugin returns with a successful return code. Define this + constant to build code that loops in one second intervals after calling + saHpiResourcePowerStateSet(SAHPI_POWER_OFF) to make sure the power is + really off. +#define IBMBC_WAIT_FOR_OFF */ + +static StonithPlugin * bladehpi_new(const char *); +static void bladehpi_destroy(StonithPlugin *); +static const char * bladehpi_getinfo(StonithPlugin *, int); +static const char * const * bladehpi_get_confignames(StonithPlugin *); +static int bladehpi_status(StonithPlugin *); +static int bladehpi_reset_req(StonithPlugin *, int, const char *); +static char ** bladehpi_hostlist(StonithPlugin *); +static int bladehpi_set_config(StonithPlugin *, StonithNVpair *); + +static struct stonith_ops bladehpiOps = { + bladehpi_new, /* Create new STONITH object */ + bladehpi_destroy, /* Destroy STONITH object */ + bladehpi_getinfo, /* Return STONITH info string */ + bladehpi_get_confignames, /* Return configuration parameters */ + bladehpi_set_config, /* Set configuration */ + bladehpi_status, /* Return STONITH device status */ + bladehpi_reset_req, /* Request a reset */ + bladehpi_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports * PluginImports; +static PILPlugin * OurPlugin; +static PILInterface * OurInterface; +static StonithImports * OurImports; +static void * interfprivate; + + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin *us, const PILPluginImports *imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin *us, const PILPluginImports *imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us + , PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &bladehpiOps + , NULL /* close */ + , &OurInterface + , (void *)&OurImports + , &interfprivate); +} + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + char * idinfo; + char * device; + int softreset; + GList * hostlist; + SaHpiVersionT ohver; /* OpenHPI interface version */ + SaHpiSessionIdT ohsession; /* session ID */ + SaHpiUint32T ohrptcnt; /* RPT count for hostlist */ + SaHpiResourceIdT ohdevid; /* device resource ID */ + SaHpiResourceIdT ohsensid; /* sensor resource ID */ + SaHpiSensorNumT ohsensnum; /* sensor number */ +}; + +static int open_hpi_session(struct pluginDevice *dev); +static void close_hpi_session(struct pluginDevice *dev); + +static const char *pluginid = "BladeCenterDevice-Stonith"; +static const char *NOTpluginID = "IBM BladeCenter device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_ENTITYROOT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_ENTITYROOT \ + XML_PARM_SHORTDESC_END + +#define XML_ENTITYROOT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The entity_root of the STONITH device from the OpenHPI config file" \ + XML_PARM_LONGDESC_END + +#define XML_ENTITYROOT_PARM \ + XML_PARAMETER_BEGIN(ST_ENTITYROOT, "string", "1", "0") \ + XML_ENTITYROOT_SHORTDESC \ + XML_ENTITYROOT_LONGDESC \ + XML_PARAMETER_END + +#define XML_SOFTRESET_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_SOFTRESET \ + XML_PARM_SHORTDESC_END + +#define XML_SOFTRESET_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "Soft reset indicator, true|1 if STONITH device should use soft reset (power cycle) to reset nodes, false|0 if device should use hard reset (power off, wait, power on); default is false" \ + XML_PARM_LONGDESC_END + +#define XML_SOFTRESET_PARM \ + XML_PARAMETER_BEGIN(ST_SOFTRESET, "string", "0", "0") \ + XML_SOFTRESET_SHORTDESC \ + XML_SOFTRESET_LONGDESC \ + XML_PARAMETER_END + +static const char *bladehpiXML = + XML_PARAMETERS_BEGIN + XML_ENTITYROOT_PARM + XML_SOFTRESET_PARM + XML_PARAMETERS_END; + +static int get_resource_type(char *, SaHpiRptEntryT *); +static int get_sensor_num(SaHpiSessionIdT, SaHpiResourceIdT); +static int get_bladehpi_hostlist(struct pluginDevice *); +static void free_bladehpi_hostlist(struct pluginDevice *); +static int get_num_tokens(char *str); + +struct blade_info { + char * name; /* blade name */ + SaHpiResourceIdT resourceId; /* blade resource ID */ + SaHpiCapabilitiesT resourceCaps; /* blade capabilities */ +}; + + +static int +bladehpi_status(StonithPlugin *s) +{ + struct pluginDevice * dev; + SaErrorT ohrc; + SaHpiDomainInfoT ohdi; + int rc = S_OK; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + ERRIFWRONGDEV(s, S_OOPS); + + dev = (struct pluginDevice *)s; + rc = open_hpi_session(dev); + if( rc != S_OK ) + return rc; + + /* Refresh the hostlist only if RPTs updated */ + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + rc = S_BADCONFIG; + goto done; + } + if (dev->ohrptcnt != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if (get_bladehpi_hostlist(dev) != S_OK) { + LOG(PIL_CRIT, "Unable to obtain list of hosts in %s" + , __FUNCTION__); + rc = S_BADCONFIG; + goto done; + } + } + + /* At this point, hostlist is up to date */ + if (dev->ohsensid && dev->ohsensnum) { + /* + * For accurate status, need to make a call that goes out to + * BladeCenter MM because the calls made so far by this + * function (and perhaps get_bladehpi_hostlist) only retrieve + * information from memory cached by OpenHPI + */ + ohrc = saHpiSensorReadingGet(dev->ohsession + , dev->ohsensid, dev->ohsensnum, NULL, NULL); + if (ohrc == SA_ERR_HPI_BUSY || ohrc == SA_ERR_HPI_NO_RESPONSE) { + LOG(PIL_CRIT, "Unable to connect to BladeCenter in %s" + , __FUNCTION__); + rc = S_OOPS; + goto done; + } + } + +done: + close_hpi_session(dev); + return (rc == S_OK) ? (dev->ohdevid ? S_OK : S_OOPS) : rc; +} + + +/* + * Return the list of hosts configured for this HMC device + */ + +static char ** +bladehpi_hostlist(StonithPlugin *s) +{ + struct pluginDevice * dev; + int numnames = 0, j; + char ** ret = NULL; + GList * node = NULL; + SaErrorT ohrc; + SaHpiDomainInfoT ohdi; + int rc = S_OK; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + ERRIFWRONGDEV(s, NULL); + + dev = (struct pluginDevice *)s; + rc = open_hpi_session(dev); + if( rc != S_OK ) + return NULL; + + /* Refresh the hostlist only if RPTs updated */ + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + goto done; + } + if (dev->ohrptcnt != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if (get_bladehpi_hostlist(dev) != S_OK) { + LOG(PIL_CRIT, "Unable to obtain list of hosts in %s" + , __FUNCTION__); + goto done; + } + } + + /* At this point, hostlist is up to date */ + numnames = g_list_length(dev->hostlist); + if (numnames < 0) { + LOG(PIL_CRIT, "Unconfigured stonith object in %s" + , __FUNCTION__); + goto done; + } + + ret = (char **)MALLOC((numnames+1) * sizeof(char *)); + if (ret == NULL) { + LOG(PIL_CRIT, "Out of memory for malloc in %s", __FUNCTION__); + goto done; + } + + memset(ret, 0, (numnames+1) * sizeof(char *)); + for (node = g_list_first(dev->hostlist), j = 0 + ; NULL != node + ; j++, node = g_list_next(node)) { + ret[j] = STRDUP(((struct blade_info *)node->data)->name); + if (ret[j] == NULL) { + LOG(PIL_CRIT, "Out of memory for strdup in %s" + , __FUNCTION__); + stonith_free_hostlist(ret); + ret = NULL; + goto done; + } + strdown(ret[j]); + } + +done: + close_hpi_session(dev); + return ret; +} + + +static const char * const * +bladehpi_get_confignames(StonithPlugin *s) +{ + static const char * names[] = {ST_ENTITYROOT, NULL}; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + return names; +} + + +/* + * Reset the given host, and obey the request type. + */ + +static int +bladehpi_reset_req(StonithPlugin *s, int request, const char *host) +{ + GList * node = NULL; + struct pluginDevice * dev = NULL; + struct blade_info * bi = NULL; + SaHpiPowerStateT ohcurstate, ohnewstate; + SaHpiDomainInfoT ohdi; + SaErrorT ohrc; + int rc = S_OK; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called, request=%d, host=%s" + , __FUNCTION__, request, host); + } + + ERRIFWRONGDEV(s, S_OOPS); + + if (host == NULL) { + LOG(PIL_CRIT, "Invalid host argument to %s", __FUNCTION__); + rc = S_OOPS; + goto done; + } + + dev = (struct pluginDevice *)s; + rc = open_hpi_session(dev); + if( rc != S_OK ) + return rc; + + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + rc = S_BADCONFIG; + goto done; + } + if (dev->ohrptcnt != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if (get_bladehpi_hostlist(dev) != S_OK) { + LOG(PIL_CRIT, "Unable to obtain list of hosts in %s" + , __FUNCTION__); + rc = S_OOPS; + goto done; + } + } + + for (node = g_list_first(dev->hostlist) + ; node != NULL + ; node = g_list_next(node)) { + bi = ((struct blade_info *)node->data); + if (Debug) { + LOG(PIL_DEBUG, "Found host %s in hostlist", bi->name); + } + + if (!strcasecmp(bi->name, host)) { + break; + } + } + + if (!node || !bi) { + LOG(PIL_CRIT + , "Host %s is not configured in this STONITH module, " + "please check your configuration information", host); + rc = S_OOPS; + goto done; + } + + /* Make sure host has proper capabilities for get */ + if (!(bi->resourceCaps & SAHPI_CAPABILITY_POWER)) { + LOG(PIL_CRIT + , "Host %s does not have power capability", host); + rc = S_OOPS; + goto done; + } + + ohrc = saHpiResourcePowerStateGet(dev->ohsession, bi->resourceId + , &ohcurstate); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get host %s power state (%d)" + , host, ohrc); + rc = S_OOPS; + goto done; + } + + switch (request) { + case ST_POWERON: + if (ohcurstate == SAHPI_POWER_ON) { + LOG(PIL_INFO, "Host %s already on", host); + goto done; + } + ohnewstate = SAHPI_POWER_ON; + + break; + + case ST_POWEROFF: + if (ohcurstate == SAHPI_POWER_OFF) { + LOG(PIL_INFO, "Host %s already off", host); + goto done; + } + ohnewstate = SAHPI_POWER_OFF; + + break; + + case ST_GENERIC_RESET: + if (ohcurstate == SAHPI_POWER_OFF) { + ohnewstate = SAHPI_POWER_ON; + } else { + ohnewstate = SAHPI_POWER_CYCLE; + } + + break; + + default: + LOG(PIL_CRIT, "Invalid request argument to %s" + , __FUNCTION__); + rc = S_INVAL; + goto done; + } + + if (!dev->softreset && (ohnewstate == SAHPI_POWER_CYCLE)) { + int maxwait; + + ohrc = saHpiResourcePowerStateSet(dev->ohsession + , bi->resourceId, SAHPI_POWER_OFF); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to set host %s power state to" + " OFF (%d)", host, ohrc); + rc = S_OOPS; + goto done; + } + + /* + * Must wait for power off here or subsequent power on request + * may take place while power is still on and thus ignored + */ + maxwait = MAX_POWEROFF_WAIT; + do { + maxwait--; + sleep(1); + ohrc = saHpiResourcePowerStateGet(dev->ohsession + , bi->resourceId, &ohcurstate); + } while ((ohrc == SA_OK) + && (ohcurstate != SAHPI_POWER_OFF) + && (maxwait > 0)); + + if (Debug) { + LOG(PIL_DEBUG, "Waited %d seconds for power off" + , MAX_POWEROFF_WAIT - maxwait); + } + + ohrc = saHpiResourcePowerStateSet(dev->ohsession + , bi->resourceId, SAHPI_POWER_ON); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to set host %s power state to" + " ON (%d)", host, ohrc); + rc = S_OOPS; + goto done; + } + } else { + /* Make sure host has proper capabilities to reset */ + if ((ohnewstate == SAHPI_POWER_CYCLE) && + (!(bi->resourceCaps & SAHPI_CAPABILITY_RESET))) { + LOG(PIL_CRIT + , "Host %s does not have reset capability" + , host); + rc = S_OOPS; + goto done; + } + + if ((ohrc = saHpiResourcePowerStateSet(dev->ohsession + , bi->resourceId, ohnewstate)) != SA_OK) { + LOG(PIL_CRIT, "Unable to set host %s power state (%d)" + , host, ohrc); + rc = S_OOPS; + goto done; + } + } + +#ifdef IBMBC_WAIT_FOR_OFF + if (ohnewstate == SAHPI_POWER_OFF) { + int maxwait = MAX_POWEROFF_WAIT; + + do { + maxwait--; + sleep(1); + ohrc = saHpiResourcePowerStateGet(dev->ohsession + , bi->resourceId, &ohcurstate); + } while ((ohrc == SA_OK) + && (ohcurstate != SAHPI_POWER_OFF) + && (maxwait > 0)); + + if (Debug) { + LOG(PIL_DEBUG, "Waited %d seconds for power off" + , MAX_POWEROFF_WAIT - maxwait); + } + } +#endif + + LOG(PIL_INFO, "Host %s %s %d.", host, __FUNCTION__, request); + +done: + close_hpi_session(dev); + return rc; +} + + +/* + * Parse the information in the given configuration file, + * and stash it away... + */ + +static int +bladehpi_set_config(StonithPlugin *s, StonithNVpair *list) +{ + struct pluginDevice * dev = NULL; + StonithNamesToGet namestocopy [] = + { {ST_ENTITYROOT, NULL} + , {NULL, NULL} + }; + int rc, i; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + ERRIFWRONGDEV(s, S_OOPS); + + dev = (struct pluginDevice *)s; + + if (Debug) { + LOG(PIL_DEBUG, "%s conditionally compiled with:" +#ifdef IBMBC_WAIT_FOR_OFF + " IBMBC_WAIT_FOR_OFF" +#endif + , dev->pluginid); + } + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s = %s", ST_ENTITYROOT + , namestocopy[0].s_value); + } + + if (get_num_tokens(namestocopy[0].s_value) == 1) { + /* name=value pairs on command line, look for soft_reset */ + const char *softreset = + OurImports->GetValue(list, ST_SOFTRESET); + if (softreset != NULL) { + if (!strcasecmp(softreset, "true") || + !strcmp(softreset, "1")) { + dev->softreset = 1; + } else if (!strcasecmp(softreset, "false") || + !strcmp(softreset, "0")) { + dev->softreset = 0; + } else { + LOG(PIL_CRIT, "Invalid %s %s, must be " + "true, 1, false or 0" + , ST_SOFTRESET, softreset); + FREE(namestocopy[0].s_value); + return S_OOPS; + } + } + } else { + /* -p or -F option with args "entity_root [soft_reset]..." */ + char *pch = namestocopy[0].s_value; + + /* skip over entity_root and null-terminate */ + pch += strcspn(pch, WHITESPACE); + *pch = EOS; + + /* skip over white-space up to next token */ + pch++; + pch += strspn(pch, WHITESPACE); + if (!strcasecmp(pch, "true") || !strcmp(pch, "1")) { + dev->softreset = 1; + } else if (!strcasecmp(pch, "false") || !strcmp(pch, "0")) { + dev->softreset = 0; + } else { + LOG(PIL_CRIT, "Invalid %s %s, must be " + "true, 1, false or 0" + , ST_SOFTRESET, pch); + FREE(namestocopy[0].s_value); + return S_OOPS; + } + } + + dev->device = STRDUP(namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + if (dev->device == NULL) { + LOG(PIL_CRIT, "Out of memory for strdup in %s", __FUNCTION__); + return S_OOPS; + } + + if (strcspn(dev->device, WHITESPACE) != strlen(dev->device) || + sscanf(dev->device, SYSTEM_CHASSIS_FMT, &i) != 1 || i < 0) { + LOG(PIL_CRIT, "Invalid %s %s, must be of format %s" + , ST_ENTITYROOT, dev->device, SYSTEM_CHASSIS_FMT); + return S_BADCONFIG; + } + + dev->ohver = saHpiVersionGet(); + if (dev->ohver > SAHPI_INTERFACE_VERSION) { + LOG(PIL_CRIT, "Installed OpenHPI interface (%x) greater than " + "one used by plugin (%x), incompatibilites may exist" + , dev->ohver, SAHPI_INTERFACE_VERSION); + return S_BADCONFIG; + } + return S_OK; +} + +static int +open_hpi_session(struct pluginDevice *dev) +{ + SaErrorT ohrc; + + ohrc = saHpiSessionOpen(SAHPI_UNSPECIFIED_DOMAIN_ID + , &dev->ohsession, NULL); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to open HPI session (%d)", ohrc); + return S_BADCONFIG; + } + + ohrc = saHpiDiscover(dev->ohsession); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to discover resources (%d)", ohrc); + return S_BADCONFIG; + } + + return S_OK; +} +static void +close_hpi_session(struct pluginDevice *dev) +{ + if (dev && dev->ohsession) { + saHpiSessionClose(dev->ohsession); + dev->ohsession = 0; + } +} + +static const char * +bladehpi_getinfo(StonithPlugin *s, int reqtype) +{ + struct pluginDevice * dev; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called, reqtype=%d" + , __FUNCTION__, reqtype); + } + + ERRIFWRONGDEV(s, NULL); + + dev = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = dev->idinfo; + break; + + case ST_DEVICENAME: + ret = dev->device; + break; + + case ST_DEVICEDESCR: + ret = "IBM BladeCenter via OpenHPI\n" + "Use for IBM xSeries systems managed by BladeCenter\n" + " Required parameter name " ST_ENTITYROOT " is " + "a string (no white-space) of\n" + "the format \""SYSTEM_CHASSIS_FMT"\" " + "which is entity_root of BladeCenter\n" + "from OpenHPI config file, where %d is a positive " + "integer\n" + " Optional parameter name " ST_SOFTRESET " is " + "true|1 if STONITH device should\n" + "use soft reset (power cycle) to reset nodes or " + "false|0 if device should\n" + "use hard reset (power off, wait, power on); " + "default is false"; + break; + + case ST_DEVICEURL: + ret = OPENHPIURL; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = bladehpiXML; + break; + + default: + ret = NULL; + break; + } + + return ret; +} + + +/* + * HMC Stonith destructor... + */ + +static void +bladehpi_destroy(StonithPlugin *s) +{ + struct pluginDevice * dev; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + dev = (struct pluginDevice *)s; + + dev->pluginid = NOTpluginID; + if (dev->device) { + FREE(dev->device); + dev->device = NULL; + } + if (dev->idinfo) { + FREE(dev->idinfo); + dev->idinfo = NULL; + } + free_bladehpi_hostlist(dev); + + if (dev->ohsession) { + saHpiSessionClose(dev->ohsession); + dev->ohsession = 0; + } + + FREE(dev); +} + + +static StonithPlugin * +bladehpi_new(const char *subplugin) +{ + struct pluginDevice * dev = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + if (dev == NULL) { + LOG(PIL_CRIT, "Out of memory in %s", __FUNCTION__); + return NULL; + } + + memset(dev, 0, sizeof(*dev)); + + dev->pluginid = pluginid; + dev->device = NULL; + dev->hostlist = NULL; + REPLSTR(dev->idinfo, DEVICE); + if (dev->idinfo == NULL) { + FREE(dev); + return NULL; + } + dev->sp.s_ops = &bladehpiOps; + + if (Debug) { + LOG(PIL_DEBUG, "%s: returning successfully", __FUNCTION__); + } + + return ((void *)dev); +} + + +static int +get_resource_type(char *entityRoot, SaHpiRptEntryT *ohRPT) +{ + int i, rc = OHRES_NONE; + int foundBlade = 0, foundExp = 0, foundMgmt = 0; + int foundRoot = 0, foundOther = 0; + char rootName[64]; + SaHpiEntityPathT * ohep = &ohRPT->ResourceEntity; + + if (ohep == NULL || entityRoot == NULL) { + return 0; + } + + /* First find root of entity path, which is last entity in entry */ + for (i = 0; i < SAHPI_MAX_ENTITY_PATH; i++) { + if (ohep->Entry[i].EntityType == SAHPI_ENT_ROOT) { + break; + } + } + + /* Then back up through entries looking for specific entity */ + for (i--; i >= 0; i--) { + switch (ohep->Entry[i].EntityType) { + case SAHPI_ENT_SBC_BLADE: + foundBlade = 1; + break; + + case SAHPI_ENT_SYS_EXPANSION_BOARD: + foundExp = 1; + break; + + case SAHPI_ENT_SYS_MGMNT_MODULE: + if (ohep->Entry[i].EntityLocation == 0) { + foundMgmt = 1; + } + break; + + case SAHPI_ENT_SYSTEM_CHASSIS: + snprintf(rootName, sizeof(rootName) + , SYSTEM_CHASSIS_FMT + , ohep->Entry[i].EntityLocation); + if (!strcmp(entityRoot, rootName)) { + foundRoot = 1; + } + break; + + default: + foundOther = 1; + break; + } + } + + /* We are only interested in specific entities on specific device */ + if (foundRoot) { + if (foundMgmt && !(foundBlade||foundExp||foundOther)) { + rc = OHRES_MGMTMOD; + } else if (!(foundMgmt||foundBlade||foundExp||foundOther)) { + rc = OHRES_BLADECENT; + } else if (foundBlade && !foundExp) { + rc = OHRES_BLADE; + } + } + + return rc; +} + + +static int +get_sensor_num(SaHpiSessionIdT ohsession, SaHpiResourceIdT ohresid) +{ + SaErrorT ohrc = SA_OK; + SaHpiEntryIdT ohnextid; + SaHpiRdrT ohRDR; + + ohnextid = SAHPI_FIRST_ENTRY; + do { + ohrc = saHpiRdrGet(ohsession, ohresid, ohnextid + , &ohnextid, &ohRDR); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get RDR entry in %s (%d)" + , __FUNCTION__, ohrc); + } else if (ohRDR.RdrType == SAHPI_SENSOR_RDR) { + return ohRDR.RdrTypeUnion.SensorRec.Num; + } + } while (ohrc == SA_OK && ohnextid != SAHPI_LAST_ENTRY); + + return 0; +} + + +/* + * Get RPT update count + * Loop through all RPT entries + * If entry is BladeCenter, save resource ID in dev->ohdevid + * If entry is MgmtMod and has sensor, save resource ID in dev->ohsensid + * and sensor number in dev->ohsensnum + * If entry is blade, save blade_info and add to dev->hostlist + * Get RPT update count + * If RPT update count changed since start of loop, repeat loop + * Save RPT update count in dev->ohrptcnt + * + * Note that not only does this function update hostlist, it also + * updates ohrptcnt, ohdevid, ohsensid and ohsensnum. However, with + * this logic it does not need to be called again until the RPT update + * count changes. + */ + +static int +get_bladehpi_hostlist(struct pluginDevice *dev) +{ + struct blade_info * bi; + SaErrorT ohrc; + SaHpiEntryIdT ohnextid; + SaHpiRptEntryT ohRPT; + SaHpiDomainInfoT ohdi; + SaHpiUint32T ohupdate; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called, dev->device=%s" + , __FUNCTION__, dev->device); + } + + if (dev->device == NULL || *dev->device == 0) { + LOG(PIL_CRIT, "Unconfigured stonith object in %s" + , __FUNCTION__); + return S_BADCONFIG; + } + + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + return S_BADCONFIG; + } + +try_again: + ohupdate = ohdi.RptUpdateCount; + dev->ohdevid = dev->ohsensid = dev->ohsensnum = 0; + ohnextid = SAHPI_FIRST_ENTRY; + do { + char blname[SAHPI_MAX_TEXT_BUFFER_LENGTH]; + int blnum; + + ohrc = saHpiRptEntryGet(dev->ohsession, ohnextid + , &ohnextid, &ohRPT); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get RPT entry in %s (%d)" + , __FUNCTION__, ohrc); + free_bladehpi_hostlist(dev); + return S_BADCONFIG; + } + + switch (get_resource_type(dev->device, &ohRPT)) { + case OHRES_BLADECENT: + dev->ohdevid = ohRPT.ResourceId; + + if (Debug) { + LOG(PIL_DEBUG, "BladeCenter '%s' has id %d" + , (char*)ohRPT.ResourceTag.Data + , dev->ohdevid); + } + break; + + case OHRES_MGMTMOD: + if (ohRPT.ResourceCapabilities&SAHPI_CAPABILITY_SENSOR){ + dev->ohsensnum = get_sensor_num(dev->ohsession + , ohRPT.ResourceId); + + if (dev->ohsensnum) { + dev->ohsensid = ohRPT.ResourceId; + + if (Debug) { + LOG(PIL_DEBUG + , "MgmtModule '%s' has id %d " + "with sensor #%d" + , (char*)ohRPT.ResourceTag.Data + , dev->ohsensid + , dev->ohsensnum); + } + } + } + break; + + case OHRES_BLADE: + if ((bi = (struct blade_info *) + MALLOC(sizeof(struct blade_info))) == NULL) { + LOG(PIL_CRIT, "Out of memory in %s" + , __FUNCTION__); + free_bladehpi_hostlist(dev); + return S_OOPS; + } + + /* + * New format consists of "Blade N - name" while older + * format consists only of "name"; we only need to + * stash name because ResourceID is the important info + */ + if (sscanf((char*)ohRPT.ResourceTag.Data, "Blade %d - %s" + , &blnum, blname) == 2) { + bi->name = STRDUP(blname); + } else { + bi->name = STRDUP((char*)ohRPT.ResourceTag.Data); + } + if (bi->name == NULL) { + LOG(PIL_CRIT, "Out of memory for strdup in %s" + , __FUNCTION__); + free_bladehpi_hostlist(dev); + return S_OOPS; + } + + bi->resourceId = ohRPT.ResourceId; + bi->resourceCaps = ohRPT.ResourceCapabilities; + dev->hostlist = g_list_append(dev->hostlist, bi); + + if (Debug) { + LOG(PIL_DEBUG, "Blade '%s' has id %d, caps %x" + , bi->name, bi->resourceId, bi->resourceCaps); + } + break; + } + } while (ohrc == SA_OK && ohnextid != SAHPI_LAST_ENTRY); + + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + free_bladehpi_hostlist(dev); + return S_BADCONFIG; + } + + if (ohupdate != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if(Debug){ + LOG(PIL_DEBUG, "Looping through entries again," + " count changed from %d to %d" + , ohupdate, ohdi.RptUpdateCount); + } + goto try_again; + } + + dev->ohrptcnt = ohupdate; + + return S_OK; +} + + +static void +free_bladehpi_hostlist(struct pluginDevice *dev) +{ + if (dev->hostlist) { + GList *node; + while (NULL != (node = g_list_first(dev->hostlist))) { + dev->hostlist = + g_list_remove_link(dev->hostlist, node); + FREE(((struct blade_info *)node->data)->name); + FREE(node->data); + g_list_free(node); + } + dev->hostlist = NULL; + } + dev->ohdevid = dev->ohsensid = dev->ohsensnum = 0; +} + + +static int +get_num_tokens(char *str) +{ + int namecount = 0; + + while (*str != EOS) { + str += strspn(str, WHITESPACE); + if (*str == EOS) + break; + str += strcspn(str, WHITESPACE); + namecount++; + } + return namecount; +} diff --git a/lib/plugins/stonith/cyclades.c b/lib/plugins/stonith/cyclades.c new file mode 100644 index 0000000..6744cd4 --- /dev/null +++ b/lib/plugins/stonith/cyclades.c @@ -0,0 +1,650 @@ +/* + * Stonith module for Cyclades AlterPath PM + * Bases off the SSH plugin + * + * Copyright (c) 2004 Cyclades corp. + * + * Author: Jon Taylor <jon.taylor@cyclades.com> + * + * Rewritten from scratch using baytech.c structure and code + * and currently maintained by + * Marcelo Tosatti <marcelo.tosatti@cyclades.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> + +#define DEVICE "Cyclades AlterPath PM" + +#define DOESNT_USE_STONITHSCANLINE + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN cyclades +#define PIL_PLUGIN_S "cyclades" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include "stonith_signal.h" + +static StonithPlugin * cyclades_new(const char *); +static void cyclades_destroy(StonithPlugin *); +static int cyclades_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * cyclades_get_confignames(StonithPlugin * s); +static const char * cyclades_get_info(StonithPlugin * s, int InfoType); +static int cyclades_status(StonithPlugin *); +static int cyclades_reset_req(StonithPlugin * s, int request, const char * host); +static char ** cyclades_hostlist(StonithPlugin *); + + + +static struct stonith_ops cycladesOps ={ + cyclades_new, /* Create new STONITH object */ + cyclades_destroy, /* Destroy STONITH object */ + cyclades_get_info, /* Return STONITH info string */ + cyclades_get_confignames, /* Return STONITH config vars */ + cyclades_set_config, /* set configuration from vars */ + cyclades_status, /* Return STONITH device status */ + cyclades_reset_req, /* Request a reset */ + cyclades_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +#include "stonith_expect_helpers.h" + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &cycladesOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * Cyclades STONITH device + * + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char * device; + char * user; + + int serial_port; + + /* pid of ssh client process and its in/out file descriptors */ + pid_t pid; + int rdfd, wrfd; +}; + +static struct Etoken StatusOutput[] = { + { "Outlet\t\tName\t\tStatus\t\tUsers\t\tInterval (s)", 1, 0}, + { "Outlet\tName\t\t\tStatus\t\tInterval (s)\tUsers", 2, 0}, + { "Outlet Name Status Post-on Delay(s)", 3, 0}, + { NULL, 0, 0} +}; + +static struct Etoken CRNL[] = { {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}}; + + +/* Commands of PM devices */ +static char status_all[] = "status all"; +static char cycle[] = "cycle"; + +static int CYC_robust_cmd(struct pluginDevice *, char *); + +static const char * pluginid = "CycladesDevice-Stonith"; +static const char * NOTpluginID = "Cyclades device has been destroyed"; + +#define MAX_OUTLETS 128 + +#define ST_SERIALPORT "serialport" + +#define ZEROEXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) \ + return(0); \ + } + +#define RESETEXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) { \ + FREE(outletstr); \ + return(errno == ETIMEDOUT \ + ? S_RESETFAIL : S_OOPS); \ + } \ + } + +#include "stonith_config_xml.h" + +#define XML_SERIALPORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_SERIALPORT \ + XML_PARM_SHORTDESC_END + +#define XML_SERIALPORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The serial port of the IPDU which can powercycle the node" \ + XML_PARM_LONGDESC_END + +#define XML_SERIALPORT_PARM \ + XML_PARAMETER_BEGIN(ST_SERIALPORT, "string", "1", "0") \ + XML_SERIALPORT_SHORTDESC \ + XML_SERIALPORT_LONGDESC \ + XML_PARAMETER_END + +static const char *cycladesXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_LOGIN_PARM + XML_SERIALPORT_PARM + XML_PARAMETERS_END; + +static int +CYCScanLine(struct pluginDevice *sd, int timeout, char * buf, int max) +{ + if (EXPECT_TOK(sd->rdfd, CRNL, timeout, buf, max, Debug) < 0) { + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + return(S_OOPS); + } + return(S_OK); +} + +static int +cyclades_status(StonithPlugin *s) +{ + struct pluginDevice *sd; + char *cmd = status_all; + + ERRIFNOTCONFIGED(s,S_OOPS); + + sd = (struct pluginDevice*) s; + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run status all command"); + return(S_OOPS); + } + + EXPECT(sd->rdfd, StatusOutput, 50); + + return(S_OK); +} + +static int CYC_run_command(struct pluginDevice *sd, char *cmd) +{ + char SshCommand[MAX_OUTLETS*4]; + + snprintf(SshCommand, sizeof(SshCommand), + "exec ssh -q %s@%s /bin/pmCommand %d %s 2>/dev/null", + sd->user, sd->device, sd->serial_port, cmd); + + sd->pid = STARTPROC(SshCommand, &sd->rdfd, &sd->wrfd); + + if (sd->pid <= 0) { + return(S_OOPS); + } + + return(S_OK); +} + +static int +CYC_robust_cmd(struct pluginDevice *sd, char *cmd) +{ + int rc = S_OOPS; + int i; + + for (i=0; i < 20 && rc != S_OK; i++) { + + if (sd->pid > 0) { + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + } + + if (CYC_run_command(sd, cmd) != S_OK) { + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + continue; + } + rc = S_OK; + } + + return rc; +} + +#define MAXSAVE 512 +static int CYCNametoOutlet(struct pluginDevice *sd, const char *host, int *outlets, int maxoutlet) +{ + char *cmd = status_all; + char savebuf[MAXSAVE]; + int err; + int outlet, numoutlet = 0; + char name[17], locked[11], on[4]; + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run status all command"); + return 0; + } + + ZEROEXPECT(sd->rdfd, StatusOutput, 50); + + ZEROEXPECT(sd->rdfd, CRNL, 50); + + do { + + memset(savebuf, 0, sizeof(savebuf)); + memset(name, 0, sizeof(name)); + memset(locked, 0, sizeof(locked)); + memset(on, 0, sizeof(on)); + + err = CYCScanLine(sd, 2, savebuf, sizeof(savebuf)); + + if ((err == S_OK) && + (sscanf(savebuf,"%3d %16s %10s %3s", &outlet, + name, locked, on) > 0)) { + if (!strncasecmp(name, host, strlen(host))) { + if (numoutlet >= maxoutlet) { + LOG(PIL_CRIT, "too many outlets"); + return 0; + } + outlets[numoutlet++] = outlet; + } + } + + } while (err == S_OK); + + return (numoutlet); +} + + +/* + * Return the list of hosts configured for this Cyclades device + */ + +static char ** +cyclades_hostlist(StonithPlugin *s) +{ + struct pluginDevice* sd; + char *cmd = status_all; + char savebuf[MAXSAVE]; + int err, i; + int outlet; + int numnames = 0; + char name[17], locked[11], on[4]; + char *NameList[MAX_OUTLETS]; + char **ret = NULL; + + ERRIFNOTCONFIGED(s,NULL); + + sd = (struct pluginDevice*) s; + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run status all command"); + return (NULL); + } + + memset(savebuf, 0, sizeof(savebuf)); + + NULLEXPECT(sd->rdfd, StatusOutput, 50); + + NULLEXPECT(sd->rdfd, CRNL, 50); + + do { + char *nm; + + memset(savebuf, 0, sizeof(savebuf)); + memset(name, 0, sizeof(name)); + memset(locked, 0, sizeof(locked)); + memset(on, 0, sizeof(on)); + + err = CYCScanLine(sd, 2, savebuf, sizeof(savebuf)); + + if ((err == S_OK) && + (sscanf(savebuf,"%3d %16s %10s %3s", &outlet, + name, locked, on) > 0)) { + nm = (char *) STRDUP (name); + if (!nm) { + goto out_of_memory; + } + strdown(nm); + NameList[numnames] = nm; + numnames++; + NameList[numnames] = NULL; + } + + } while (err == S_OK); + + if (numnames) { + + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + goto out_of_memory; + } else { + memcpy(ret, NameList, (numnames+1)*sizeof(char*)); + } + return (ret); + } + return(ret); + +out_of_memory: + LOG(PIL_CRIT, "out of memory"); + for (i=0; i<numnames; i++) { + FREE(NameList[i]); + } + + return (NULL); +} + + +static char *cyclades_outletstr(int *outlet, int numoutlet) +{ + int i, len; + char *ret; + + /* maximum length per outlet is currently four (outlet is one to + * three digits, followed by either a comma or null), so add one + * for good measure */ + len = numoutlet * 5 * sizeof(char); + if ((ret = MALLOC(len)) != NULL) { + snprintf(ret, len, "%d", outlet[0]); + for (i = 1; i < numoutlet; i++) { + char buf[5]; + snprintf(buf, sizeof(buf), ",%d", outlet[i]); + strcat(ret, buf); + } + } + return(ret); +} + + +static int cyclades_onoff(struct pluginDevice *sd, int *outlet, int numoutlet, + const char *unitid, int req) +{ + const char * onoff; + char cmd[MAX_OUTLETS*4], expstring[64]; + struct Etoken exp[] = {{NULL, 0, 0}, {NULL, 0, 0}}; + char *outletstr; + int i; + + onoff = (req == ST_POWERON ? "on" : "off"); + + memset(cmd, 0, sizeof(cmd)); + + outletstr = cyclades_outletstr(outlet, numoutlet); + if (outletstr == NULL) { + LOG(PIL_CRIT, "out of memory"); + return (S_OOPS); + } + snprintf(cmd, sizeof(cmd), "%s %s", onoff, outletstr); + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run %s command", onoff); + FREE(outletstr); + return(S_OOPS); + } + + for (i = 0; i < numoutlet; i++) { + memset(expstring, 0, sizeof(expstring)); + snprintf(expstring, sizeof(expstring), "%d: Outlet turned %s." + , outlet[i], onoff); + + exp[0].string = expstring; + + /* FIXME: should handle "already powered on/off" case and inform + to log */ + + EXPECT(sd->rdfd, exp, 50); + } + + LOG(PIL_DEBUG, "Power to host %s turned %s", unitid, onoff); + + FREE(outletstr); + return (S_OK); +} + +static int cyclades_reset(struct pluginDevice *sd, int *outlet, int numoutlet, + const char *unitid) +{ + char cmd[MAX_OUTLETS*4], expstring[64]; + struct Etoken exp[] = {{NULL, 0, 0}, {NULL, 0, 0}}; + char *outletstr; + int i; + + memset(cmd, 0, sizeof(cmd)); + + outletstr = cyclades_outletstr(outlet, numoutlet); + if (outletstr == NULL) { + LOG(PIL_CRIT, "out of memory"); + return (S_OOPS); + } + snprintf(cmd, sizeof(cmd), "%s %s", cycle, outletstr); + + LOG(PIL_INFO, "Host %s being rebooted.", unitid); + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run cycle command"); + FREE(outletstr); + return(S_OOPS); + } + + for (i = 0; i < numoutlet; i++) { + memset(expstring, 0, sizeof(expstring)); + snprintf(expstring, sizeof(expstring) + , "%d: Outlet turned off.", outlet[i]); + + exp[0].string = expstring; + RESETEXPECT(sd->rdfd, exp, 50); + } + + for (i = 0; i < numoutlet; i++) { + memset(expstring, 0, sizeof(expstring)); + snprintf(expstring, sizeof(expstring) + , "%d: Outlet turned on.", outlet[i]); + + exp[0].string = expstring; + RESETEXPECT(sd->rdfd, exp, 50); + } + + FREE(outletstr); + return (S_OK); +} + +/* + * Reset the given host on this Stonith device. + */ +static int +cyclades_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice *sd; + int rc = 0; + int numoutlet, outlets[MAX_OUTLETS]; + + ERRIFNOTCONFIGED(s,S_OOPS); + + sd = (struct pluginDevice*) s; + + numoutlet = CYCNametoOutlet(sd, host, outlets, MAX_OUTLETS); + + if (!numoutlet) { + LOG(PIL_CRIT, "Unknown host %s to Cyclades PM", host); + return (S_OOPS); + } + + + switch (request) { + case ST_POWERON: + case ST_POWEROFF: + rc = cyclades_onoff(sd, outlets, numoutlet, host, request); + break; + + case ST_GENERIC_RESET: + rc = cyclades_reset(sd, outlets, numoutlet, host); + break; + default: + rc = S_INVAL; + break; + } + + return rc; +} + +static const char * const * +cyclades_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_LOGIN, ST_SERIALPORT, NULL}; + return ret; +} + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +cyclades_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy[] = + { {ST_IPADDR, NULL} + , {ST_LOGIN, NULL} + , {ST_SERIALPORT, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s, S_OOPS); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->device = namestocopy[0].s_value; + sd->user = namestocopy[1].s_value; + sd->serial_port = atoi(namestocopy[2].s_value); + FREE(namestocopy[2].s_value); + + return(S_OK); +} + +static const char * +cyclades_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice * sd; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + + sd = (struct pluginDevice*) s; + + switch (reqtype) { + case ST_DEVICEID: /* What type of device? */ + /* FIXME: could inform the exact PM model */ + ret = sd->idinfo; + break; + + case ST_DEVICENAME: /* What particular device? */ + ret = sd->device; + break; + + case ST_DEVICEDESCR: /* Description of dev type */ + ret = "Cyclades AlterPath PM " + "series power switches (via TS/ACS/KVM)."; + break; + + case ST_DEVICEURL: /* Manufacturer's web site */ + ret = "http://www.cyclades.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = cycladesXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Cyclades Stonith destructor... + */ +static void +cyclades_destroy(StonithPlugin *s) +{ + struct pluginDevice* sd; + + VOIDERRIFWRONGDEV(s); + + sd = (struct pluginDevice*) s; + + sd->pluginid = NOTpluginID; + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + if (sd->device != NULL) { + FREE(sd->device); + sd->device = NULL; + } + if (sd->user != NULL) { + FREE(sd->user); + sd->user = NULL; + } + + FREE(sd); +} + +/* Create a new cyclades Stonith device */ +static StonithPlugin * +cyclades_new(const char *plugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + sd->pid = -1; + sd->rdfd = -1; + sd->wrfd = -1; + sd->idinfo = DEVICE; + sd->sp.s_ops = &cycladesOps; + + return &(sd->sp); /* same as sd */ +} diff --git a/lib/plugins/stonith/drac3.c b/lib/plugins/stonith/drac3.c new file mode 100644 index 0000000..95be775 --- /dev/null +++ b/lib/plugins/stonith/drac3.c @@ -0,0 +1,359 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda <moreda@alfa21.com> + * Tiny bits Copyright 2005 International Business Machines + * Significantly Mangled by Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005 + * + * (Using snippets of other stonith modules code) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define DEVICE "Dell DRACIII Card" +#include "stonith_plugin_common.h" + +#include <curl/curl.h> +#include "drac3_command.h" + +#define PIL_PLUGIN drac3 +#define PIL_PLUGIN_S "drac3" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> +#include "stonith_signal.h" + +static StonithPlugin * drac3_new(const char *); +static void drac3_destroy(StonithPlugin *); +static const char * const * drac3_get_confignames(StonithPlugin *); +static int drac3_set_config(StonithPlugin *, StonithNVpair *); +static const char * drac3_getinfo(StonithPlugin * s, int InfoType); +static int drac3_status(StonithPlugin * ); +static int drac3_reset_req(StonithPlugin * s, int request, const char * host); +static char ** drac3_hostlist(StonithPlugin *); + +static struct stonith_ops drac3Ops ={ + drac3_new, /* Create new STONITH object */ + drac3_destroy, /* Destroy STONITH object */ + drac3_getinfo, /* Return STONITH info string */ + drac3_get_confignames, /* Return configuration parameters */ + drac3_set_config, /* Set configuration */ + drac3_status, /* Return STONITH device status */ + drac3_reset_req, /* Request a reset */ + drac3_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &drac3Ops + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#define BUFLEN 1024 +#define ST_HOST "host" + +struct pluginDevice { + StonithPlugin sp; + const char *pluginid; + const char *idinfo; + CURL *curl; + char *host; + char *user; + char *pass; +}; + +static const char *pluginid = "Dell-DRACIII-Stonith"; +static const char *NOTpluginID = "Dell DRACIII device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_HOST_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_HOST \ + XML_PARM_SHORTDESC_END + +#define XML_HOST_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The hostname of the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_HOST_PARM \ + XML_PARAMETER_BEGIN(ST_HOST, "string", "1", "1") \ + XML_HOST_SHORTDESC \ + XML_HOST_LONGDESC \ + XML_PARAMETER_END + +static const char *drac3XML = + XML_PARAMETERS_BEGIN + XML_HOST_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +/* ------------------------------------------------------------------ */ +/* STONITH PLUGIN API */ +/* ------------------------------------------------------------------ */ +static StonithPlugin * +drac3_new(const char *subplugin) +{ + struct pluginDevice *drac3d = ST_MALLOCT(struct pluginDevice); + + if (drac3d == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(drac3d, 0, sizeof(*drac3d)); + drac3d->pluginid = pluginid; + drac3d->curl = curl_easy_init(); + drac3InitCurl(drac3d->curl); + drac3d->host = NULL; + drac3d->user = NULL; + drac3d->pass = NULL; + drac3d->idinfo = DEVICE; + drac3d->sp.s_ops = &drac3Ops; + return (&(drac3d->sp)); +} + +/* ------------------------------------------------------------------ */ +static void +drac3_destroy(StonithPlugin * s) +{ + struct pluginDevice *drac3d; + + VOIDERRIFWRONGDEV(s); + + drac3d = (struct pluginDevice *) s; + + drac3d->pluginid = NOTpluginID; + + /* release curl connection */ + if (drac3d->curl != NULL) { + drac3Logout(drac3d->curl, drac3d->host); + curl_easy_cleanup(drac3d->curl); + drac3d->curl = NULL; + } + + if (drac3d->host != NULL) { + FREE(drac3d->host); + drac3d->host = NULL; + } + if (drac3d->user != NULL) { + FREE(drac3d->user); + drac3d->user = NULL; + } + if (drac3d->pass != NULL) { + FREE(drac3d->pass); + drac3d->pass = NULL; + } + + /* release stonith-object itself */ + FREE(drac3d); +} + +/* ------------------------------------------------------------------ */ +static const char * const * +drac3_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_HOST, ST_LOGIN, ST_PASSWD, NULL}; + return ret; +} + +/* ------------------------------------------------------------------ */ +static int +drac3_set_config(StonithPlugin * s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_HOST, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s, S_OOPS); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->host = namestocopy[0].s_value; + sd->user = namestocopy[1].s_value; + sd->pass = namestocopy[2].s_value; + + return(S_OK); +} + +/* ------------------------------------------------------------------ */ +const char * +drac3_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *drac3d; + const char *ret = NULL; + + ERRIFWRONGDEV(s,NULL); + + drac3d = (struct pluginDevice *) s; + + switch (reqtype) { + case ST_DEVICEID: + ret = drac3d->idinfo; + break; + case ST_DEVICENAME: + ret = drac3d->host; + break; + case ST_DEVICEDESCR: + ret = "Dell DRACIII (via HTTPS)\n" + "The Dell Remote Access Controller accepts XML " + "commands over HTTPS"; + break; + case ST_DEVICEURL: + ret = "http://www.dell.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = drac3XML; + break; + default: + ret = NULL; + break; + } + + return(ret); +} + +/* ------------------------------------------------------------------ */ +int +drac3_status(StonithPlugin *s) +{ + struct pluginDevice *drac3d; + + ERRIFNOTCONFIGED(s,S_OOPS); + + drac3d = (struct pluginDevice *) s; + + if (drac3VerifyLogin(drac3d->curl, drac3d->host)) { + if (drac3Login(drac3d->curl, drac3d->host, + drac3d->user, drac3d->pass)) { + LOG(PIL_CRIT, "%s: cannot log into %s at %s", + __FUNCTION__, + drac3d->idinfo, + drac3d->host); + return(S_ACCESS); + } + } + + if (drac3GetSysInfo(drac3d->curl, drac3d->host)) { + return(S_ACCESS); + }else{ + return(S_OK); + } +} + +/* ------------------------------------------------------------------ */ +int +drac3_reset_req(StonithPlugin * s, int request, const char *host) +{ + struct pluginDevice *drac3d; + int rc = S_OK; + + ERRIFNOTCONFIGED(s,S_OOPS); + + drac3d = (struct pluginDevice *) s; + + if (strcasecmp(host, drac3d->host)) { + LOG(PIL_CRIT, "%s doesn't control host [%s]" + , drac3d->idinfo, host); + return(S_BADHOST); + } + + if (drac3VerifyLogin(drac3d->curl, drac3d->host)) { + if (drac3Login(drac3d->curl, drac3d->host, + drac3d->user, drac3d->pass)) { + LOG(PIL_CRIT, "%s: cannot log into %s at %s", + __FUNCTION__, + drac3d->idinfo, + drac3d->host); + return(S_ACCESS); + } + } + + switch(request) { +#if defined(ST_POWERON) && defined(ST_POWEROFF) + case ST_POWERON: + case ST_POWEROFF: + /* TODO... */ +#endif + case ST_GENERIC_RESET: + if (drac3PowerCycle(drac3d->curl, drac3d->host)) + rc = S_ACCESS; + break; + default: + rc = S_INVAL; + break; + } + + return(rc); +} + +/* ------------------------------------------------------------------ */ +char ** +drac3_hostlist(StonithPlugin * s) +{ + struct pluginDevice *drac3d; + char **hl; + + ERRIFNOTCONFIGED(s,NULL); + + drac3d = (struct pluginDevice *) s; + + hl = OurImports->StringToHostList(drac3d->host); + if (hl == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + } else { + strdown(hl[0]); + } + + return(hl); +} diff --git a/lib/plugins/stonith/drac3_command.c b/lib/plugins/stonith/drac3_command.c new file mode 100644 index 0000000..4d9002d --- /dev/null +++ b/lib/plugins/stonith/drac3_command.c @@ -0,0 +1,342 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda <moreda@alfa21.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> + +#include <curl/curl.h> + +#include <libxml/xmlmemory.h> +#include <libxml/parser.h> +#include <libxml/xpath.h> + +#include "drac3_command.h" +#include "drac3_hash.h" + +#define BUFLEN 1024 /* buffer */ +#define SBUFLEN 256 /* small buffer */ +#define MD5LEN 16 /* md5 buffer */ + +#define DEBUG 0 + +/* Hardcoded XML commands and response codes */ +#define CMD_POWERCYCLE "<?XML version=\"1.0\"?><?RMCXML version=\"1.0\"?><RMCSEQ><REQ CMD=\"serveraction\"><ACT>powercycle</ACT></REQ></RMCSEQ>\n" +#define CMD_GETSYSINFO "<?XML version=\"1.0\"?><?RMCXML version=\"1.0\"?><RMCSEQ><REQ CMD=\"xml2cli\"><CMDINPUT>getsysinfo -A</CMDINPUT></REQ></RMCSEQ>\n" +#define RC_OK "0x0\n" + +struct Chunk { + char *memory; + size_t size; +}; + +/* prototypes */ +int xmlGetXPathString (const char *str, const char * expr, char * rc, const int len); +size_t writeFunction (void *ptr, size_t size, size_t nmemb, void *data); + + +/* ---------------------------------------------------------------------- * + * XML PARSING * + * ---------------------------------------------------------------------- */ + +int +xmlGetXPathString (const char *str, + const char * expr, + char * rc, + const int len) +{ + xmlDocPtr doc; + xmlNodePtr cur; + xmlXPathContextPtr ctx; + xmlXPathObjectPtr path; + xmlChar *xmlRC; + + if (!strchr(str,'<')) { + fprintf(stderr,"%s malformed\n", str); + rc[0] = 0x00; + return(1); + } + + doc = xmlParseMemory(str, strlen(str)); + xmlXPathInit(); + ctx = xmlXPathNewContext(doc); + path = xmlXPathEvalExpression((const xmlChar *)expr, ctx); + cur = path->nodesetval->nodeTab[0]; + + if (cur != NULL) { + xmlRC = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); + snprintf(rc, len, "%s\n", xmlRC); + xmlFree(xmlRC); + xmlFreeDoc(doc); + xmlCleanupParser(); + xmlXPathFreeObject(path); + xmlXPathFreeContext(ctx); + + return(0); + } else { + fprintf(stderr,"error in obtaining XPath %s\n", expr); + xmlFreeDoc(doc); + xmlCleanupParser(); + xmlXPathFreeObject(path); + xmlXPathFreeContext(ctx); + + rc[0] = 0x00; + return(1); + } +} + + +/* ---------------------------------------------------------------------- * + * CURL CALLBACKS * + * ---------------------------------------------------------------------- */ + +size_t +writeFunction (void *ptr, size_t size, size_t nmemb, void *data) +{ + + register int realsize = size * nmemb; + struct Chunk *mem = (struct Chunk *)data; + + mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1); + if (mem->memory) { + memcpy(&(mem->memory[mem->size]), ptr, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + } + return realsize; +} + + +/* ---------------------------------------------------------------------- * + * DRAC3 CURL COMMANDS * + * ---------------------------------------------------------------------- */ + +int +drac3InitCurl (CURL *curl) +{ +#ifdef CURLOPT_NOSIGNAL + if (curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1)) return(1); +#endif + if (curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30)) return(1); + if (curl_easy_setopt(curl, CURLOPT_VERBOSE, 0)) return(1); + if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunction)) return(1); + if (curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "/dev/null")) return(1); + if (curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0)) return(1); + if (curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0)) return(1); + return(0); +} + +int +drac3Login (CURL *curl, + const char *host, + const char *user, + const char *pass) +{ + char url[BUFLEN]; + char chall[BUFLEN]; + char token[BUFLEN]; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) + return(1); + + /* ask for challenge */ + snprintf(url, BUFLEN, "https://%s/cgi/challenge", host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) + return(1); + if (curl_easy_perform(curl)) + return(1); + + /* extract challenge */ + status = xmlGetXPathString(chunk.memory, "//CHALLENGE", chall, BUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + + /* calculate authToken */ + drac3AuthHash(chall, pass, token, BUFLEN); + + if (DEBUG) printf("T: %s\n", token); + + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + if (status) return(1); + chunk.memory = NULL; + chunk.size = 0; + + /* sends authToken */ + snprintf(url, BUFLEN, "https://%s/cgi/login?user=%s&hash=%s", + host, + user, + token); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) + return(1); + if (curl_easy_perform(curl)) + return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + +int +drac3PowerCycle (CURL *curl, + const char *host) +{ + char url[BUFLEN]; + char cmd[]=CMD_POWERCYCLE; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) return(1); + + snprintf(url, BUFLEN, "https://%s/cgi/bin", + host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) return(1); + if (curl_easy_setopt(curl, CURLOPT_POSTFIELDS, cmd)) return(1); + if (curl_easy_perform(curl)) return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + + +int +drac3GetSysInfo (CURL *curl, + const char *host) +{ + char url[BUFLEN]; + char cmd[]=CMD_GETSYSINFO; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) return(1); + + snprintf(url, BUFLEN, "https://%s/cgi/bin", + host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) return(1); + if (curl_easy_setopt(curl, CURLOPT_POSTFIELDS, cmd)) return(1); + if (curl_easy_perform(curl)) return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + + +int +drac3Logout (CURL *curl, + const char *host) +{ + char url[BUFLEN]; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) return(1); + + snprintf(url, BUFLEN, "https://%s/cgi/logout", + host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) return(1); + if (curl_easy_perform(curl)) return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + +int +drac3VerifyLogin (CURL *curl, + const char *host) +{ + /*We try to do a GetSysInfo */ + return(drac3GetSysInfo (curl, host)); +} + +/* -------------------------------------------------------------------- */ + diff --git a/lib/plugins/stonith/drac3_command.h b/lib/plugins/stonith/drac3_command.h new file mode 100644 index 0000000..cd03e15 --- /dev/null +++ b/lib/plugins/stonith/drac3_command.h @@ -0,0 +1,29 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda <moreda@alfa21.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +int drac3InitCurl (CURL *curl); +int drac3Login (CURL *curl, const char *host, const char *user, const char *pass); +int drac3PowerCycle (CURL *curl, const char *host); +int drac3GetSysInfo (CURL *curl, const char *host); +int drac3Logout (CURL *curl, const char *host); +int drac3VerifyLogin (CURL *curl, const char *host); + diff --git a/lib/plugins/stonith/drac3_hash.c b/lib/plugins/stonith/drac3_hash.c new file mode 100644 index 0000000..605a126 --- /dev/null +++ b/lib/plugins/stonith/drac3_hash.c @@ -0,0 +1,106 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda <moreda@alfa21.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> + +#include <string.h> +#include <stdio.h> +#include <clplumbing/base64.h> +#include <clplumbing/md5.h> +#include <glib.h> + +#include "drac3_hash.h" + +#define BUFLEN 1024 /* buffer */ +#define SBUFLEN 256 /* small buffer */ +#define MD5LEN 16 /* md5 buffer */ + +/* Hash functions for DRAC3 authentication */ + +guint16 +drac3Crc16(const char *str, + const int l) { + + int i,j; + guint16 crc = 0; + + for (i=0; i<l; i++) { + crc = crc ^ (str[i] << 8); + for (j=0; j<8; j++) + crc = ( (crc & 0x8000) == 32768 ? (crc<<1) ^ 0x1021 : crc<<1); + } + crc = crc & 0xFFFF; + return crc; +} + +void +drac3AuthHash(const char * chall, + const char * pass, + char * token, + int len) { + + char * chall_dup; + char challBytes[MD5LEN]; + char passMD5[MD5LEN]; + char xorBytes[MD5LEN]; + char xorBytesMD5[MD5LEN]; + guint16 crc; + char response[MD5LEN+2]; + char responseb64[SBUFLEN]; + int i; + + /* decodes chall -> challBytes */ + memset(challBytes, 0, MD5LEN); + chall_dup = g_strdup(chall); + if (chall_dup[strlen(chall_dup) - 1] == '\n' ) { + chall_dup[strlen(chall_dup) - 1] = '\0'; + } + base64_to_binary(chall_dup, strlen(chall_dup), challBytes, MD5LEN); + + /* gets MD5 from pass -> passMD5 */ + MD5((const unsigned char *)pass, strlen(pass), (unsigned char *)passMD5); + + /* calculate challBytes and passMD5 xor -> xorBytes */ + for (i=0; i<MD5LEN; i++) { + xorBytes[i] = challBytes[i] ^ passMD5[i]; + } + + /* calculate xorBytes MD5 -> xorBytesMD5 */ + MD5((unsigned char *)xorBytes, MD5LEN, (unsigned char *)xorBytesMD5); + + /* calculate xorBytesMD5 crc16 */ + crc = drac3Crc16(xorBytesMD5, MD5LEN); + + /* joins xorBytesMD5 and crc16 -> response */ + memcpy(response, xorBytesMD5, MD5LEN); + memcpy(response+MD5LEN, &crc, 2); + + /* calculate response base64 -> responseb64 */ + memset(responseb64, 0, SBUFLEN); + binary_to_base64(response, MD5LEN+2, responseb64, SBUFLEN); + + /* assuring null-termination */ + responseb64[SBUFLEN-1]=0x00; + + snprintf(token, len, "%s", responseb64); + token[len-1]=0x00; +} diff --git a/lib/plugins/stonith/drac3_hash.h b/lib/plugins/stonith/drac3_hash.h new file mode 100644 index 0000000..fab2f58 --- /dev/null +++ b/lib/plugins/stonith/drac3_hash.h @@ -0,0 +1,28 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda <moreda@alfa21.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sys/types.h> +#include <glib.h> + +guint16 drac3Crc16(const char *str, const int l); +void drac3AuthHash(const char *chall, const char *pass, char *token, int len); + diff --git a/lib/plugins/stonith/external.c b/lib/plugins/stonith/external.c new file mode 100644 index 0000000..da03665 --- /dev/null +++ b/lib/plugins/stonith/external.c @@ -0,0 +1,868 @@ +/* + * Stonith module for EXTERNAL Stonith device + * + * Copyright (c) 2001 SuSE Linux AG + * Portions Copyright (c) 2004, tummy.com, ltd. + * + * Based on ssh.c, Authors: Joachim Gleissner <jg@suse.de>, + * Lars Marowsky-Bree <lmb@suse.de> + * Modified for external.c: Scott Kleihege <scott@tummy.com> + * Reviewed, tested, and config parsing: Sean Reifschneider <jafo@tummy.com> + * And overhauled by Lars Marowsky-Bree <lmb@suse.de>, so the circle + * closes... + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * Changed to allow full-featured external plugins by Dave Blaschke + * <debltc@us.ibm.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> + +#include <dirent.h> + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN external +#define PIL_PLUGIN_S "external" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +#include <pils/plugin.h> + +static StonithPlugin * external_new(const char *); +static void external_destroy(StonithPlugin *); +static int external_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * external_get_confignames(StonithPlugin *); +static const char * external_getinfo(StonithPlugin * s, int InfoType); +static int external_status(StonithPlugin * ); +static int external_reset_req(StonithPlugin * s, int request, const char * host); +static char ** external_hostlist(StonithPlugin *); + +static struct stonith_ops externalOps ={ + external_new, /* Create new STONITH object */ + external_destroy, /* Destroy STONITH object */ + external_getinfo, /* Return STONITH info string */ + external_get_confignames, /* Return STONITH info string */ + external_set_config, /* Get configuration from NVpairs */ + external_status, /* Return STONITH device status */ + external_reset_req, /* Request a reset */ + external_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &externalOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * EXTERNAL STONITH device + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + GHashTable * cmd_opts; + char * subplugin; + char ** confignames; + char * outputbuf; +}; + +static const char * pluginid = "ExternalDevice-Stonith"; +static const char * NOTpluginID = "External device has been destroyed"; + +/* Prototypes */ + +/* Run the command with op and return the exit status + the output + * (NULL -> discard output) */ +static int external_run_cmd(struct pluginDevice *sd, const char *op, + char **output); +/* Just free up the configuration and the memory, if any */ +static void external_unconfig(struct pluginDevice *sd); + +static int +external_status(StonithPlugin *s) +{ + struct pluginDevice * sd; + const char * op = "status"; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + + rc = external_run_cmd(sd, op, NULL); + if (rc != 0) { + LOG(PIL_WARN, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + } + return rc; +} + +static int +get_num_tokens(char *str) +{ + int namecount = 0; + + while (*str != EOS) { + str += strspn(str, WHITESPACE); + if (*str == EOS) + break; + str += strcspn(str, WHITESPACE); + namecount++; + } + return namecount; +} + +static char ** +external_hostlist(StonithPlugin *s) +{ + struct pluginDevice* sd; + const char * op = "gethosts"; + int rc, i, namecount; + char ** ret; + char * output = NULL; + char * tmp; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,NULL); + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(NULL); + } + + rc = external_run_cmd(sd, op, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + return NULL; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + + if (!output) { + LOG(PIL_CRIT, "%s: '%s %s' returned an empty hostlist", + __FUNCTION__, sd->subplugin, op); + return NULL; + } + + namecount = get_num_tokens(output); + ret = MALLOC((namecount+1)*sizeof(char *)); + if (!ret) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + FREE(output); + return NULL; + } + memset(ret, 0, (namecount+1)*sizeof(char *)); + + /* White-space split the output here */ + i = 0; + tmp = strtok(output, WHITESPACE); + while (tmp != NULL) { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s host %s", + __FUNCTION__, sd->subplugin, tmp); + } + ret[i] = STRDUP(tmp); + if (!ret[i]) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + FREE(output); + stonith_free_hostlist(ret); + return NULL; + } + i++; + tmp = strtok(NULL, WHITESPACE); + } + + FREE(output); + + if (i == 0) { + LOG(PIL_CRIT, "%s: '%s %s' returned an empty hostlist", + __FUNCTION__, sd->subplugin, op); + stonith_free_hostlist(ret); + ret = NULL; + } + + return(ret); +} + +static int +external_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice * sd; + const char * op; + int rc; + char * args1and2; + int argslen; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + if (Debug) { + LOG(PIL_DEBUG, "Host external-reset initiating on %s", host); + } + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + + switch (request) { + case ST_GENERIC_RESET: + op = "reset"; + break; + + case ST_POWEROFF: + op = "off"; + break; + + case ST_POWERON: + op = "on"; + break; + + default: + LOG(PIL_CRIT, "%s: Unknown stonith request %d", + __FUNCTION__, request); + return S_OOPS; + break; + } + + argslen = strlen(op) + strlen(host) + 2 /* 1 for blank, 1 for EOS */; + args1and2 = (char *)MALLOC(argslen); + if (args1and2 == NULL) { + LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__); + return S_OOPS; + } + rc = snprintf(args1and2, argslen, "%s %s", op, host); + if (rc <= 0 || rc >= argslen) { + FREE(args1and2); + return S_OOPS; + } + + rc = external_run_cmd(sd, args1and2, NULL); + FREE(args1and2); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' for host %s failed with rc %d", + __FUNCTION__, sd->subplugin, op, host, rc); + return S_RESETFAIL; + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + return S_OK; + } + +} + +static int +external_parse_config_info(struct pluginDevice* sd, StonithNVpair * info) +{ + char * key; + char * value; + StonithNVpair * nv; + + sd->cmd_opts = g_hash_table_new(g_str_hash, g_str_equal); + + /* TODO: Maybe treat "" as delimeters too so + * whitespace can be passed to the plugins... */ + for (nv = info; nv->s_name; nv++) { + if (!nv->s_name || !nv->s_value) { + continue; + } + + key = STRDUP(nv->s_name); + if (!key) { + goto err_mem; + } + value = STRDUP(nv->s_value); + if (!value) { + FREE(key); + goto err_mem; + } + g_hash_table_insert(sd->cmd_opts, key, value); + } + + return(S_OK); + +err_mem: + LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__); + external_unconfig(sd); + + return(S_OOPS); +} + +static gboolean +let_remove_eachitem(gpointer key, gpointer value, gpointer user_data) +{ + if (key) { + FREE(key); + } + if (value) { + FREE(value); + } + return TRUE; +} + +static void +external_unconfig(struct pluginDevice *sd) { + if (sd->cmd_opts) { + g_hash_table_foreach_remove(sd->cmd_opts, + let_remove_eachitem, NULL); + g_hash_table_destroy(sd->cmd_opts); + sd->cmd_opts = NULL; + } +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +external_set_config(StonithPlugin* s, StonithNVpair *list) +{ + struct pluginDevice * sd; + char ** p; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + /* make sure that command has not already been set */ + if (s->isconfigured) { + return(S_OOPS); + } + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + + if (sd->confignames == NULL) { + /* specified by name=value pairs, check required parms */ + if (external_get_confignames(s) == NULL) { + return(S_OOPS); + } + + for (p = sd->confignames; *p; p++) { + if (OurImports->GetValue(list, *p) == NULL) { + LOG(PIL_DEBUG, "Cannot get parameter %s from " + "StonithNVpair", *p); + } + } + } + + return external_parse_config_info(sd, list); +} + + +/* Only interested in regular files that are also executable */ +static int +exec_select(const struct dirent *dire) +{ + struct stat statf; + char filename[FILENAME_MAX]; + int rc; + + rc = snprintf(filename, FILENAME_MAX, "%s/%s", + STONITH_EXT_PLUGINDIR, dire->d_name); + if (rc <= 0 || rc >= FILENAME_MAX) { + return 0; + } + + if ((stat(filename, &statf) == 0) && + (S_ISREG(statf.st_mode)) && + (statf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))) { + if (statf.st_mode & (S_IWGRP|S_IWOTH)) { + LOG(PIL_WARN, "Executable file %s ignored " + "(writable by group/others)", filename); + return 0; + }else{ + return 1; + } + } + + return 0; +} + +/* + * Return STONITH config vars + */ +static const char * const * +external_get_confignames(StonithPlugin* p) +{ + struct pluginDevice * sd; + const char * op = "getconfignames"; + int i, rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + sd = (struct pluginDevice *)p; + + if (sd->subplugin != NULL) { + /* return list of subplugin's required parameters */ + char *output = NULL, *pch; + int namecount; + + rc = external_run_cmd(sd, op, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + return NULL; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_DEBUG, "plugin output: %s", output); + } + } + + namecount = get_num_tokens(output); + sd->confignames = (char **)MALLOC((namecount+1)*sizeof(char *)); + if (sd->confignames == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + if (output) { FREE(output); } + return NULL; + } + + /* now copy over confignames */ + pch = strtok(output, WHITESPACE); + for (i = 0; i < namecount; i++) { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s configname %s", + __FUNCTION__, sd->subplugin, pch); + } + sd->confignames[i] = STRDUP(pch); + pch = strtok(NULL, WHITESPACE); + } + FREE(output); + sd->confignames[namecount] = NULL; + }else{ + /* return list of subplugins in external directory */ + struct dirent ** files = NULL; + int dircount; + + /* get the external plugin's confignames (list of subplugins) */ + dircount = scandir(STONITH_EXT_PLUGINDIR, &files, + SCANSEL_CAST exec_select, NULL); + if (dircount < 0) { + return NULL; + } + + sd->confignames = (char **)MALLOC((dircount+1)*sizeof(char *)); + if (!sd->confignames) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return NULL; + } + + for (i = 0; i < dircount; i++) { + sd->confignames[i] = STRDUP(files[i]->d_name); + free(files[i]); + files[i] = NULL; + } + free(files); + sd->confignames[dircount] = NULL; + } + + return (const char * const *)sd->confignames; +} + +/* + * Return STONITH info string + */ +static const char * +external_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd; + char * output = NULL; + const char * op; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + sd = (struct pluginDevice *)s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(NULL); + } + + switch (reqtype) { + case ST_DEVICEID: + op = "getinfo-devid"; + break; + + case ST_DEVICENAME: + op = "getinfo-devname"; + break; + + case ST_DEVICEDESCR: + op = "getinfo-devdescr"; + break; + + case ST_DEVICEURL: + op = "getinfo-devurl"; + break; + + case ST_CONF_XML: + op = "getinfo-xml"; + break; + + default: + return NULL; + } + + rc = external_run_cmd(sd, op, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + if (sd->outputbuf != NULL) { + FREE(sd->outputbuf); + } + sd->outputbuf = output; + return(output); + } + return(NULL); +} + +/* + * EXTERNAL Stonith destructor... + */ +static void +external_destroy(StonithPlugin *s) +{ + struct pluginDevice * sd; + char ** p; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + sd = (struct pluginDevice *)s; + + sd->pluginid = NOTpluginID; + external_unconfig(sd); + if (sd->confignames != NULL) { + for (p = sd->confignames; *p; p++) { + FREE(*p); + } + FREE(sd->confignames); + sd->confignames = NULL; + } + if (sd->subplugin != NULL) { + FREE(sd->subplugin); + sd->subplugin = NULL; + } + if (sd->outputbuf != NULL) { + FREE(sd->outputbuf); + sd->outputbuf = NULL; + } + FREE(sd); +} + +/* Create a new external Stonith device */ +static StonithPlugin * +external_new(const char *subplugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + if (subplugin != NULL) { + sd->subplugin = STRDUP(subplugin); + if (sd->subplugin == NULL) { + FREE(sd); + return(NULL); + } + } + sd->sp.s_ops = &externalOps; + return &(sd->sp); +} + +static void +ext_add_to_env(gpointer key, gpointer value, gpointer user_data) +{ + if (setenv((char *)key, (char *)value, 1) != 0) { + LOG(PIL_CRIT, "%s: setenv failed.", __FUNCTION__); + } +} + +static void +ext_del_from_env(gpointer key, gpointer value, gpointer user_data) +{ + unsetenv((char *)key); +} + +#define LOGTAG_VAR "HA_LOGTAG" + +/* Run the command with op as command line argument(s) and return the exit + * status + the output */ +static int +external_run_cmd(struct pluginDevice *sd, const char *op, char **output) +{ + const int BUFF_LEN=4096; + char buff[BUFF_LEN]; + int read_len = 0; + int status, rc; + char * data = NULL; + FILE * file; + char cmd[FILENAME_MAX+64]; + struct stat buf; + int slen; + char *path, *new_path, *logtag, *savevar = NULL; + int new_path_len, logtag_len; + gboolean nodata; + + rc = snprintf(cmd, FILENAME_MAX, "%s/%s", + STONITH_EXT_PLUGINDIR, sd->subplugin); + if (rc <= 0 || rc >= FILENAME_MAX) { + LOG(PIL_CRIT, "%s: external command too long.", __FUNCTION__); + return -1; + } + + if (stat(cmd, &buf) != 0) { + LOG(PIL_CRIT, "%s: stat(2) of %s failed: %s", + __FUNCTION__, cmd, strerror(errno)); + return -1; + } + + if (!S_ISREG(buf.st_mode) + || (!(buf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) { + LOG(PIL_CRIT, "%s: %s found NOT to be executable.", + __FUNCTION__, cmd); + return -1; + } + + if (buf.st_mode & (S_IWGRP|S_IWOTH)) { + LOG(PIL_CRIT, "%s: %s found to be writable by group/others, " + "NOT executing for security purposes.", + __FUNCTION__, cmd); + return -1; + } + + strcat(cmd, " "); + strcat(cmd, op); + + /* We only have a global environment to use here. So we add our + * options to it, and then later remove them again. */ + if (sd->cmd_opts) { + g_hash_table_foreach(sd->cmd_opts, ext_add_to_env, NULL); + } + + /* external plugins need path to ha_log.sh */ + path = getenv("PATH"); + if (strncmp(GLUE_SHARED_DIR,path,strlen(GLUE_SHARED_DIR))) { + new_path_len = strlen(path)+strlen(GLUE_SHARED_DIR)+2; + new_path = (char *)g_malloc(new_path_len); + snprintf(new_path, new_path_len, "%s:%s", GLUE_SHARED_DIR, path); + setenv("PATH", new_path, 1); + g_free(new_path); + } + + /* set the logtag appropriately */ + logtag_len = strlen(PIL_PLUGIN_S)+strlen(sd->subplugin)+2; + logtag = (char *)g_malloc(logtag_len); + snprintf(logtag, logtag_len, "%s/%s", PIL_PLUGIN_S, sd->subplugin); + if (getenv(LOGTAG_VAR)) { + savevar = g_strdup(getenv(LOGTAG_VAR)); + } + setenv(LOGTAG_VAR, logtag, 1); + g_free(logtag); + + if (Debug) { + LOG(PIL_DEBUG, "%s: Calling '%s'", __FUNCTION__, cmd ); + } + file = popen(cmd, "r"); + if (NULL==file) { + LOG(PIL_CRIT, "%s: Calling '%s' failed", + __FUNCTION__, cmd); + rc = -1; + goto out; + } + + if (output) { + slen=0; + data = MALLOC(1); + data[slen] = EOS; + } + while (!feof(file)) { + nodata = TRUE; + if (output) { + read_len = fread(buff, 1, BUFF_LEN, file); + if (read_len > 0) { + data = REALLOC(data, slen+read_len+1); + if (data == NULL) { + break; + } + memcpy(data + slen, buff, read_len); + slen += read_len; + data[slen] = EOS; + nodata = FALSE; + } + } else { + if (fgets(buff, BUFF_LEN, file)) { + LOG(PIL_INFO, "%s: '%s' output: %s", __FUNCTION__, cmd, buff); + nodata = FALSE; + } + } + if (nodata) { + sleep(1); + } + } + if (output && !data) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + rc = -1; + goto out; + } + + status = pclose(file); + if (WIFEXITED(status)) { + rc = WEXITSTATUS(status); + if (rc != 0 && Debug) { + LOG(PIL_DEBUG, + "%s: Calling '%s' returned %d", __FUNCTION__, cmd, rc); + } + } else { + if (WIFSIGNALED(status)) { + LOG(PIL_CRIT, "%s: '%s' got signal %d", + __FUNCTION__, cmd, WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + LOG(PIL_INFO, "%s: '%s' stopped with signal %d", + __FUNCTION__, cmd, WSTOPSIG(status)); + } else { + LOG(PIL_CRIT, "%s: '%s' exited abnormally (core dumped?)", + __FUNCTION__, cmd); + } + rc = -1; + } + if (Debug && output && data) { + LOG(PIL_DEBUG, "%s: '%s' output: %s", __FUNCTION__, cmd, data); + } + +out: + if (savevar) { + setenv(LOGTAG_VAR, savevar, 1); + g_free(savevar); + } else { + unsetenv(LOGTAG_VAR); + } + if (sd->cmd_opts) { + g_hash_table_foreach(sd->cmd_opts, ext_del_from_env, NULL); + } + if (!rc) { + if (output) { + *output = data; + } + } else { + if (data) { + FREE(data); + } + if (output) { + *output = NULL; + } + } + return rc; +} diff --git a/lib/plugins/stonith/external/Makefile.am b/lib/plugins/stonith/external/Makefile.am new file mode 100644 index 0000000..42e0046 --- /dev/null +++ b/lib/plugins/stonith/external/Makefile.am @@ -0,0 +1,33 @@ +# Makefile.am for OCF RAs +# +# Author: Sun Jing Dong +# Copyright (C) 2004 IBM +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +EXTRA_DIST = drac5 dracmc-telnet ibmrsa-telnet ipmi rackpdu vmware vcenter xen0 \ + xen0-ha-dom0-stonith-helper kdumpcheck nut + +extdir = $(stonith_ext_plugindir) + +helperdir = $(stonith_plugindir) + +ext_SCRIPTS = drac5 dracmc-telnet ibmrsa ibmrsa-telnet ipmi riloe ssh vmware vcenter rackpdu xen0 hmchttp \ + xen0-ha kdumpcheck ippower9258 nut libvirt \ + hetzner + +helper_SCRIPTS = xen0-ha-dom0-stonith-helper diff --git a/lib/plugins/stonith/external/drac5.in b/lib/plugins/stonith/external/drac5.in new file mode 100644 index 0000000..218cbd3 --- /dev/null +++ b/lib/plugins/stonith/external/drac5.in @@ -0,0 +1,113 @@ +#!/bin/sh +# +# External STONITH module for DRAC5 adapters. +# +# Author: Jun Wang +# License: GNU General Public License (GPL) +# + +trap 'if [ -n "$outf" ]; then ha_log.sh err "`cat $outf`"; rm -f "$outf"; fi' 0 +outf=`mktemp` || { + ha_log.sh err "mktemp failed" + exit 1 +} + +sshlogin() { + if [ x = "x$ipaddr" -o x = "x$userid" ] + then + ha_log.sh err "ipaddr or userid missing; check configuration" + return 1 + fi + @SSH@ -q -x -n $userid@$ipaddr racadm serveraction "$1" >$outf 2>&1 +} + +drac_reset() { + sshlogin hardreset +} + +drac_on() { + sshlogin poweron +} + +drac_off() { + sshlogin poweroff +} + +drac_status() { + sshlogin powerstatus +} + +case $1 in +gethosts) + echo $hostname + ;; +on) + drac_poweron + ;; +off) + drac_poweroff + ;; +reset) + drac_reset + ;; +status) + drac_status + ;; +getconfignames) + for i in hostname ipaddr userid; do + echo $i + done + ;; +getinfo-devid) + echo "DRAC5 STONITH device" + ;; +getinfo-devname) + echo "DRAC5 STONITH device" + ;; +getinfo-devdescr) + echo "DRAC5 host reset/poweron/poweroff" + ;; +getinfo-devurl) + echo "http://www.dell.com" + ;; +getinfo-xml) + cat <<EOF +<parameters> + +<parameter name="hostname" unique="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostname +</shortdesc> +<longdesc lang="en"> +The hostname of the host to be managed by this STONITH device +</longdesc> +</parameter> + +<parameter name="ipaddr" unique="1"> +<content type="string" /> +<shortdesc lang="en"> +IP Address +</shortdesc> +<longdesc lang="en"> +The IP address of the STONITH device +</longdesc> +</parameter> + +<parameter name="userid" unique="1"> +<content type="string" /> +<shortdesc lang="en"> +Login +</shortdesc> +<longdesc lang="en"> +The username used for logging in to the STONITH device +</longdesc> +</parameter> + +</parameters> +EOF + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/dracmc-telnet b/lib/plugins/stonith/external/dracmc-telnet new file mode 100644 index 0000000..d993961 --- /dev/null +++ b/lib/plugins/stonith/external/dracmc-telnet @@ -0,0 +1,377 @@ +#!/usr/bin/env python +# vim: set filetype=python +####################################################################### +# +# dracmc-telnet - External stonith plugin for HAv2 (http://linux-ha.org/wiki) +# Connects to Dell Drac/MC Blade Enclosure via a Cyclades +# terminal server with telnet and switches power of named +# blade servers appropriatelly. +# +# Required parameters: +# nodename: The name of the server you want to touch on your network +# cyclades_ip: The IP address of the cyclades terminal server +# cyclades_port: The port for telnet to access on the cyclades (i.e. 7032) +# servername: The DRAC/MC server name of the blade (i.e. Server-7) +# username: The login user name for the DRAC/MC +# password: The login password for the DRAC/MC +# +# Author: Alex Tsariounov <alext@novell.com> +# +# Based on ibmrsa-telnet external stonith plugin by Andreas Mock +# (andreas.mock@web.de), Copyright by Adreas Mock and released as part +# of HAv2. +# +# History: +# 2009-10-12 First release. +# +# Copyright (c) 2009 Novell, Inc. +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 or later of the GNU General Public +# License as published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# +####################################################################### +import sys +import os +import time +import telnetlib +import random +import subprocess + +LOGINRETRIES = 10 + +class TimeoutException(Exception): + def __init__(self, value=None): + Exception.__init__(self) + self.value = value + + def __str__(self): + return repr(self.value) + +class DracMC(telnetlib.Telnet): + def __init__(self, *args, **kwargs): + telnetlib.Telnet.__init__(self) + self._timeout = 4 + self._loggedin = 0 + self._history = [] + self._appl = os.path.basename(sys.argv[0]) + self._server = args[0] + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def write(self, buffer): + self._history.append(self._get_timestamp() + ': WRITE: ' + repr(buffer)) + telnetlib.Telnet.write(self, buffer) + + def read_until(self, what, timeout=2): + line = telnetlib.Telnet.read_until(self, what, timeout) + self._history.append(self._get_timestamp() + ': READ : ' + repr(line)) + if not line.endswith(what): + raise TimeoutException("Timeout while waiting for '%s'." % (what, )) + return line + + def login(self, user, passwd): + time.sleep(0.3) + try: + line = self.read_until('Login: ', self._timeout) + self.write(user) + self.write('\r') + line = self.read_until('Password: ', self._timeout) + self.write(passwd) + self.write('\r') + except: + self.write("\r") + line = self.read_until('Login: ', self._timeout) + self.write(user) + self.write('\r') + line = self.read_until('Password: ', self._timeout) + self.write(passwd) + self.write('\r') + try: + line = self.read_until('DRAC/MC:', self._timeout) + except: + self.write("\r") + line = self.read_until('DRAC/MC:', self._timeout) + + def hardreset(self): + self.write('serveraction -s %s hardreset\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def powercycle(self): + self.write('serveraction -s %s powercycle\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def on(self): + self.write('serveraction -s %s powerup\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def off(self): + self.write('serveraction -s %s powerdown\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def exit(self): + self.write('exit\r') + + def get_history(self): + return "\n".join(self._history) + + +class DracMCStonithPlugin: + def __init__(self): + # define the external stonith plugin api + self._required_cmds = \ + 'reset gethosts status getconfignames getinfo-devid ' \ + 'getinfo-devname getinfo-devdescr getinfo-devurl ' \ + 'getinfo-xml' + self._optional_cmds = 'on off' + self._required_cmds_list = self._required_cmds.split() + self._optional_cmds_list = self._optional_cmds.split() + + # who am i + self._appl = os.path.basename(sys.argv[0]) + + # telnet connection object + self._connection = None + + # the list of configuration names + self._confignames = ['nodename', 'cyclades_ip', 'cyclades_port', + 'servername', 'username', 'password'] + + # catch the parameters provided by environment + self._parameters = {} + for name in self._confignames: + try: + self._parameters[name] = os.environ.get(name, '').split()[0] + except IndexError: + self._parameters[name] = '' + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def _echo_debug(self, *args): + subprocess.call("ha_log.sh debug '%s'" % ' '.join(args), shell=True) + + def echo(self, *args): + what = ''.join([str(x) for x in args]) + sys.stdout.write(what) + sys.stdout.write('\n') + sys.stdout.flush() + self._echo_debug("STDOUT:", what) + + def echo_log(self, level, *args): + subprocess.call("ha_log.sh %s '%s'" % (level,' '.join(args)), shell=True) + + def _get_connection(self): + if not self._connection: + c = DracMC(self._parameters['servername']) + self._echo_debug("Connecting to '%s:%s'" % + (self._parameters['cyclades_ip'], + self._parameters['cyclades_port'])) + tries = 0 + while tries < LOGINRETRIES: + try: + c.open(self._parameters['cyclades_ip'], + self._parameters['cyclades_port']) + c.login(self._parameters['username'], + self._parameters['password']) + except Exception, args: + if "Connection reset by peer" in str(args): + self._echo_debug("Someone is already logged in... retry=%s" % tries) + c.close() + time.sleep(random.uniform(1.0, 5.0)) + else: + raise + else: + break + tries += 1 + + if tries == LOGINRETRIES: + c.close() + raise Exception("Could not log in to %s:%s" % + (self._parameters['cyclades_ip'], + self._parameters['cyclades_port'])) + self._connection = c + + def _end_connection(self): + if self._connection: + self._connection.exit() + self._connection.close() + + def reset(self): + self._get_connection() + # self._connection.hardreset() + self._connection.powercycle() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Reset of node '%s' done" % + (self._parameters['nodename'],)) + return(0) + + def on(self): + self._get_connection() + self._connection.on() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' ON" % + (self._parameters['nodename'],)) + return(0) + + def off(self): + self._get_connection() + self._connection.off() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' OFF" % + (self._parameters['nodename'],)) + return(0) + + def gethosts(self): + self.echo(self._parameters['nodename']) + return(0) + + def status(self): + self._get_connection() + self._end_connection() + self._echo_debug(self._connection.get_history()) + return(0) + + def getconfignames(self): + for name in ['nodename', 'cyclades_ip', 'cyclades_port', 'servername', + 'username', 'password']: + self.echo(name) + return(0) + + def getinfo_devid(self): + self.echo("External Stonith Plugin for Dell DRAC/MC via Cyclades") + return(0) + + def getinfo_devname(self): + self.echo("External Stonith Plugin for Dell Drac/MC connecting " + "via Telnet to a Cyclades port") + return(0) + + def getinfo_devdescr(self): + self.echo("External stonith plugin for HAv2 which connects to " + "a Dell DRAC/MC connected via a Cyclades port with telnet. " + "Commands to turn on/off power and to reset server are sent " + "appropriately. " + "(c) 2009 by Novell, Inc. (alext@novell.com)") + return(0) + + def getinfo_devurl(self): + self.echo("http://support.dell.com/support/edocs/software/smdrac3/dracmc/1.3/en/index.htm") + + def getinfo_xml(self): + info = """<parameters> + <parameter name="nodename" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">nodename to shoot</shortdesc> + <longdesc lang="en"> + Name of the node to be stonithed. + </longdesc> + </parameter> + <parameter name="cyclades_ip" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">hostname or ip address of cyclades</shortdesc> + <longdesc lang="en"> + Hostname or IP address of Cyclades connected to DRAC/MC. + </longdesc> + </parameter> + <parameter name="cyclades_port" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">telnet port to use on cyclades</shortdesc> + <longdesc lang="en"> + Port used with the Cyclades telnet interface which is connected to the DRAC/MC. + </longdesc> + </parameter> + <parameter name="servername" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">DRAC/MC name of blade to be stonithed</shortdesc> + <longdesc lang="en"> + Name of server blade to be stonithed on the DRAC/MC (example: Server-7) + </longdesc> + </parameter> + <parameter name="username" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">username to login on the DRAC/MC</shortdesc> + <longdesc lang="en"> + Username to login to the DRAC/MC once connected via the Cyclades port. + </longdesc> + </parameter> + <parameter name="password" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">password to login on the DRAC/MC</shortdesc> + <longdesc lang="en"> + Password to login to the DRAC/MC once connected via the Cyclades port. + </longdesc> + </parameter> + </parameters> + """ + self.echo(info) + return(0) + + def not_implemented(self, cmd): + self.echo_log("err", "Command '%s' not implemented." % (cmd,)) + return(1) + + def usage(self): + usage = "Call me with one of the allowed commands: %s, %s" % ( + ', '.join(self._required_cmds_list), + ', '.join(self._optional_cmds_list)) + return usage + + def process(self, argv): + self._echo_debug("========== Start =============") + if len(argv) < 1: + self.echo_log("err", 'At least one commandline argument required.') + return(1) + cmd = argv[0] + self._echo_debug("cmd:", cmd) + if cmd not in self._required_cmds_list and \ + cmd not in self._optional_cmds_list: + self.echo_log("err", "Command '%s' not supported." % (cmd,)) + return(1) + try: + cmd = cmd.lower().replace('-', '_') + func = getattr(self, cmd, self.not_implemented) + rc = func() + return(rc) + except Exception, args: + self.echo_log("err", 'Exception raised:', str(args)) + if self._connection: + self.echo_log("err", self._connection.get_history()) + self._connection.close() + return(1) + + +if __name__ == '__main__': + stonith = DracMCStonithPlugin() + rc = stonith.process(sys.argv[1:]) + sys.exit(rc) diff --git a/lib/plugins/stonith/external/hetzner b/lib/plugins/stonith/external/hetzner new file mode 100755 index 0000000..2b3e675 --- /dev/null +++ b/lib/plugins/stonith/external/hetzner @@ -0,0 +1,139 @@ +#!/bin/sh +# +# External STONITH module for Hetzner. +# +# Copyright (c) 2011 MMUL S.a.S. - Raoul Scarazzini <rasca@mmul.it> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +# Read parameters from config file, format is based upon the hetzner OCF resource agent +# developed by Kumina: http://blog.kumina.nl/2011/02/hetzner-failover-ip-ocf-script/ +conf_file="/etc/hetzner.cfg" + +case $1 in + get*) ;; # don't print errors if conf_file not present + *) + user=`sed -n 's/^user.*=\ *//p' $conf_file` + pass=`sed -n 's/^pass.*=\ *//p' $conf_file` + ;; +esac + +hetzner_server="https://robot-ws.your-server.de" + +check_http_response() { + # If the response is 200 then return 0 + if [ $1 = 200 ] + then + return 0 + else + # If the response is not 200 then display a description of the problem and return 1 + case $1 in + 400) ha_log.sh err "INVALID_INPUT - Invalid input parameters" + ;; + 404) ha_log.sh err "SERVER_NOT_FOUND - Server with ip $remote_ip not found" + ;; + 409) ha_log.sh err "RESET_MANUAL_ACTIVE - There is already a running manual reset" + ;; + 500) ha_log.sh err "RESET_FAILED - Resetting failed due to an internal error" + ;; + esac + return 1 + fi +} + +case $1 in +gethosts) + echo $hostname + exit 0 + ;; +on) + # Can't really be implemented because Hetzner's webservice cannot power on a system + ha_log.sh err "Power on is not available since Hetzner's webservice can't do this operation." + exit 1 + ;; +off) + # Can't really be implemented because Hetzner's webservice cannot power on a system + ha_log.sh err "Power off is not available since Hetzner's webservice can't do this operation." + exit 1 + ;; +reset) + # Launching the reset action via webservice + check_http_response $(curl --silent -o /dev/null -w '%{http_code}' -u $user:$pass $hetzner_server/reset/$remote_ip -d type=hw) + exit $? + ;; +status) + # Check if we can contact the webservice + check_http_response "$(curl --silent -o /dev/null -w '%{http_code}' -u $user:$pass $hetzner_server/server/$remote_ip)" + exit $? + ;; +getconfignames) + echo "hostname" + echo "remote_ip" + exit 0 + ;; +getinfo-devid) + echo "Hetzner STONITH device" + exit 0 + ;; +getinfo-devname) + echo "Hetzner STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "Hetzner host reset" + echo "Manages the remote webservice for reset a remote server." + exit 0 + ;; +getinfo-devurl) + echo "http://wiki.hetzner.de/index.php/Robot_Webservice_en" + exit 0 + ;; +getinfo-xml) + cat << HETZNERXML +<parameters> +<parameter name="hostname" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostname +</shortdesc> +<longdesc lang="en"> +The name of the host to be managed by this STONITH device. +</longdesc> +</parameter> + +<parameter name="remote_ip" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Remote IP +</shortdesc> +<longdesc lang="en"> +The address of the remote IP that manages this server. +</longdesc> +</parameter> +</parameters> +HETZNERXML + exit 0 + ;; +*) + ha_log.sh err "Don't know what to do for '$remote_ip'" + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/hmchttp b/lib/plugins/stonith/external/hmchttp new file mode 100644 index 0000000..9d111bc --- /dev/null +++ b/lib/plugins/stonith/external/hmchttp @@ -0,0 +1,218 @@ +#!/bin/sh +# External STONITH module for HMC web console +# +# Copyright (c) 2007 Xinwei Hu <hxinwei@gmail.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +#set -x +hostlist=`echo $hostlist | tr ',' ' '` + +trap '[ ! -e "$COOKIEFILE" ] || rm -f "$COOKIEFILE"' 0 +COOKIEFILE=`mktemp` || exit 1 + +: ${CURLBIN="/usr/bin/curl"} +: ${user=admin} +: ${password=admin} + +check_parameter() { + if [ ! -x $CURLBIN ] + then + ha_log.sh err "Curl can't be found in normal place. Set CURLBIN to override the default value" + exit 1 + fi + + if [ -z $hmc_ipaddr ] + then + ha_log.sh err "The address of HMC web console is not specified" + exit 1 + fi +} + +HMCUSERNAME=$user +HMCPASSWORD=$password + +HMC_LOGIN_COMMAND="$CURLBIN -3 -k -c $COOKIEFILE -d user=$HMCUSERNAME -d password=$HMCPASSWORD -d lang=0 -d submit=Log+in " +HMC_LOGOUT_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d submit=Log+out " +HMC_TOC_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=2 " +HMC_POWERON_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=60 -d sp=255 -d is=0 -d om=4 -d id=1 -d ip=2 -d plt=1 -d pm=0 -d on=Save+settings+and+power+on " +HMC_POWERSTATE_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=60 " +HMC_POWEROFF_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=30 -d submit=Continue " +HMC_REBOOT_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=74 -d submit=Continue " + +hmc_login() { + iamin=0 + while [ $iamin -eq 0 ]; do + $HMC_LOGIN_COMMAND https://$hmc_ipaddr/cgi-bin/cgi >/dev/null 2>&1 + $HMC_TOC_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null | grep -q "Too many users" + iamin=$? + sleep 2 + done +} +hmc_logout() { + $HMC_LOGOUT_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null +} + +hmc_reboot() { + check_parameter + $HMC_REBOOT_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null +} +hmc_poweron() { + r=1 + while [ 0 -ne $r ]; do + $HMC_POWERON_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null | grep -q "Operation completed successfully" + r=$? + done +} +hmc_poweroff() { + check_parameter + $HMC_POWEROFF_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null +} +hmc_powerstate() { + check_parameter + r=`$HMC_POWERSTATE_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null| grep "Current system power state:" | sed 's/<br>//g' | awk '{print $5}'` + echo $r +} + +hmc_poweroffon() { + check_parameter + hmc_poweroff + while [ 1 ]; do + r=`hmc_powerstate` + ha_log.sh debug "power state: $r" + if [ $r = "Off" ]; then + break + fi + sleep 5 + done + sleep 3 + hmc_poweron +} + +case $1 in +gethosts) + for h in $hostlist; do + echo $h + done + exit 0 + ;; +status) + if + ping -w1 -c1 "$hmc_ipaddr" 2>&1 + then + exit 0 + fi + exit 1 + ;; +getconfignames) + for f in hostlist hmc_ipaddr user password; do + echo $f + done + exit 0 + ;; +getinfo-devid) + echo "HMC web console STONITH device" + exit 0 + ;; +getinfo-devname) + echo "HMC web console STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "HMC web console based host power control" + echo "Use for i5, p5, pSeries and OpenPower systems that are managed via " + echo "web console through a direct connection to system's HMC port." + exit 0 + ;; +getinfo-devurl) + echo "http://www.ibm.com" + exit 0 + ;; +getinfo-xml) + cat << HMCXML +<parameters> + +<parameter name="hostlist" unique="1" required="1"> +<content type="string"/> +<shortdesc lang="en">Hostlist</shortdesc> +<longdesc lang="en"> +The list of hosts that the STONITH device controls +</longdesc> +</parameter> + +<parameter name="hmc_ipaddr" unique="1" required="1"> +<content type="string"/> +<shortdesc lang="en">HMC IPAddr</shortdesc> +<longdesc lang="en"> +The IP address of the HMC web console +</longdesc> +</parameter> + +<parameter name="user" unique="1" required="1"> +<content type="string"/> +<shortdesc lang="en">User</shortdesc> +<longdesc lang="en"> +User name to log into HMC web console +</longdesc> +</parameter> + +<parameter name="password" unique="1" required="1"> +<content type="string"/> +<shortdesc lang="en">Password</shortdesc> +<longdesc lang="en"> +The password of user name to log into HMC web console +</longdesc> +</parameter> + +</parameters> +HMCXML + exit 0 + ;; +esac + +case $1 in +on|off|reset|powerstate|poweroffon) + hmc_login + case $1 in + on) + hmc_poweron $hmc_ipaddr + ;; + off) + hmc_poweroff $hmc_ipaddr + ;; + reset) +# hmc_reboot $hmc_ipaddr + hmc_poweroffon $hmc_ipaddr + ;; + powerstate) + hmc_powerstate + ;; + poweroffon) + hmc_poweroffon + ;; + esac + hmc_logout + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/ibmrsa b/lib/plugins/stonith/external/ibmrsa new file mode 100644 index 0000000..7408465 --- /dev/null +++ b/lib/plugins/stonith/external/ibmrsa @@ -0,0 +1,157 @@ +#!/bin/sh +# +# Copyright (c) 2006 Dejan Muhamedagic <dmuhamedagic@at.ibm.com>, IBM Austria +# +# External STONITH module for IBM RSA adapters. +# External STONITH module for IBM BMC. +# This STONITH module depends on IBMmpcli. +# + +trap 'rm -f "$outf"' 0 +outf=`mktemp` || { + ha_log.sh err 'mktemp failed' + exit 1 +} + +chkmpcli() { + test -x /opt/IBMmpcli/bin/MPCLI.sh +} +mpcli() { + chkmpcli || { + ha_log.sh err "IBM mpcli not installed" + return 1 + } + if [ x = "x$ipaddr" -o x = "x$userid" -o x = "x$passwd" ] + then + ha_log.sh err "ipaddr, userid, or passwd missing; check configuration" + return 1 + fi + type=${type:-"ibm"} + + goodstg="SUCCESS" + failstg="FAILURE" + ( + echo "logonip -h $ipaddr -u $userid -p $passwd -t $type" + echo "outputfile $outf" + cat + ) | /opt/IBMmpcli/bin/MPCLI.sh | grep -w $goodstg >/dev/null 2>&1 + rc=$? + grep -w $failstg $outf >/dev/null + if [ $rc -eq 0 -a $? -eq 1 ]; then + return 0 + else + ha_log.sh err "MPCLI.sh failed: `cat $outf`" + return 1 + fi +} +ibmrsa_reboot() { + echo restart -now | mpcli +} +ibmrsa_poweron() { + echo poweron | mpcli +} +ibmrsa_poweroff() { + echo poweroff | mpcli +} +ibmrsa_status() { + echo | mpcli +} + +hostname=`echo ${hostname} | tr ',' ' '` + +case $1 in +gethosts) + echo $hostname + ;; +on) + ibmrsa_poweron + ;; +off) + ibmrsa_poweroff + ;; +reset) + ibmrsa_reboot + ;; +status) + ibmrsa_status + ;; +getconfignames) + for i in hostname ipaddr userid passwd type; do + echo $i + done + ;; +getinfo-devid) + echo "IBM MP STONITH device" + ;; +getinfo-devname) + echo "IBM MP STONITH device" + ;; +getinfo-devdescr) + echo "IBM MP host reboot/poweron/poweroff" + ;; +getinfo-devurl) + echo "http://www.ibm.com" + ;; +getinfo-xml) + cat <<EOF +<parameters> + +<parameter name="hostname" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostname +</shortdesc> +<longdesc lang="en"> +The hostname of the host to be managed by this STONITH device +</longdesc> +</parameter> + +<parameter name="ipaddr" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +IP Address +</shortdesc> +<longdesc lang="en"> +The IP address of the STONITH device +</longdesc> +</parameter> + +<parameter name="userid" unique="0" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Login +</shortdesc> +<longdesc lang="en"> +The username used to login into the STONITH device +</longdesc> +</parameter> + +<parameter name="passwd" unique="0" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Password +</shortdesc> +<longdesc lang="en"> +The password used to login into the STONITH device +</longdesc> +</parameter> + +<parameter name="type" unique="0" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Management processor type +</shortdesc> +<longdesc lang="en"> +The type of the management processor. Possible values are +"ibm" (default, typically used for RSA) and "ipmi" +(for IPMI compliant processors such as BMC). +</longdesc> +</parameter> + +</parameters> +EOF + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/ibmrsa-telnet b/lib/plugins/stonith/external/ibmrsa-telnet new file mode 100644 index 0000000..4d75d9a --- /dev/null +++ b/lib/plugins/stonith/external/ibmrsa-telnet @@ -0,0 +1,320 @@ +#!/usr/bin/python +# vim: set filetype=python +####################################################################### +# +# ibmrsa-telnet - External stonith plugin for HAv2 (http://linux-ha.org/wiki) +# Connects to IBM RSA Board via telnet and switches power +# of server appropriately. +# +# Author: Andreas Mock (andreas.mock@web.de) +# +# History: +# 2007-10-19 Fixed bad commandline handling in case of stonithing +# 2007-10-11 First release. +# +# Comment: Please send bug fixes and enhancements. +# I hope the functionality of communicating via telnet is encapsulated +# enough so that someone can use it for similar purposes. +# +# Description: IBM offers Remote Supervisor Adapters II for several +# servers. These RSA boards can be accessed in different ways. +# One of that is via telnet. Once logged in you can use 'help' to +# show all available commands. With 'power' you can reset, power on and +# off the controlled server. This command is used in combination +# with python's standard library 'telnetlib' to do it automatically. +# +# cib-snippet: Please see README.ibmrsa-telnet for examples. +# +# Copyright (c) 2007 Andreas Mock (andreas.mock@web.de) +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 or later of the GNU General Public +# License as published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# +####################################################################### +import sys +import os +import time +import telnetlib +import subprocess + +class TimeoutException(Exception): + def __init__(self, value=None): + Exception.__init__(self) + self.value = value + + def __str__(self): + return repr(self.value) + +class RSABoard(telnetlib.Telnet): + def __init__(self, *args, **kwargs): + telnetlib.Telnet.__init__(self, *args, **kwargs) + self._timeout = 10 + self._loggedin = 0 + self._history = [] + self._appl = os.path.basename(sys.argv[0]) + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def write(self, buffer, nolog = False): + self._history.append(self._get_timestamp() + ': WRITE: ' + + (nolog and '******' or repr(buffer))) + telnetlib.Telnet.write(self, buffer) + + def expect(self, what, timeout=20): + line = telnetlib.Telnet.expect(self, what, timeout) + self._history.append(self._get_timestamp() + ': READ : ' + repr(line)) + if not line: + raise TimeoutException("Timeout while waiting for '%s'." % (what, )) + return line + + def login(self, user, passwd): + time.sleep(1) + line = self.expect(['\nlogin : ', '\nusername: '], self._timeout) + self.write(user) + self.write('\r') + line = self.expect(['\nPassword: ', '\npassword: '], self._timeout) + self.write(passwd, nolog = True) + self.write('\r') + line = self.expect(['\nsystem>', '> '], self._timeout) + + def reset(self): + self.write('power cycle\r') + line = self.expect(['\nok'], self._timeout) + line = self.expect(['\nsystem>', '> '], self._timeout) + + def on(self): + self.write('power on\r') + line = self.expect(['\nok'], self._timeout) + line = self.expect(['\nsystem>', '> '], self._timeout) + + def off(self): + self.write('power off\r') + line = self.expect(['\nok'], self._timeout) + line = self.expect(['\nsystem>', '> '], self._timeout) + + def exit(self): + self.write('exit\r') + + def get_history(self): + return "\n".join(self._history) + + +class RSAStonithPlugin: + def __init__(self): + # define the external stonith plugin api + self._required_cmds = \ + 'reset gethosts status getconfignames getinfo-devid ' \ + 'getinfo-devname getinfo-devdescr getinfo-devurl ' \ + 'getinfo-xml' + self._optional_cmds = 'on off' + self._required_cmds_list = self._required_cmds.split() + self._optional_cmds_list = self._optional_cmds.split() + + # who am i + self._appl = os.path.basename(sys.argv[0]) + + # telnet connection object + self._connection = None + + # the list of configuration names + self._confignames = ['nodename', 'ip_address', 'username', 'password'] + + # catch the parameters provided by environment + self._parameters = {} + for name in self._confignames: + try: + self._parameters[name] = os.environ.get(name, '').split()[0] + except IndexError: + self._parameters[name] = '' + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def _echo_debug(self, *args): + self.echo_log('debug', *args) + + def echo(self, *args): + what = ''.join([str(x) for x in args]) + sys.stdout.write(what) + sys.stdout.write('\n') + sys.stdout.flush() + self._echo_debug("STDOUT:", what) + + def echo_log(self, level, *args): + subprocess.call(('ha_log.sh', level) + args) + + def _get_connection(self): + if not self._connection: + c = RSABoard() + self._echo_debug("Connect to '%s'" % + (self._parameters['ip_address'],)) + c.open(self._parameters['ip_address']) + self._echo_debug("Connection established") + c.login(self._parameters['username'], + self._parameters['password']) + self._connection = c + + def _end_connection(self): + if self._connection: + self._connection.exit() + self._connection.close() + + def reset(self): + self._get_connection() + self._connection.reset() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Reset of node '%s' done" % + (self._parameters['nodename'],)) + return(0) + + def on(self): + self._get_connection() + self._connection.on() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' ON" % + (self._parameters['nodename'],)) + return(0) + + def off(self): + self._get_connection() + self._connection.off() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' OFF" % + (self._parameters['nodename'],)) + return(0) + + def gethosts(self): + self.echo(self._parameters['nodename']) + return(0) + + def status(self): + self._get_connection() + self._end_connection() + self._echo_debug(self._connection.get_history()) + return(0) + + def getconfignames(self): + for name in ['nodename', 'ip_address', 'username', 'password']: + self.echo(name) + return(0) + + def getinfo_devid(self): + self.echo("External Stonith Plugin for IBM RSA Boards") + return(0) + + def getinfo_devname(self): + self.echo("External Stonith Plugin for IBM RSA Boards connecting " + "via Telnet") + return(0) + + def getinfo_devdescr(self): + self.echo("External stonith plugin for HAv2 which connects to " + "a RSA board on IBM servers via telnet. Commands to " + "turn on/off power and to reset server are sent " + "appropriately. " + "(c) 2007 by Andreas Mock (andreas.mock@web.de)") + return(0) + + def getinfo_devurl(self): + self.echo("http://www.ibm.com/Search/?q=remote+supervisor+adapter") + + def getinfo_xml(self): + info = """<parameters> + <parameter name="nodename" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">nodename to shoot</shortdesc> + <longdesc lang="en"> + Name of the node which has to be stonithed in case. + </longdesc> + </parameter> + <parameter name="ip_address" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">hostname or ip address of RSA</shortdesc> + <longdesc lang="en"> + Hostname or ip address of RSA board used to reset node. + </longdesc> + </parameter> + <parameter name="username" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">username to login on RSA board</shortdesc> + <longdesc lang="en"> + Username to login on RSA board. + </longdesc> + </parameter> + <parameter name="password" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">password to login on RSA board</shortdesc> + <longdesc lang="en"> + Password to login on RSA board. + </longdesc> + </parameter> + </parameters> + """ + self.echo(info) + return(0) + + def not_implemented(self, cmd): + self.echo_log("err", "Command '%s' not implemented." % (cmd,)) + return(1) + + def usage(self): + usage = "Call me with one of the allowed commands: %s, %s" % ( + ', '.join(self._required_cmds_list), + ', '.join(self._optional_cmds_list)) + return usage + + def process(self, argv): + self._echo_debug("========== Start =============") + if len(argv) < 1: + self.echo_log("err", 'At least one commandline argument required.') + return(1) + cmd = argv[0] + self._echo_debug("cmd:", cmd) + if cmd not in self._required_cmds_list and \ + cmd not in self._optional_cmds_list: + self.echo_log("err", "Command '%s' not supported." % (cmd,)) + return(1) + try: + cmd = cmd.lower().replace('-', '_') + func = getattr(self, cmd, self.not_implemented) + rc = func() + return(rc) + except Exception, args: + self.echo_log("err", 'Exception raised:', str(args)) + if self._connection: + self.echo_log("err", self._connection.get_history()) + self._connection.close() + return(1) + + +if __name__ == '__main__': + stonith = RSAStonithPlugin() + rc = stonith.process(sys.argv[1:]) + sys.exit(rc) diff --git a/lib/plugins/stonith/external/ipmi b/lib/plugins/stonith/external/ipmi new file mode 100644 index 0000000..abadd5a --- /dev/null +++ b/lib/plugins/stonith/external/ipmi @@ -0,0 +1,276 @@ +#!/bin/sh +# +# External STONITH module using IPMI. +# This modules uses uses the ipmitool program available from +# http://ipmitool.sf.net/ for actual communication with the +# managed device. +# +# Copyright (c) 2007 Martin Bene <martin.bene@icomedias.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +# Initialization -- fix locale settings so we can parse output from +# binaries if we need it +LANG=C +LC_ALL=C + +RESET="power reset" +POWEROFF="power off" +POWERON="power on" +STATUS="power status" +IPMITOOL=${ipmitool:-"`which ipmitool 2>/dev/null`"} + +have_ipmi() { + test -x "${IPMITOOL}" +} + +# Wrapper function for ipmitool that sets the correct host IP address, +# username, and password, and invokes ipmitool with any arguments +# passed in +run_ipmitool() { + local ipmitool_opts privlvl="" + have_ipmi || { + ha_log.sh err "ipmitool not installed" + return 1 + } + if [ -z "${ipaddr}" -o -z "${userid}" -o -z "${passwd}" ]; then + ha_log.sh err "ipaddr, userid or passwd missing; check configuration" + return 1 + fi + + if [ -z "${interface}" ]; then + # default to "lan" interface + interface="lan" + fi + if [ -n "${priv}" ]; then + # default to "lan" interface + privlvl="-L $priv" + fi + + ipmitool_opts="-I ${interface} -H ${ipaddr} $privlvl" + + case "${passwd_method}" in + param|'') + passwd_method=param + M="-P" + ;; + env) + M="-E" + ;; + file) + M="-f" + ;; + *) + ha_log.sh err "invalid passwd_method: \"${passwd_method}\"" + return 1 + esac + + action="$*" + + if [ $passwd_method = env ] + then + IPMI_PASSWORD="${passwd}" ${IPMITOOL} $ipmitool_opts -U "${userid}" -E ${action} + else + ${IPMITOOL} $ipmitool_opts -U "${userid}" $M "${passwd}" ${action} + fi 2>&1 +} + +# Yet another convenience wrapper that invokes run_ipmitool, captures +# its output, logs the output, returns either 0 (on success) or 1 (on +# any error) +do_ipmi() { + if outp=`run_ipmitool $*`; then + ha_log.sh debug "ipmitool output: `echo $outp`" + return 0 + else + ha_log.sh err "error executing ipmitool: `echo $outp`" + return 1 + fi +} + +# Check if the managed node is powered on. To do so, issue the "power +# status" command. Should return either "Chassis Power is on" or +# "Chassis Power is off". +ipmi_is_power_on() { + local outp + outp=`run_ipmitool ${STATUS}` + case "${outp}" in + *on) + return 0 + ;; + *off) + return 1 + ;; + esac +} + + +case ${1} in +gethosts) + echo $hostname + exit 0 + ;; +on) + do_ipmi "${POWERON}" + exit + ;; +off) + do_ipmi "${POWEROFF}" + exit + ;; +reset) + if ipmi_is_power_on; then + do_ipmi "${RESET}" + else + do_ipmi "${POWERON}" + fi + exit + ;; +status) + # "status" reflects the status of the stonith _device_, not + # the managed node. Hence, only check if we can contact the + # IPMI device with "power status" command, don't pay attention + # to whether the node is in fact powered on or off. + do_ipmi "${STATUS}" + exit $? + ;; +getconfignames) + for i in hostname ipaddr userid passwd interface; do + echo $i + done + exit 0 + ;; +getinfo-devid) + echo "IPMI STONITH device" + exit 0 + ;; +getinfo-devname) + echo "IPMI STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ipmitool based power management. Apparently, the power off" + echo "method of ipmitool is intercepted by ACPI which then makes" + echo "a regular shutdown. If case of a split brain on a two-node" + echo "it may happen that no node survives. For two-node clusters" + echo "use only the reset method." + exit 0 + ;; +getinfo-devurl) + echo "http://ipmitool.sf.net/" + exit 0 + ;; +getinfo-xml) + cat << IPMIXML +<parameters> +<parameter name="hostname" unique="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostname +</shortdesc> +<longdesc lang="en"> +The name of the host to be managed by this STONITH device. +</longdesc> +</parameter> + +<parameter name="ipaddr" unique="1"> +<content type="string" /> +<shortdesc lang="en"> +IP Address +</shortdesc> +<longdesc lang="en"> +The IP address of the STONITH device. +</longdesc> +</parameter> + +<parameter name="userid" unique="0"> +<content type="string" /> +<shortdesc lang="en"> +Login +</shortdesc> +<longdesc lang="en"> +The username used for logging in to the STONITH device. +</longdesc> +</parameter> + +<parameter name="passwd" unique="0"> +<content type="string" /> +<shortdesc lang="en"> +Password +</shortdesc> +<longdesc lang="en"> +The password used for logging in to the STONITH device. +</longdesc> +</parameter> + +<parameter name="passwd_method" unique="0"> +<content type="string" default="param"/> +<shortdesc lang="en"> +Method for passing passwd parameter +</shortdesc> +<longdesc lang="en"> +Method for passing the passwd parameter to ipmitool + param: pass as parameter (-P) + env: pass via environment (-E) + file: value of "passwd" is actually a file name, pass with (-f) +</longdesc> +</parameter> + +<parameter name="interface" unique="0"> +<content type="string" default="lan"/> +<shortdesc lang="en"> +IPMI interface +</shortdesc> +<longdesc lang="en"> +IPMI interface to use, such as "lan" or "lanplus". +</longdesc> +</parameter> + +<parameter name="priv" unique="0"> +<content type="string" default=""/> +<shortdesc lang="en"> +The privilege level of the user. +</shortdesc> +<longdesc lang="en"> +The privilege level of the user, for instance OPERATOR. If +unspecified the privilege level is ADMINISTRATOR. See +ipmitool(1) -L option for more information. +</longdesc> +</parameter> + +<parameter name="ipmitool" unique="0"> +<content type="string" default=""/> +<shortdesc lang="en"> +IPMI command(ipmitool) +</shortdesc> +<longdesc lang="en"> +Specify the full path to IPMI command. +</longdesc> +</parameter> + +</parameters> +IPMIXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/ippower9258.in b/lib/plugins/stonith/external/ippower9258.in new file mode 100755 index 0000000..6ae7e02 --- /dev/null +++ b/lib/plugins/stonith/external/ippower9258.in @@ -0,0 +1,316 @@ +#!/bin/sh +# +# External STONITH module using IP Power 9258 or compatible devices. +# +# Copyright (c) 2010 Helmut Weymann (Helmut (at) h-weymann (dot) de) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +# +# Basic commands & parameters independent from individual device + +DEVICE="IP Power 9258" +IPPowerOn="1" +IPPowerOff="0" +IPGetPower="Set.cmd?CMD=GetPower" +IPSetPower="Set.cmd?CMD=SetPower" +IPPort_name="P" +IPPort0=60 +HTTP_COMMAND="wget -q -O - --" +LOG_ERROR="ha_log.sh err" +LOG_WARNING="ha_log.sh warn" +LOG_INFO="ha_log.sh info" +LOG_DEBUG="ha_log.sh debug" +MY_COOKIES="cookies.txt" +MY_TEMPFILE="temp.htm" +PORT_STATUS="iocontrol.htm" +UNDEFINED_HOSTNAME="*not-defined*" + +# +# check MY_ROOT_PATH for IP Power 9258 and create it if necessary +MY_ROOT_PATH="@GLUE_STATE_DIR@/heartbeat/rsctmp/ippower9258" + +# +# script functions +# + +get_challenge() { + # + # device sends a challenge for md5 encryption of username, password and challenge + send_web_command - "http://$deviceip/" | grep Challenge | grep input | cut -d '"' -f 6 +} + +get_cookie_from_device(){ + # the form on the login page has these fields: + # Username, Password, Challenge, Response, ScreenWidth + # + challenge=`get_challenge` + response=`echo -n "$username$password$challenge" | md5sum | cut -b -32` + postdata="Username=$username&Password=&Challenge=&Response=$response&ScreenWidth=1024" + send_web_command " $MY_PATH/$MY_TEMPFILE --post-data=$postdata" "http://$deviceip/tgi/login.tgi" + if grep -qs "Invalid User name or Password" $MY_PATH/$MY_TEMPFILE + then + $LOG_ERROR "Login to device $deviceip failed." + $LOG_ERROR "Received Challenge = <<<$challenge>>>." + $LOG_ERROR "Sent postdata = <<<$postdata>>>." + exit 1 + fi +} + +get_data_from_device() { + # If successful all device info is available in MY_PATH + rm -f "$MY_PATH/$PORT_STATUS" + send_web_command "$MY_PATH/$PORT_STATUS" "http://$deviceip/$PORT_STATUS" + if grep -qs "Cookie Time Out" $MY_PATH/$PORT_STATUS + then + $LOG_ERROR "received no port data from $deviceip (Cookie Time Out)" + exit 1 + fi +} + +send_http_request() { + # ececution of http commands supported by the device + $HTTP_COMMAND "http://$username:$password@$deviceip/$1" +} + +send_web_command(){ + # ececution of web commands through the web-interface + WEB_COMMAND="wget -q --keep-session-cookies" + WEB_COMMAND="$WEB_COMMAND --load-cookies $MY_PATH/$MY_COOKIES" + WEB_COMMAND="$WEB_COMMAND --save-cookies $MY_PATH/$MY_COOKIES" + $WEB_COMMAND -O $1 -- $2 +} + +name2port() { + local name=$1 + local i=$IPPort0 + for h in $device_hostlist ; do + if [ $h = $name ]; then + echo $IPPort_name$i + return + fi + i=`expr $i + 1` + done + echo "invalid" +} + +set_port() { + # + # port status is always set. Even if requested status is current status. + # host status is not considered. + local host=$1 + local requested_status=$2 # 0 or 1 + local port=`name2port $host` + if [ "$port" = "invalid" ] + then + $LOG_ERROR "Host $host is not in hostlist ($hostlist) for $deviceip." + exit 1 + fi + ret=`send_http_request "$IPSetPower+$port=$requested_status" | cut -b 11` + if [ "$ret" != "$requested_status" ] + then + $LOG_ERROR "$DEVICE at $deviceip responds with wrong status $ret for host $host at port $port." + exit 1 + fi + return 0 +} + +build_device_hostlist() { + # + # hostnames are available from http://$deviceip/iocontrol.htm" + # check for number of ports + # + device_hostlist=$( + w3m -dump $MY_PATH/$PORT_STATUS | grep 'Power[1-8]' | + sed 's/[^[]*\[//;s/\].*//;s/ *//' | + while read h; do + [ -z "$h" ] && + echo $UNDEFINED_HOSTNAME || + echo $h + done + ) + if [ x = x"$device_hostlist" ]; then + $LOG_ERROR "cannot get hostlist for $deviceip" + exit 1 + fi + $LOG_DEBUG "Got new hostlist ($device_hostlist) from $deviceip" +} + +filter_device_hostlist() { + # check the given hostlist against the device hostlist + local host + for host in $device_hostlist; do + [ "$host" != "$UNDEFINED_HOSTNAME" ] && + echo $host + done +} + +check_hostlist() { + # check the given hostlist against the device hostlist + local cnt=`echo "$hostlist" | wc -w` + local cnt2=0 + local host + for host in $hostlist; do + if [ `name2port $host` != "invalid" ]; then + cnt2=$((cnt2+1)) + else + $LOG_ERROR "host $host not defined at $deviceip" + fi + done + [ $cnt -ne $cnt2 ] && + exit 1 +} + +get_http_status() { + pattern="P60=[01],P61=[01],P62=[01],P63=[01],P64=[01],P65=[01],P66=[01],P67=[01]" + ret=`send_http_request "$IPGetPower" | grep $pattern` + if [ "X$ret" = "X" ] + then + $LOG_ERROR "$DEVICE at $deviceip returns invalid or no string." + exit 1 + fi +} + +hostlist=`echo $hostlist | tr ',' ' '` +case $1 in +gethosts|on|off|reset|status) + # need environment from stonithd + # and device information from individual device + # + # default device username is admin + # IP Power 9258 does not allow user management. + # + if [ "X$username" = "X" ] + then + username="admin" + fi + + mkdir -p $MY_ROOT_PATH + tmp_path="$deviceip" # ensure a simple unique pathname + MY_PATH="$MY_ROOT_PATH/$tmp_path" + mkdir -p $MY_PATH + get_cookie_from_device + get_data_from_device + build_device_hostlist + if [ "X$hostlist" = "X" ]; then + hostlist="`filter_device_hostlist`" + else + check_hostlist + fi + ;; +*) + # the client is asking for meta-data + ;; +esac + +target=`echo $2 | sed 's/[.].*//'` +# the necessary actions for stonithd +case $1 in +gethosts) + echo $hostlist + ;; +on) + set_port $target $IPPowerOn + ;; +off) + set_port $target $IPPowerOff + ;; +reset) + set_port $target $IPPowerOff + sleep 5 + set_port $target $IPPowerOn + ;; +status) + # werify http command interface + get_http_status + ;; +getconfignames) + # return all the config names + for ipparam in deviceip username password hostlist + do + echo $ipparam + done + ;; +getinfo-devid) + echo "IP Power 9258" + ;; +getinfo-devname) + echo "IP Power 9258 power switch" + ;; +getinfo-devdescr) + echo "Power switch IP Power 9258 with 4 or 8 power outlets." + echo "WARNING: It is different from IP Power 9258 HP" + ;; +getinfo-devurl) + echo "http://www.aviosys.com/manual.htm" + ;; +getinfo-xml) + cat << IPPOWERXML +<parameters> +<parameter name="deviceip" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +IP address or hostname of the device. +</shortdesc> +<longdesc lang="en"> +The IP Address or the hostname of the device. +</longdesc> +</parameter> + +<parameter name="password" unique="0" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Password +</shortdesc> +<longdesc lang="en"> +The password to log in with. +</longdesc> +</parameter> + +<parameter name="hostlist" unique="0" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Hostlist +</shortdesc> +<longdesc lang="en"> +The list of hosts that the device controls. +If you leave this list empty, we will retrieve the hostnames from the device. +</longdesc> +</parameter> + +<parameter name="username" unique="0" required="0"> +<content type="string" default="admin"/> +<shortdesc lang="en"> +Account Name +</shortdesc> +<longdesc lang="en"> +The user to log in with. +</longdesc> +</parameter> + +</parameters> +IPPOWERXML + ;; +*) + $LOG_ERROR "Unexpected command $1 for $DEVICE at $deviceip." + exit 1; + ;; +esac diff --git a/lib/plugins/stonith/external/kdumpcheck.in b/lib/plugins/stonith/external/kdumpcheck.in new file mode 100644 index 0000000..7f3f752 --- /dev/null +++ b/lib/plugins/stonith/external/kdumpcheck.in @@ -0,0 +1,274 @@ +#!/bin/sh +# +# External STONITH module to check kdump. +# +# Copyright (c) 2008 NIPPON TELEGRAPH AND TELEPHONE CORPORATION +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +SSH_COMMAND="@SSH@ -q -x -o PasswordAuthentication=no -o StrictHostKeyChecking=no -n" +#Set default user name. +USERNAME="kdumpchecker" +#Initialize identity file-path options for ssh command +IDENTITY_OPTS="" + +#Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo ${hostlist} | tr ',' ' '` + +## +# Check the parameter hostlist is set or not. +# If not, exit with 6 (ERR_CONFIGURED). +## +check_hostlist() { + if [ -z "${hostlist}" ]; then + ha_log.sh err "hostlist is empty" + exit 6 #ERR_CONFIGURED + fi +} + +## +# Set kdump check user name to USERNAME. +# always return 0. +## +get_username() { + kdump_conf="/etc/kdump.conf" + + if [ ! -f "${kdump_conf}" ]; then + ha_log.sh debug "${kdump_conf} doesn't exist" + return 0 + fi + + tmp="" + while read config_opt config_val; do + if [ "${config_opt}" = "kdump_check_user" ]; then + tmp="${config_val}" + fi + done < "${kdump_conf}" + if [ -n "${tmp}" ]; then + USERNAME="${tmp}" + fi + + ha_log.sh debug "kdump check user name is ${USERNAME}." +} + +## +# Check the specified or default identity file exists or not. +# If not, exit with 6 (ERR_CONFIGURED). +## +check_identity_file() { + IDENTITY_OPTS="" + if [ -n "${identity_file}" ]; then + if [ ! -f "${identity_file}" ]; then + ha_log.sh err "${identity_file} doesn't exist." + exit 6 #ERR_CONFIGURED + fi + IDENTITY_OPTS="-i ${identity_file}" + else + flg_file_exists=0 + homedir=`eval echo "~${USERNAME}"` + for filename in "${homedir}/.ssh/id_rsa" \ + "${homedir}/.ssh/id_dsa" \ + "${homedir}/.ssh/identity" + do + if [ -f "${filename}" ]; then + flg_file_exists=1 + IDENTITY_OPTS="${IDENTITY_OPTS} -i ${filename}" + fi + done + if [ ${flg_file_exists} -eq 0 ]; then + ha_log.sh err "${USERNAME}'s identity file for ssh command" \ + " doesn't exist." + exit 6 #ERR_CONFIGURED + fi + fi +} + +## +# Check the user to check doing kdump exists or not. +# If not, exit with 6 (ERR_CONFIGURED). +## +check_user_existence() { + + # Get kdump check user name and check whether he exists or not. + grep -q "^${USERNAME}:" /etc/passwd > /dev/null 2>&1 + ret=$? + if [ ${ret} != 0 ]; then + ha_log.sh err "user ${USERNAME} doesn't exist." \ + "please confirm \"kdump_check_user\" setting in /etc/kdump.conf." \ + "(default user name is \"kdumpchecker\")" + exit 6 #ERR_CONFIGURED + fi +} + +## +# Check the target node is kdumping or not. +# arg1 : target node name. +# ret : 0 -> the target is kdumping. +# : 1 -> the target is _not_ kdumping. +# : else -> failed to check. +## +check_kdump() { + target_node="$1" + + # Get kdump check user name. + get_username + check_user_existence + exec_cmd="${SSH_COMMAND} -l ${USERNAME}" + + # Specify kdump check user's identity file for ssh command. + check_identity_file + exec_cmd="${exec_cmd} ${IDENTITY_OPTS}" + + # Now, check the target! + # In advance, Write the following setting at the head of + # kdump_check_user's public key in authorized_keys file on target node. + # command="test -s /proc/vmcore", \ + # no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty + ha_log.sh debug "execute the command [${exec_cmd} ${target_node}]." + ${exec_cmd} ${target_node} > /dev/null 2>&1 + ret=$? + ha_log.sh debug "the command's result is ${ret}." + + #ret -> 0 : vmcore file's size is not zero. the node is kdumping. + #ret -> 1 : the node is _not_ kdumping (vmcore didn't exist or + # its size is zero). It still needs to be STONITH'ed. + #ret -> 255 : ssh command is failed. + # else : Maybe command strings in authorized_keys is wrong... + return ${ret} +} + +### +# +# Main function. +# +### +case $1 in +gethosts) + check_hostlist + for hostname in ${hostlist} ; do + echo "${hostname}" + done + exit 0 + ;; +on) + # This plugin does only check whether a target node is kdumping or not. + exit 1 + ;; +reset|off) + check_hostlist + ret=1 + h_target=`echo $2 | tr A-Z a-z` + for hostname in ${hostlist} + do + hostname=`echo $hostname | tr A-Z a-z` + if [ "${hostname}" != "$h_target" ]; then + continue + fi + while [ 1 ] + do + check_kdump "$2" + ret=$? + if [ ${ret} -ne 255 ]; then + exit ${ret} + fi + #255 means ssh command itself is failed. + #For example, connection failure as if network doesn't start yet + #in 2nd kernel on the target node. + #So, retry to check after a little while. + sleep 1 + done + done + exit ${ret} + ;; +status) + check_hostlist + for hostname in ${hostlist} + do + if ping -w1 -c1 "${hostname}" 2>&1 | grep "unknown host" + then + exit 1 + fi + done + get_username + check_user_existence + check_identity_file + exit 0 + ;; +getconfignames) + echo "hostlist identity_file" + exit 0 + ;; +getinfo-devid) + echo "kdump check STONITH device" + exit 0 + ;; +getinfo-devname) + echo "kdump check STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ssh-based kdump checker" + echo "To check whether a target node is dumping or not." + exit 0 + ;; +getinfo-devurl) + echo "kdump -> http://lse.sourceforge.net/kdump/" + echo "ssh -> http://openssh.org" + exit 0 + ;; +getinfo-xml) + cat << SSHXML +<parameters> +<parameter name="hostlist" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostlist +</shortdesc> +<longdesc lang="en"> +The list of hosts that the STONITH device controls +</longdesc> +</parameter> + +<parameter name="identity_file" unique="1" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Identity file's full path for kdump check user +</shortdesc> +<longdesc lang="en"> +The full path of kdump check user's identity file for ssh command. +The identity in the specified file have to be restricted to execute +only the following command. +"test -s /proc/vmcore" +Default: kdump check user's default identity file path. +NOTE: You can specify kdump check user name in /etc/kdump.conf. + The parameter name is "kdump_check_user". + Default user is "kdumpchecker". +</longdesc> +</parameter> + +</parameters> +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/libvirt b/lib/plugins/stonith/external/libvirt new file mode 100644 index 0000000..494b048 --- /dev/null +++ b/lib/plugins/stonith/external/libvirt @@ -0,0 +1,298 @@ +#!/bin/sh +# +# External STONITH module for a libvirt managed hypervisor (kvm/Xen). +# Uses libvirt as a STONITH device to control guest. +# +# Copyright (c) 2010 Holger Teutsch <holger.teutsch@web.de> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +# start a domain +libvirt_start() { + out=$($VIRSH -c $hypervisor_uri start $domain_id 2>&1) + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id was started" + return 0 + fi + + $VIRSH -c $hypervisor_uri dominfo $domain_id 2>&1 | + egrep -q '^State:.*(running|idle)|already active' + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id is already active" + return 0 + fi + + ha_log.sh err "Failed to start domain $domain_id" + ha_log.sh err "$out" + return 1 +} +# reboot a domain +# return +# 0: success +# 1: error +libvirt_reboot() { + local rc out + out=$($VIRSH -c $hypervisor_uri reboot $domain_id 2>&1) + rc=$? + if [ $rc -eq 0 ] + then + ha_log.sh notice "Domain $domain_id was rebooted" + return 0 + fi + ha_log.sh err "Failed to reboot domain $domain_id (exit code: $rc)" + ha_log.sh err "$out" + return 1 +} + +# stop a domain +# return +# 0: success +# 1: error +# 2: was already stopped +libvirt_stop() { + out=$($VIRSH -c $hypervisor_uri destroy $domain_id 2>&1) + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id was stopped" + return 0 + fi + + $VIRSH -c $hypervisor_uri dominfo $domain_id 2>&1 | + egrep -q '^State:.*shut off|not found|not running' + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id is already stopped" + return 2 + fi + + ha_log.sh err "Failed to stop domain $domain_id" + ha_log.sh err "$out" + return 1 +} + +# get status of stonith device (*NOT* of the domain). +# If we can retrieve some info from the hypervisor +# the stonith device is OK. +libvirt_status() { + out=$($VIRSH -c $hypervisor_uri version 2>&1) + if [ $? -eq 0 ] + then + return 0 + fi + + ha_log.sh err "Failed to get status for $hypervisor_uri" + ha_log.sh err "$out" + return 1 +} + +# check config and set variables +# does not return on error +libvirt_check_config() { + VIRSH=`which virsh 2>/dev/null` + + if [ ! -x "$VIRSH" ] + then + ha_log.sh err "virsh not installed" + exit 1 + fi + + if [ -z "$hostlist" -o -z "$hypervisor_uri" ] + then + ha_log.sh err "hostlist or hypervisor_uri missing; check configuration" + exit 1 + fi + + case "$reset_method" in + power_cycle|reboot) : ;; + *) + ha_log.sh err "unrecognized reset_method: $reset_method" + exit 1 + ;; + esac +} + +# set variable domain_id for the host specified as arg +libvirt_set_domain_id () +{ + for h in $hostlist + do + case $h in + $1:*) + domain_id=`expr $h : '.*:\(.*\)'` + return + ;; + + $1) + domain_id=$1 + return + esac + done + + ha_log.sh err "Should never happen: Called for host $1 but $1 is not in $hostlist." + exit 1 +} + +libvirt_info() { +cat << LVIRTXML +<parameters> +<parameter name="hostlist" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +List of hostname[:domain_id].. +</shortdesc> +<longdesc lang="en"> +List of controlled hosts: hostname[:domain_id].. +The optional domain_id defaults to the hostname. +</longdesc> +</parameter> + +<parameter name="hypervisor_uri" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hypervisor URI +</shortdesc> +<longdesc lang="en"> +URI for connection to the hypervisor. +driver[+transport]://[username@][hostlist][:port]/[path][?extraparameters] +e.g. +qemu+ssh://my_kvm_server.mydomain.my/system (uses ssh for root) +xen://my_kvm_server.mydomain.my/ (uses TLS for client) + +virsh must be installed (e.g. libvir-client package) and access control must +be configured for your selected URI. +</longdesc> +</parameter> + +<parameter name="reset_method" required="0"> +<content type="string" default="power_cycle"/> +<shortdesc lang="en"> +How to reset a guest. +</shortdesc> +<longdesc lang="en"> +A guest reset may be done by a sequence of off and on commands +(power_cycle) or by the reboot command. Which method works +depend on the hypervisor and guest configuration management. +</longdesc> +</parameter> +</parameters> +LVIRTXML +exit 0 +} + +############# +# Main code # +############# + +# don't fool yourself when testing with stonith(8) +# and transport ssh +unset SSH_AUTH_SOCK + +# support , as a separator as well +hostlist=`echo $hostlist| sed -e 's/,/ /g'` + +reset_method=${reset_method:-"power_cycle"} + +case $1 in + gethosts) + hostnames=`echo $hostlist|sed -e 's/:[^ ]*//g'` + for h in $hostnames + do + echo $h + done + exit 0 + ;; + + on) + libvirt_check_config + libvirt_set_domain_id $2 + + libvirt_start + exit $? + ;; + + off) + libvirt_check_config + libvirt_set_domain_id $2 + + libvirt_stop + [ $? = 1 ] && exit 1 + exit 0 + ;; + + reset) + libvirt_check_config + libvirt_set_domain_id $2 + + if [ "$reset_method" = "power_cycle" ]; then + libvirt_stop + [ $? = 1 ] && exit 1 + sleep 2 + libvirt_start + else + libvirt_reboot + fi + exit $? + ;; + + status) + libvirt_check_config + libvirt_status + exit $? + ;; + + getconfignames) + echo "hostlist hypervisor_uri reboot_method" + exit 0 + ;; + + getinfo-devid) + echo "libvirt STONITH device" + exit 0 + ;; + + getinfo-devname) + echo "libvirt STONITH external device" + exit 0 + ;; + + getinfo-devdescr) + echo "libvirt-based host reset for Xen/KVM guest domain through hypervisor" + exit 0 + ;; + + getinfo-devurl) + echo "http://libvirt.org/uri.html http://linux-ha.org/wiki" + exit 0 + ;; + + getinfo-xml) + libvirt_info + echo 0; + ;; + + *) + exit 1 + ;; +esac + +# vi:et:ts=4:sw=4 diff --git a/lib/plugins/stonith/external/nut b/lib/plugins/stonith/external/nut new file mode 100644 index 0000000..9e51bb8 --- /dev/null +++ b/lib/plugins/stonith/external/nut @@ -0,0 +1,302 @@ +#!/bin/sh + +# External STONITH module that uses the NUT daemon to control an external UPS. +# See the comments below, and the various NUT man pages, for how this +# script works. It should work unchanged with most modern "smart" APC UPSes in +# a Redhat/Fedora/RHEL-style distribution with the nut package installed. + +# Author: William Seligman <seligman@nevis.columbia.edu> +# License: GPLv2 + +# As you're designing your UPS and STONITH set-up, it may help to consider that +# there can be potentially three computers involved: +# 1) the machine running this STONITH module; +# 2) the machine being controlled by this STONITH module ($hostname); +# 3) the machine that can send commands to the UPS. + +# On my cluster, all the UPSes have SNMP smartcards, so every host can communicate +# with every UPS; in other words, machines (1) and (3) are the same. If your UPSes +# are controlled via serial or USB connections, then you might have a +# situation in which $hostname is plugged into a UPS, which has a serial connection +# to some master "power-control" computer, and can potentially be STONITHed +# by any other machine in your cluster. + +# In general, you'll probably need the nut daemon running on both the hosts (1) and +# (3) in the above list. The NUT daemon will also have to run on (2) if you want the +# reset command to gracefully reboot $hostname. + +# The NUT command default locations. In the RHEL-type nut packages, these binaries +# are in /usr/bin. +RHELUPSCMD="/usr/bin/upscmd" +RHELUPSC="/usr/bin/upsc" + +# Defaults for APC smart UPSes: + +# Reset = reboot $hostname; this will be a graceful reboot if the host +# is running NUT and monitoring $ups. +APCRESET="shutdown.return" + +# Poweroff = turn off $hostname immediately by cutting the power on $ups. +# For a graceful shutdown, use shutdown.stayoff instead of load.off, +# but it might take a few minutes to shutdown in this way. +APCPOWEROFF="load.off" + +# Poweron = turn on the power to $ups, which will presumably turn on $hostname. +# (Did you set $hostname's BIOS to boot up on AC power restore, as opposed to +# "last state"?) +APCPOWERON="load.on" + +# Status = returns a short string with the $ups status; OL = on-line, OFF = off-line, etc. +APCSTATUSVAR="ups.status" + + +# Stick in the defaults, if needed. +if [ -z "${poweron}" ]; then + poweron=${APCPOWERON} +fi +if [ -z "${poweroff}" ]; then + poweroff=${APCPOWEROFF} +fi +if [ -z "${reset}" ]; then + reset=${APCRESET} +fi +if [ -z "${statusvar}" ]; then + statusvar=${APCSTATUSVAR} +fi +if [ -z "${upscmd}" ]; then + upscmd=${RHELUPSCMD} +fi +if [ -z "${upsc}" ]; then + upsc=${RHELUPSC} +fi + + +# Define the command to fetch the UPS status. +STATUSCMD="${upsc} ${ups} ${statusvar}" + +usage() { + echo "Usage: $0 {on|off|reset|status|gethosts|getconfignames|getinfo-devid|getinfo-devname|getinfo-devdescr|getinfo-devurl|getinfo-xml}" +} + +# Can we find the NUT binary? +have_nut() { + test -x "${upscmd}" +} +have_upsc() { + test -x "${upsc}" +} + +do_nut() { + have_nut || { + echo "Can't find NUT upscmd command" + return 1 + } + if [ -z "${username}" -o -z "${password}" -o -z "${ups}" ]; then + echo "username, password or ups name missing; check configuration" + return 1 + fi + # Execute the command given in argument 1. + ${upscmd} -u ${username} -p ${password} ${ups} ${1} || { + echo "error executing nut command" + return 1 + } +} + +case ${1} in +gethosts) + echo ${hostname} + exit 0 + ;; +on) + result=1 + do_nut "${poweron}" + result=$? + exit ${result} + ;; +off) + result=1 + do_nut "${poweroff}" + result=$? + exit ${result} + ;; +reset) + result=1 + do_nut "${reset}" + result=$? + exit $result + ;; +status) + have_upsc || { + echo "Can't find NUT upsc command" + exit 1 + } + ${STATUSCMD} + exit $? + ;; +getconfignames) + echo "hostname ups username password poweron poweroff reset statusvar upscmd upsc" + exit 0 + ;; +getinfo-devid) + echo "NUT STONITH device" + exit 0 + ;; +getinfo-devname) + echo "NUT STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "A STONITH device based on NUT (Network UPS Tools)." + echo " " + echo "For this STONITH script to work, the following conditions have" + echo "to be met:" + echo " " + echo "- NUT has to be installed on both the host running this script" + echo " and the host that controls the UPS (on RHEL systems, NUT is" + echo " in packages nut and nut-client) and the nut daemon services" + echo " (normally called the ups or upsd service) must be running" + echo " on both systems." + echo " " + echo "- The UPS name has to be defined in ups.conf on the host" + echo " that controls the UPS." + echo " " + echo "- The username/password to access the UPS must be defined in" + echo " upsd.users on the host that controls the UPS, with the instcmds" + echo " for poweron, poweroff, and reset allowed." + echo " " + echo "- The host that is running this script must be allowed access" + echo " via upsd.conf and upsd.users on the host the controls the UPS." + echo " " + echo "On RHEL systems, the files listed above are in /etc/ups." + echo " " + echo "The defaults will probably work with APC UPS devices. It might" + echo "work on others; 'upscmd -l (ups)' and 'upsc (ups)' will list" + echo "the commands and variables, and you can change the values" + echo "for poweron, poweroff, reset, and statusvar to suit your UPS." + echo "Change upscmd and upsc if your NUT binaries are not in /usr/bin." + exit 0 + ;; +getinfo-devurl) + echo "http://www.networkupstools.org/" + exit 0 + ;; +getinfo-xml) +cat << nutXML +<parameters> + +<parameter name="hostname" unique="1" required="1"> +<content type="string" default="" /> +<shortdesc lang="en">Hostname</shortdesc> +<longdesc lang="en"> +The name of the host to be managed by this STONITH device. +The nut daemon must be running on the host controllng the +UPS _and_ on the host running this script; this script does +not start/stop the daemons for you. +</longdesc> +</parameter> + +<parameter name="ups" required="1"> +<content type="string" default="" /> +<shortdesc lang="en">UPS name</shortdesc> +<longdesc lang="en"> +The name of the UPS as defined in ups.conf on the host +controlling the UPS. The format for this option is +upsname[@controlhost[:port]]. The default controlhost is +"localhost". +</longdesc> +</parameter> + +<parameter name="username" required="1"> +<content type="string" default="" /> +<shortdesc lang="en">Username</shortdesc> +<longdesc lang="en"> +The username used for accessing the UPS. This is defined in +upsd.conf on the host controlling the UPS. +</longdesc> +</parameter> + +<parameter name="password" required="1"> +<content type="string" default="" /> +<shortdesc lang="en">Password</shortdesc> +<longdesc lang="en"> +The password used for logging in to the UPS for the host +controlling the UPS, as defined in upsd.conf on that host. +</longdesc> +</parameter> + +<parameter name="poweron"> +<content type="string" default="$APCPOWERON" /> +<shortdesc lang="en">UPS Power On command</shortdesc> +<longdesc lang="en"> +The NUT hardware command to turn on the UPS. The default +should work for most "smart" APC UPSes. For a list of +commands that your UPS can support, type 'upscmd -l (ups)' +on the command line.</longdesc> +</parameter> + +<parameter name="poweroff"> +<content type="string" default="$APCPOWEROFF" /> +<shortdesc lang="en">UPS Power Off command</shortdesc> +<longdesc lang="en"> +The NUT hardware command to turn off the UPS. On most APC +"smart" UPSes, the command shutdown.stayoff will result +in a graceful shutdown, provided the host is running the +nut daemon, but this might take a few minutes; load.off +will cut the power immediately. For a list of commands that +your UPS can support, type 'upscmd -l (ups)' on the command +line. +</longdesc> +</parameter> + +<parameter name="reset"> +<content type="string" default="$APCRESET" /> +<shortdesc lang="en">UPS Reset command</shortdesc> +<longdesc lang="en"> +The NUT hardware command to reset the host. On most APC +"smart" UPSes, the command shutdown.return will result +in a graceful shutdown, with power restored after perhaps +a short interval. For a list of commands that your UPS can + support, type 'upscmd -l (ups)' on the command line. +</longdesc> +</parameter> + +<parameter name="statusvar"> +<content type="string" default="$APCSTATUSVAR" /> +<shortdesc lang="en">UPS Status variable</shortdesc> +<longdesc lang="en"> +The NUT variable that returns the status of the UPS. On APC +UPSes, the value of ups.status will be "OL" if the UPS is +"on-line." For a list of variables that your UPS supports, +type 'upsc (ups)' on the command line. +</longdesc> +</parameter> + +<parameter name="upscmd"> +<content type="string" default="$RHELUPSCMD" /> +<shortdesc lang="en">upscmd binary location</shortdesc> +<longdesc lang="en"> +The full path to the NUT binary command 'upscmd'. On RHEL +systems with the nut RPM installed, this location is +/usr/bin/upscmd. +</longdesc> +</parameter> + +<parameter name="upsc"> +<content type="string" default="$RHELUPSC" /> +<shortdesc lang="en">upsc binary location</shortdesc> +<longdesc lang="en"> +The full path to the NUT binary command 'upsc'. On RHEL +systems with the nut RPM installed, this location is +/usr/bin/upsc. +</longdesc> +</parameter> + +</parameters> +nutXML +exit 0 +;; +*) + usage + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/rackpdu b/lib/plugins/stonith/external/rackpdu new file mode 100644 index 0000000..7d0e20b --- /dev/null +++ b/lib/plugins/stonith/external/rackpdu @@ -0,0 +1,280 @@ +#!/bin/sh +# +# External STONITH module for APC Switched Rack PDU +# +# Copyright (c) 2008 Sergey Maznichenko <msergeyb@gmail.com> <inbox@it-consultant.su> +# Version 1.2 +# +# See http://www.it-consultant.su/rackpdu +# for additional information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +SWITCH_ON="1" +SWITCH_OFF="2" +SWITCH_RESET="3" + +DEFAULT_NAMES_OID=".1.3.6.1.4.1.318.1.1.12.3.3.1.1.2" +DEFAULT_COMMAND_OID=".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4" + +if [ -z "$oid" ]; then + oid=$DEFAULT_COMMAND_OID +fi + +if [ -z "$names_oid" ]; then + names_oid=$DEFAULT_NAMES_OID +fi + +if [ -z "$outlet_config" ]; then + outlet_config="none" +fi + +GetOutletNumber() { + local nodename=$1 + + if [ "$outlet_config" != "none" ]; then + # Get outlet number from file + + if [ -f "$outlet_config" ]; then + local outlet_num=`grep $nodename $outlet_config | tr -d ' ' | cut -f2 -d'='` + if [ -z "$outlet_num" ]; then + ha_log.sh err "Outlet number not found for node $nodename. Check configuration file $outlet_config" + return 0 + fi + return $outlet_num + else + ha_log.sh err "File $outlet_config not found." + return 0 + fi + else + # Get outlet number from device + + local outlet_num=1 + local snmp_result + snmp_result=`snmpwalk -v1 -c $community $pduip $names_oid 2>&1` + if [ $? -ne 0 ]; then + ha_log.sh err "snmpwalk $community $pduip $names_oid failed. Result: $snmp_result" + return 0 + fi + + local names + names=`echo "$snmp_result" | cut -f2 -d'"' | tr ' ' '_' | tr '\012' ' '` + for name in $names; do + if [ "$name" != "$nodename" ]; then + local outlet_num=`expr $outlet_num + 1` + continue + fi + + return $outlet_num + done + + ha_log.sh err "Outlet number not found for node $nodename. Result: $snmp_result" + return 0 + fi +} + +SendCommand() { + + local host=$1 + local command=$2 + + GetOutletNumber $host + local outlet=$? + + if [ $outlet -gt 0 ]; then + local set_result + set_result=`snmpset -v1 -c $community $pduip $oid.$outlet i $command 2>&1` + if [ $? -ne 0 ]; then + ha_log.sh err "Write SNMP to $pduip value $oid.$outlet=$command failed. Result: $set_result" + return 1 + fi + if echo "$set_result" | grep -qs "Timeout"; then + ha_log.sh err "Write SNMP to $pduip value $oid.$outlet=$command timed out. Result: $set_result" + return 1 + fi + return 0 + else + return 1 + fi +} + +hostlist=`echo $hostlist | tr ',' ' '` +incommand=$1 +innode=$2 + +case $incommand in +gethosts) + if [ "$hostlist" = "AUTO" ]; then + snmp_result=`snmpwalk -v1 -c $community $pduip $names_oid 2>&1` + if [ $? -ne 0 ]; then + ha_log.sh err "snmpwalk $community $pduip $names_oid failed. Result: $snmp_result" + exit 1 + fi + if echo "$snmp_result" | grep -qs "Timeout"; then + ha_log.sh err "snmpwalk $community $pduip $names_oid timed out. Result: $snmp_result" + exit 1 + else + hostlist=`echo "$snmp_result" | cut -f2 -d'"' | tr ' ' '_' | tr '\012' ' '` + fi + fi + + for h in $hostlist ; do + echo $h + done + + exit 0 + ;; +on) + if + SendCommand $innode $SWITCH_ON + then + exit 0 + else + exit 1 + fi + ;; +off) + if + SendCommand $innode $SWITCH_OFF + then + exit 0 + else + exit 1 + fi + ;; +reset) + if + SendCommand $innode $SWITCH_RESET + then + exit 0 + else + exit 1 + fi + ;; +status) + if [ -z "$pduip" ]; then + exit 1 + fi + + if ping -w1 -c1 $pduip >/dev/null 2>&1; then + exit 0 + else + exit 1 + fi + ;; +getconfignames) + echo "hostlist pduip community" + exit 0 + ;; +getinfo-devid) + echo "rackpdu STONITH device" + exit 0 + ;; +getinfo-devname) + echo "rackpdu STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "APC Switched Rack PDU" + exit 0 + ;; +getinfo-devurl) + echo "http://www.apcc.com/products/family/index.cfm?id=30" + exit 0 + ;; +getinfo-xml) + cat << PDUXML +<parameters> + <parameter name="hostlist" unique="1" required="1"> + <content type="string" default="AUTO" /> + <shortdesc lang="en">Hostlist</shortdesc> + <longdesc lang="en"> +The list of hosts that the STONITH device controls (comma or space separated). +If you set value of this parameter to AUTO, list of hosts will be get from Rack PDU device. + </longdesc> + </parameter> + + <parameter name="pduip" unique="1" required="1"> + <content type="string" /> + <shortdesc lang="en">Name or IP address of Rack PDU device.</shortdesc> + <longdesc lang="en">Name or IP address of Rack PDU device.</longdesc> + </parameter> + + <parameter name="community" unique="1" required="1"> + <content type="string" default="private" /> + <shortdesc lang="en">Name of write community.</shortdesc> + <longdesc lang="en">Name of write community.</longdesc> + </parameter> + + <parameter name="oid" unique="1" required="0"> + <content type="string" /> + <shortdesc lang="en"> + The OID without the outlet number. + </shortdesc> + <longdesc lang="en"> +The SNMP OID for the PDU. minus the outlet number. +Try .1.3.6.1.4.1.318.1.1.12.3.3.1.1.4 (default value) +or use mib from ftp://ftp.apcc.com/apc/public/software/pnetmib/mib/ +Varies on different APC hardware and firmware. +Warning! No dot at the end of OID + </longdesc> + </parameter> + + <parameter name="names_oid" unique="1" required="0"> + <content type="string" /> + <shortdesc lang="en">The OID for getting names of outlets.</shortdesc> + <longdesc lang="en"> +The SNMP OID for getting names of outlets. +It is required to recognize outlet number by nodename. +Try ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.2" (default value) +or use mib from ftp://ftp.apcc.com/apc/public/software/pnetmib/mib/ +Names of nodes must be equal names of outlets, in other way use outlet_config parameter. +If you set 'names_oid' parameter then parameter outlet_config must not be use. +Varies on different APC hardware and firmware. +Warning! No dot at the end of OID + </longdesc> + </parameter> + + <parameter name="outlet_config" unique="1" required="0"> + <content type="string" /> + <shortdesc lang="en">Configuration file. Other way to recognize outlet number by nodename.</shortdesc> + <longdesc lang="en"> +Configuration file. Other way to recognize outlet number by nodename. +Configuration file which contains +node_name=outlet_number +strings. + +Example: +server1=1 +server2=2 + +If you use outlet_config parameter then names_oid parameter can have any value and it is not uses. + </longdesc> + </parameter> + +</parameters> +PDUXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/riloe b/lib/plugins/stonith/external/riloe new file mode 100644 index 0000000..ce98847 --- /dev/null +++ b/lib/plugins/stonith/external/riloe @@ -0,0 +1,530 @@ +#!/usr/bin/env python +# +# Stonith module for RILOE Stonith device +# +# Copyright (c) 2004 Alain St-Denis <alain.st-denis@ec.gc.ca> +# +# Modified by Alan Robertson <alanr@unix.sh> for STONITH external compatibility. +# +# Extended and merged by Tijl Van den broeck <subspawn@gmail.com> +# with ilo-v2 script from Guy Coates +# +# Cleanup by Andrew Beekhof <abeekhof@suse.de> +# +# Rewritten by Dejan Muhamedagic <dejan@suse.de> +# Now, the plugin actually reads replies from iLO. +# +# Extended by Jochen Roeder <jochen.roeder@novell.com> +# to enable access via proxies +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +import sys +import os +import socket +import subprocess +import xml.dom.minidom +import httplib +import time +import re + +def log_msg(level,msg): + subprocess.call("ha_log.sh %s '%s'" % (level,msg), shell=True) +def my_err(msg): + log_msg("err", msg) +def my_warn(msg): + log_msg("warn", msg) +def my_debug(msg): + log_msg("debug", msg) +def fatal(msg): + my_err(msg) + sys.exit(1) + +argv = sys.argv + +try: + cmd = argv[1] +except IndexError: + my_err("Not enough arguments") + sys.exit(1) + +legacy_RI_HOST = os.environ.get('RI_HOST', '') +legacy_RI_HOSTRI = os.environ.get('RI_HOSTRI', '') +legacy_RI_LOGIN = os.environ.get('RI_LOGIN', 'Administrator') +legacy_RI_PASSWORD = os.environ.get('RI_PASSWORD', '') + +reset_ok = os.environ.get('ilo_can_reset', '0') +ilo_protocol = os.environ.get('ilo_protocol', '1.2') +power_method = os.environ.get('ilo_powerdown_method', 'power') + +realhost = os.environ.get('hostlist', legacy_RI_HOST) +rihost = os.environ.get('ilo_hostname', legacy_RI_HOSTRI) +ilouser = os.environ.get('ilo_user', legacy_RI_LOGIN) +ilopass = os.environ.get('ilo_password', legacy_RI_PASSWORD) +iloproxyhost = os.environ.get('ilo_proxyhost', '') +try: + iloproxyport = int(os.environ.get('ilo_proxyport', 3128)) +except ValueError: + my_err("ilo_proxyport is not a number") + sys.exit(1) + +xmlinfo = '''<parameters> + <parameter name="hostlist" unique="1" required="1"> + <content type="string"/> + <shortdesc lang="en">ilo target hostname</shortdesc> + <longdesc lang="en"> + Contains the hostname that the ilo controls + </longdesc> + </parameter> +<parameter name="ilo_hostname" unique="1" required="1"> + <content type="string"/> + <shortdesc lang="en">ilo device hostname</shortdesc> + <longdesc lang="en"> + The hostname of the ilo device + </longdesc> + </parameter> +<parameter name="ilo_user" unique="0" required="1"> + <content type="string" default="Administrator"/> + <shortdesc lang="en">ilo user</shortdesc> + <longdesc lang="en"> + The user for connecting to the ilo device + </longdesc> + </parameter> +<parameter name="ilo_password" unique="0" required="1"> + <content type="string" default=""/> + <shortdesc lang="en">password</shortdesc> + <longdesc lang="en"> + The password for the ilo device user + </longdesc> + </parameter> +<parameter name="ilo_can_reset" unique="0" required="0"> + <content type="string" default="0"/> + <shortdesc lang="en">Device can reset</shortdesc> + <longdesc lang="en"> + Does the ILO device support RESET commands (hint: older ones cannot) + </longdesc> + </parameter> +<parameter name="ilo_protocol" unique="0" required="0"> + <content type="string" default="1.2"/> + <shortdesc lang="en">ILO Protocol</shortdesc> + <longdesc lang="en"> + Protocol version supported by the ILO device. + Known supported versions: 1.2, 2.0 + </longdesc> + </parameter> +<parameter name="ilo_powerdown_method" unique="0" required="0"> + <content type="string" default="power"/> + <shortdesc lang="en">Power down method</shortdesc> + <longdesc lang="en"> + The method to powerdown the host in question. + * button - Emulate holding down the power button + * power - Emulate turning off the machines power + + NB: A button request takes around 20 seconds. The power method + about half a minute. + </longdesc> + </parameter> +<parameter name="ilo_proxyhost" unique="0" required="0"> + <content type="string" default=""/> + <shortdesc lang="en">Proxy hostname</shortdesc> + <longdesc lang="en"> + proxy hostname if required to access ILO board + </longdesc> + </parameter> +<parameter name="ilo_proxyport" unique="0" required="0"> + <content type="string" default="3128"/> + <shortdesc lang="en">Proxy port</shortdesc> + <longdesc lang="en"> + proxy port if required to access ILO board + parameter will be ignored if proxy hostname is not set + </longdesc> + </parameter> + +</parameters>''' + +info = { + 'getinfo-devid': 'iLO2', + 'getinfo-devname': 'ilo2 ' + rihost, + 'getinfo-devdescr': 'HP/COMPAQ iLO2 STONITH device', + 'getinfo-devurl': 'http://www.hp.com/', + 'gethosts': realhost, + 'getinfo-xml': xmlinfo +} + +if cmd in info: + print info[cmd] + sys.exit(0) + +if cmd == 'getconfignames': + for arg in [ "hostlist", "ilo_hostname", "ilo_user", "ilo_password", "ilo_can_reset", "ilo_protocol", "ilo_powerdown_method", "ilo_proxyhost", "ilo_proxyport"]: + print arg + sys.exit(0) + +if not rihost: + fatal("ILO device hostname not specified") + +if not realhost: + fatal("Host controlled by this ILO device not specified") + +if not power_method in ("power","button"): + my_err('unknown power method %s, setting to "power"') + power_method = "power" + +# XML elements +E_RIBCL = "RIBCL" +E_LOGIN = "LOGIN" +E_SERVER_INFO = "SERVER_INFO" + +# power mgmt methods +E_RESET = "RESET_SERVER" # error if powered off +E_COLD_BOOT = "COLD_BOOT_SERVER" # error if powered off +E_WARM_BOOT = "WARM_BOOT_SERVER" # error if powered off +E_PRESS_BUTTON = "PRESS_PWR_BTN" +E_HOLD_BUTTON = "HOLD_PWR_BTN" + +# get/set status elements +E_SET_POWER = "SET_HOST_POWER" +E_GET_PSTATUS = "GET_HOST_POWER_STATUS" + +# whatever this means, but we have to use it to get good XML +E_LOCFG = "LOCFG" +LOCFG_VER = '2.21' + +# attributes +A_VERSION = "VERSION" # ilo_protocol +A_USER = "USER_LOGIN" +A_PWD = "PASSWORD" +A_MODE = "MODE" # info mode (read or write) +A_POWER_SW = "HOST_POWER" # "Y" or "N" +A_POWER_STATE = "HOST_POWER" # "ON" or "OFF" + +def new_power_req(tag, name = None, value = None): + ''' + Create a new RIBCL request (as XML). + ''' + my_debug("creating power request: %s,%s,%s"%(tag,name,value)) + doc = xml.dom.minidom.Document() + locfg = doc.createElement(E_LOCFG) + locfg.setAttribute(A_VERSION,LOCFG_VER) + ribcl = doc.createElement(E_RIBCL) + ribcl.setAttribute(A_VERSION,ilo_protocol) + login = doc.createElement(E_LOGIN) + login.setAttribute(A_USER,ilouser) + login.setAttribute(A_PWD,ilopass) + serv_info = doc.createElement(E_SERVER_INFO) + # read or write, it doesn't really matter, i.e. even if we + # say "write" that doesn't mean we can't read + serv_info.setAttribute(A_MODE,"write") + doc.appendChild(locfg) + locfg.appendChild(ribcl) + ribcl.appendChild(login) + login.appendChild(serv_info) + el_node = doc.createElement(tag) + if name: + el_node.setAttribute(name,value) + serv_info.appendChild(el_node) + s = doc.toprettyxml() + doc.unlink() + # work around an iLO bug: last line containing "</LOCFG>" + # produces a syntax error + lines = s.split('\n') + return '\n'.join(lines[:-2]) + +E_RESPONSE = "RESPONSE" +E_HOST_POWER = "GET_HOST_POWER" +A_STATUS = "STATUS" +# documentation mentions both; better safe than sorry +A_MSG = "MSG" +A_MSG2 = "MESSAGE" + +def is_element(xmlnode): + return xmlnode.nodeType == xmlnode.ELEMENT_NODE + +def read_resp(node): + ''' + Check if the RESPONSE XML is OK. + ''' + msg = "" + str_status = "" + for attr in node.attributes.keys(): + if attr == A_STATUS: + str_status = node.getAttribute(attr) + elif attr == A_MSG: + msg = node.getAttribute(attr) + elif attr == A_MSG2: + msg = node.getAttribute(attr) + else: + my_warn("unexpected attribute %s in %s" % (attr,E_RESPONSE)) + if not str_status: + my_err("no status in response") + return -1 + try: + status = int(str_status,16) + except ValueError: + my_err("unexpected status %s in response" % str_status) + return -1 + if status != 0: + my_err("%s (rc: %s)"%(msg,str_status)) + return -1 + return 0 + +def read_power(node): + ''' + Read the power from the XML node. Set the global power + variable correspondingly. + ''' + global power + for attr in node.attributes.keys(): + if attr == A_POWER_STATE: + power_state = node.getAttribute(attr).upper() + else: + my_warn("unexpected attribute %s in %s" % (attr,node.tagName)) + if not power_state: + my_err("no %s attribute in %s" % (A_POWER_STATE,node.tagName)) + return -1 + if power_state not in ("ON","OFF"): + my_err("unexpected value for %s: %s" % (A_POWER_STATE,power_state)) + return -1 + power = (power_state == "ON") + my_debug("Host has power: %s"%power) + return 0 + +el_parsers = { + E_RESPONSE:read_resp, + E_HOST_POWER:read_power +} +def proc_resp(doc): + ''' + Process one iLO reply. Real work is done in el_parsers. + ''' + ribcl = doc.childNodes[0] + if not is_element(ribcl) or ribcl.tagName != E_RIBCL: + my_err("unexpected top element in response") + return -1 + for child in ribcl.childNodes: + if not is_element(child): + continue + if child.tagName in el_parsers: + rc = el_parsers[child.tagName](child) + if rc != 0: + return -1 + else: + my_warn("unexpected element in response: %s" % child.toxml()) + return 0 + +def open_ilo(host): + # open https connection + try: + if iloproxyhost != "" and iloproxyport != 0: + proxy=socket.socket(socket.AF_INET,socket.SOCK_STREAM) + proxy.connect((iloproxyhost, iloproxyport)) + proxy_connect='CONNECT %s:%s HTTP/1.1\r\n'%(host,443) + user_agent='User-Agent: python\r\n' + proxy_pieces=proxy_connect+user_agent+'\r\n' + proxy.sendall(proxy_pieces) + response=proxy.recv(8192) + status=response.split()[1] + if status!=str(200): + fatal("Error status=: %s" %(response)) + import ssl + sock = ssl.wrap_socket(proxy) + h=httplib.HTTPConnection('localhost') + h.sock=sock + return h + else: + return httplib.HTTPSConnection(host) + except socket.gaierror, msg: + fatal("%s: %s" %(msg,host)) + except socket.sslerror, msg: + fatal("%s for %s" %(msg,host)) + except socket.error, msg: + fatal("%s while talking to %s" %(msg,host)) + except ImportError, msg: + fatal("ssl support missing (%s)" %msg) + +def send_request(req,proc_f): + ''' + 1. After every request, the iLO closes the connection. + 2. For every request, there are multiple replies. Each reply + is an XML document. Most of replies are just a kind of + (verbose) XML "OK". + ''' + t_begin = time.time() + c = open_ilo(rihost) + try: + c.send(req+'\r\n') + except socket.error, msg: + fatal("%s, while talking to %s" %(msg,rihost)) + t_end = time.time() + my_debug("request sent in %0.2f s" % ((t_end-t_begin))) + + t_begin = time.time() + result = [] + while True: + try: + reply = c.sock.recv(1024) + if not reply: + break + result.append(reply) + except socket.error, msg: + if msg[0] == 6: # connection closed + break + my_err("%s, while talking to %s" %(msg,rihost)) + return -1 + c.close() + t_end = time.time() + + if not result: + fatal("no response from %s within %0.2f s"%(rihost,(t_end-t_begin))) + for reply in result: + # work around the iLO bug, i.e. element RIBCL closed twice + if re.search("</RIBCL", reply) and re.search("<RIBCL.*/>", reply): + reply = re.sub("<(RIBCL.*)/>", r"<\1>", reply) + try: + doc = xml.dom.minidom.parseString(reply) + except xml.parsers.expat.ExpatError,msg: + fatal("malformed response: %s\n%s"%(msg,reply)) + rc = proc_f(doc) + doc.unlink() + if rc != 0: + break + my_debug("iLO processed request (rc=%d) in %0.2f s" % (rc,(t_end-t_begin))) + return rc + +def manage_power(cmd): + ''' + Before trying to send a request we have to check the power + state. + ''' + rc = 0 + req = '' + # it won't do to turn it on if it's already on! + if cmd == "on" and not power: + req = new_power_req(E_SET_POWER,A_POWER_SW,"Y") + # also to turn it off if it's already off + elif cmd == "off" and power: + req = new_power_req(E_SET_POWER,A_POWER_SW,"N") + elif cmd == "cold_boot" and power: + req = new_power_req(E_COLD_BOOT) + elif cmd == "warm_boot" and power: + req = new_power_req(E_WARM_BOOT) + elif cmd == "reset": + if power: + req = new_power_req(E_RESET) + # reset doesn't work if the host's off + else: + req = new_power_req(E_SET_POWER,A_POWER_SW,"Y") + if req: + rc = send_request(req,proc_resp) + return rc +def power_on(): + ''' + Update the power variable without checking the power state. + The iLO is slow at times to report the actual power state, so + we assume that it changed if the request succeeded. + ''' + rc = manage_power("on") + if rc == 0: + global power + power = True + return rc +def power_off(): + rc = manage_power("off") + if rc == 0: + global power + power = False + return rc +def cold_boot(): + rc = manage_power("cold_boot") + return rc +def warm_boot(): + rc = manage_power("warm_boot") + return rc +def reset(): + rc = manage_power("reset") + if rc == 0: + global power + power = True + return rc +def hold_button(): + ''' + Hold the power button. Got this error message when tried + without the TOGGLE attribute: + Command without TOGGLE="Yes" attribute is ignored + when host power is off. (rc: 0x0054) + Didn't find any documentation about TOGGLE. + ''' + if power: + req = new_power_req(E_HOLD_BUTTON) + else: + req = new_power_req(E_HOLD_BUTTON,"TOGGLE","Yes") + rc = send_request(req,proc_resp) + return rc +def read_power_state(): + req = new_power_req(E_GET_PSTATUS) + rc = send_request(req,proc_resp) + return rc + +def reset_power(): + ''' + Three methods to reset: + - hold power button + - reset (only if host has power and user said that reset is ok) + - power off/on + ''' + do_power_on = False + if power_method == 'button': + rc = hold_button() + elif reset_ok != '0': + if power: + return reset() + else: + return power_on() + else: + do_power_on = True + rc = power_off() + if rc == 0: + rc = read_power_state() + if do_power_on: + while rc == 0 and power: # wait for the power state to go off + time.sleep(5) + rc = read_power_state() + if rc == 0 and do_power_on and not power: + rc = power_on() + return rc + +# track state of host power +power = -1 + +todo = { +'reset':reset_power, +'on':power_on, +'off':power_off, +'cold':cold_boot, +'warm':warm_boot, +'status':lambda: 0 # just return 0, we already read the state +} + +rc = read_power_state() +if rc == 0: + if cmd in todo: + rc = todo[cmd]() + else: + fatal('Invalid command: %s' % cmd) +if rc != 0: + fatal("request failed") +sys.exit(rc) + +# vi:ts=4:sw=4:et: diff --git a/lib/plugins/stonith/external/ssh.in b/lib/plugins/stonith/external/ssh.in new file mode 100644 index 0000000..2a8eb73 --- /dev/null +++ b/lib/plugins/stonith/external/ssh.in @@ -0,0 +1,176 @@ +#!/bin/sh +# +# External STONITH module for ssh. +# +# Copyright (c) 2004 SUSE LINUX AG - Lars Marowsky-Bree <lmb@suse.de> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +SSH_COMMAND="@SSH@ -q -x -o PasswordAuthentication=no -o StrictHostKeyChecking=no -n -l root" +#SSH_COMMAND="@SSH@ -q -x -n -l root" + +REBOOT_COMMAND="echo 'sleep 2; @REBOOT@ @REBOOT_OPTIONS@' | SHELL=/bin/sh at now >/dev/null 2>&1" + +# Warning: If you select this poweroff command, it'll physically +# power-off the machine, and quite a number of systems won't be remotely +# revivable. +# TODO: Probably should touch a file on the server instead to just +# prevent heartbeat et al from being started after the reboot. +# POWEROFF_COMMAND="echo 'sleep 2; /sbin/poweroff -nf' | SHELL=/bin/sh at now >/dev/null 2>&1" +POWEROFF_COMMAND="echo 'sleep 2; @REBOOT@ @REBOOT_OPTIONS@' | SHELL=/bin/sh at now >/dev/null 2>&1" + +# Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo $hostlist | tr ',' ' '` + +is_host_up() { + for j in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + do + if + ping -w1 -c1 "$1" >/dev/null 2>&1 + then + sleep 1 + else + return 1 + fi + done + return 0 +} + + +case $1 in +gethosts) + for h in $hostlist ; do + echo $h + done + exit 0 + ;; +on) + # Can't really be implemented because ssh cannot power on a system + # when it is powered off. + exit 1 + ;; +off) + # Shouldn't really be implemented because if ssh cannot power on a + # system, it shouldn't be allowed to power it off. + exit 1 + ;; +reset) + h_target=`echo $2 | tr A-Z a-z` + for h in $hostlist + do + h=`echo $h | tr A-Z a-z` + [ "$h" != "$h_target" ] && + continue + if + case ${livedangerously} in + [Yy]*) is_host_up $h;; + *) true;; + esac + then + $SSH_COMMAND "$2" "$REBOOT_COMMAND" + # Good thing this is only for testing... + if + is_host_up $h + then + exit 1 + else + exit 0 + fi + else + # well... Let's call it successful, after all this is only for testing... + exit 0 + fi + done + exit 1 + ;; +status) + if + [ -z "$hostlist" ] + then + exit 1 + fi + for h in $hostlist + do + if + ping -w1 -c1 "$h" 2>&1 | grep "unknown host" + then + exit 1 + fi + done + exit 0 + ;; +getconfignames) + echo "hostlist" + exit 0 + ;; +getinfo-devid) + echo "ssh STONITH device" + exit 0 + ;; +getinfo-devname) + echo "ssh STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ssh-based host reset" + echo "Fine for testing, but not suitable for production!" + echo "Only reboot action supported, no poweroff, and, surprisingly enough, no poweron." + exit 0 + ;; +getinfo-devurl) + echo "http://openssh.org" + exit 0 + ;; +getinfo-xml) + cat << SSHXML +<parameters> +<parameter name="hostlist" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostlist +</shortdesc> +<longdesc lang="en"> +The list of hosts that the STONITH device controls +</longdesc> +</parameter> + +<parameter name="livedangerously" unique="0" required="0"> +<content type="enum" /> +<shortdesc lang="en"> +Live Dangerously!! +</shortdesc> +<longdesc lang="en"> +Set to "yes" if you want to risk your system's integrity. +Of course, since this plugin isn't for production, using it +in production at all is a bad idea. On the other hand, +setting this parameter to yes makes it an even worse idea. +Viva la Vida Loca! +</longdesc> +</parameter> + +</parameters> +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/vcenter b/lib/plugins/stonith/external/vcenter new file mode 100755 index 0000000..71a6302 --- /dev/null +++ b/lib/plugins/stonith/external/vcenter @@ -0,0 +1,280 @@ +#!/usr/bin/env perl +# +# External STONITH module for VMWare vCenter/ESX +# +# Author: Nhan Ngo Dinh +# License: GNU General Public License (GPL) +# + +require 5.010; + +use strict; +use warnings; + +sub dielog { + my $msg = "["; + $msg .= "$ARGV[0]" if defined($ARGV[0]); + $msg .= " $ARGV[1]" if defined($ARGV[1]); + $msg .= "]"; + ( $_ ) = @_; + $msg .= " $_"; + system("ha_log.sh", "err", "$msg"); + die(); +} + +# Define command groups +my @configCommands = qw{getconfignames getinfo-devid getinfo-devname getinfo-devdescr getinfo-devurl getinfo-xml}; +my @actionCommands = qw{reset on off}; +my @netCommands = (@actionCommands, qw{status gethosts listvms}); + +# Process command line arguments +my $command = $ARGV[0] || dielog("No command specified\n"); + +# Command belongs to the group of commands that do not require any connection to VMware vCenter +if ($command ~~ @configCommands) { + if ($command eq "getconfignames") { + print "VI_SERVER\nVI_PORTNUMBER\nVI_PROTOCOL\nVI_SERVICEPATH\nVI_CREDSTORE\nHOSTLIST\nRESETPOWERON\n"; + } + elsif ($command eq "getinfo-devid") { + print "VMware vCenter STONITH device\n"; + } + elsif ($command eq "getinfo-devname") { + print "VMware vCenter STONITH device\n"; + } + elsif ($command eq "getinfo-devdescr") { + print "VMWare vCenter STONITH device\n"; + } + elsif ($command eq "getinfo-devurl") { + print "http://www.vmware.com/\n"; + } + elsif ($command eq "getinfo-xml") { + print q{<parameters> +<parameter name="HOSTLIST" required="1"> +<content type="string"/> +<shortdesc lang="en">List of hosts and virtual machines (required)</shortdesc> +<longdesc lang="en"> +The list of hosts that the VMware vCenter STONITH device controls. +Syntax is: + hostname1[=VirtualMachineName1];hostname2[=VirtualMachineName2] + +NOTE: omit =VirtualMachineName if hostname and virtual machine names are identical + +Example: + cluster1=VMCL1;cluster2=VMCL2 +</longdesc> +</parameter> +<parameter name="VI_SERVER"> +<content type="string" default="localhost"/> +<shortdesc lang="en">VMware vCenter address</shortdesc> +<longdesc lang="en"> +The VMware vCenter address +</longdesc> +</parameter> +<parameter name="VI_PROTOCOL"> +<content type="string" default="https"/> +<shortdesc lang="en">VMware vCenter protocol</shortdesc> +<longdesc lang="en"> +The VMware vCenter protocol +</longdesc> +</parameter> +<parameter name="VI_PORTNUMBER"> +<content type="string" default="443"/> +<shortdesc lang="en">VMware vCenter port number</shortdesc> +<longdesc lang="en"> +The VMware vCenter port number +</longdesc> +</parameter> +<parameter name="VI_SERVICEPATH"> +<content type="string" default="/sdk"/> +<shortdesc lang="en">VMware vCenter service path</shortdesc> +<longdesc lang="en"> +The VMware vCenter services path +</longdesc> +</parameter> +<parameter name="VI_CREDSTORE" required="1"> +<content type="string"/> +<shortdesc lang="en">VMware vCenter credentials store file</shortdesc> +<longdesc lang="en"> +VMware vCenter credentials store file +</longdesc> +</parameter> +<parameter name="RESETPOWERON"> +<content type="string" default="1"/> +<shortdesc lang="en">PowerOnVM on reset</shortdesc> +<longdesc lang="en"> +Enable/disable a PowerOnVM on reset when the target virtual machine is off +Allowed values: 0, 1 +</longdesc> +</parameter> +<parameter name="PERL_LWP_SSL_VERIFY_HOSTNAME"> +<content type="string"/> +<shortdesc lang="en">Enable or disable SSL hostname verification</shortdesc> +<longdesc lang="en"> +To disable SSL hostname verification set this option to 0. +To enable hostname verification, set this option to 1. +This option is actually part of the LWP Perl library. +See LWP(3pm) for more information. +</longdesc> +</parameter> +</parameters>} . "\n"; + } + else { dielog("Invalid command specified: $command\n"); } +} + +# Command belongs to the group of commands that require connecting to VMware vCenter +elsif ($command ~~ @netCommands) { + + eval { require VMware::VIRuntime; } + or dielog("Missing perl module VMware::VIRuntime. Download and install 'VMware Infrastructure (VI) Perl Toolkit', available at http://www.vmware.com/support/developer/viperltoolkit/ \n"); + + # A valid VI_CREDSTORE is required to avoid interactive prompt + ( exists $ENV{'VI_CREDSTORE'} ) || dielog("VI_CREDSTORE not specified\n"); + + # HOSTLIST is mandatory + exists $ENV{'HOSTLIST'} || dielog("HOSTLIST not specified\n"); + + # Parse HOSTLIST to %host_to_vm and %vm_to_host + my @hostlist = split(';', $ENV{'HOSTLIST'}); + my %host_to_vm = (); + my %vm_to_host = (); + foreach my $host (@hostlist) { + my @config = split(/=/, $host); + my $key = $config[0]; my $value = $config[1]; + if (!defined($value)) { $value = $config[0]; } + $host_to_vm{$key} = $value; + $vm_to_host{(lc $value)} = $key; + } + + eval { + # VI API: reads options from the environment variables into appropriate data structures for validation. + Opts::parse(); + # VI API: ensures that input values from environment variable are complete, consistent and valid. + Opts::validate(); + # VI API: establishes a session with the VirtualCenter Management Server or ESX Server Web service + Util::connect(); + }; + if ($@) { + # This is just a placeholder for any error handling procedure + dielog($@); + } + + # Command belongs to the group of commands that performs actions on Virtual Machines + if ($command ~~ @actionCommands) { + + my $targetHost = $ARGV[1] || dielog("No target specified\n"); + + # Require that specified target host exists in the specified HOSTLIST + if (exists $host_to_vm{$targetHost}) { + + my $vm; + my $esx; + eval { + # VI API: searches the inventory tree for a VirtualMachine managed entity whose name matches + # the name of the virtual machine assigned to the target host in HOSTLIST + $vm = Vim::find_entity_view(view_type => "VirtualMachine", filter => { name => qr/^\Q$host_to_vm{$targetHost}\E/i }); + if (!defined $vm) { + dielog("Machine $targetHost was not found"); + } + + # VI API: retrieves the properties of the managed object reference runtime.host of the VirtualMachine + # managed entity obtained by the previous command + # NOTE: This is essentially a workaround to vSphere Perl SDK + # to allow pointing to the right HostSystem. This is probably + # done by changing the current HostSystem in the Web Service + # session context. WARNING: Do not use the same session for any + # other concurrent operation. + $esx = Vim::get_view(mo_ref => $vm->{"runtime"}{"host"})->name; + }; + if ($@) { + if (ref($@) eq "SoapFault") { dielog("$@->detail\n"); } + dielog($@); + } + + my $powerState = $vm->get_property('runtime.powerState')->val; + if ($powerState eq "suspended") { + # This implementation assumes that suspending a cluster node can cause + # severe failures on shared resources, thus any failover operation should + # be blocked. + dielog("Machine $esx:$vm->{'name'} is in a suspended state\n"); + } + + eval { + if ($command eq "reset") { + if ($powerState eq "poweredOn") { + $vm->ResetVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been reset"); + } else { + system("ha_log.sh", "warn", "Tried to ResetVM $esx:$vm->{'name'} that was $powerState"); + # Start a virtual machine on reset only if explicitly allowed by RESETPOWERON + if ($powerState eq "poweredOff" && (! exists $ENV{'RESETPOWERON'} || $ENV{'RESETPOWERON'} ne 0)) { + $vm->PowerOnVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been powered on"); + } else { + dielog("Could not complete $esx:$vm->{'name'} power cycle"); + } + } + } + elsif ($command eq "off") { + if ($powerState eq "poweredOn") { + $vm->PowerOffVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been powered off"); + } else { + system("ha_log.sh", "warn", "Tried to PowerOffVM $esx:$vm->{'name'} that was $powerState"); + + } + } + elsif ($command eq "on") { + if ($powerState eq "poweredOff") { + $vm->PowerOnVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been powered on"); + } else { + system("ha_log.sh", "warn", "Tried to PowerOnVM $esx:$vm->{'name'} that was $powerState"); + } + } + else { dielog("Invalid command specified: $command\n"); } + }; + if ($@) { + if (ref($@) eq "SoapFault") { dielog("$@->detail\n"); } + dielog($@); + } + + } else { dielog("Invalid target specified\n"); } + } else { + # Command belongs to the group of commands that lookup the status of VMware vCenter and/or virtual machines + if ($command eq "status") { + # we already connect to the vcenter, no need to do + # anything else in status + ; + } + elsif ($command eq "gethosts") { + foreach my $key (keys(%host_to_vm)) { + print "$key \n"; + } + } + elsif ($command eq "listvms") { + eval { + # VI API: Searches the inventory tree for all VirtualMachine managed objects + my $vms = Vim::find_entity_views(view_type => "VirtualMachine"); + if (defined $vms) { + printf(STDERR "%-50s %-20s\n", "VM Name", "Power state"); + print STDERR "-" x 70 . "\n"; + foreach my $vm (@$vms) { + my $powerState = $vm->get_property('runtime.powerState')->val; + printf("%-50s %-20s\n", $vm->{name}, $powerState); + } + } + }; + } + else { dielog("Invalid command specified: $command\n"); } + } + eval { + Util::disconnect(); + }; + if ($@) { + # This is just a placeholder for any error handling procedure + dielog($@); + } +} +else { dielog("Invalid command specified: $command\n"); } + +exit(0); diff --git a/lib/plugins/stonith/external/vmware b/lib/plugins/stonith/external/vmware new file mode 100644 index 0000000..55966ba --- /dev/null +++ b/lib/plugins/stonith/external/vmware @@ -0,0 +1,216 @@ +#!/usr/bin/perl +# External STONITH module for VMWare Server Guests +# +# Copyright (c) 2004 SUSE LINUX AG - Andrew Beekhof <abeekhof@suse.de> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +sub supply_default +{ + my $name = $_[0]; + my $value = $_[1]; + + if ( defined $ENV{$name} ) { + #print "Set: $name=$ENV{$name}\n"; + } else { + $ENV{$name} = $value; + #print "Default: $name=$ENV{$name}\n"; + } +} + +sub vmware_command +{ + my $config = $_[0]; + my $action = $_[1]; + my @lines; + + my $device = $ENV{'device_host'}; + + if ( $device =~ /localhost/ ) { + @lines = readpipe "vmware-cmd $config $action"; + + } else { + @lines = readpipe "ssh $device \"vmware-cmd \\\"$config\\\" $action\""; + } + + #print @lines; + return @lines; +} + +sub is_host_active +{ + my $config = config_for_host($_[0]); + my @lines = vmware_command($config, "getstate"); + foreach $line (@lines) { + if ( $line =~ /getstate.* = on/ ) { + return 1; + } + } + return 0; +} + +sub supported_hosts +{ + my $line; + my @lines; + + if ( defined $ENV{'host_map'} ) { + @lines = split(/;/, $ENV{'host_map'}); + foreach $line (@lines){ + @config = split(/=/, $line); + $host = $config[0]; + if ( is_host_active($host) == 1 ) { + print "$host\n"; + } + } + + } else { + @lines = vmware_command("-l"); + foreach $line (@lines){ + my @elements = split(/\//, $line); + $host = $elements[$#elements-1]; + if ( is_host_active($host) == 1 ) { + print "$host\n"; + } + } + } +} + +sub config_for_host +{ + my $line; + my @lines; + my $target = $_[0]; + + if ( defined $ENV{'host_map'} ) { + @lines = split(/;/, $ENV{'host_map'}); + foreach $line (@lines){ + if ( $line =~ /^$target=/ ) { + @config = split(/=/, $line); + return $config[1]; + } + } + + } else { + @lines = vmware_command("-l"); + + foreach $line (@lines){ + if ( $line =~ /\/$target\// ) { + chop($line); + return $line; + } + } + } +} + +$command = $ARGV[0]; +if ( defined $ARGV[1] ) { + $targetHost = $ARGV[1]; +} + +supply_default("device_host", "localhost"); + +if ( $command =~ /^gethosts$/ ) { + supported_hosts; + +} elsif ( $command =~ /^getconfignames$/ ) { + print "device_host\n"; + +} elsif ( $command =~ /^getinfo-devid$/ ) { + print "VMware Server STONITH device\n"; +} elsif ( $command =~ /^getinfo-devname$/ ) { + print "VMware Server STONITH device\n"; +} elsif ( $command =~ /^getinfo-devdescr$/ ) { + print "VMware Server STONITH device\n"; +} elsif ( $command =~ /^getinfo-devurl$/ ) { + print "http://www.vmware.com/"; + +} elsif ( $command =~ /^on$/ ) { + $config = config_for_host($targetHost); + my @lines = vmware_command($config, "start hard"); + print @lines; + +} elsif ( $command =~ /^off$/ ) { + $config = config_for_host($targetHost); + my @lines = vmware_command($config, "stop hard"); + print @lines; + +} elsif ( $command =~ /^reset$/ ) { + $config = config_for_host($targetHost); + my @lines = vmware_command($config, "reset hard"); + print @lines; + +} elsif ( $command =~ /^status$/ ) { + my $rc = 7; + my $device = $ENV{'device_host'}; + if ( $device =~ /localhost/ ) { + $rc = 0; + # TODO: Check for the vmware process + print "Local version: always running\n"; + + } else { + print "Remote version: running ping\n"; + @lines = readpipe "ping -c1 $device"; + print @lines; + + foreach $line ( @lines ) { + if ( $line =~ /0% packet loss/ ) { + $rc = 0; + last; + } + } + } + exit($rc); + +} elsif ( $command =~ /^getinfo-xml$/ ) { + $metadata = <<END; +<parameters> +<parameter name="host_map" unique="0" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Host Map +</shortdesc> +<longdesc lang="en"> +A mapping of hostnames to config paths supported by this device. +Eg. host1=/config/path/1;host2=/config/path/2;host3=/config/path/3; +</longdesc> +</parameter> +<parameter name="device_host" unique="0" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Device Host +</shortdesc> +<longdesc lang="en"> +The machine _hosting_ the virtual machines +</longdesc> +</parameter> +</parameters> +END + +print $metadata; + +} else { + print "Command $command: not supported\n"; + exit(3); # Not implemented +} + + +exit(0); diff --git a/lib/plugins/stonith/external/xen0 b/lib/plugins/stonith/external/xen0 new file mode 100644 index 0000000..ef1ee40 --- /dev/null +++ b/lib/plugins/stonith/external/xen0 @@ -0,0 +1,253 @@ +#!/bin/sh +# +# External STONITH module for Xen Dom0 through ssh. +# +# Description: Uses Xen Dom0 Domain as a STONITH device +# to control DomUs. +# +# +# Author: Serge Dubrouski (sergeyfd@gmail.com) +# Inspired by Lars Marowsky-Bree's external/ssh agent. +# +# Copyright 2007 Serge Dubrouski <sergeyfd@gmail.com> +# License: GNU General Public License (GPL) +# + +STOP_COMMAND="xm destroy" +START_COMMAND="xm create" +DUMP_COMMAND="xm dump-core" +DEFAULT_XEN_DIR="/etc/xen" +SSH_COMMAND="/usr/bin/ssh -q -x -n" + +# Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo $hostlist | tr ',' ' '` + +CheckIfDead() { + for j in 1 2 3 4 5 + do + if ! ping -w1 -c1 "$1" >/dev/null 2>&1 + then + return 0 + fi + sleep 1 + done + + return 1 +} + +CheckHostList() { + if [ "x" = "x$hostlist" ] + then + ha_log.sh err "hostlist isn't set" + exit 1 + fi +} + +CheckDom0() { + if [ "x" = "x$dom0" ] + then + ha_log.sh err "dom0 isn't set" + exit 1 + fi +} + +RunCommand() { + CheckHostList + CheckDom0 + + for h in $hostlist + do + CIFS=$IFS + IFS=: + read node cfg << -! +$h +-! + IFS=$CIFS + + if [ "x" = "x$node" ] + then + ha_log.sh err "Syntax error in host list" + exit 1 + fi + + if [ "x" = "x$cfg" ] + then + cfg="${DEFAULT_XEN_DIR}/${node}.cfg" + fi + + if [ "$node" != "$1" ] + then + continue + fi + + case $2 in + stop) + kill_node=`$SSH_COMMAND $dom0 "grep ^[[:space:]]*name $cfg" | cut -f 2 -d '=' | sed -e 's,",,g'` + if [ "x" = "x$kill_node" ] + then + ha_log.sh err "Couldn't find a node name to stop" + exit 1 + fi + + if [ "x$run_dump" != "x" ] + then + #Need to run core dump + if [ "x$dump_dir" != "x" ] + then + #Dump with the specified core file + TIMESTAMP=`date +%Y-%m%d-%H%M.%S` + DOMAINNAME=`printf "%s" $kill_node` + COREFILE=$dump_dir/$TIMESTAMP-$DOMAINNAME.core + $SSH_COMMAND $dom0 "(mkdir -p $dump_dir; $DUMP_COMMAND $kill_node $COREFILE) >/dev/null 2>&1" + else + $SSH_COMMAND $dom0 "$DUMP_COMMAND $kill_node >/dev/null 2>&1" + fi + fi + $SSH_COMMAND $dom0 "(sleep 2; $STOP_COMMAND $kill_node) >/dev/null 2>&1 &" + break;; + start) + $SSH_COMMAND $dom0 "(sleep 2; $START_COMMAND $cfg) >/dev/null 2>&1 &" + break;; + esac + exit 0 + done +} + + +# Main code + +case $1 in +gethosts) + CheckHostList + + for h in $hostlist ; do + CIFS=$IFS + IFS=: + read node cfg << -! +$h +-! + IFS=$CIFS + + echo $node + done + exit 0 + ;; +on) + RunCommand $2 start + exit $? + ;; +off) + if RunCommand $2 stop + then + if CheckIfDead $2 + then + exit 0 + fi + fi + + exit 1 + ;; +reset) + RunCommand $2 stop + + if CheckIfDead $2 + then + RunCommand $2 start + exit 0 + fi + + exit 1 + ;; +status) + CheckHostList + + for h in $hostlist + do + CIFS=$IFS + IFS=: + read node cfg << -! +$h +-! + IFS=$CIFS + + echo $node + if ping -w1 -c1 "$node" 2>&1 | grep "unknown host" + then + exit 1 + fi + done + exit 0 + ;; +getconfignames) + echo "hostlist dom0" + exit 0 + ;; +getinfo-devid) + echo "xen0 STONITH device" + exit 0 + ;; +getinfo-devname) + echo "xen0 STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ssh-based host reset for Xen DomU trough Dom0" + echo "Fine for testing, but not really suitable for production!" + exit 0 + ;; +getinfo-devurl) + echo "http://openssh.org http://www.xensource.com/ http://linux-ha.org/wiki" + exit 0 + ;; +getinfo-xml) + cat << SSHXML +<parameters> +<parameter name="hostlist" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostlist +</shortdesc> +<longdesc lang="en"> +The list of controlled nodes in a format node[:config_file]. +For example: "node1:/opt/xen/node1.cfg node2" +If config file isn't set it defaults to /etc/xen/{node_name}.cfg +</longdesc> +</parameter> +<parameter name="dom0" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Dom0 +</shortdesc> +<longdesc lang="en"> +Name of the Dom0 Xen node. Root user shall be able to ssh to that node. +</longdesc> +</parameter> +<parameter name="run_dump" unique="0" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Run dump-core +</shortdesc> +<longdesc lang="en"> +If set plugin will call "xm dump-core" before killing DomU +</longdesc> +</parameter> +<parameter name="dump_dir" unique="1" required="0"> +<content type="string" /> +<shortdesc lang="en"> +Run dump-core with the specified directory +</shortdesc> +<longdesc lang="en"> +This parameter can indicate the dump destination. +Should be set as a full path format, ex.) "/var/log/dump" +The above example would dump the core, like; +/var/log/dump/2009-0316-1403.37-domU.core +</longdesc> +</parameter> +</parameters> +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper b/lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper new file mode 100755 index 0000000..b313f8b --- /dev/null +++ b/lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper @@ -0,0 +1,72 @@ +#!/bin/bash +# Author: Lars Marowsky-Bree +# +# Copyright 2008 Lars Marowsky-Bree +# License: GNU General Public License (GPL) + +# This is not an external/stonith plugin by itself, but instead a helper +# script which gets installed in Dom0. + +# TODO: +# - Error handling +# - How to handle if the DomU resource doesn't exist? +# - Does this truly work with split-brain? +# - Is the handling of non-existent resources adequate? +# ... +# Basically: more testing. This is proof-of-concept and works, but deserves +# validation. + +CMD="$1" +DOMU="$2" +TIMEOUT="$3" + +# Make sure the timeout is an integer: +if [ "0$TIMEOUT" -eq 0 ]; then + TIMEOUT=300 +fi + +SetTargetRole() { + local new_role="$1" + crm_resource -r $DOMU --meta -p target_role -v $new_role + + local timeout="$TIMEOUT" + + # We only need to wait for "stopped". + if [ "$new_role" != "stopped" ]; then + return 0 + fi + + while [ $timeout -gt 0 ]; do + local rc + crm_resource -W -r $DOMU 2>&1 | grep -q "is NOT running" + rc=$? + if [ $rc -eq 0 ]; then + return 0 + fi + timeout=$[timeout-1]; + sleep 1 + done + return 1 +} + + +case $CMD in +on) SetTargetRole started + exit $? + ;; +off) SetTargetRole stopped + exit $? + ;; +reset) SetTargetRole stopped || exit 1 + SetTargetRole started + exit $? + ;; +status) exit 0 + ;; +*) ha_log.sh err "Called with unknown command: $CMD" + exit 1 + ;; +esac + +exit 1 + diff --git a/lib/plugins/stonith/external/xen0-ha.in b/lib/plugins/stonith/external/xen0-ha.in new file mode 100755 index 0000000..cb42cbc --- /dev/null +++ b/lib/plugins/stonith/external/xen0-ha.in @@ -0,0 +1,96 @@ +#!/bin/bash +# +# This STONITH script integrates a cluster running within DomUs +# with the CRM/Pacemaker cluster running in Dom0. +# +# Author: Lars Marowsky-Bree +# Copyright: 2008 Lars Marowsky-Bree +# License: GNU General Public License (GPL) +# + +SSH_COMMAND="@SSH@ -q -x -n" +HVM_HELPER="@stonith_plugindir@/xen0-ha-dom0-stonith-helper" + +# Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo $hostlist | tr ',' ' '` + +# Runs a command on the host, waiting for it to return +RunHVMCommand() { + $SSH_COMMAND $dom0_cluster_ip "$HVM_HELPER $1 $2 $stop_timeout" +} + +# Main code +case $1 in +gethosts) + echo $hostlist + exit 0 + ;; +on|off|reset|status) + RunHVMCommand $1 $2 + exit $? + ;; +getconfignames) + echo "hostlist dom0_cluster_ip timeout" + exit 0 + ;; +getinfo-devid) + echo "xen0-ha DomU/Dom0 device" + exit 0 + ;; +getinfo-devname) + echo "xen0-ha DomU/Dom0 external device" + exit 0 + ;; +getinfo-devdescr) + echo "Allows STONITH to control DomUs managed by a CRM/Pacemaker Dom0." + echo "Requires Xen + CRM/Pacemaker at both layers." + echo "Proof-of-concept code!" + exit 0 + ;; +getinfo-devurl) + echo "http://linux-ha.org/wiki/DomUClusters" + exit 0 + ;; +getinfo-xml) + cat << SSHXML +<parameters> +<parameter name="hostlist" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Hostlist +</shortdesc> +<longdesc lang="en"> +The list of controlled DomUs, separated by whitespace. +These must be configured as Xen RA resources with a name with a matching +id. +For example: "xen-1 xen-2 xen-3" +</longdesc> +</parameter> +<parameter name="dom0_cluster_ip" unique="1" required="1"> +<content type="string" /> +<shortdesc lang="en"> +Dom0 cluster ip +</shortdesc> +<longdesc lang="en"> +The cluster IP address associated with Dom0. +Root user must be able to ssh to that node. +</longdesc> +</parameter> +<parameter name="stop_timeout"> +<content type="integer" /> +<shortdesc lang="en"> +Stop timeout +</shortdesc> +<longdesc lang="en"> +The timeout, in seconds, for which to wait for Dom0 to report that the +DomU has been stopped, before aborting with a failure. +</longdesc> +</parameter> +</parameters> +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/ibmhmc.c b/lib/plugins/stonith/ibmhmc.c new file mode 100644 index 0000000..d33fea9 --- /dev/null +++ b/lib/plugins/stonith/ibmhmc.c @@ -0,0 +1,1261 @@ +/* + * Stonith module for IBM Hardware Management Console (HMC) + * + * Author: Huang Zhen <zhenh@cn.ibm.com> + * Support for HMC V4+ added by Dave Blaschke <debltc@us.ibm.com> + * + * Copyright (c) 2004 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * + * This code has been tested in following environment: + * + * Hardware Management Console (HMC): Release 3, Version 2.4 + * - Both FullSystemPartition and LPAR Partition: + * - p630 7028-6C4 two LPAR partitions + * - p650 7038-6M2 one LPAR partition and FullSystemPartition + * + * Hardware Management Console (HMC): Version 4, Release 2.1 + * - OP720 1000-6CA three LPAR partitions + * + * Note: Only SSH access to the HMC devices are supported. + * + * This command would make a nice status command: + * + * lshmc -r -F ssh + * + * The following V3 command will get the list of systems we control and their + * mode: + * + * lssyscfg -r sys -F name:mode --all + * + * 0 indicates full system partition + * 255 indicates the system is partitioned + * + * The following V4 command will get the list of systems we control: + * + * lssyscfg -r sys -F name + * + * The following V3 command will get the list of partitions for a given managed + * system running partitioned: + * + * lssyscfg -m managed-system -r lpar -F name --all + * + * Note that we should probably only consider partitions whose boot mode + * is normal (1). (that's my guess, anyway...) + * + * The following V4 command will get the list of partitions for a given managed + * system running partitioned: + * + * lssyscfg -m managed-system -r lpar -F name + * + * The following V3 commands provide the reset/on/off actions: + * + * FULL SYSTEM: + * on: chsysstate -m %1 -r sys -o on -n %1 -c full + * off: chsysstate -m %1 -r sys -o off -n %1 -c full -b norm + * reset:chsysstate -m %1 -r sys -o reset -n %1 -c full -b norm + * + * Partitioned SYSTEM: + * on: chsysstate -m %1 -r lpar -o on -n %2 + * off: reset_partition -m %1 -p %2 -t hard + * reset:do off action above, followed by on action... + * + * where %1 is managed-system, %2 is-lpar name + * + * The following V4 commands provide the reset/on/off actions: + * + * on: chsysstate -m %1 -r lpar -o on -n %2 -f %3 + * off: chsysstate -m %1 -r lpar -o shutdown -n %2 --immed + * reset:chsysstate -m %1 -r lpar -o shutdown -n %2 --immed --restart + * + * where %1 is managed-system, %2 is lpar-name, %3 is profile-name + * + * Of course, to do all this, we need to track which partition name goes with + * which managed system's name, and which systems on the HMC are partitioned + * and which ones aren't... + */ + +#include <lha_internal.h> + +#define DEVICE "IBM HMC" + +#include "stonith_plugin_common.h" + +#ifndef SSH_CMD +# define SSH_CMD "ssh" +#endif +#ifndef HMCROOT +# define HMCROOT "hscroot" +#endif + +#define PIL_PLUGIN ibmhmc +#define PIL_PLUGIN_S "ibmhmc" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#define MAX_HOST_NAME_LEN (256*4) +#define MAX_CMD_LEN 2048 +#define FULLSYSTEMPARTITION "FullSystemPartition" +#define MAX_POWERON_RETRY 10 +#define MAX_HMC_NAME_LEN 256 + +#define ST_MANSYSPAT "managedsyspat" +#define NOPASS "nopass" + +#define STATE_UNKNOWN -1 +#define STATE_OFF 0 +#define STATE_ON 1 +#define STATE_INVALID 2 + +#define HMCURL "http://publib-b.boulder.ibm.com/redbooks.nsf/RedbookAbstracts"\ + "/SG247038.html" + +static StonithPlugin * ibmhmc_new(const char *); +static void ibmhmc_destroy(StonithPlugin *); +static const char * ibmhmc_getinfo(StonithPlugin * s, int InfoType); +static const char * const * ibmhmc_get_confignames(StonithPlugin* p); +static int ibmhmc_status(StonithPlugin * ); +static int ibmhmc_reset_req(StonithPlugin * s,int request,const char* host); +static char ** ibmhmc_hostlist(StonithPlugin *); +static int ibmhmc_set_config(StonithPlugin *, StonithNVpair*); + +static struct stonith_ops ibmhmcOps = { + ibmhmc_new, /* Create new STONITH object */ + ibmhmc_destroy, /* Destroy STONITH object */ + ibmhmc_getinfo, /* Return STONITH info string */ + ibmhmc_get_confignames, /* Return configuration parameters */ + ibmhmc_set_config, /* Set configuration */ + ibmhmc_status, /* Return STONITH device status */ + ibmhmc_reset_req, /* Request a reset */ + ibmhmc_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &ibmhmcOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + char * idinfo; + char * hmc; + GList* hostlist; + int hmcver; + char * password; + char ** mansyspats; +}; + +static const char * pluginid = "HMCDevice-Stonith"; +static const char * NOTpluginID = "IBM HMC device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_MANSYSPAT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_MANSYSPAT \ + XML_PARM_SHORTDESC_END + +#define XML_MANSYSPAT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "White-space delimited list of patterns used to match managed system names; if last character is '*', all names that begin with the pattern are matched" \ + XML_PARM_LONGDESC_END + +#define XML_MANSYSPAT_PARM \ + XML_PARAMETER_BEGIN(ST_MANSYSPAT, "string", "0", "0") \ + XML_MANSYSPAT_SHORTDESC \ + XML_MANSYSPAT_LONGDESC \ + XML_PARAMETER_END + +#define XML_OPTPASSWD_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "Password for " HMCROOT " if passwordless ssh access to HMC has NOT been setup (to do so, it is necessary to create a public/private key pair with empty passphrase - see \"Configure the OpenSSH Client\" in the redbook at " HMCURL " for more details)" \ + XML_PARM_LONGDESC_END + +#define XML_OPTPASSWD_PARM \ + XML_PARAMETER_BEGIN(ST_PASSWD, "string", "0", "0") \ + XML_PASSWD_SHORTDESC \ + XML_OPTPASSWD_LONGDESC \ + XML_PARAMETER_END + +static const char *ibmhmcXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_MANSYSPAT_PARM + XML_OPTPASSWD_PARM + XML_PARAMETERS_END; + +static int get_hmc_hostlist(struct pluginDevice* dev); +static void free_hmc_hostlist(struct pluginDevice* dev); +static int get_hmc_mansyspats(struct pluginDevice* dev, const char* mansyspats); +static void free_hmc_mansyspats(struct pluginDevice* dev); +static char* do_shell_cmd(const char* cmd, int* status, const char* password); +static int check_hmc_status(struct pluginDevice* dev); +static int get_num_tokens(char *str); +static gboolean pattern_match(char **patterns, char *string); +/* static char* do_shell_cmd_fake(const char* cmd, int* status); */ + +static int +ibmhmc_status(StonithPlugin *s) +{ + struct pluginDevice* dev = NULL; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + dev = (struct pluginDevice*) s; + + return check_hmc_status(dev); +} + + +/* + * Return the list of hosts configured for this HMC device + */ + +static char ** +ibmhmc_hostlist(StonithPlugin *s) +{ + int j; + struct pluginDevice* dev; + int numnames = 0; + char** ret = NULL; + GList* node = NULL; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + dev = (struct pluginDevice*) s; + + /* refresh the hostlist */ + free_hmc_hostlist(dev); + if (S_OK != get_hmc_hostlist(dev)){ + LOG(PIL_CRIT, "unable to obtain list of managed systems in %s" + , __FUNCTION__); + return NULL; + } + + numnames = g_list_length(dev->hostlist); + if (numnames < 0) { + LOG(PIL_CRIT, "unconfigured stonith object in %s" + , __FUNCTION__); + return(NULL); + } + + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return ret; + } + + memset(ret, 0, (numnames+1)*sizeof(char*)); + for (node = g_list_first(dev->hostlist), j = 0 + ; NULL != node + ; j++, node = g_list_next(node)) { + char* host = strchr((char*)node->data, '/'); + ret[j] = STRDUP(++host); + if (ret[j] == NULL) { + LOG(PIL_CRIT, "out of memory"); + stonith_free_hostlist(ret); + return NULL; + } + strdown(ret[j]); + } + return ret; +} + + +static const char * const * +ibmhmc_get_confignames(StonithPlugin* p) +{ + static const char * names[] = {ST_IPADDR, NULL}; + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + return names; +} + + +/* + * Reset the given host, and obey the request type. + * We should reset without power cycle for the non-partitioned case + */ + +static int +ibmhmc_reset_req(StonithPlugin * s, int request, const char * host) +{ + GList* node = NULL; + struct pluginDevice* dev = NULL; + char off_cmd[MAX_CMD_LEN]; + char on_cmd[MAX_CMD_LEN]; + char reset_cmd[MAX_CMD_LEN]; + gchar** names = NULL; + int i; + int is_lpar = FALSE; + int status; + char* pch; + char* output = NULL; + char state_cmd[MAX_CMD_LEN]; + int state = STATE_UNKNOWN; + + status = 0; + if(Debug){ + LOG(PIL_DEBUG, "%s: called, host=%s\n", __FUNCTION__, host); + } + + ERRIFWRONGDEV(s,S_OOPS); + + if (NULL == host) { + LOG(PIL_CRIT, "invalid argument to %s", __FUNCTION__); + return(S_OOPS); + } + + dev = (struct pluginDevice*) s; + + for (node = g_list_first(dev->hostlist) + ; NULL != node + ; node = g_list_next(node)) { + if(Debug){ + LOG(PIL_DEBUG, "%s: node->data=%s\n" + , __FUNCTION__, (char*)node->data); + } + + if ((pch = strchr((char*)node->data, '/')) != NULL + && 0 == strcasecmp(++pch, host)) { + break; + } + } + + if (!node) { + LOG(PIL_CRIT + , "Host %s is not configured in this STONITH module. " + "Please check your configuration information.", host); + return (S_OOPS); + } + + names = g_strsplit((char*)node->data, "/", 2); + /* names[0] will be the name of managed system */ + /* names[1] will be the name of the lpar partition */ + if(Debug){ + LOG(PIL_DEBUG, "%s: names[0]=%s, names[1]=%s\n" + , __FUNCTION__, names[0], names[1]); + } + + if (dev->hmcver < 4) { + if (0 == strcasecmp(names[1], FULLSYSTEMPARTITION)) { + is_lpar = FALSE; + + snprintf(off_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r sys -m %s -o off -n %s -c full" + , dev->hmc, dev->hmc, names[0]); + + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r sys -m %s -o on -n %s -c full -b norm" + , dev->hmc, names[0], names[0]); + + snprintf(reset_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r sys -m %s -o reset -n %s -c full -b norm" + , dev->hmc, names[0], names[0]); + + *state_cmd = 0; + }else{ + is_lpar = TRUE; + + snprintf(off_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s reset_partition" + " -m %s -p %s -t hard" + , dev->hmc, names[0], names[1]); + + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r lpar -m %s -o on -n %s" + , dev->hmc, names[0], names[1]); + + *reset_cmd = 0; + + snprintf(state_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lssyscfg" + " -r lpar -m %s -F state -n %s" + , dev->hmc, names[0], names[1]); + } + }else{ + is_lpar = TRUE; + + snprintf(off_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -m %s -r lpar -o shutdown -n \"%s\" --immed" + , dev->hmc, names[0], names[1]); + + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lssyscfg" + " -m %s -r lpar -F \"default_profile\"" + " --filter \"lpar_names=%s\"" + , dev->hmc, names[0], names[1]); + + output = do_shell_cmd(on_cmd, &status, dev->password); + if (output == NULL) { + LOG(PIL_CRIT, "command %s failed", on_cmd); + return (S_OOPS); + } + if ((pch = strchr(output, '\n')) != NULL) { + *pch = 0; + } + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -m %s -r lpar -o on -n %s -f %s" + , dev->hmc, names[0], names[1], output); + FREE(output); + output = NULL; + + snprintf(reset_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -m %s -r lpar -o shutdown -n %s --immed --restart" + , dev->hmc, names[0], names[1]); + + snprintf(state_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lssyscfg" + " -m %s -r lpar -F state --filter \"lpar_names=%s\"" + , dev->hmc, names[0], names[1]); + } + g_strfreev(names); + + if(Debug){ + LOG(PIL_DEBUG, "%s: off_cmd=%s, on_cmd=%s," + "reset_cmd=%s, state_cmd=%s\n" + , __FUNCTION__, off_cmd, on_cmd, reset_cmd, state_cmd); + } + + output = do_shell_cmd(state_cmd, &status, dev->password); + if (output == NULL) { + LOG(PIL_CRIT, "command %s failed", on_cmd); + return S_OOPS; + } + if ((pch = strchr(output, '\n')) != NULL) { + *pch = 0; + } + if (strcmp(output, "Running") == 0 + || strcmp(output, "Starting") == 0 + || strcmp(output, "Open Firmware") == 0) { + state = STATE_ON; + }else if (strcmp(output, "Shutting Down") == 0 + || strcmp(output, "Not Activated") == 0 + || strcmp(output, "Ready") == 0) { + state = STATE_OFF; + }else if (strcmp(output, "Not Available") == 0 + || strcmp(output, "Error") == 0) { + state = STATE_INVALID; + } + FREE(output); + output = NULL; + + if (state == STATE_INVALID) { + LOG(PIL_CRIT, "host %s in invalid state", host); + return S_OOPS; + } + + switch (request) { + case ST_POWERON: + if (state == STATE_ON) { + LOG(PIL_INFO, "host %s already on", host); + return S_OK; + } + + output = do_shell_cmd(on_cmd, &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s failed", on_cmd); + return S_OOPS; + } + break; + case ST_POWEROFF: + if (state == STATE_OFF) { + LOG(PIL_INFO, "host %s already off", host); + return S_OK; + } + + output = do_shell_cmd(off_cmd, &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s failed", off_cmd); + return S_OOPS; + } + break; + case ST_GENERIC_RESET: + if (dev->hmcver < 4) { + if (is_lpar) { + if (state == STATE_ON) { + output = do_shell_cmd(off_cmd + , &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s " + "failed", off_cmd); + return S_OOPS; + } + } + for (i = 0; i < MAX_POWERON_RETRY; i++) { + char *output2; + output2 = do_shell_cmd(on_cmd + , &status, dev->password); + if (output2 != NULL) { + FREE(output2); + } + if (0 != status) { + sleep(1); + }else{ + break; + } + } + if (MAX_POWERON_RETRY == i) { + LOG(PIL_CRIT, "command %s failed" + , on_cmd); + return S_OOPS; + } + }else{ + output = do_shell_cmd(reset_cmd + , &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s failed" , reset_cmd); + return S_OOPS; + } + break; + } + }else{ + if (state == STATE_ON) { + output = do_shell_cmd(reset_cmd + , &status, dev->password); + }else{ + output = do_shell_cmd(on_cmd + , &status, dev->password); + } + if (0 != status) { + LOG(PIL_CRIT, "command %s failed", reset_cmd); + return S_OOPS; + } + } + break; + default: + return S_INVAL; + } + + if (output != NULL) { + FREE(output); + } + + LOG(PIL_INFO, "Host %s %s %d.", host, __FUNCTION__, request); + + return S_OK; +} + + +/* + * Parse the information in the given configuration file, + * and stash it away... + */ + +static int +ibmhmc_set_config(StonithPlugin * s, StonithNVpair* list) +{ + struct pluginDevice* dev = NULL; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {NULL, NULL} + }; + int rc; + char get_hmcver[MAX_CMD_LEN]; + char firstchar; + int firstnum; + char* output = NULL; + int status; + const char *mansyspats; + int len; + + ERRIFWRONGDEV(s,S_OOPS); + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + dev = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + if(Debug){ + LOG(PIL_DEBUG, "%s: ipaddr=%s\n", __FUNCTION__ + , namestocopy[0].s_value); + } + + if (get_num_tokens(namestocopy[0].s_value) == 1) { + /* name=value pairs on command line, look for managedsyspat */ + mansyspats = OurImports->GetValue(list, ST_MANSYSPAT); + if (mansyspats != NULL) { + if (get_hmc_mansyspats(dev, mansyspats) != S_OK) { + FREE(namestocopy[0].s_value); + return S_OOPS; + } + } + /* look for password */ + dev->password = STRDUP(OurImports->GetValue(list, ST_PASSWD)); + dev->hmc = namestocopy[0].s_value; + }else{ + /* -p or -F option with args "ipaddr [managedsyspat]..." */ + char *pch = namestocopy[0].s_value; + + /* skip over ipaddr and null-terminate */ + pch += strcspn(pch, WHITESPACE); + *pch = EOS; + + /* skip over white-space up to next token */ + pch++; + pch += strspn(pch, WHITESPACE); + if (get_hmc_mansyspats(dev, pch) != S_OK) { + FREE(namestocopy[0].s_value); + return S_OOPS; + } + + dev->hmc = STRDUP(namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + } + + /* check whether the HMC has ssh command enabled */ + if (check_hmc_status(dev) != S_OK) { + LOG(PIL_CRIT, "HMC %s does not have remote " + "command execution using the ssh facility enabled", dev->hmc); + return S_BADCONFIG; + } + + /* get the HMC's version info */ + snprintf(get_hmcver, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lshmc -v | grep RM", dev->hmc); + if (Debug) { + LOG(PIL_DEBUG, "%s: get_hmcver=%s", __FUNCTION__, get_hmcver); + } + + output = do_shell_cmd(get_hmcver, &status, dev->password); + if (Debug) { + LOG(PIL_DEBUG, "%s: output=%s\n", __FUNCTION__ + , output ? output : "(nil)"); + } + if (output == NULL) { + return S_BADCONFIG; + } + + /* parse the HMC's version info (i.e. "*RM V4R2.1" or "*RM R3V2.6") */ + if ((sscanf(output, "*RM %c%1d", &firstchar, &firstnum) == 2) + && ((firstchar == 'V') || (firstchar == 'R'))) { + dev->hmcver = firstnum; + if(Debug){ + LOG(PIL_DEBUG, "%s: HMC %s version is %d" + , __FUNCTION__, dev->hmc, dev->hmcver); + } + }else{ + LOG(PIL_CRIT, "%s: unable to determine HMC %s version" + , __FUNCTION__, dev->hmc); + FREE(output); + return S_BADCONFIG; + } + + len = strlen(output+4) + sizeof(DEVICE) + 1; + if (dev->idinfo != NULL) { + FREE(dev->idinfo); + dev->idinfo = NULL; + } + dev->idinfo = MALLOC(len * sizeof(char)); + if (dev->idinfo == NULL) { + LOG(PIL_CRIT, "out of memory"); + FREE(output); + return S_OOPS; + } + snprintf(dev->idinfo, len, "%s %s", DEVICE, output+4); + FREE(output); + + if (S_OK != get_hmc_hostlist(dev)){ + LOG(PIL_CRIT, "unable to obtain list of managed systems in %s" + , __FUNCTION__); + return S_BADCONFIG; + } + + return S_OK; +} + + +static const char* +ibmhmc_getinfo(StonithPlugin* s, int reqtype) +{ + struct pluginDevice* dev; + const char* ret; + + ERRIFWRONGDEV(s,NULL); + + dev = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = dev->idinfo; + break; + + case ST_DEVICENAME: + ret = dev->hmc; + break; + + case ST_DEVICEDESCR: + ret = "IBM Hardware Management Console (HMC)\n" + "Use for IBM i5, p5, pSeries and OpenPower systems " + "managed by HMC\n" + " Optional parameter name " ST_MANSYSPAT " is " + "white-space delimited list of\n" + "patterns used to match managed system names; if last " + "character is '*',\n" + "all names that begin with the pattern are matched\n" + " Optional parameter name " ST_PASSWD " is password " + "for " HMCROOT " if passwordless\n" + "ssh access to HMC has NOT been setup (to do so, it " + "is necessary to create\n" + "a public/private key pair with empty passphrase - " + "see \"Configure the\n" + "OpenSSH client\" in the redbook for more details)"; + break; + + case ST_DEVICEURL: + ret = HMCURL; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = ibmhmcXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + + +/* + * HMC Stonith destructor... + */ + +static void +ibmhmc_destroy(StonithPlugin *s) +{ + struct pluginDevice* dev; + + if(Debug){ + LOG(PIL_DEBUG, "%s : called\n", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + dev = (struct pluginDevice *)s; + + dev->pluginid = NOTpluginID; + if (dev->hmc) { + FREE(dev->hmc); + dev->hmc = NULL; + } + if (dev->password) { + FREE(dev->password); + dev->password = NULL; + } + if (dev->idinfo) { + FREE(dev->idinfo); + dev->idinfo = NULL; + } + free_hmc_hostlist(dev); + free_hmc_mansyspats(dev); + + FREE(dev); +} + + +static StonithPlugin * +ibmhmc_new(const char *subplugin) +{ + struct pluginDevice* dev = ST_MALLOCT(struct pluginDevice); + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + if (dev == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return(NULL); + } + + memset(dev, 0, sizeof(*dev)); + + dev->pluginid = pluginid; + dev->hmc = NULL; + dev->password = NULL; + dev->hostlist = NULL; + dev->mansyspats = NULL; + dev->hmcver = -1; + REPLSTR(dev->idinfo, DEVICE); + if (dev->idinfo == NULL) { + FREE(dev); + return(NULL); + } + dev->sp.s_ops = &ibmhmcOps; + + if(Debug){ + LOG(PIL_DEBUG, "%s: returning successfully\n", __FUNCTION__); + } + + return((void *)dev); +} + +static int +get_hmc_hostlist(struct pluginDevice* dev) +{ + int i, j, status; + char* output = NULL; + char get_syslist[MAX_CMD_LEN]; + char host[MAX_HOST_NAME_LEN]; + gchar** syslist = NULL; + gchar** name_mode = NULL; + char get_lpar[MAX_CMD_LEN]; + gchar** lparlist = NULL; + char* pch; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, dev->hmc=%s\n", __FUNCTION__ + , dev->hmc); + } + + if (dev->hmc == NULL || *dev->hmc == 0){ + return S_BADCONFIG; + } + + /* get the managed system's names of the hmc */ + if (dev->hmcver < 4) { + snprintf(get_syslist, MAX_CMD_LEN, SSH_CMD " -l " HMCROOT + " %s lssyscfg -r sys -F name:mode --all", dev->hmc); + }else{ + snprintf(get_syslist, MAX_CMD_LEN, SSH_CMD + " -l " HMCROOT " %s lssyscfg -r sys -F name", dev->hmc); + } + if(Debug){ + LOG(PIL_DEBUG, "%s: get_syslist=%s", __FUNCTION__, get_syslist); + } + + output = do_shell_cmd(get_syslist, &status, dev->password); + if (output == NULL) { + return S_BADCONFIG; + } + syslist = g_strsplit(output, "\n", 0); + FREE(output); + + /* for each managed system */ + for (i = 0; syslist[i] != NULL && syslist[i][0] != 0; i++) { + if (dev->hmcver < 4) { + name_mode = g_strsplit(syslist[i], ":", 2); + if(Debug){ + LOG(PIL_DEBUG, "%s: name_mode0=%s, name_mode1=%s\n" + , __FUNCTION__, name_mode[0], name_mode[1]); + } + + if (dev->mansyspats != NULL + && !pattern_match(dev->mansyspats, name_mode[0])) { + continue; + } + + /* if it is in fullsystempartition */ + if (NULL != name_mode[1] + && 0 == strncmp(name_mode[1], "0", 1)) { + /* add the FullSystemPartition */ + snprintf(host, MAX_HOST_NAME_LEN + , "%s/FullSystemPartition", name_mode[0]); + dev->hostlist = g_list_append(dev->hostlist + , STRDUP(host)); + }else if (NULL != name_mode[1] + && 0 == strncmp(name_mode[1], "255", 3)){ + /* get its lpars */ + snprintf(get_lpar, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT + " %s lssyscfg -m %s -r lpar -F name --all" + , dev->hmc, name_mode[0]); + if(Debug){ + LOG(PIL_DEBUG, "%s: get_lpar=%s\n" + , __FUNCTION__, get_lpar); + } + + output = do_shell_cmd(get_lpar + , &status, dev->password); + if (output == NULL) { + g_strfreev(name_mode); + g_strfreev(syslist); + return S_BADCONFIG; + } + lparlist = g_strsplit(output, "\n", 0); + FREE(output); + + /* for each lpar */ + for (j = 0 + ; NULL != lparlist[j] && 0 != lparlist[j][0] + ; j++) { + /* skip the full system partition */ + if (0 == strncmp(lparlist[j] + , FULLSYSTEMPARTITION + , strlen(FULLSYSTEMPARTITION))) { + continue; + } + /* add the lpar */ + snprintf(host, MAX_HOST_NAME_LEN + , "%s/%s", name_mode[0] + , lparlist[j]); + dev->hostlist = + g_list_append(dev->hostlist + , STRDUP(host)); + } + g_strfreev(lparlist); + } + g_strfreev(name_mode); + }else{ + if (dev->mansyspats != NULL + && !pattern_match(dev->mansyspats, syslist[i])) { + continue; + } + + /* get its state */ + snprintf(get_lpar, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT + " %s lssyscfg -m %s -r sys -F state" + , dev->hmc, syslist[i]); + if(Debug){ + LOG(PIL_DEBUG, "%s: get_lpar=%s\n" + , __FUNCTION__, get_lpar); + } + + output = do_shell_cmd(get_lpar, &status, dev->password); + if (output == NULL) { + g_strfreev(syslist); + return S_BADCONFIG; + } + if ((pch = strchr(output, '\n')) != NULL) { + *pch = 0; + } + if (!strcmp(output, "No Connection")){ + FREE(output); + continue; + } + FREE(output); + + /* get its lpars */ + snprintf(get_lpar, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT + " %s lssyscfg -m %s -r lpar -F name" + , dev->hmc, syslist[i]); + if(Debug){ + LOG(PIL_DEBUG, "%s: get_lpar=%s\n" + , __FUNCTION__, get_lpar); + } + + output = do_shell_cmd(get_lpar, &status, dev->password); + if (output == NULL) { + g_strfreev(syslist); + return S_BADCONFIG; + } + lparlist = g_strsplit(output, "\n", 0); + FREE(output); + + /* for each lpar */ + for (j = 0 + ; NULL != lparlist[j] && 0 != lparlist[j][0] + ; j++) { + /* add the lpar */ + snprintf(host, MAX_HOST_NAME_LEN + , "%s/%s", syslist[i],lparlist[j]); + dev->hostlist = g_list_append(dev->hostlist + , STRDUP(host)); + } + g_strfreev(lparlist); + } + } + g_strfreev(syslist); + + return S_OK; +} + +static void +free_hmc_hostlist(struct pluginDevice* dev) +{ + if (dev->hostlist) { + GList* node; + while (NULL != (node=g_list_first(dev->hostlist))) { + dev->hostlist = g_list_remove_link(dev->hostlist, node); + FREE(node->data); + g_list_free(node); + } + dev->hostlist = NULL; + } +} + +static int +get_hmc_mansyspats(struct pluginDevice * dev, const char *mansyspats) +{ + char *patscopy; + int numpats; + int i; + char *tmp; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, mansyspats=%s\n" + , __FUNCTION__, mansyspats); + } + + patscopy = STRDUP(mansyspats); + if (patscopy == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return S_OOPS; + } + + numpats = get_num_tokens(patscopy); + if (numpats > 0) { + dev->mansyspats = MALLOC((numpats+1)*sizeof(char *)); + if (dev->mansyspats == NULL) { + LOG(PIL_CRIT, "%s: out of memory" + , __FUNCTION__); + FREE(patscopy); + return S_OOPS; + } + + memset(dev->mansyspats, 0, (numpats+1)*sizeof(char *)); + + /* White-space split the output here */ + i = 0; + tmp = strtok(patscopy, WHITESPACE); + while (tmp != NULL) { + dev->mansyspats[i] = STRDUP(tmp); + if (dev->mansyspats[i] == NULL) { + LOG(PIL_CRIT, "%s: out of memory" + , __FUNCTION__); + free_hmc_mansyspats(dev); + dev->mansyspats = NULL; + FREE(patscopy); + return S_OOPS; + } + + if(Debug){ + LOG(PIL_DEBUG, "%s: adding pattern %s\n" + , __FUNCTION__, dev->mansyspats[i]); + } + + /* no patterns necessary if all specified */ + if (strcmp(dev->mansyspats[i], "*") == 0) { + stonith_free_hostlist(dev->mansyspats); + dev->mansyspats = NULL; + break; + } + + i++; + tmp = strtok(NULL, WHITESPACE); + } + } + FREE(patscopy); + return S_OK; +} + +static void +free_hmc_mansyspats(struct pluginDevice* dev) +{ + if (dev->mansyspats) { + stonith_free_hostlist(dev->mansyspats); + dev->mansyspats = NULL; + } +} + +static char* +do_shell_cmd(const char* cmd, int* status, const char* password) +{ + const int BUFF_LEN=4096; + int read_len = 0; + char buff[BUFF_LEN]; + char cmd_password[MAX_CMD_LEN]; + char* data = NULL; + GString* g_str_tmp = NULL; + + FILE* file; + if (NULL == password) { + file = popen(cmd, "r"); + } else { + snprintf(cmd_password, MAX_CMD_LEN + ,"umask 077;" + "if [ ! -d " HA_VARRUNDIR "/heartbeat/rsctmp/ibmhmc ];" + "then mkdir " HA_VARRUNDIR "/heartbeat/rsctmp/ibmhmc 2>/dev/null;" + "fi;" + "export ibmhmc_tmp=`mktemp -p " HA_VARRUNDIR "/heartbeat/rsctmp/ibmhmc/`;" + "echo \"echo '%s'\">$ibmhmc_tmp;" + "chmod +x $ibmhmc_tmp;" + "unset SSH_AGENT_SOCK SSH_AGENT_PID;" + "SSH_ASKPASS=$ibmhmc_tmp DISPLAY=ibmhmc_foo setsid %s;" + "rm $ibmhmc_tmp -f;" + "unset ibmhmc_tmp" + ,password, cmd); + file = popen(cmd_password, "r"); + } + if (NULL == file) { + return NULL; + } + + g_str_tmp = g_string_new(""); + while(!feof(file)) { + memset(buff, 0, BUFF_LEN); + read_len = fread(buff, 1, BUFF_LEN, file); + if (0 < read_len) { + g_string_append(g_str_tmp, buff); + }else{ + sleep(1); + } + } + data = (char*)MALLOC(g_str_tmp->len+1); + if (data != NULL) { + data[0] = data[g_str_tmp->len] = 0; + strncpy(data, g_str_tmp->str, g_str_tmp->len); + } + g_string_free(g_str_tmp, TRUE); + *status = pclose(file); + return data; +} + +static int +check_hmc_status(struct pluginDevice* dev) +{ + int status; + char check_status[MAX_CMD_LEN]; + char* output = NULL; + int rc = S_OK; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, hmc=%s\n", __FUNCTION__, dev->hmc); + } + + snprintf(check_status, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lshmc -r -F ssh", dev->hmc); + if(Debug){ + LOG(PIL_DEBUG, "%s: check_status %s\n", __FUNCTION__ + , check_status); + } + + output = do_shell_cmd(check_status, &status, dev->password); + if (Debug) { + LOG(PIL_DEBUG, "%s: status=%d, output=%s\n", __FUNCTION__ + , status, output ? output : "(nil)"); + } + + if (NULL == output || strncmp(output, "enable", 6) != 0) { + rc = S_BADCONFIG; + } + if (NULL != output) { + FREE(output); + } + return rc; +} + +static int +get_num_tokens(char *str) +{ + int namecount = 0; + + while (*str != EOS) { + str += strspn(str, WHITESPACE); + if (*str == EOS) + break; + str += strcspn(str, WHITESPACE); + namecount++; + } + return namecount; +} + +static gboolean +pattern_match(char **patterns, char *string) +{ + char **pattern; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, string=%s\n", __FUNCTION__, string); + } + + for (pattern = patterns; *pattern; pattern++) { + int patlen = strlen(*pattern); + + if (pattern[0][patlen-1] == '*') { + /* prefix match */ + if (strncmp(string, *pattern, patlen-1) == 0) { + return TRUE; + } + }else{ + /* exact match */ + if (strcmp(string, *pattern) == 0) { + return TRUE; + } + } + } + + return FALSE; +} + +/* +static char* +do_shell_cmd_fake(const char* cmd, int* status) +{ + printf("%s()\n", __FUNCTION__); + printf("cmd:%s\n", cmd); + *status=0; + return NULL; +} +*/ diff --git a/lib/plugins/stonith/ipmi_os_handler.c b/lib/plugins/stonith/ipmi_os_handler.c new file mode 100644 index 0000000..bdb6d6e --- /dev/null +++ b/lib/plugins/stonith/ipmi_os_handler.c @@ -0,0 +1,257 @@ +/* + * This program is largely based on the ipmicmd.c program that's part of OpenIPMI package. + * + * Copyright Intel Corp. + * Yixiong.Zou@intel.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <stdlib.h> +#include <errno.h> +#include <stdio.h> +#include <OpenIPMI/os_handler.h> +#include <OpenIPMI/selector.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + + +#include <OpenIPMI/ipmi_int.h> + +#include <time.h> + +extern selector_t *os_sel; + +#if 0 +static void check_no_locks(os_handler_t *handler); +#define CHECK_NO_LOCKS(handler) check_no_locks(handler) +#else +#define CHECK_NO_LOCKS(handler) do {} while(0) +#endif + +struct os_hnd_fd_id_s +{ + int fd; + void *cb_data; + os_data_ready_t data_ready; + os_handler_t *handler; +}; + +static void +fd_handler(int fd, void *data) +{ + + os_hnd_fd_id_t *fd_data = (os_hnd_fd_id_t *) data; + + CHECK_NO_LOCKS(fd_data->handler); + fd_data->data_ready(fd, fd_data->cb_data, fd_data); + CHECK_NO_LOCKS(fd_data->handler); +} + +static int +add_fd(os_handler_t *handler, + int fd, + os_data_ready_t data_ready, + void *cb_data, + os_hnd_fd_id_t **id) +{ + os_hnd_fd_id_t *fd_data; + + fd_data = ipmi_mem_alloc(sizeof(*fd_data)); + if (!fd_data) + return ENOMEM; + + fd_data->fd = fd; + fd_data->cb_data = cb_data; + fd_data->data_ready = data_ready; + fd_data->handler = handler; + sel_set_fd_handlers(os_sel, fd, fd_data, fd_handler, NULL, NULL, NULL); + sel_set_fd_read_handler(os_sel, fd, SEL_FD_HANDLER_ENABLED); + sel_set_fd_write_handler(os_sel, fd, SEL_FD_HANDLER_DISABLED); + sel_set_fd_except_handler(os_sel, fd, SEL_FD_HANDLER_DISABLED); + + *id = fd_data; + return 0; +} + +static int +remove_fd(os_handler_t *handler, os_hnd_fd_id_t *fd_data) +{ + sel_clear_fd_handlers(os_sel, fd_data->fd); + sel_set_fd_read_handler(os_sel, fd_data->fd, SEL_FD_HANDLER_DISABLED); + ipmi_mem_free(fd_data); + return 0; +} + +struct os_hnd_timer_id_s +{ + void *cb_data; + os_timed_out_t timed_out; + sel_timer_t *timer; + int running; + os_handler_t *handler; +}; + +static void +timer_handler(selector_t *sel, + sel_timer_t *timer, + void *data) +{ + os_hnd_timer_id_t *timer_data = (os_hnd_timer_id_t *) data; + void *cb_data; + os_timed_out_t timed_out; + + CHECK_NO_LOCKS(timer_data->handler); + timed_out = timer_data->timed_out; + cb_data = timer_data->cb_data; + timer_data->running = 0; + timed_out(cb_data, timer_data); + CHECK_NO_LOCKS(timer_data->handler); +} + +static int +start_timer(os_handler_t *handler, + os_hnd_timer_id_t *id, + struct timeval *timeout, + os_timed_out_t timed_out, + void *cb_data) +{ + struct timeval now; + + if (id->running) + return EBUSY; + + id->running = 1; + id->cb_data = cb_data; + id->timed_out = timed_out; + + gettimeofday(&now, NULL); + now.tv_sec += timeout->tv_sec; + now.tv_usec += timeout->tv_usec; + while (now.tv_usec >= 1000000) { + now.tv_usec -= 1000000; + now.tv_sec += 1; + } + + return sel_start_timer(id->timer, &now); +} + +static int +stop_timer(os_handler_t *handler, os_hnd_timer_id_t *timer_data) +{ + return sel_stop_timer(timer_data->timer); +} + +static int +alloc_timer(os_handler_t *handler, + os_hnd_timer_id_t **id) +{ + os_hnd_timer_id_t *timer_data; + int rv; + + timer_data = ipmi_mem_alloc(sizeof(*timer_data)); + if (!timer_data) + return ENOMEM; + + timer_data->running = 0; + timer_data->timed_out = NULL; + timer_data->handler = handler; + + rv = sel_alloc_timer(os_sel, timer_handler, timer_data, + &(timer_data->timer)); + if (rv) { + ipmi_mem_free(timer_data); + return rv; + } + + *id = timer_data; + return 0; +} + +static int +free_timer(os_handler_t *handler, os_hnd_timer_id_t *timer_data) +{ + sel_free_timer(timer_data->timer); + ipmi_mem_free(timer_data); + return 0; +} + +static int +get_random(os_handler_t *handler, void *data, unsigned int len) +{ + int fd = open("/dev/urandom", O_RDONLY); + int rv; + + if (fd == -1) + return errno; + + rv = read(fd, data, len); + + close(fd); + return rv; +} + +static void +sui_log(os_handler_t *handler, + enum ipmi_log_type_e log_type, + char *format, + ...) +{ + return; +} + +static void +sui_vlog(os_handler_t *handler, + enum ipmi_log_type_e log_type, + char *format, + va_list ap) +{ + return; +} + + +os_handler_t ipmi_os_cb_handlers = +{ + .add_fd_to_wait_for = add_fd, + .remove_fd_to_wait_for = remove_fd, + + .start_timer = start_timer, + .stop_timer = stop_timer, + .alloc_timer = alloc_timer, + .free_timer = free_timer, + + .create_lock = NULL, + .destroy_lock = NULL, + .is_locked = NULL, + .lock = NULL, + .unlock = NULL, + .create_rwlock = NULL, + .destroy_rwlock = NULL, + .read_lock = NULL, + .write_lock = NULL, + .read_unlock = NULL, + .write_unlock = NULL, + .is_readlocked = NULL, + .is_writelocked = NULL, + + .get_random = get_random, + + .log = sui_log, + .vlog = sui_vlog +}; + + diff --git a/lib/plugins/stonith/ipmilan.c b/lib/plugins/stonith/ipmilan.c new file mode 100644 index 0000000..1efdfee --- /dev/null +++ b/lib/plugins/stonith/ipmilan.c @@ -0,0 +1,587 @@ +/* + * Stonith module for ipmi lan Stonith device + * + * Copyright (c) 2003 Intel Corp. + * Yixiong Zou <yixiong.zou@intel.com> + * + * Mangled by Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005. + * And passed the compiling with OpenIPMI-1.4.8. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + +/* + * See README.ipmi for information regarding this plugin. + * + */ + +#define DEVICE "IPMI Over LAN" + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN ipmilan +#define PIL_PLUGIN_S "ipmilan" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include <OpenIPMI/ipmi_types.h> +#include <OpenIPMI/ipmi_auth.h> + +#include "ipmilan.h" + +static StonithPlugin * ipmilan_new(const char *); +static void ipmilan_destroy(StonithPlugin *); +static const char * const * ipmilan_get_confignames(StonithPlugin *); +static int ipmilan_set_config(StonithPlugin *, StonithNVpair *); +static const char * ipmilan_getinfo(StonithPlugin * s, int InfoType); +static int ipmilan_status(StonithPlugin * ); +static int ipmilan_reset_req(StonithPlugin * s, int request, const char * host); +static char ** ipmilan_hostlist(StonithPlugin *); + +static struct stonith_ops ipmilanOps ={ + ipmilan_new, /* Create new STONITH object */ + ipmilan_destroy, /* Destroy STONITH object */ + ipmilan_getinfo, /* Return STONITH info string */ + ipmilan_get_confignames,/* Get configuration parameter names */ + ipmilan_set_config, /* Set configuration */ + ipmilan_status, /* Return STONITH device status */ + ipmilan_reset_req, /* Request a reset */ + ipmilan_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug); +const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &ipmilanOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * ipmilan STONITH device. + * + * ipmilanHostInfo is a double linked list. Where the prev of the head always + * points to the tail. This is a little wierd. But it saves me from looping + * around to find the tail when destroying the list. + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + int hostcount; + struct ipmilanHostInfo * hostlist; +}; + +static const char * pluginid = "IPMI-LANDevice-Stonith"; +static const char * NOTpluginid = "IPMI-LAN device has been destroyed"; + +#define ST_HOSTNAME "hostname" +#define ST_PORT "port" +#define ST_AUTH "auth" +#define ST_PRIV "priv" +#define ST_RESET_METHOD "reset_method" + +#include "stonith_config_xml.h" + +#define XML_HOSTNAME_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_HOSTNAME \ + XML_PARM_SHORTDESC_END + +#define XML_HOSTNAME_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The hostname of the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_HOSTNAME_PARM \ + XML_PARAMETER_BEGIN(ST_HOSTNAME, "string", "1", "1") \ + XML_HOSTNAME_SHORTDESC \ + XML_HOSTNAME_LONGDESC \ + XML_PARAMETER_END + +#define XML_PORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PORT \ + XML_PARM_SHORTDESC_END + +#define XML_PORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The port number to where the IPMI message is sent" \ + XML_PARM_LONGDESC_END + +#define XML_PORT_PARM \ + XML_PARAMETER_BEGIN(ST_PORT, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +#define XML_AUTH_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_AUTH \ + XML_PARM_SHORTDESC_END + +#define XML_AUTH_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The authorization type of the IPMI session (\"none\", \"straight\", \"md2\", or \"md5\")" \ + XML_PARM_LONGDESC_END + +#define XML_AUTH_PARM \ + XML_PARAMETER_BEGIN(ST_AUTH, "string", "1", "0") \ + XML_AUTH_SHORTDESC \ + XML_AUTH_LONGDESC \ + XML_PARAMETER_END + +#define XML_PRIV_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PRIV \ + XML_PARM_SHORTDESC_END + +#define XML_PRIV_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The privilege level of the user (\"operator\" or \"admin\")" \ + XML_PARM_LONGDESC_END + +#define XML_PRIV_PARM \ + XML_PARAMETER_BEGIN(ST_PRIV, "string", "1", "0") \ + XML_PRIV_SHORTDESC \ + XML_PRIV_LONGDESC \ + XML_PARAMETER_END + +#define XML_RESET_METHOD_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_RESET_METHOD \ + XML_PARM_SHORTDESC_END + +#define XML_RESET_METHOD_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "How to reset the host (\"power_cycle\" or \"hard_reset\")" \ + XML_PARM_LONGDESC_END + +#define XML_RESET_METHOD_PARM \ + XML_PARAMETER_BEGIN(ST_RESET_METHOD, "string", "0", "0") \ + XML_RESET_METHOD_SHORTDESC \ + XML_RESET_METHOD_LONGDESC \ + XML_PARAMETER_END + +static const char *ipmilanXML = + XML_PARAMETERS_BEGIN + XML_HOSTNAME_PARM + XML_IPADDR_PARM + XML_PORT_PARM + XML_AUTH_PARM + XML_PRIV_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +/* + * Check the status of the IPMI Lan STONITH device. + * + * NOTE: not sure what we should do here since each host is configured + * seperately. + * + * Two options: + * 1) always return S_OK. + * 2) using IPMI ping to confirm the status for every host that's + * configured. + * + * For now I choose the option 1 hoping that I can get by. Maybe we should + * change it to option 2 later. + */ + +static int +ipmilan_status(StonithPlugin *s) +{ + struct pluginDevice * nd; + struct ipmilanHostInfo * node; + int ret, rv; + int i; + + ERRIFWRONGDEV(s,S_OOPS); + + ret = S_OK; + + nd = (struct pluginDevice *)s; + for( i=0, node = nd->hostlist; + i < nd->hostcount; i++, node = node->next ) { + rv = do_ipmi_cmd(node, ST_IPMI_STATUS); + if (rv) { + LOG(PIL_INFO, "Host %s ipmilan status failure." + , node->hostname); + ret = S_ACCESS; + } else { + LOG(PIL_INFO, "Host %s ipmilan status OK." + , node->hostname); + } + + } + + return ret; +} + +/* + * This function returns the list of hosts that's configured. + * + * The detailed configuration is disabled because the STONITH command can be + * run by anyone so there is a security risk if that to be exposed. + */ + +static char * +get_config_string(struct pluginDevice * nd, int index) +{ + struct ipmilanHostInfo * host; + int i; + + if (index >= nd->hostcount || index < 0) { + return (NULL); + } + + host = nd->hostlist; + for (i = 0; i < index; i++) { + host = host->next; + } + + return STRDUP(host->hostname); +} + + +/* + * Return the list of hosts configured for this ipmilan device + * + */ + +static char ** +ipmilan_hostlist(StonithPlugin *s) +{ + int numnames = 0; + char ** ret = NULL; + struct pluginDevice* nd; + int j; + + ERRIFWRONGDEV(s,NULL); + + nd = (struct pluginDevice*) s; + if (nd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in ipmi_hostlist"); + return(NULL); + } + numnames = nd->hostcount; + + ret = (char **)MALLOC((numnames + 1)*sizeof(char*)); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return (ret); + } + + memset(ret, 0, (numnames + 1)*sizeof(char*)); + + for (j = 0; j < numnames; ++j) { + ret[j] = get_config_string(nd, j); + if (!ret[j]) { + stonith_free_hostlist(ret); + ret = NULL; + break; + } + strdown(ret[j]); + } + + return(ret); +} + +/* + * Parse the config information, and stash it away... + * + * The buffer for each string is MAX_IPMI_STRING_LEN bytes long. + * Right now it is set to 64. Hope this is enough. + * + */ + +#define MAX_IPMI_STRING_LEN 64 + +/* + * Reset the given host on this StonithPlugin device. + */ +static int +ipmilan_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = 0; + struct pluginDevice * nd; + struct ipmilanHostInfo * node; + int i; + + ERRIFWRONGDEV(s,S_OOPS); + + nd = (struct pluginDevice *)s; + for( i=0, node = nd->hostlist; + i < nd->hostcount; i++, node = node->next ) { + if (strcasecmp(node->hostname, host) == 0) { + break; + } + } + + if (i >= nd->hostcount) { + LOG(PIL_CRIT, "Host %s is not configured in this STONITH " + " module. Please check your configuration file.", host); + return (S_OOPS); + } + + rc = do_ipmi_cmd(node, request); + if (!rc) { + LOG(PIL_INFO, "Host %s ipmilan-reset.", host); + } else { + LOG(PIL_INFO, "Host %s ipmilan-reset error. Error = %d." + , host, rc); + } + return rc; +} + +/* + * Get configuration parameter names + */ +static const char * const * +ipmilan_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = + { ST_HOSTNAME, ST_IPADDR, ST_PORT, ST_AUTH, + ST_PRIV, ST_LOGIN, ST_PASSWD, ST_RESET_METHOD, NULL}; + return ret; +} + +/* + * Set the configuration parameters + */ +static int +ipmilan_set_config(StonithPlugin* s, StonithNVpair * list) +{ + struct pluginDevice* nd; + int rc; + struct ipmilanHostInfo * tmp; + const char *reset_opt; + + StonithNamesToGet namestocopy [] = + { {ST_HOSTNAME, NULL} + , {ST_IPADDR, NULL} + , {ST_PORT, NULL} + , {ST_AUTH, NULL} + , {ST_PRIV, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s,S_OOPS); + nd = (struct pluginDevice *)s; + + ERRIFWRONGDEV(s, S_OOPS); + if (nd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + tmp = ST_MALLOCT(struct ipmilanHostInfo); + tmp->hostname = namestocopy[0].s_value; + tmp->ipaddr = namestocopy[1].s_value; + tmp->portnumber = atoi(namestocopy[2].s_value); + FREE(namestocopy[2].s_value); + if (namestocopy[3].s_value == NULL) { + LOG(PIL_CRIT, "ipmilan auth type is NULL. See " + "README.ipmilan for allowed values"); + return S_OOPS; + } else if (strcmp(namestocopy[3].s_value, "none") == 0) { + tmp->authtype = 0; + } else if (strcmp(namestocopy[3].s_value, "md2") == 0) { + tmp->authtype = 1; + } else if (strcmp(namestocopy[3].s_value, "md5") == 0) { + tmp->authtype = 2; + } else if (strcmp(namestocopy[3].s_value, "key") == 0 || + strcmp(namestocopy[3].s_value, "password") == 0 || + strcmp(namestocopy[3].s_value, "straight") == 0) { + tmp->authtype = 4; + } else { + LOG(PIL_CRIT, "ipmilan auth type '%s' invalid. See " + "README.ipmilan for allowed values", namestocopy[3].s_value); + return S_OOPS; + } + FREE(namestocopy[3].s_value); + if (namestocopy[4].s_value == NULL) { + LOG(PIL_CRIT, "ipmilan priv value is NULL. See " + "README.ipmilan for allowed values"); + return S_OOPS; + } else if (strcmp(namestocopy[4].s_value, "operator") == 0) { + tmp->privilege = 3; + } else if (strcmp(namestocopy[4].s_value, "admin") == 0) { + tmp->privilege = 4; + } else { + LOG(PIL_CRIT, "ipmilan priv value '%s' invalid. See " + "README.ipmilan for allowed values", namestocopy[4].s_value); + return(S_OOPS); + } + FREE(namestocopy[4].s_value); + tmp->username = namestocopy[5].s_value; + tmp->password = namestocopy[6].s_value; + reset_opt = OurImports->GetValue(list, ST_RESET_METHOD); + if (!reset_opt || !strcmp(reset_opt, "power_cycle")) { + tmp->reset_method = 0; + } else if (!strcmp(reset_opt, "hard_reset")) { + tmp->reset_method = 1; + } else { + LOG(PIL_CRIT, "ipmilan reset_method '%s' invalid", reset_opt); + return S_OOPS; + } + + if (nd->hostlist == NULL ) { + nd->hostlist = tmp; + nd->hostlist->prev = tmp; + nd->hostlist->next = tmp; + } else { + tmp->prev = nd->hostlist->prev; + tmp->next = nd->hostlist; + nd->hostlist->prev->next = tmp; + nd->hostlist->prev = tmp; + } + nd->hostcount++; + + return(S_OK); +} + +static const char * +ipmilan_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice * nd; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + + case ST_DEVICENAME: + ret = nd->hostlist ? nd->hostlist->hostname : NULL; + break; + + case ST_DEVICEDESCR: + ret = "IPMI LAN STONITH device\n"; + break; + + case ST_DEVICEURL: + ret = "http://www.intel.com/design/servers/ipmi/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = ipmilanXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * ipmilan StonithPlugin destructor... + * + * The hostlist is a link list. So have to iterate through. + */ +static void +ipmilan_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + struct ipmilanHostInfo * host; + int i; + + VOIDERRIFWRONGDEV(s); + + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTpluginid; + + if (nd->hostlist) { + host = nd->hostlist->prev; + for (i = 0; i < nd->hostcount; i++) { + struct ipmilanHostInfo * host_prev = host->prev; + + FREE(host->hostname); + FREE(host->ipaddr); + FREE(host->username); + FREE(host->password); + + FREE(host); + host = host_prev; + } + } + + nd->hostcount = -1; + FREE(nd); + ipmi_leave(); +} + +/* Create a new ipmilan StonithPlugin device. Too bad this function can't be static */ +static StonithPlugin * +ipmilan_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + LOG(PIL_WARN, "The ipmilan stonith plugin is deprecated! Please use external/ipmi."); + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->hostlist = NULL; + nd->hostcount = 0; + nd->idinfo = DEVICE; + nd->sp.s_ops = &ipmilanOps; + return(&(nd->sp)); +} diff --git a/lib/plugins/stonith/ipmilan.h b/lib/plugins/stonith/ipmilan.h new file mode 100644 index 0000000..fb548f0 --- /dev/null +++ b/lib/plugins/stonith/ipmilan.h @@ -0,0 +1,41 @@ +/* + * Stonith module for ipmi lan Stonith device + * + * Copyright (c) 2003 Intel Corp. + * Yixiong Zou <yixiong.zou@intel.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define ST_IPMI_STATUS 4 +#include <time.h> + +struct ipmilanHostInfo { + char * hostname; + char * ipaddr; + int portnumber; + int authtype; + int privilege; + char * username; + char * password; + int reset_method; + + struct ipmilanHostInfo * prev; + struct ipmilanHostInfo * next; +}; + +int do_ipmi_cmd(struct ipmilanHostInfo * host, int request); +void ipmi_leave(void); diff --git a/lib/plugins/stonith/ipmilan_command.c b/lib/plugins/stonith/ipmilan_command.c new file mode 100644 index 0000000..a3de493 --- /dev/null +++ b/lib/plugins/stonith/ipmilan_command.c @@ -0,0 +1,399 @@ +/* + * This program is largely based on the ipmicmd.c program that's part of OpenIPMI package. + * + * Copyright Intel Corp. + * Yixiong.Zou@intel.com + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <stdio.h> + +#include <stdlib.h> /* malloc() */ +#include <unistd.h> /* getopt() */ +#include <string.h> /* strerror() */ +#include <netdb.h> /* gethostbyname() */ +#include <sys/types.h> +#include <sys/socket.h> + +#include <OpenIPMI/ipmiif.h> +#include <OpenIPMI/selector.h> +#include <OpenIPMI/ipmi_conn.h> +#include <OpenIPMI/ipmi_lan.h> +#include <OpenIPMI/ipmi_smi.h> +#include <OpenIPMI/ipmi_auth.h> +#include <OpenIPMI/ipmi_msgbits.h> +#include <OpenIPMI/ipmi_posix.h> +#include <OpenIPMI/ipmi_debug.h> + +#include "ipmilan.h" +#include <stonith/stonith.h> +#include <clplumbing/cl_log.h> + +#include <pils/plugin.h> +extern const PILPluginImports* PluginImports; + +/* #define DUMP_MSG 0 */ +#define OPERATION_TIME_OUT 10 + +os_handler_t *os_hnd=NULL; +selector_t *os_sel; +static ipmi_con_t *con; +extern os_handler_t ipmi_os_cb_handlers; +static int reset_method; + +static int request_done = 0; +static int op_done = 0; + +typedef enum ipmi_status { + /* + IPMI_CONNECTION_FAILURE, + IPMI_SEND_FAILURE, + IPMI_BAD_REQUEST, + IPMI_REQUEST_FAILED, + IPMI_TIME_OUT, + */ + IPMI_RUNNING = 99, +} ipmi_status_t; + +static ipmi_status_t gstatus; + +typedef enum chassis_control_request { + POWER_DOWN = 0X00, + POWER_UP = 0X01, + POWER_CYCLE = 0X02, + HARD_RESET = 0X03, + PULSE_DIAGNOSTIC_INTERRUPT = 0X04, + SOFT_SHUTDOWN = 0X05 +} chassis_control_request_t; + +void dump_msg_data(ipmi_msg_t *msg, ipmi_addr_t *addr, const char *type); +int rsp_handler(ipmi_con_t *ipmi, ipmi_msgi_t *rspi); + +void send_ipmi_cmd(ipmi_con_t *con, int request); + +void timed_out(selector_t *sel, sel_timer_t *timer, void *data); + +void +timed_out(selector_t *sel, sel_timer_t *timer, void *data) +{ + PILCallLog(PluginImports->log,PIL_CRIT, "IPMI operation timed out... :(\n"); + gstatus = S_TIMEOUT; +} + +void +dump_msg_data(ipmi_msg_t *msg, ipmi_addr_t *addr, const char *type) +{ + ipmi_system_interface_addr_t *smi_addr = NULL; + int i; + ipmi_ipmb_addr_t *ipmb_addr = NULL; + + if (addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) { + smi_addr = (struct ipmi_system_interface_addr *) addr; + + fprintf(stderr, "%2.2x %2.2x %2.2x %2.2x ", + addr->channel, + msg->netfn, + smi_addr->lun, + msg->cmd); + } else if ((addr->addr_type == IPMI_IPMB_ADDR_TYPE) + || (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE)) { + ipmb_addr = (struct ipmi_ipmb_addr *) addr; + + fprintf(stderr, "%2.2x %2.2x %2.2x %2.2x ", + addr->channel, + msg->netfn, + ipmb_addr->lun, + msg->cmd); + } + + for (i = 0; i < msg->data_len; i++) { + if (((i%16) == 0) && (i != 0)) { + printf("\n "); + } + fprintf(stderr, "%2.2x ", msg->data[i]); + } + fprintf(stderr, "\n"); +} + +/* + * This function gets called after the response comes back + * from the IPMI device. + * + * Some IPMI device does not return success, 0x00, to the + * remote node when the power-reset was issued. + * + * The host who sent the ipmi cmd might get a 0xc3, + * a timeout instead. This creates problems for + * STONITH operation, where status is critical. :( + * + * Right now I am only checking 0xc3 as the return. + * If your IPMI device returns some wired code after + * reset, you might want to add it in this code block. + * + */ + +int +rsp_handler(ipmi_con_t *ipmi, ipmi_msgi_t *rspi) +{ + int rv; + long request; + + /*dump_msg_data(&rspi->msg, &rspi->addr, "response");*/ + request = (long) rspi->data1; + + op_done = 1; + if( !rspi || !(rspi->msg.data) ) { + PILCallLog(PluginImports->log,PIL_CRIT, "No data received\n"); + gstatus = S_RESETFAIL; + return IPMI_MSG_ITEM_NOT_USED; + } + rv = rspi->msg.data[0]; + /* some IPMI device might not issue 0x00, success, for reset command. + instead, a 0xc3, timeout, is returned. */ + if (rv == 0x00) { + gstatus = S_OK; + } else if((rv == 0xc3 || rv == 0xff) && request == ST_GENERIC_RESET) { + PILCallLog(PluginImports->log,PIL_WARN , + "IPMI reset request failed: %x, but we assume that it succeeded\n", rv); + gstatus = S_OK; + } else { + PILCallLog(PluginImports->log,PIL_INFO + , "IPMI request %ld failed: %x\n", request, rv); + gstatus = S_RESETFAIL; + } + return IPMI_MSG_ITEM_NOT_USED; +} + +void +send_ipmi_cmd(ipmi_con_t *con, int request) +{ + ipmi_addr_t addr; + unsigned int addr_len; + ipmi_msg_t msg; + struct ipmi_system_interface_addr *si; + int rv; + ipmi_msgi_t *rspi; + /* chassis control command request is only 1 byte long */ + unsigned char cc_data = POWER_CYCLE; + + si = (void *) &addr; + si->lun = 0x00; + si->channel = IPMI_BMC_CHANNEL; + si->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; + addr_len = sizeof(*si); + + msg.netfn = IPMI_CHASSIS_NETFN; + msg.cmd = IPMI_CHASSIS_CONTROL_CMD; + msg.data = &cc_data; + msg.data_len = 1; + + switch (request) { + case ST_POWERON: + cc_data = POWER_UP; + break; + + case ST_POWEROFF: + cc_data = POWER_DOWN; + break; + + case ST_GENERIC_RESET: + cc_data = (reset_method ? POWER_CYCLE : HARD_RESET); + break; + + case ST_IPMI_STATUS: + msg.netfn = IPMI_APP_NETFN; + msg.cmd = IPMI_GET_DEVICE_ID_CMD; + msg.data_len = 0; + break; + + default: + gstatus = S_INVAL; + return; + } + + gstatus = S_ACCESS; + rspi = calloc(1, sizeof(ipmi_msgi_t)); + if (NULL == rspi) { + PILCallLog(PluginImports->log,PIL_CRIT, "Error sending IPMI command: Out of memory\n"); + } else { + rspi->data1 = (void *) (long) request; + rv = con->send_command(con, &addr, addr_len, &msg, rsp_handler, rspi); + if (rv == -1) { + PILCallLog(PluginImports->log,PIL_CRIT, "Error sending IPMI command: %x\n", rv); + } else { + request_done = 1; + } + } + + return; +} + +static void +con_changed_handler(ipmi_con_t *ipmi, int err, unsigned int port_num, + int still_connected, void *cb_data) +{ + int * request; + + if (err) { + PILCallLog(PluginImports->log,PIL_CRIT, "Unable to setup connection: %x\n", err); + return; + } + + if( !request_done ) { + request = (int *) cb_data; + send_ipmi_cmd(ipmi, *request); + } +} + +static int +setup_ipmi_conn(struct ipmilanHostInfo * host, int *request) +{ + int rv; + + struct hostent *ent; + struct in_addr lan_addr[2]; + int lan_port[2]; + int num_addr = 1; + int authtype = 0; + int privilege = 0; + char username[17]; + char password[17]; + + /*DEBUG_MSG_ENABLE();*/ + + os_hnd = ipmi_posix_get_os_handler(); + if (!os_hnd) { + PILCallLog(PluginImports->log,PIL_CRIT, "ipmi_smi_setup_con: Unable to allocate os handler"); + return 1; + } + + rv = sel_alloc_selector(os_hnd, &os_sel); + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "Could not allocate selector\n"); + return rv; + } + + ipmi_posix_os_handler_set_sel(os_hnd, os_sel); + + rv = ipmi_init(os_hnd); + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "ipmi_init erro: %d ", rv); + return rv; + } + + ent = gethostbyname(host->ipaddr); + if (!ent) { + PILCallLog(PluginImports->log,PIL_CRIT, "gethostbyname failed: %s\n", strerror(h_errno)); + return 1; + } + + memcpy(&lan_addr[0], ent->h_addr_list[0], ent->h_length); + lan_port[0] = host->portnumber; + lan_port[1] = 0; + + authtype = host->authtype; + privilege = host->privilege; + + memcpy(username, host->username, sizeof(username)); + memcpy(password, host->password, sizeof(password)); + + reset_method = host->reset_method; + + rv = ipmi_lan_setup_con(lan_addr, lan_port, num_addr, + authtype, privilege, + username, strlen(username), + password, strlen(password), + os_hnd, os_sel, + &con); + + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "ipmi_lan_setup_con: %s\n", strerror(rv)); + return S_ACCESS; + } + +#if OPENIPMI_VERSION_MAJOR < 2 + con->set_con_change_handler(con, con_changed_handler, request); +#else + con->add_con_change_handler(con, con_changed_handler, request); +#endif + + gstatus = IPMI_RUNNING; + + rv = con->start_con(con); + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "Could not start IPMI connection: %x\n", rv); + gstatus = S_BADCONFIG; + return rv; + } + return S_OK; +} + +void +ipmi_leave() +{ + if( con && con->close_connection ) { + con->close_connection(con); + con = NULL; + } + if( os_sel ) { + sel_free_selector(os_sel); + os_sel = NULL; + } +} + +int +do_ipmi_cmd(struct ipmilanHostInfo * host, int request) +{ + int rv; + sel_timer_t * timer; + struct timeval timeout; + + request_done = 0; + op_done = 0; + + if( !os_hnd ) { + rv = setup_ipmi_conn(host, &request); + if( rv ) { + return rv; + } + } else { + send_ipmi_cmd(con, request); + } + + gettimeofday(&timeout, NULL); + timeout.tv_sec += OPERATION_TIME_OUT; + timeout.tv_usec += 0; + + sel_alloc_timer(os_sel, timed_out, NULL, &timer); + sel_start_timer(timer, &timeout); + + while (!op_done) { + rv = sel_select(os_sel, NULL, 0, NULL, NULL); + if (rv == -1) { + break; + } + } + + sel_free_timer(timer); + return gstatus; +} + +#if OPENIPMI_VERSION_MAJOR < 2 +void +posix_vlog(char *format, enum ipmi_log_type_e log_type, va_list ap) +{ +} +#endif diff --git a/lib/plugins/stonith/ipmilan_test.c b/lib/plugins/stonith/ipmilan_test.c new file mode 100644 index 0000000..47859a0 --- /dev/null +++ b/lib/plugins/stonith/ipmilan_test.c @@ -0,0 +1,63 @@ +/* + * Stonith module for ipmi lan Stonith device + * + * Copyright (c) 2003 Intel Corp. + * Yixiong Zou <yixiong.zou@intel.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * A quick test program to verify that IPMI host is setup correctly. + * + * You will need to modify the values in user, pass, ip, and port. + */ + +#include <stdio.h> +#include <string.h> +#include "ipmilan.h" +#include <OpenIPMI/ipmi_auth.h> + +int main(int argc, char * argv[]) +{ + struct ipmilanHostInfo host; + int request = 2; + int rv; + + char user[] = "joe"; + char pass[] = "blow"; + char ip[] = "192.168.1.7"; + + host.hostname = NULL; + host.portnumber = 999; + host.authtype = IPMI_AUTHTYPE_NONE; + host.privilege = IPMI_PRIVILEGE_ADMIN; + + host.ipaddr = ip; + memcpy(host.username, user, sizeof(user)); + memcpy(host.password, pass, sizeof(pass)); + /* + memset(host.username, 0, sizeof(host.username)); + memset(host.password, 0, sizeof(host.password)); + */ + + rv = do_ipmi_cmd(&host, request); + if (rv) + printf("rv = %d, operation failed. \n", rv); + else + printf("operation succeeded. \n"); + return rv; +} diff --git a/lib/plugins/stonith/meatware.c b/lib/plugins/stonith/meatware.c new file mode 100644 index 0000000..029ba35 --- /dev/null +++ b/lib/plugins/stonith/meatware.c @@ -0,0 +1,351 @@ +/* + * Stonith module for Human Operator Stonith device + * + * Copyright (c) 2001 Gregor Binder <gbinder@sysfive.com> + * + * This module is largely based on the "NULL Stonith device", written + * by Alan Robertson <alanr@unix.sh>, using code by David C. Teigland + * <teigland@sistina.com> originally appeared in the GFS stomith + * meatware agent. + * + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> + +#define DEVICE "Meatware STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN meatware +#define PIL_PLUGIN_S "meatware" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +static StonithPlugin * meatware_new(const char *); +static void meatware_destroy(StonithPlugin *); +static int meatware_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * meatware_get_confignames(StonithPlugin *); +static const char * meatware_getinfo(StonithPlugin * s, int InfoType); +static int meatware_status(StonithPlugin * ); +static int meatware_reset_req(StonithPlugin * s, int request, const char * host); +static char ** meatware_hostlist(StonithPlugin *); + +static struct stonith_ops meatwareOps ={ + meatware_new, /* Create new STONITH object */ + meatware_destroy, /* Destroy STONITH object */ + meatware_getinfo, /* Return STONITH info string */ + meatware_get_confignames,/* Return STONITH info string */ + meatware_set_config, /* Get configuration from NVpairs */ + meatware_status, /* Return STONITH device status */ + meatware_reset_req, /* Request a reset */ + meatware_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &meatwareOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * Meatware STONITH device. + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static const char * pluginid = "MeatwareDevice-Stonith"; +static const char * NOTpluginID = "Meatware device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *meatwareXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +meatware_status(StonithPlugin *s) +{ + ERRIFWRONGDEV(s,S_OOPS); + return S_OK; +} + + +/* + * Return the list of hosts configured for this Meat device + */ + +static char ** +meatware_hostlist(StonithPlugin *s) +{ + struct pluginDevice* nd; + + ERRIFWRONGDEV(s,NULL); + nd = (struct pluginDevice*) s; + if (nd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in Meatware_list_hosts"); + return(NULL); + } + + return OurImports->CopyHostList((const char * const *)nd->hostlist); +} + +/* + * Parse the config information, and stash it away... + */ + +static int +Meat_parse_config_info(struct pluginDevice* nd, const char * info) +{ + LOG(PIL_INFO , "parse config info info=%s",info); + if (nd->hostcount >= 0) { + return(S_OOPS); + } + + nd->hostlist = OurImports->StringToHostList(info); + if (nd->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (nd->hostcount = 0; nd->hostlist[nd->hostcount]; nd->hostcount++) { + strdown(nd->hostlist[nd->hostcount]); + } + return(S_OK); +} + + +/* + * Indicate that host must be power cycled manually. + */ +static int +meatware_reset_req(StonithPlugin * s, int request, const char * host) +{ + int fd, rc; + const char * meatpipe_pr = HA_VARRUNDIR "/meatware"; /* if you intend to + change this, modify + meatclient.c as well */ + + char line[256], meatpipe[256]; + char resp_addr[50], resp_mw[50], resp_result[50]; + + + ERRIFWRONGDEV(s,S_OOPS); + + snprintf(meatpipe, 256, "%s.%s", meatpipe_pr, host); + umask(0); + unlink(meatpipe); + + rc = mkfifo(meatpipe, (S_IRUSR | S_IWUSR)); + + if (rc < 0) { + LOG(PIL_CRIT, "cannot create FIFO for Meatware_reset_host"); + return S_OOPS; + } + + LOG(PIL_CRIT, "OPERATOR INTERVENTION REQUIRED to reset %s.", host); + LOG(PIL_CRIT, "Run \"meatclient -c %s\" AFTER power-cycling the " + "machine.", host); + + fd = open(meatpipe, O_RDONLY); + + if (fd < 0) { + LOG(PIL_CRIT, "cannot open FIFO for Meatware_reset_host"); + return S_OOPS; + } + + alarm(600); + memset(line, 0, 256); + rc = read(fd, line, 256); + alarm(0); + + if (rc < 0) { + LOG(PIL_CRIT, "read error on FIFO for Meatware_reset_host"); + return S_OOPS; + } + + memset(resp_mw, 0, 50); + memset(resp_result, 0, 50); + memset(resp_addr, 0, 50); + + if (sscanf(line, "%s %s %s", resp_mw, resp_result, resp_addr) < 3) { + LOG(PIL_CRIT, "Format error - failed to Meatware-reset node %s", + host); + return S_RESETFAIL; + } + + strdown(resp_addr); + + if (strncmp(resp_mw, "meatware", 8) || + strncmp(resp_result, "reply", 5) || + strncasecmp(resp_addr, host, strlen(resp_addr))) { + LOG(PIL_CRIT, "failed to Meatware-reset node %s", host); + return S_RESETFAIL; + }else{ + LOG(PIL_INFO, "node Meatware-reset: %s", host); + unlink(meatpipe); + return S_OK; + } +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +meatware_set_config(StonithPlugin* s, StonithNVpair *list) +{ + + struct pluginDevice* nd; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s,S_OOPS); + nd = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + rc = Meat_parse_config_info(nd, namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + return rc; +} + +/* + * Return STONITH config vars + */ +static const char * const * +meatware_get_confignames(StonithPlugin* p) +{ + static const char * MeatwareParams[] = {ST_HOSTLIST, NULL }; + return MeatwareParams; +} + +/* + * Return STONITH info string + */ +static const char * +meatware_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nd; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + case ST_DEVICENAME: + ret = "Your Name Here"; + break; + case ST_DEVICEDESCR: + ret = "Human (meatware) intervention STONITH device.\n" + "This STONITH agent prompts a human to reset a machine.\n" + "The human tells it when the reset was completed."; + break; + case ST_CONF_XML: /* XML metadata */ + ret = meatwareXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Meat Stonith destructor... + */ +static void +meatware_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + + VOIDERRIFWRONGDEV(s); + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTpluginID; + if (nd->hostlist) { + stonith_free_hostlist(nd->hostlist); + nd->hostlist = NULL; + } + nd->hostcount = -1; + FREE(nd); +} + +/* Create a new Meatware Stonith device. */ + +static StonithPlugin * +meatware_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->hostlist = NULL; + nd->hostcount = -1; + nd->idinfo = DEVICE; + nd->sp.s_ops = &meatwareOps; + + return &(nd->sp); +} diff --git a/lib/plugins/stonith/null.c b/lib/plugins/stonith/null.c new file mode 100644 index 0000000..0d0cf04 --- /dev/null +++ b/lib/plugins/stonith/null.c @@ -0,0 +1,260 @@ +/* + * Stonith module for NULL Stonith device + * + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> + +#define DEVICE "NULL STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN null +#define PIL_PLUGIN_S "null" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static StonithPlugin* null_new(const char *); +static void null_destroy(StonithPlugin *); +static int null_set_config(StonithPlugin* +, StonithNVpair*); +static const char * const * null_get_confignames(StonithPlugin*); +static const char * null_getinfo(StonithPlugin * s, int InfoType); +static int null_status(StonithPlugin * ); +static int null_reset_req(StonithPlugin * s +, int request, const char * host); +static char ** null_hostlist(StonithPlugin *); + +static struct stonith_ops nullOps ={ + null_new, /* Create new STONITH object */ + null_destroy, /* Destroy STONITH object */ + null_getinfo, /* Return STONITH info string */ + null_get_confignames, /* Return list of config params */ + null_set_config, /* configure fron NV pairs */ + null_status, /* Return STONITH device status */ + null_reset_req, /* Request a reset */ + null_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &nullOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * Null STONITH device. We are very agreeable, but don't do much :-) + */ + + +static const char * pluginid = "nullDevice-Stonith"; +static const char * NOTpluginID = "Null device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *nullXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +null_status(StonithPlugin *s) +{ + + ERRIFWRONGDEV(s, S_OOPS); + return S_OK; +} + + +/* + * Return the list of hosts configured for this NULL device + */ + +static char ** +null_hostlist(StonithPlugin *s) +{ + struct pluginDevice* nd = (struct pluginDevice*)s; + + ERRIFWRONGDEV(s, NULL); + return OurImports->CopyHostList((const char * const *)nd->hostlist); +} + + +/* + * Pretend to reset the given host on this Stonith device. + * (we don't even error check the "request" type) + */ +static int +null_reset_req(StonithPlugin * s, int request, const char * host) +{ + + ERRIFWRONGDEV(s,S_OOPS); + + /* Real devices need to pay attention to the "request" */ + /* (but we don't care ;-)) */ + + LOG(PIL_INFO, "Host null-reset: %s", host); + return S_OK; +} + + +static const char * const * +null_get_confignames(StonithPlugin* p) +{ + static const char * NullParams[] = {ST_HOSTLIST, NULL }; + return NullParams; +} + +/* + * Parse the config information in the given string, + * and stash it away... + */ +static int +null_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* nd = (struct pluginDevice*) s; + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + int rc; + + ERRIFWRONGDEV(s, S_OOPS); + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + nd->hostlist = OurImports->StringToHostList(namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + if (nd->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (nd->hostcount = 0; nd->hostlist[nd->hostcount] + ; nd->hostcount++) { + strdown(nd->hostlist[nd->hostcount]); + } + return nd->hostcount ? S_OK : S_BADCONFIG; +} + +static const char * +null_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nd = (struct pluginDevice*) s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + + case ST_DEVICENAME: + ret = "(nil)"; + break; + + case ST_DEVICEDESCR: + ret = "Dummy (do-nothing) STONITH device\n" + "FOR TESTING ONLY!"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = nullXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * NULL Stonith destructor... + */ +static void +null_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + + VOIDERRIFWRONGDEV(s); + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTpluginID; + if (nd->hostlist) { + stonith_free_hostlist(nd->hostlist); + nd->hostlist = NULL; + } + nd->hostcount = -1; + FREE(s); +} + +/* Create a new Null Stonith device. + * Too bad this function can't be static + */ +static StonithPlugin * +null_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->idinfo = DEVICE; + nd->sp.s_ops = &nullOps; + return (StonithPlugin *)nd; +} diff --git a/lib/plugins/stonith/nw_rpc100s.c b/lib/plugins/stonith/nw_rpc100s.c new file mode 100644 index 0000000..5ba0827 --- /dev/null +++ b/lib/plugins/stonith/nw_rpc100s.c @@ -0,0 +1,779 @@ +/* + * Stonith module for Night/Ware RPC100S + * + * Original code from baytech.c by + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * Modifications for NW RPC100S + * Copyright (c) 2000 Computer Generation Incorporated + * Eric Z. Ayers <eric.ayers@compgen.com> + * + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> +#define DEVICE "NW RPC100S Power Switch" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN nw_rpc100s +#define PIL_PLUGIN_S "nw_rpc100s" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#define MAX_CFGLINE 256 +#include <pils/plugin.h> + +static StonithPlugin * nw_rpc100s_new(const char *); +static void nw_rpc100s_destroy(StonithPlugin *); +static int nw_rpc100s_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * nw_rpc100s_get_confignames(StonithPlugin *); +static const char * nw_rpc100s_getinfo(StonithPlugin * s, int InfoType); +static int nw_rpc100s_status(StonithPlugin * ); +static int nw_rpc100s_reset_req(StonithPlugin * s, int request, const char * host); +static char ** nw_rpc100s_hostlist(StonithPlugin *); + +static struct stonith_ops nw_rpc100sOps ={ + nw_rpc100s_new, /* Create new STONITH object */ + nw_rpc100s_destroy, /* Destroy STONITH object */ + nw_rpc100s_getinfo, /* Return STONITH info string */ + nw_rpc100s_get_confignames,/* Return STONITH info string */ + nw_rpc100s_set_config, /* Get configuration from NVpairs */ + nw_rpc100s_status, /* Return STONITH device status */ + nw_rpc100s_reset_req, /* Request a reset */ + nw_rpc100s_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +#include "stonith_signal.h" + +#define DOESNT_USE_STONITHKILLCOMM +#define DOESNT_USE_STONITHSCANLINE +#include "stonith_expect_helpers.h" + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &nw_rpc100sOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + The Nightware RPS-100S is manufactured by: + + Micro Energetics Corp + +1 703 250-3000 + http://www.nightware.com/ + + Thank you to David Hicks of Micro Energetics Corp. for providing + a demo unit to write this software. + + This switch has a very simple protocol, + You issue a command and it gives a response. + Sample commands are conveniently documented on a sticker on the + bottom of the device. + + The switch accepts a single command of the form + + //0,yyy,zzz[/m][/h]<CR> + + Where yyy is the wait time before activiting the relay. + zzz is the relay time. + + The default is that the relay is in a default state of ON, which + means that usually yyy is the number of seconds to wait + before shutting off the power and zzz is the number of seconds the + power remains off. There is a dip switch to change the default + state to 'OFF'. Don't set this switch. It will screw up this code. + + An asterisk can be used for zzz to specify an infinite switch time. + The /m /and /h command options will convert the specified wait and + switch times to either minutewes or hours. + + A response is either + <cr><lf>OK<cr><lf> + or + <cr><lf>Invalid Entry<cr><lf> + + + As far as THIS software is concerned, we have to implement 4 commands: + + status --> //0,0,BOGUS; # Not a real command, this is just a + # probe to see if switch is alive + open(on) --> //0,0,0; # turn power to default state (on) + close(off) --> //0,0,*; # leave power off indefinitely + reboot --> //0,0,10; # immediately turn power off for 10 seconds. + + and expect the response 'OK' to confirm that the unit is operational. +*/ + + + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + + int fd; /* FD open to the serial port */ + + char * device; /* Serial device name to use to communicate + to this RPS10 + */ + + char * node; /* Name of the node that this is controlling */ + +}; + +/* This string is used to identify this type of object in the config file */ +static const char * pluginid = "NW_RPC100S"; +static const char * NOTrpcid = "NW RPC100S device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *nw_rpc100sXML = + XML_PARAMETERS_BEGIN + XML_TTYDEV_PARM + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +/* + * Different expect strings that we get from the NW_RPC100S + * Remote Power Controllers... + */ + +static struct Etoken NWtokOK[] = { {"OK", 0, 0}, {NULL,0,0}}; +static struct Etoken NWtokInvalidEntry[] = { {"Invalid Entry", 0, 0}, {NULL,0,0}}; +/* Accept either a CR/NL or an NL/CR */ +static struct Etoken NWtokCRNL[] = { {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}}; + +static int RPCConnect(struct pluginDevice * ctx); +static int RPCDisconnect(struct pluginDevice * ctx); + +static int RPCReset(struct pluginDevice*, int unitnum, const char * rebootid); +#if defined(ST_POWERON) +static int RPCOn(struct pluginDevice*, int unitnum, const char * rebootid); +#endif +#if defined(ST_POWEROFF) +static int RPCOff(struct pluginDevice*, int unitnum, const char * rebootid); +#endif +static int RPCNametoOutlet ( struct pluginDevice * ctx, const char * host ); + +/*static int RPC_parse_config_info(struct pluginDevice* ctx, const char * info);*/ + + +#define SENDCMD(cmd, timeout) { \ + int return_val = RPCSendCommand(ctx, cmd, timeout); \ + if (return_val != S_OK) { \ + return return_val; \ + } \ + } + +/* + * RPCSendCommand - send a command to the specified outlet + */ +static int +RPCSendCommand (struct pluginDevice *ctx, const char *command, int timeout) +{ + char writebuf[64]; /* All commands are short. + They should be WAY LESS + than 64 chars long! + */ + int return_val; /* system call result */ + fd_set rfds, wfds, xfds; + /* list of FDs for select() */ + struct timeval tv; /* */ + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&xfds); + + snprintf (writebuf, sizeof(writebuf), "%s\r", command); + + if (Debug) { + LOG(PIL_DEBUG, "Sending %s", writebuf); + } + + /* Make sure the serial port won't block on us. use select() */ + FD_SET(ctx->fd, &wfds); + FD_SET(ctx->fd, &xfds); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + return_val = select(ctx->fd+1, NULL, &wfds,&xfds, &tv); + if (return_val == 0) { + /* timeout waiting on serial port */ + LOG(PIL_CRIT, "%s: Timeout writing to %s" + , pluginid, ctx->device); + return S_TIMEOUT; + } else if ((return_val == -1) || FD_ISSET(ctx->fd, &xfds)) { + /* an error occured */ + LOG(PIL_CRIT, "%s: Error before writing to %s: %s" + , pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* send the command */ + if (write(ctx->fd, writebuf, strlen(writebuf)) != + (int)strlen(writebuf)) { + LOG(PIL_CRIT, "%s: Error writing to %s : %s" + , pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* suceeded! */ + return S_OK; + +} /* end RPCSendCommand() */ + +/* + * RPCReset - Reset (power-cycle) the given outlet number + * + * This device can only control one power outlet - unitnum is ignored. + * + */ +static int +RPCReset(struct pluginDevice* ctx, int unitnum, const char * rebootid) +{ + + if (Debug) { + LOG(PIL_DEBUG, "Calling RPCReset (%s)", pluginid); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid + , ctx->device); + return S_OOPS; + } + + /* send the "toggle power" command */ + SENDCMD("//0,0,10;\r\n", 12); + + /* Expect "OK" */ + EXPECT(ctx->fd, NWtokOK, 5); + if (Debug) { + LOG(PIL_DEBUG, "Got OK"); + } + EXPECT(ctx->fd, NWtokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL"); + } + + return(S_OK); + +} /* end RPCReset() */ + + +#if defined(ST_POWERON) +/* + * RPCOn - Turn OFF the given outlet number + */ +static int +RPCOn(struct pluginDevice* ctx, int unitnum, const char * host) +{ + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid + , ctx->device); + return S_OOPS; + } + + /* send the "On" command */ + SENDCMD("//0,0,0;\r\n", 10); + + /* Expect "OK" */ + EXPECT(ctx->fd, NWtokOK, 5); + EXPECT(ctx->fd, NWtokCRNL, 2); + + return(S_OK); + +} /* end RPCOn() */ +#endif + + +#if defined(ST_POWEROFF) +/* + * RPCOff - Turn Off the given outlet number + */ +static int +RPCOff(struct pluginDevice* ctx, int unitnum, const char * host) +{ + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid + , ctx->device); + return S_OOPS; + } + + /* send the "Off" command */ + SENDCMD("//0,0,*;\r\n", 10); + + /* Expect "OK" */ + EXPECT(ctx->fd, NWtokOK, 5); + EXPECT(ctx->fd, NWtokCRNL, 2); + + return(S_OK); + +} /* end RPCOff() */ +#endif + + +/* + * nw_rpc100s_status - API entry point to probe the status of the stonith device + * (basically just "is it reachable and functional?", not the + * status of the individual outlets) + * + * Returns: + * S_OOPS - some error occured + * S_OK - if the stonith device is reachable and online. + */ +static int +nw_rpc100s_status(StonithPlugin *s) +{ + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "Calling nw_rpc100s_status (%s)", pluginid); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + if (RPCConnect(ctx) != S_OK) { + return(S_OOPS); + } + + /* The "connect" really does enough work to see if the + controller is alive... It verifies that it is returning + RPS-10 Ready + */ + + return(RPCDisconnect(ctx)); +} + +/* + * nw_rpc100s_hostlist - API entry point to return the list of hosts + * for the devices on this NW_RPC100S unit + * + * This type of device is configured from the config file, + * so we don't actually have to connect to figure this + * out, just peruse the 'ctx' structure. + * Returns: + * NULL on error + * a malloced array, terminated with a NULL, + * of null-terminated malloc'ed strings. + */ +static char ** +nw_rpc100s_hostlist(StonithPlugin *s) +{ + char ** ret = NULL; /* list to return */ + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "Calling nw_rpc100s_hostlist (%s)", pluginid); + } + + ERRIFNOTCONFIGED(s,NULL); + + ctx = (struct pluginDevice*) s; + + ret = OurImports->StringToHostList(ctx->node); + if (ret == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + } else { + strdown(ret[0]); + } + + return(ret); +} /* end si_hostlist() */ + +/* + * Parse the given configuration information, and stash it away... + * + * <info> contains the parameters specific to this type of object + * + * The format of <parameters> for this module is: + * <serial device> <remotenode> <outlet> [<remotenode> <outlet>] ... + * + * e.g. A machine named 'nodea' can kill a machine named 'nodeb' through + * a device attached to serial port /dev/ttyS0. + * A machine named 'nodeb' can kill machines 'nodea' and 'nodec' + * through a device attached to serial port /dev/ttyS1 (outlets 0 + * and 1 respectively) + * + * stonith nodea NW_RPC100S /dev/ttyS0 nodeb 0 + * stonith nodeb NW_RPC100S /dev/ttyS0 nodea 0 nodec 1 + * + * Another possible configuration is for 2 stonith devices accessible + * through 2 different serial ports on nodeb: + * + * stonith nodeb NW_RPC100S /dev/ttyS0 nodea 0 + * stonith nodeb NW_RPC100S /dev/ttyS1 nodec 0 + */ + +/*static int +RPC_parse_config_info(struct pluginDevice* ctx, const char * info) +{ +}*/ + + +/* + * RPCConnect - + * + * Connect to the given NW_RPC100S device. + * Side Effects + * ctx->fd now contains a valid file descriptor to the serial port + * ??? LOCK THE SERIAL PORT ??? + * + * Returns + * S_OK on success + * S_OOPS on error + * S_TIMEOUT if the device did not respond + * + */ +static int +RPCConnect(struct pluginDevice * ctx) +{ + + /* Open the serial port if it isn't already open */ + if (ctx->fd < 0) { + struct termios tio; + + if (OurImports->TtyLock(ctx->device) < 0) { + LOG(PIL_CRIT, "%s: TtyLock failed.", pluginid); + return S_OOPS; + } + + ctx->fd = open (ctx->device, O_RDWR); + if (ctx->fd <0) { + LOG(PIL_CRIT, "%s: Can't open %s : %s" + , pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* set the baudrate to 9600 8 - N - 1 */ + memset (&tio, 0, sizeof(tio)); + + /* ??? ALAN - the -tradtitional flag on gcc causes the + CRTSCTS constant to generate a warning, and warnings + are treated as errors, so I can't set this flag! - EZA ??? + + Hmmm. now that I look at the documentation, RTS + is just wired high on this device! we don't need it. + */ + /* tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD | CRTSCTS ;*/ + tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD ; + tio.c_lflag = ICANON; + + if (tcsetattr (ctx->fd, TCSANOW, &tio) < 0) { + LOG(PIL_CRIT, "%s: Can't set attributes %s : %s" + , pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + /* flush all data to and fro the serial port before we start */ + if (tcflush (ctx->fd, TCIOFLUSH) < 0) { + LOG(PIL_CRIT, "%s: Can't flush %s : %s" + , pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + + } + + + /* Send a BOGUS string */ + SENDCMD("//0,0,BOGUS;\r\n", 10); + + /* Should reply with "Invalid Command" */ + if (Debug) { + LOG(PIL_DEBUG, "Waiting for \"Invalid Entry\""); + } + EXPECT(ctx->fd, NWtokInvalidEntry, 12); + if (Debug) { + LOG(PIL_DEBUG, "Got Invalid Entry"); + } + EXPECT(ctx->fd, NWtokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL"); + } + + return(S_OK); +} + +static int +RPCDisconnect(struct pluginDevice * ctx) +{ + + if (ctx->fd >= 0) { + /* Flush the serial port, we don't care what happens to the characters + and failing to do this can cause close to hang. + */ + tcflush(ctx->fd, TCIOFLUSH); + close (ctx->fd); + if (ctx->device != NULL) { + OurImports->TtyUnlock(ctx->device); + } + } + ctx->fd = -1; + + return S_OK; +} + +/* + * RPCNametoOutlet - Map a hostname to an outlet number on this stonith device. + * + * Returns: + * 0 on success ( the outlet number on the RPS10 - there is only one ) + * -1 on failure (host not found in the config file) + * + */ +static int +RPCNametoOutlet ( struct pluginDevice * ctx, const char * host ) +{ + int rc = -1; + + if (!strcasecmp(ctx->node, host)) { + rc = 0; + } + + return rc; +} + + +/* + * nw_rpc100s_reset - API call to Reset (reboot) the given host on + * this Stonith device. This involves toggling the power off + * and then on again, OR just calling the builtin reset command + * on the stonith device. + */ +static int +nw_rpc100s_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = S_OK; + int lorc = S_OK; + int outletnum = -1; + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "Calling nw_rpc100s_reset (%s)", pluginid); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + + if ((rc = RPCConnect(ctx)) != S_OK) { + return(rc); + } + + outletnum = RPCNametoOutlet(ctx, host); + LOG(PIL_DEBUG, "zk:outletname=%d", outletnum); + + if (outletnum < 0) { + LOG(PIL_WARN, "%s doesn't control host [%s]" + , ctx->device, host); + RPCDisconnect(ctx); + return(S_BADHOST); + } + + switch(request) { + +#if defined(ST_POWERON) + case ST_POWERON: + rc = RPCOn(ctx, outletnum, host); + break; +#endif +#if defined(ST_POWEROFF) + case ST_POWEROFF: + rc = RPCOff(ctx, outletnum, host); + break; +#endif + case ST_GENERIC_RESET: + rc = RPCReset(ctx, outletnum, host); + break; + default: + rc = S_INVAL; + break; + } + + lorc = RPCDisconnect(ctx); + + return(rc != S_OK ? rc : lorc); +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +nw_rpc100s_set_config(StonithPlugin* s, StonithNVpair *list) +{ + struct pluginDevice* ctx; + StonithNamesToGet namestocopy [] = + { {ST_TTYDEV, NULL} + , {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + int rc; + + + ERRIFWRONGDEV(s,S_OOPS); + if (s->isconfigured) { + return S_OOPS; + } + + ctx = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + ctx->device = namestocopy[0].s_value; + ctx->node = namestocopy[1].s_value; + + return S_OK; +} + +/* + * Return STONITH config vars + */ +static const char * const * +nw_rpc100s_get_confignames(StonithPlugin* p) +{ + static const char * RpcParams[] = {ST_TTYDEV , ST_HOSTLIST, NULL }; + return RpcParams; +} + + + +/* + * nw_rpc100s_getinfo - API entry point to retrieve something from the handle + */ +static const char * +nw_rpc100s_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* ctx; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + ctx = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ctx->idinfo; + break; + case ST_DEVICENAME: + ret = ctx->device; + break; + case ST_DEVICEDESCR: + ret = "Micro Energetics Night/Ware RPC100S"; + break; + case ST_DEVICEURL: + ret = "http://www.microenergeticscorp.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = nw_rpc100sXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * nw_rpc100s_destroy - API entry point to destroy a NW_RPC100S Stonith object. + */ +static void +nw_rpc100s_destroy(StonithPlugin *s) +{ + struct pluginDevice* ctx; + + VOIDERRIFWRONGDEV(s); + + ctx = (struct pluginDevice *)s; + + ctx->pluginid = NOTrpcid; + + /* close the fd if open and set ctx->fd to invalid */ + RPCDisconnect(ctx); + + if (ctx->device != NULL) { + FREE(ctx->device); + ctx->device = NULL; + } + if (ctx->node != NULL) { + FREE(ctx->node); + ctx->node = NULL; + } + FREE(ctx); +} + +/* + * nw_rpc100s_new - API entry point called to create a new NW_RPC100S Stonith + * device object. + */ +static StonithPlugin * +nw_rpc100s_new(const char *subplugin) +{ + struct pluginDevice* ctx = ST_MALLOCT(struct pluginDevice); + + if (ctx == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(ctx, 0, sizeof(*ctx)); + ctx->pluginid = pluginid; + ctx->fd = -1; + ctx->device = NULL; + ctx->node = NULL; + ctx->idinfo = DEVICE; + ctx->sp.s_ops = &nw_rpc100sOps; + + return &(ctx->sp); +} diff --git a/lib/plugins/stonith/rcd_serial.c b/lib/plugins/stonith/rcd_serial.c new file mode 100644 index 0000000..f1396a7 --- /dev/null +++ b/lib/plugins/stonith/rcd_serial.c @@ -0,0 +1,602 @@ +/* + * Stonith module for RCD_SERIAL Stonith device + * + * Original code from null.c by + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * Copious borrowings from nw_rpc100s.c by + * Copyright (c) 2000 Computer Generation Incorporated + * Eric Z. Ayers <eric.ayers@compgen.com> + * + * and from apcsmart.c by + * Copyright (c) 2000 Andreas Piesk <a.piesk@gmx.net> + * + * Modifications for RC Delayed Serial Ciruit by + * Copyright (c) 2002 John Sutton <john@scl.co.uk> + * + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> + +#define DEVICE "RC Delayed Serial" +#include "stonith_plugin_common.h" +#include "stonith_signal.h" + +#define PIL_PLUGIN rcd_serial +#define PIL_PLUGIN_S "rcd_serial" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +#define ST_DTRRTS "dtr_rts" +#define ST_MSDURATION "msduration" +#define MAX_RCD_SERIALLINE 512 + +#include <pils/plugin.h> +#include <sys/ioctl.h> +#include <sys/time.h> + +static StonithPlugin* rcd_serial_new(const char *); +static void rcd_serial_destroy(StonithPlugin *); +static int rcd_serial_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * rcd_serial_get_confignames(StonithPlugin *); +static const char * rcd_serial_getinfo(StonithPlugin * s, int InfoType); +static int rcd_serial_status(StonithPlugin * ); +static int rcd_serial_reset_req(StonithPlugin * s, int request, const char * host); +static char ** rcd_serial_hostlist(StonithPlugin *); + +static struct stonith_ops rcd_serialOps ={ + rcd_serial_new, /* Create new STONITH object */ + rcd_serial_destroy, /* Destroy STONITH object */ + rcd_serial_getinfo, /* Return STONITH info string */ + rcd_serial_get_confignames,/* Return STONITH info string */ + rcd_serial_set_config, /* Get configuration from NVpairs */ + rcd_serial_status, /* Return STONITH device status */ + rcd_serial_reset_req, /* Request a reset */ + rcd_serial_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &rcd_serialOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* ------------------- RCD specific stuff -------------- */ + +/* + A diagram of a circuit suitable for use with this plugin is in + README.rcd_serial which should be somewhere in the distribution (if Alan + includes it ;-) and/or at http://www.scl.co.uk/rcd_serial/ (if I remember + to put it there ;-). + + Once you've got this built, you can test things using the stonith command + as follows: + + stonith -L + will show a list of plugin types, including rcd_serial + + stonith -t rcd_serial testhost + will show required parameters + + In these 3 you can either pass the params after the -p option or you can + put them in a config file and use -F configname instead of -p "param ...". + + stonith -t rcd_serial -p "testhost /dev/ttyS0 rts 1500" -S + will show the status of the device + + stonith -t rcd_serial -p "testhost /dev/ttyS0 rts 1500" -l + will list the single host testhost + + stonith -t rcd_serial -p "testhost /dev/ttyS0 rts 1500" testhost + will reset testhost (provided testhost has its reset pins + suitably wired to the RTS signal coming out of port /dev/ttyS0 + and that 1.5s is enough time to cause a reset ;-) +*/ + +/* + Define RCD_NOPAUSE if you are using the serial port for some purpose + _in_addition_ to using it as a stonith device. For example, I use one + of the input pins on the same serial port for monitoring the state of a + power supply. Periodically, a cron job has to open the port to read the + state of this input and thus has to clear down the output pins DTR and RTS + in order to avoid causing a spurious stonith reset. Now, if it should + happen that just at the same time as we are _really_ trying to do a stonith + reset, this cron job starts up, then the stonith reset won't occur ;-(. + To avoid this (albeit unlikely) outcome, you should #define RCD_NOPAUSE. + The effect of this is that instead of setting the line high just once and + then falling into a pause until an alarm goes off, rather, the program falls + into a loop which is continuously setting the line high. That costs us a bit + of CPU as compared with sitting in a pause, but hey, how often is this code + going to get exercised! Never, we hope... +*/ +#undef RCD_NOPAUSE + +#ifdef RCD_NOPAUSE +static int RCD_alarmcaught; +#endif + +/* + * own prototypes + */ + +static void RCD_alarm_handler(int sig); +static int RCD_open_serial_port(char *device); +static int RCD_close_serial_port(char *device, int fd); + +static void +RCD_alarm_handler(int sig) { +#if !defined(HAVE_POSIX_SIGNALS) + if (sig) { + signal(sig, SIG_DFL); + }else{ + signal(sig, RCD_alarm_handler); + } +#else + struct sigaction sa; + sigset_t sigmask; + + /* Maybe a bit naughty but it works and it saves duplicating all */ + /* this setup code - if handler called with 0 for sig, we install */ + /* ourself as handler. */ + if (sig) { + sa.sa_handler = (void (*)(int))SIG_DFL; + }else{ + sa.sa_handler = RCD_alarm_handler; + } + + sigemptyset(&sigmask); + sa.sa_mask = sigmask; + sa.sa_flags = 0; + sigaction(SIGALRM, &sa, NULL); +#endif + +#ifdef RCD_NOPAUSE + RCD_alarmcaught = 1; +#endif + return; +} + +static int +RCD_open_serial_port(char *device) { + int fd; + int status; + int bothbits; + + if (OurImports->TtyLock(device) < 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: ttylock failed.", __FUNCTION__); + } + return -1; + } + + bothbits = TIOCM_RTS | TIOCM_DTR; + + if ((fd = open(device, O_RDONLY | O_NDELAY)) != -1) { + /* + Opening the device always sets DTR & CTS high. + Clear them down immediately. + */ + status = ioctl(fd, TIOCMBIC, &bothbits); + /* If there was an error clearing bits, set the fd to -1 + * ( indicates error ) */ + if (status != 0 ) { + fd = -1; + } + } + + return fd; +} + +static int +RCD_close_serial_port(char *device, int fd) { + int rc = close(fd); + if (device != NULL) { + OurImports->TtyUnlock(device); + } + return rc; +} + +/* + * RCD_Serial STONITH device. + */ +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; /* name of single host we can reset */ + int hostcount; /* i.e. 1 after initialisation */ + char * device; /* serial device name */ + char * signal; /* either rts or dtr */ + long msduration; /* how long (ms) to assert the signal */ +}; + +static const char * pluginid = "RCD_SerialDevice-Stonith"; +static const char * NOTrcd_serialID = "RCD_Serial device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_DTRRTS_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_DTRRTS \ + XML_PARM_SHORTDESC_END + +#define XML_DTRRTS_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The hardware handshaking technique to use with " ST_TTYDEV "(\"dtr\" or \"rts\")" \ + XML_PARM_LONGDESC_END + +#define XML_DTRRTS_PARM \ + XML_PARAMETER_BEGIN(ST_DTRRTS, "string", "1", "0") \ + XML_DTRRTS_SHORTDESC \ + XML_DTRRTS_LONGDESC \ + XML_PARAMETER_END + +#define XML_MSDURATION_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_MSDURATION \ + XML_PARM_SHORTDESC_END + +#define XML_MSDURATION_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The delay duration (in milliseconds) between the assertion of the control signal on " ST_TTYDEV " and the closing of the reset switch" \ + XML_PARM_LONGDESC_END + +#define XML_MSDURATION_PARM \ + XML_PARAMETER_BEGIN(ST_MSDURATION, "string", "1", "0") \ + XML_MSDURATION_SHORTDESC \ + XML_MSDURATION_LONGDESC \ + XML_PARAMETER_END + +static const char *rcd_serialXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_TTYDEV_PARM + XML_DTRRTS_PARM + XML_MSDURATION_PARM + XML_PARAMETERS_END; + +static int +rcd_serial_status(StonithPlugin *s) +{ + struct pluginDevice* rcd; + int fd; + const char * err; + + ERRIFWRONGDEV(s,S_OOPS); + + rcd = (struct pluginDevice*) s; + + /* + All we can do is make sure the serial device exists and + can be opened and closed without error. + */ + + if ((fd = RCD_open_serial_port(rcd->device)) == -1) { + err = strerror(errno); + LOG(PIL_CRIT, "%s: open of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + if (RCD_close_serial_port(rcd->device, fd) != 0) { + err = strerror(errno); + LOG(PIL_CRIT, "%s: close of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + return S_OK; +} + + +/* + * Return the list of hosts configured for this RCD_SERIAL device + */ +static char ** +rcd_serial_hostlist(StonithPlugin *s) +{ + struct pluginDevice* rcd; + + ERRIFWRONGDEV(s,NULL); + rcd = (struct pluginDevice*) s; + if (rcd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in RCD_SERIAL_list_hosts"); + return(NULL); + } + + return OurImports->CopyHostList((const char * const *)rcd->hostlist); +} + +/* + * At last, we really do it! I don't know what the request argument + * is so am just ignoring it... + */ +static int +rcd_serial_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice* rcd; + int fd; + int sigbit; + struct itimerval timer; + const char * err; + + ERRIFWRONGDEV(s,S_OOPS); + + rcd = (struct pluginDevice *) s; + + /* check that host matches */ + if (strcasecmp(host, rcd->hostlist[0])) { + LOG(PIL_CRIT, "%s: host '%s' not in hostlist.", + __FUNCTION__, host); + return(S_BADHOST); + } + + /* Set the appropriate bit for the signal */ + sigbit = *(rcd->signal)=='r' ? TIOCM_RTS : TIOCM_DTR; + + /* Set up the timer */ + timer.it_interval.tv_sec = 0; + timer.it_interval.tv_usec = 0; + timer.it_value.tv_sec = rcd->msduration / 1000; + timer.it_value.tv_usec = (rcd->msduration % 1000) * 1000; + + /* Open the device */ + if ((fd = RCD_open_serial_port(rcd->device)) == -1) { +#ifdef HAVE_STRERROR + err = strerror(errno); +#else + err = sys_errlist[errno]; +#endif + LOG(PIL_CRIT, "%s: open of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + /* Start the timer */ + RCD_alarm_handler(0); +#ifdef RCD_NOPAUSE + RCD_alarmcaught = 0; +#endif + setitimer(ITIMER_REAL, &timer, 0); + + /* Set the line high */ + ioctl(fd, TIOCMBIS, &sigbit); + + /* Wait for the alarm signal */ +#ifdef RCD_NOPAUSE + while(!RCD_alarmcaught) ioctl(fd, TIOCMBIS, &sigbit); +#else + pause(); +#endif + + /* Clear the line low */ + ioctl(fd, TIOCMBIC, &sigbit); + + /* Close the port */ + if (RCD_close_serial_port(rcd->device, fd) != 0) { + err = strerror(errno); + LOG(PIL_CRIT, "%s: close of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + LOG(PIL_INFO,"Host rcd_serial-reset: %s", host); + return S_OK; +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +rcd_serial_set_config(StonithPlugin* s, StonithNVpair *list) +{ + struct pluginDevice* rcd; + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {ST_TTYDEV, NULL} + , {ST_DTRRTS, NULL} + , {ST_MSDURATION, NULL} + , {NULL, NULL} + }; + char *endptr; + int rc = 0; + + LOG(PIL_DEBUG, "%s:called", __FUNCTION__); + + ERRIFWRONGDEV(s,S_OOPS); + if (s->isconfigured) { + return S_OOPS; + } + + rcd = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + if ((rcd->hostlist = (char **)MALLOC(2*sizeof(char*))) == NULL) { + LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__); + FREE(namestocopy[0].s_value); + FREE(namestocopy[1].s_value); + FREE(namestocopy[2].s_value); + FREE(namestocopy[3].s_value); + return S_OOPS; + } + rcd->hostlist[0] = namestocopy[0].s_value; + strdown(rcd->hostlist[0]); + rcd->hostlist[1] = NULL; + rcd->hostcount = 1; + rcd->device = namestocopy[1].s_value; + rcd->signal = namestocopy[2].s_value; + if (strcmp(rcd->signal, "rts") && strcmp(rcd->signal, "dtr")) { + LOG(PIL_CRIT, "%s: Invalid signal name '%s'", + pluginid, rcd->signal); + FREE(namestocopy[3].s_value); + return S_BADCONFIG; + } + + errno = 0; + rcd->msduration = strtol(namestocopy[3].s_value, &endptr, 0); + if (((errno == ERANGE) + && (rcd->msduration == LONG_MIN || rcd->msduration == LONG_MAX)) + || *endptr != 0 || rcd->msduration < 1) { + LOG(PIL_CRIT, "%s: Invalid msduration '%s'", + pluginid, namestocopy[3].s_value); + FREE(namestocopy[3].s_value); + return S_BADCONFIG; + } + FREE(namestocopy[3].s_value); + + return S_OK; +} + +/* + * Return STONITH config vars + */ +static const char * const * +rcd_serial_get_confignames(StonithPlugin* p) +{ + static const char * RcdParams[] = {ST_HOSTLIST, ST_TTYDEV + , ST_DTRRTS, ST_MSDURATION, NULL }; + return RcdParams; +} + +/* + * Return STONITH info string + */ +static const char * +rcd_serial_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* rcd; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + rcd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = rcd->idinfo; + break; + case ST_DEVICENAME: + ret = rcd->device; + break; + case ST_DEVICEDESCR: + ret = "RC Delayed Serial STONITH Device\n" + "This device can be constructed cheaply from" + " readily available components,\n" + "with sufficient expertise and testing.\n" + "See README.rcd_serial for circuit diagram.\n"; + break; + case ST_DEVICEURL: + ret = "http://www.scl.co.uk/rcd_serial/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = rcd_serialXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * RCD_SERIAL Stonith destructor... + */ +static void +rcd_serial_destroy(StonithPlugin *s) +{ + struct pluginDevice* rcd; + + VOIDERRIFWRONGDEV(s); + + rcd = (struct pluginDevice *)s; + + rcd->pluginid = NOTrcd_serialID; + if (rcd->hostlist) { + stonith_free_hostlist(rcd->hostlist); + rcd->hostlist = NULL; + } + rcd->hostcount = -1; + if (rcd->device) { + FREE(rcd->device); + } + if (rcd->signal) { + FREE(rcd->signal); + } + FREE(rcd); +} + +/* + * Create a new RCD_Serial Stonith device. + * Too bad this function can't be static. (Hmm, weird, it _is_ static?) + */ +static StonithPlugin * +rcd_serial_new(const char *subplugin) +{ + struct pluginDevice* rcd = ST_MALLOCT(struct pluginDevice); + + if (rcd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(rcd, 0, sizeof(*rcd)); + + rcd->pluginid = pluginid; + rcd->hostlist = NULL; + rcd->hostcount = -1; + rcd->device = NULL; + rcd->signal = NULL; + rcd->msduration = 0; + rcd->idinfo = DEVICE; + rcd->sp.s_ops = &rcd_serialOps; + + return &(rcd->sp); +} diff --git a/lib/plugins/stonith/rhcs.c b/lib/plugins/stonith/rhcs.c new file mode 100644 index 0000000..293a081 --- /dev/null +++ b/lib/plugins/stonith/rhcs.c @@ -0,0 +1,1035 @@ +/* + * Stonith module for RedHat Cluster Suite fencing plugins + * + * Copyright (c) 2001 SuSE Linux AG + * Portions Copyright (c) 2004, tummy.com, ltd. + * + * Based on ssh.c, Authors: Joachim Gleissner <jg@suse.de>, + * Lars Marowsky-Bree <lmb@suse.de> + * Modified for external.c: Scott Kleihege <scott@tummy.com> + * Reviewed, tested, and config parsing: Sean Reifschneider <jafo@tummy.com> + * And overhauled by Lars Marowsky-Bree <lmb@suse.de>, so the circle + * closes... + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * Changed to allow full-featured external plugins by Dave Blaschke + * <debltc@us.ibm.com> + * Modified for rhcs.c: Dejan Muhamedagic <dejan@suse.de> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> + +#include <dirent.h> +#include <libxml/xmlmemory.h> +#include <libxml/xmlreader.h> +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <libxml/xpath.h> +#include <libxml/xpathInternals.h> + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN rhcs +#define PIL_PLUGIN_S "rhcs" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +#include <pils/plugin.h> + +static StonithPlugin * rhcs_new(const char *); +static void rhcs_destroy(StonithPlugin *); +static int rhcs_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * rhcs_get_confignames(StonithPlugin *); +static const char * rhcs_getinfo(StonithPlugin * s, int InfoType); +static int rhcs_status(StonithPlugin * ); +static int rhcs_reset_req(StonithPlugin * s, int request, const char * host); +static char ** rhcs_hostlist(StonithPlugin *); + +static struct stonith_ops rhcsOps ={ + rhcs_new, /* Create new STONITH object */ + rhcs_destroy, /* Destroy STONITH object */ + rhcs_getinfo, /* Return STONITH info string */ + rhcs_get_confignames, /* Return STONITH info string */ + rhcs_set_config, /* Get configuration from NVpairs */ + rhcs_status, /* Return STONITH device status */ + rhcs_reset_req, /* Request a reset */ + rhcs_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &rhcsOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * RHCS STONITH device + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + GHashTable * cmd_opts; + char * subplugin; + char ** confignames; + char * hostlist; + char * outputbuf; + xmlDoc * metadata; +}; + +static const char * pluginid = "RHCSDevice-Stonith"; +static const char * NOTpluginID = "RHCS device has been destroyed"; + +/* Prototypes */ + +/* Run the command with op and return the exit status + the output + * (NULL -> discard output) */ +static int rhcs_run_cmd(struct pluginDevice *sd, const char *op, + const char *host, char **output); +/* Just free up the configuration and the memory, if any */ +static void rhcs_unconfig(struct pluginDevice *sd); + +static int +rhcs_status(StonithPlugin *s) +{ + struct pluginDevice * sd; + const char * op = "monitor"; + int rc; + char * output = NULL; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + + rc = rhcs_run_cmd(sd, op, NULL, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + } + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + } + if (output) { + FREE(output); + } + return rc; +} + +static int +get_num_tokens(char *str) +{ + int namecount = 0; + + if (!str) + return namecount; + while (*str != EOS) { + str += strspn(str, WHITESPACE); + if (*str == EOS) + break; + str += strcspn(str, WHITESPACE); + namecount++; + } + return namecount; +} + +static char ** +rhcs_hostlist(StonithPlugin *s) +{ + struct pluginDevice* sd; + const char * op = "gethosts"; + int i, namecount; + char ** ret; + char * tmp; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,NULL); + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(NULL); + } + + namecount = get_num_tokens(sd->hostlist); + ret = MALLOC((namecount+1)*sizeof(char *)); + if (!ret) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return NULL; + } + memset(ret, 0, (namecount+1)*sizeof(char *)); + + /* White-space split the sd->hostlist here */ + i = 0; + tmp = strtok(sd->hostlist, WHITESPACE); + while (tmp != NULL) { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s host %s", + __FUNCTION__, sd->subplugin, tmp); + } + ret[i] = STRDUP(tmp); + if (!ret[i]) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + stonith_free_hostlist(ret); + return NULL; + } + i++; + tmp = strtok(NULL, WHITESPACE); + } + + if (i == 0) { + LOG(PIL_CRIT, "%s: '%s %s' returned an empty hostlist", + __FUNCTION__, sd->subplugin, op); + stonith_free_hostlist(ret); + ret = NULL; + } + + return(ret); +} + +static int +rhcs_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice * sd; + const char * op; + int rc; + char * output = NULL; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + if (Debug) { + LOG(PIL_DEBUG, "Host rhcs-reset initiating on %s", host); + } + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + + switch (request) { + case ST_GENERIC_RESET: + op = "reboot"; + break; + + case ST_POWEROFF: + op = "off"; + break; + + case ST_POWERON: + op = "on"; + break; + + default: + LOG(PIL_CRIT, "%s: Unknown stonith request %d", + __FUNCTION__, request); + return S_OOPS; + break; + } + + rc = rhcs_run_cmd(sd, op, host, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' for host %s failed with rc %d", + __FUNCTION__, sd->subplugin, op, host, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + return S_RESETFAIL; + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + if (output) { + LOG(PIL_INFO, "plugin output: %s", output); + FREE(output); + } + return S_OK; + } + +} + +static int +rhcs_parse_config_info(struct pluginDevice* sd, StonithNVpair * info) +{ + char * key; + char * value; + StonithNVpair * nv; + + sd->hostlist = NULL; + sd->cmd_opts = g_hash_table_new(g_str_hash, g_str_equal); + + /* TODO: Maybe treat "" as delimeters too so + * whitespace can be passed to the plugins... */ + for (nv = info; nv->s_name; nv++) { + if (!nv->s_name || !nv->s_value) { + continue; + } + key = STRDUP(nv->s_name); + if (!key) { + goto err_mem; + } + value = STRDUP(nv->s_value); + if (!value) { + FREE(key); + goto err_mem; + } + if (!strcmp(key,"hostlist")) { + sd->hostlist = value; + FREE(key); + } else { + g_hash_table_insert(sd->cmd_opts, key, value); + } + } + + return(S_OK); + +err_mem: + LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__); + rhcs_unconfig(sd); + + return(S_OOPS); +} + +static gboolean +let_remove_eachitem(gpointer key, gpointer value, gpointer user_data) +{ + if (key) { + FREE(key); + } + if (value) { + FREE(value); + } + return TRUE; +} + +static void +rhcs_unconfig(struct pluginDevice *sd) { + if (sd->cmd_opts) { + g_hash_table_foreach_remove(sd->cmd_opts, + let_remove_eachitem, NULL); + g_hash_table_destroy(sd->cmd_opts); + sd->cmd_opts = NULL; + } + if (sd->hostlist) { + FREE(sd->hostlist); + sd->hostlist = NULL; + } + if (sd->metadata) { + xmlFreeDoc(sd->metadata); + xmlCleanupParser(); + sd->metadata = NULL; + } +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +rhcs_set_config(StonithPlugin* s, StonithNVpair *list) +{ + struct pluginDevice * sd; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + /* make sure that command has not already been set */ + if (s->isconfigured) { + return(S_OOPS); + } + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + +#if 0 + /* the required parameters may be acquired from the metadata + * */ + if (sd->confignames == NULL) { + /* specified by name=value pairs, check required parms */ + if (rhcs_get_confignames(s) == NULL) { + return(S_OOPS); + } + + for (p = sd->confignames; *p; p++) { + if (OurImports->GetValue(list, *p) == NULL) { + LOG(PIL_INFO, "Cannot get parameter %s from " + "StonithNVpair", *p); + } + } + } +#endif + + return rhcs_parse_config_info(sd, list); +} + + +/* Only interested in regular files starting with fence_ that are also executable */ +static int +rhcs_exec_select(const struct dirent *dire) +{ + struct stat statf; + char filename[FILENAME_MAX]; + int rc; + + rc = snprintf(filename, FILENAME_MAX, "%s/%s", + STONITH_RHCS_PLUGINDIR, dire->d_name); + if (rc <= 0 || rc >= FILENAME_MAX) { + return 0; + } + + if ((stat(filename, &statf) == 0) && + (S_ISREG(statf.st_mode)) && + (statf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))) { + if (statf.st_mode & (S_IWGRP|S_IWOTH)) { + LOG(PIL_WARN, "Executable file %s ignored " + "(writable by group/others)", filename); + return 0; + }else{ + return 1; + } + } + + return 0; +} + +static xmlDoc * +load_metadata(struct pluginDevice * sd) +{ + xmlDoc *doc = NULL; + const char *op = "metadata"; + int rc; + char *ret = NULL; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + rc = rhcs_run_cmd(sd, op, NULL, &ret); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (ret) { + LOG(PIL_CRIT, "plugin output: %s", ret); + FREE(ret); + } + goto err; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + + doc = xmlParseMemory(ret, strlen(ret)); + if (!doc) { + LOG(PIL_CRIT, "%s: could not parse metadata", + __FUNCTION__); + goto err; + } + sd->metadata = doc; + +err: + if (ret) { + FREE(ret); + } + return doc; +} + +static const char *skip_attrs[] = { + "action", "verbose", "debug", "version", "help", "separator", + NULL +}; +/* XML stuff */ +typedef int (*node_proc) + (xmlNodeSet *nodes, struct pluginDevice *sd); + +static int +proc_xpath(const char *xpathexp, struct pluginDevice *sd, node_proc fun) +{ + xmlXPathObject *xpathObj = NULL; + xmlXPathContext *xpathCtx = NULL; + int rc = 1; + + if (!sd->metadata && !load_metadata(sd)) { + LOG(PIL_INFO, "%s: no metadata", __FUNCTION__); + return 1; + } + + /* Create xpath evaluation context */ + xpathCtx = xmlXPathNewContext(sd->metadata); + if(xpathCtx == NULL) { + LOG(PIL_CRIT, "%s: unable to create new XPath context", __FUNCTION__); + return 1; + } + /* Evaluate xpath expression */ + xpathObj = xmlXPathEvalExpression((const xmlChar*)xpathexp, xpathCtx); + if(xpathObj == NULL) { + LOG(PIL_CRIT, "%s: unable to evaluate expression %s", + __FUNCTION__, xpathexp); + goto err; + } + + if (sd->outputbuf != NULL) { + FREE(sd->outputbuf); + sd->outputbuf = NULL; + } + rc = fun(xpathObj->nodesetval, sd); +err: + if (xpathObj) + xmlXPathFreeObject(xpathObj); + if (xpathCtx) + xmlXPathFreeContext(xpathCtx); + return rc; +} + +static int +load_confignames(xmlNodeSet *nodes, struct pluginDevice *sd) +{ + xmlChar *attr; + const char * const*skip; + xmlNode *cur; + int i, j, namecount; + + namecount = nodes->nodeNr; + if (!namecount) { + LOG(PIL_INFO, "%s: no configuration parameters", __FUNCTION__); + return 1; + } + sd->confignames = (char **)MALLOC((namecount+1)*sizeof(char *)); + if (sd->confignames == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return 1; + } + + /* now copy over confignames */ + j = 0; + for (i = 0; i < nodes->nodeNr; i++) { + cur = nodes->nodeTab[i]; + attr = xmlGetProp(cur, (const xmlChar*)"name"); + for (skip = skip_attrs; *skip; skip++) { + if (!strcmp(*skip,(char *)attr)) + goto skip; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: %s configname %s", + __FUNCTION__, sd->subplugin, (char *)attr); + } + sd->confignames[j++] = strdup((char *)attr); + xmlFree(attr); + skip: + continue; + } + sd->confignames[j] = NULL; + + return 0; +} + +static int +dump_content(xmlNodeSet *nodes, struct pluginDevice *sd) +{ + xmlChar *content = NULL; + xmlNode *cur; + int rc = 1; + + if (!nodes || !nodes->nodeTab || !nodes->nodeTab[0]) { + LOG(PIL_WARN, "%s: %s no nodes", + __FUNCTION__, sd->subplugin); + return 1; + } + cur = nodes->nodeTab[0]; + content = xmlNodeGetContent(cur); + if (content && strlen((char *)content) > 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s found content for %s", + __FUNCTION__, sd->subplugin, cur->name); + } + sd->outputbuf = STRDUP((char *)content); + rc = !(*sd->outputbuf); + } else { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s no content for %s", + __FUNCTION__, sd->subplugin, cur->name); + } + rc = 1; + } + + if (content) + xmlFree(content); + return rc; +} + +static int +dump_params_xml(xmlNodeSet *nodes, struct pluginDevice *sd) +{ + int len = 0; + xmlNode *cur; + xmlBuffer *xml_buffer = NULL; + int rc = 0; + + xml_buffer = xmlBufferCreate(); + if (!xml_buffer) { + LOG(PIL_CRIT, "%s: failed to create xml buffer", __FUNCTION__); + return 1; + } + cur = nodes->nodeTab[0]; + len = xmlNodeDump(xml_buffer, sd->metadata, cur, 0, TRUE); + if (len <= 0) { + LOG(PIL_CRIT, "%s: could not dump xml for %s", + __FUNCTION__, (char *)xmlGetProp(cur, (const xmlChar*)"name")); + rc = 1; + goto err; + } + sd->outputbuf = STRDUP((char *)xml_buffer->content); +err: + xmlBufferFree(xml_buffer); + return rc; +} + +/* + * Return STONITH config vars + */ +static const char * const * +rhcs_get_confignames(StonithPlugin* p) +{ + struct pluginDevice * sd; + int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + sd = (struct pluginDevice *)p; + + if (sd->subplugin != NULL) { + if (!sd->metadata && !load_metadata(sd)) { + return NULL; + } + proc_xpath("/resource-agent/parameters/parameter", sd, load_confignames); + } else { + /* return list of subplugins in rhcs directory */ + struct dirent ** files = NULL; + int dircount; + + /* get the rhcs plugin's confignames (list of subplugins) */ + dircount = scandir(STONITH_RHCS_PLUGINDIR, &files, + SCANSEL_CAST rhcs_exec_select, NULL); + if (dircount < 0) { + return NULL; + } + + sd->confignames = (char **)MALLOC((dircount+1)*sizeof(char *)); + if (!sd->confignames) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return NULL; + } + + for (i = 0; i < dircount; i++) { + sd->confignames[i] = STRDUP(files[i]->d_name+strlen("fence_")); + free(files[i]); + files[i] = NULL; + } + free(files); + sd->confignames[dircount] = NULL; + } + + return (const char * const *)sd->confignames; +} + +/* + * Return STONITH info string + */ +static const char * +fake_op(struct pluginDevice * sd, const char *op) +{ + const char *pfx = "RHCS plugin "; + char *ret = NULL; + + LOG(PIL_INFO, "rhcs plugins don't really support %s", op); + ret = MALLOC(strlen(pfx) + strlen(op) + 1); + strcpy(ret, pfx); + strcat(ret, op); + sd->outputbuf = ret; + return(ret); +} + +static const char * +rhcs_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd; + const char * op; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + sd = (struct pluginDevice *)s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(NULL); + } + + if (!sd->metadata && !load_metadata(sd)) { + return NULL; + } + + switch (reqtype) { + case ST_DEVICEID: + op = "getinfo-devid"; + return fake_op(sd, op); + break; + + case ST_DEVICENAME: + if (!proc_xpath("/resource-agent/shortdesc", sd, dump_content)) { + return sd->outputbuf; + } else { + op = "getinfo-devname"; + return fake_op(sd, op); + } + break; + + case ST_DEVICEDESCR: + if (!proc_xpath("/resource-agent/longdesc", sd, dump_content)) { + return sd->outputbuf; + } else { + op = "getinfo-devdescr"; + return fake_op(sd, op); + } + break; + + case ST_DEVICEURL: + op = "getinfo-devurl"; + return fake_op(sd, op); + break; + + case ST_CONF_XML: + if (!proc_xpath("/resource-agent/parameters", sd, dump_params_xml)) { + return sd->outputbuf; + } + break; + + default: + return NULL; + } + return NULL; +} + +/* + * RHCS Stonith destructor... + */ +static void +rhcs_destroy(StonithPlugin *s) +{ + struct pluginDevice * sd; + char ** p; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + sd = (struct pluginDevice *)s; + + sd->pluginid = NOTpluginID; + rhcs_unconfig(sd); + if (sd->confignames != NULL) { + for (p = sd->confignames; *p; p++) { + FREE(*p); + } + FREE(sd->confignames); + sd->confignames = NULL; + } + if (sd->subplugin != NULL) { + FREE(sd->subplugin); + sd->subplugin = NULL; + } + if (sd->outputbuf != NULL) { + FREE(sd->outputbuf); + sd->outputbuf = NULL; + } + FREE(sd); +} + +/* Create a new rhcs Stonith device */ +static StonithPlugin * +rhcs_new(const char *subplugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + if (subplugin != NULL) { + sd->subplugin = STRDUP(subplugin); + if (sd->subplugin == NULL) { + FREE(sd); + return(NULL); + } + } + sd->sp.s_ops = &rhcsOps; + return &(sd->sp); +} + +#define MAXLINE 512 + +static void +printparam_to_fd(int fd, const char *key, const char *value) +{ + char arg[MAXLINE]; + int cnt; + + cnt = snprintf(arg, MAXLINE, "%s=%s\n", key, value); + if (cnt <= 0 || cnt >= MAXLINE) { + LOG(PIL_CRIT, "%s: param/value pair too large", __FUNCTION__); + return; + } + if (Debug) { + LOG(PIL_DEBUG, "set rhcs plugin param '%s=%s'", key, value); + } + if (write(fd, arg, cnt) < 0) { + LOG(PIL_CRIT, "%s: write: %m", __FUNCTION__); + } +} + +static void +rhcs_print_var(gpointer key, gpointer value, gpointer user_data) +{ + printparam_to_fd(GPOINTER_TO_UINT(user_data), (char *)key, (char *)value); +} + +/* Run the command with op as command line argument(s) and return the exit + * status + the output */ + +static int +rhcs_run_cmd(struct pluginDevice *sd, const char *op, const char *host, char **output) +{ + const int BUFF_LEN=4096; + char buff[BUFF_LEN]; + int read_len = 0; + int rc; + char * data = NULL; + char cmd[FILENAME_MAX+64]; + struct stat buf; + int slen; + int pid, status; + int fd1[2]; /* our stdout/their stdin */ + int fd2[2]; /* our stdin/their stdout and stderr */ + + rc = snprintf(cmd, FILENAME_MAX, "%s/fence_%s", + STONITH_RHCS_PLUGINDIR, sd->subplugin); + if (rc <= 0 || rc >= FILENAME_MAX) { + LOG(PIL_CRIT, "%s: external command too long.", __FUNCTION__); + return -1; + } + + if (stat(cmd, &buf) != 0) { + LOG(PIL_CRIT, "%s: stat(2) of %s failed: %s", + __FUNCTION__, cmd, strerror(errno)); + return -1; + } + + if (!S_ISREG(buf.st_mode) + || (!(buf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) { + LOG(PIL_CRIT, "%s: %s found NOT to be executable.", + __FUNCTION__, cmd); + return -1; + } + + if (buf.st_mode & (S_IWGRP|S_IWOTH)) { + LOG(PIL_CRIT, "%s: %s found to be writable by group/others, " + "NOT executing for security purposes.", + __FUNCTION__, cmd); + return -1; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: Calling '%s'", __FUNCTION__, cmd ); + } + + if (pipe(fd1) || pipe(fd2)) + goto err; + + pid = fork(); + if (pid < 0) { + LOG(PIL_CRIT, "%s: fork: %m", __FUNCTION__); + goto err; + } + if (pid) { /* parent */ + close(fd1[0]); + close(fd2[1]); + + if (sd->cmd_opts) { + printparam_to_fd(fd1[1], "agent", sd->subplugin); + printparam_to_fd(fd1[1], "action", op); + if( host ) + printparam_to_fd(fd1[1], "nodename", host); + g_hash_table_foreach(sd->cmd_opts, rhcs_print_var, + GUINT_TO_POINTER(fd1[1])); + } + close(fd1[1]); /* we have nothing more to say */ + + fcntl(fd2[0], F_SETFL, fcntl(fd2[0], F_GETFL, 0) | O_NONBLOCK); + data = NULL; + slen=0; + data = MALLOC(1); + /* read stdout/stderr from the fence agent */ + do { + data[slen]=EOS; + read_len = read(fd2[0], buff, BUFF_LEN); + if (read_len > 0) { + data=REALLOC(data, slen+read_len+1); + if (data == NULL) { + goto err; + } + memcpy(data+slen, buff, read_len); + slen += read_len; + data[slen] = EOS; + } else if (read_len < 0) { + if (errno == EAGAIN) + continue; + LOG(PIL_CRIT, "%s: read from pipe: %m", __FUNCTION__); + goto err; + } + } while (read_len); + + if (!data) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + goto err; + } + close(fd2[0]); + waitpid(pid, &status, 0); + if (!WIFEXITED(status)) { + LOG(PIL_CRIT, "%s: fence agent failed: %m", __FUNCTION__); + goto err; + } else { + rc = WEXITSTATUS(status); + if (rc) { + LOG(PIL_CRIT, "%s: fence agent exit code: %d", + __FUNCTION__, rc); + goto err; + } + } + } else { /* child */ + close(fd1[1]); + close(fd2[0]); + close(STDIN_FILENO); + if (dup(fd1[0]) < 0) + goto err; + close(fd1[0]); + close(STDOUT_FILENO); + if (dup(fd2[1]) < 0) + goto err; + close(STDERR_FILENO); + if (dup(fd2[1]) < 0) + goto err; + close(fd2[1]); + rc = sd->cmd_opts ? + execlp(cmd, cmd, NULL) : execlp(cmd, cmd, "-o", op, NULL); + if (rc < 0) { + LOG(PIL_CRIT, "%s: Calling '%s' failed: %m", + __FUNCTION__, cmd); + } + goto err; + } + + if (Debug && data) { + LOG(PIL_DEBUG, "%s: '%s' output: %s", __FUNCTION__, cmd, data); + } + + if (output) { + *output = data; + } else { + FREE(data); + } + + return 0; + +err: + if (data) { + FREE(data); + } + if (output) { + *output = NULL; + } + + return(-1); + +} diff --git a/lib/plugins/stonith/ribcl.py.in b/lib/plugins/stonith/ribcl.py.in new file mode 100644 index 0000000..14e070c --- /dev/null +++ b/lib/plugins/stonith/ribcl.py.in @@ -0,0 +1,101 @@ +#!@PYTHON@ + + +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import sys +import socket +from httplib import * +from time import sleep + + +argv = sys.argv + + +try: + host = argv[1].split('.')[0]+'-rm' + cmd = argv[2] +except IndexError: + print "Not enough arguments" + sys.exit(1) + + +login = [ '<RIBCL VERSION="1.2">', + '<LOGIN USER_LOGIN="Administrator" PASSWORD="********">' ] + + +logout = [ '</LOGIN>', '</RIBCL>' ] + + +status = [ '<SERVER_INFO MODE="read">', '<GET_HOST_POWER_STATUS/>', + '</SERVER_INFO>' ] + + +reset = [ '<SERVER_INFO MODE="write">', '<RESET_SERVER/>', '</SERVER_INFO>' ] + + +off = [ '<SERVER_INFO MODE = "write">', '<SET_HOST_POWER HOST_POWER = "N"/>', + '</SERVER_INFO>' ] + + +on = [ '<SERVER_INFO MODE = "write">', '<SET_HOST_POWER HOST_POWER = "Y"/>', + '</SERVER_INFO>' ] + + +todo = { 'reset':reset, 'on':on, 'off':off, 'status':status } + + +acmds=[] +try: + if cmd == 'reset' and host.startswith('gfxcl'): + acmds.append(login + todo['off'] + logout) + acmds.append(login + todo['on'] + logout) + else: + acmds.append(login + todo[cmd] + logout) +except KeyError: + print "Invalid command: "+ cmd + sys.exit(1) + + +try: + for cmds in acmds: + + + c=HTTPSConnection(host) + c.send('<?xml version="1.0"?>\r\n') + c.sock.recv(1024) + + + for line in cmds: + c.send(line+'\r\n') + c.sock.recv(1024) + + + c.close() + sleep(1) + + +except socket.gaierror, msg: + print msg + sys.exit(1) +except socket.sslerror, msg: + print msg + sys.exit(1) +except socket.error, msg: + print msg + sys.exit(1) + diff --git a/lib/plugins/stonith/riloe.c b/lib/plugins/stonith/riloe.c new file mode 100644 index 0000000..a4a8312 --- /dev/null +++ b/lib/plugins/stonith/riloe.c @@ -0,0 +1,338 @@ +/* + * Stonith module for RILOE Stonith device + * + * Copyright (c) 2004 Alain St-Denis <alain.st-denis@ec.gc.ca> + * + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define DEVICE "Compaq RILOE" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN riloe +#define PIL_PLUGIN_S "riloe" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +static StonithPlugin * riloe_new(const char *); +static void riloe_destroy(StonithPlugin *); +static int riloe_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * riloe_get_confignames(StonithPlugin * ); +static const char * riloe_getinfo(StonithPlugin * s, int InfoType); +static int riloe_status(StonithPlugin * ); +static int riloe_reset_req(StonithPlugin * s, int request, const char * host); +static char ** riloe_hostlist(StonithPlugin *); + +static struct stonith_ops riloeOps ={ + riloe_new, /* Create new STONITH object */ + riloe_destroy, /* Destroy STONITH object */ + riloe_getinfo, /* Return STONITH info string */ + riloe_get_confignames, /* Return STONITH info string */ + riloe_set_config, /* Get configuration from NVpairs */ + riloe_status, /* Return STONITH device status */ + riloe_reset_req, /* Request a reset */ + riloe_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &riloeOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#define RILOE_COMMAND STONITH_MODULES "/ribcl.py" + +/* + * Riloe STONITH device. We are very agreeable, but don't do much :-) + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static const char * pluginid = "RiloeDevice-Stonith"; +static const char * NOTriloeID = "Riloe device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *riloeXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +riloe_status(StonithPlugin *s) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + return S_OK; +} + + +/* + * Return the list of hosts configured for this RILOE device + */ + +static char ** +riloe_hostlist(StonithPlugin *s) +{ + struct pluginDevice* nd; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + nd = (struct pluginDevice*) s; + if (nd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in %s", __FUNCTION__); + return(NULL); + } + + return OurImports->CopyHostList((const char * const*)nd->hostlist); +} + +/* + * Parse the config information, and stash it away... + */ + +static int +RILOE_parse_config_info(struct pluginDevice* nd, const char * info) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (nd->hostcount >= 0) { + return(S_OOPS); + } + + nd->hostlist = OurImports->StringToHostList(info); + if (nd->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (nd->hostcount = 0; nd->hostlist[nd->hostcount]; nd->hostcount++) { + strdown(nd->hostlist[nd->hostcount]); + } + return(S_OK); +} + + +/* + * Pretend to reset the given host on this Stonith device. + * (we don't even error check the "request" type) + */ +static int +riloe_reset_req(StonithPlugin * s, int request, const char * host) +{ + char cmd[4096]; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + snprintf(cmd, sizeof(cmd), "%s %s reset", RILOE_COMMAND, host); + + if (Debug) { + LOG(PIL_DEBUG, "command %s will be executed", cmd); + } + + if (system(cmd) == 0) { + return S_OK; + } else { + LOG(PIL_CRIT, "command %s failed", cmd); + return(S_RESETFAIL); + } +} + +/* + * Parse the information in the given string, + * and stash it away... + */ +static int +riloe_set_config(StonithPlugin* s, StonithNVpair *list) +{ + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + struct pluginDevice* nd; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + nd = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + rc = RILOE_parse_config_info(nd , namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + return rc; +} + +/* + * Return the Stonith plugin configuration parameter + */ +static const char* const * +riloe_get_confignames(StonithPlugin* p) +{ + static const char * RiloeParams[] = {ST_HOSTLIST, NULL }; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + return RiloeParams; +} + +/* + * Return STONITH info string + */ + +static const char * +riloe_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nd; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + case ST_DEVICEDESCR: + ret = "Compaq RILOE STONITH device\n" + "Very early version!"; + break; + case ST_DEVICEURL: + ret = "http://www.hp.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = riloeXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * RILOE Stonith destructor... + */ +static void +riloe_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTriloeID; + if (nd->hostlist) { + stonith_free_hostlist(nd->hostlist); + nd->hostlist = NULL; + } + nd->hostcount = -1; + FREE(nd); +} + +/* Create a new Riloe Stonith device. Too bad this function can't be static */ +static StonithPlugin * +riloe_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->hostlist = NULL; + nd->hostcount = -1; + nd->idinfo = DEVICE; + nd->sp.s_ops = &riloeOps; + + return &(nd->sp); +} diff --git a/lib/plugins/stonith/rps10.c b/lib/plugins/stonith/rps10.c new file mode 100644 index 0000000..08d9873 --- /dev/null +++ b/lib/plugins/stonith/rps10.c @@ -0,0 +1,1070 @@ +/* + * Stonith module for WTI Remote Power Controllers (RPS-10M device) + * + * Original code from baytech.c by + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * Modifications for WTI RPS10 + * Copyright (c) 2000 Computer Generation Incorporated + * Eric Z. Ayers <eric.ayers@compgen.com> + * + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> + +#define DEVICE "WTI RPS10 Power Switch" +#include "stonith_plugin_common.h" + +#include <termios.h> +#define PIL_PLUGIN rps10 +#define PIL_PLUGIN_S "rps10" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#define ST_RPS10 "serial_to_targets" +#define MAX_PRSID 256 +#include <pils/plugin.h> + +static StonithPlugin * rps10_new(const char *); +static void rps10_destroy(StonithPlugin *); +static int rps10_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * rps10_get_confignames(StonithPlugin *); +static const char * rps10_getinfo(StonithPlugin * s, int InfoType); +static int rps10_status(StonithPlugin * ); +static int rps10_reset_req(StonithPlugin * s, int request, const char * host); +static char ** rps10_hostlist(StonithPlugin *); + +static struct stonith_ops rps10Ops ={ + rps10_new, /* Create new STONITH object */ + rps10_destroy, /* Destroy STONITH object */ + rps10_getinfo, /* Return STONITH info string */ + rps10_get_confignames, /* Return STONITH info string */ + rps10_set_config, /* Get configuration from NVpairs */ + rps10_status, /* Return STONITH device status */ + rps10_reset_req, /* Request a reset */ + rps10_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +#include "stonith_signal.h" +#define DOESNT_USE_STONITHKILLCOMM +#define DOESNT_USE_STONITHSCANLINE +#include "stonith_expect_helpers.h" + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &rps10Ops + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * This was written for a Western Telematic Inc. (WTI) + * Remote Power Switch - RPS-10M. + * + * It has a DB9 serial port, a Rotary Address Switch, + * and a pair of RJ-11 jacks for linking multiple switches + * together. The 'M' unit is a master unit which can control + * up to 9 additional slave units. (the master unit also has an + * A/C outlet, so you can control up to 10 devices) + * + * There are a set of dip switches. The default shipping configuration + * is with all dip switches down. I highly recommend that you flip + * switch #3 up, so that when the device is plugged in, the power + * to the unit comes on. + * + * The serial interface is fixed at 9600 BPS (well, you *CAN* + * select 2400 BPS with a dip switch, but why?) 8-N-1 + * + * The ASCII command string is: + * + * ^B^X^X^B^X^Xac^M + * + * ^B^X^X^B^X^X "fixed password" prefix (CTRL-B CTRL-X ... ) + * ^M the carriage return character + * + * a = 0-9 Indicates the address of the module to receive the command + * a = * Sends the command to all modules + * + * c = 0 Switch the AC outlet OFF + * Returns: + * Plug 0 Off + * Complete + * + * c = 1 Switch the AC outlet ON + * Returns: + * Plug 0 On + * Complete + * + * c = T Toggle AC OFF (delay) then back ON + * Returns: + * Plug 0 Off + * Plug 0 On + * Complete + * + * c = ? Read and display status of the selected module + * Returns: + * Plug 0 On # or Plug 0 Off + * Complete + * + * e.g. ^B^X^X^B^X^X0T^M toggles the power on plug 0 OFF and then ON + * + * 21 September 2000 + * Eric Z. Ayers + * Computer Generation, Inc. + */ + +struct cntrlr_str { + char outlet_id; /* value 0-9, '*' */ + char * node; /* name of the node attached to this outlet */ +}; + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + + int fd; /* FD open to the serial port */ + + char * device; /* Serial device name to use to communicate + to this RPS10 + */ + +#define WTI_NUM_CONTROLLERS 10 + struct cntrlr_str + controllers[WTI_NUM_CONTROLLERS]; + /* one master switch can address 10 controllers */ + + /* Number of actually configured units */ + int unit_count; + +}; + +/* This string is used to identify this type of object in the config file */ +static const char * pluginid = "WTI_RPS10"; +static const char * NOTwtiid = "OBJECT DESTROYED: (WTI RPS-10)"; + +#include "stonith_config_xml.h" + +#define XML_RPS10_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Value in the format \"serial_device remotenode outlet [remotenode outlet]...\"" \ + XML_PARM_SHORTDESC_END + +#define XML_RPS10_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The RPS-10 STONITH device configuration information in the format \"serial_device remotenode outlet [remotenode outlet]...\"" \ + XML_PARM_LONGDESC_END + +#define XML_RPS10_PARM \ + XML_PARAMETER_BEGIN(ST_RPS10, "string", "1", "1") \ + XML_RPS10_SHORTDESC \ + XML_RPS10_LONGDESC \ + XML_PARAMETER_END + +static const char *rps10XML = + XML_PARAMETERS_BEGIN + XML_RPS10_PARM + XML_PARAMETERS_END; + +/* WTIpassword - The fixed string ^B^X^X^B^X^X */ +static const char WTIpassword[7] = {2,24,24,2,24,24,0}; + +/* + * Different expect strings that we get from the WTI_RPS10 + * Remote Power Controllers... + */ + +static struct Etoken WTItokReady[] = { {"RPS-10 Ready", 0, 0}, {NULL,0,0}}; +static struct Etoken WTItokComplete[] = { {"Complete", 0, 0} ,{NULL,0,0}}; +static struct Etoken WTItokPlug[] = { {"Plug", 0, 0}, {NULL,0,0}}; +static struct Etoken WTItokOutlet[] = { {"0", 0, 0}, + {"1", 0, 0}, + {"2", 0, 0}, + {"3", 0, 0}, + {"4", 0, 0}, + {"5", 0, 0}, + {"6", 0, 0}, + {"7", 0, 0}, + {"8", 0, 0}, + {"9", 0, 0}, + {NULL,0,0}}; + +static struct Etoken WTItokOff[] = { {"Off", 0, 0}, {NULL,0,0}}; + +/* + * Tokens currently not used because they don't show up on all RPS10 units: + * + */ +static struct Etoken WTItokOn[] = { {"On", 0, 0}, {NULL,0,0}}; + +/* Accept either a CR/NL or an NL/CR */ +static struct Etoken WTItokCRNL[] = { {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}}; + +static int RPSConnect(struct pluginDevice * ctx); +static int RPSDisconnect(struct pluginDevice * ctx); + +static int RPSReset(struct pluginDevice*, char unit_id, const char * rebootid); +#if defined(ST_POWERON) +static int RPSOn(struct pluginDevice*, char unit_id, const char * rebootid); +#endif +#if defined(ST_POWEROFF) +static int RPSOff(struct pluginDevice*, char unit_id, const char * rebootid); +#endif +static signed char RPSNametoOutlet ( struct pluginDevice * ctx, const char * host ); + +static int RPS_parse_config_info(struct pluginDevice* ctx, const char * info); + +#define SENDCMD(outlet, cmd, timeout) { \ + int ret_val = RPSSendCommand(ctx, outlet, cmd, timeout);\ + if (ret_val != S_OK) { \ + return ret_val; \ + } \ + } + +/* + * RPSSendCommand - send a command to the specified outlet + */ +static int +RPSSendCommand (struct pluginDevice *ctx, char outlet, char command, int timeout) +{ + char writebuf[10]; /* all commands are 9 chars long! */ + int return_val; /* system call result */ + fd_set rfds, wfds, xfds; + struct timeval tv; /* */ + + /* list of FDs for select() */ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&xfds); + + snprintf (writebuf, sizeof(writebuf), "%s%c%c\r", + WTIpassword, outlet, command); + + if (Debug) { + LOG(PIL_DEBUG, "Sending %s\n", writebuf); + } + + /* Make sure the serial port won't block on us. use select() */ + FD_SET(ctx->fd, &wfds); + FD_SET(ctx->fd, &xfds); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + return_val = select(ctx->fd+1, NULL, &wfds,&xfds, &tv); + if (return_val == 0) { + /* timeout waiting on serial port */ + LOG(PIL_CRIT, "%s: Timeout writing to %s", + pluginid, ctx->device); + return S_TIMEOUT; + } else if ((return_val == -1) || FD_ISSET(ctx->fd, &xfds)) { + /* an error occured */ + LOG(PIL_CRIT, "%s: Error before writing to %s: %s", + pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* send the command */ + if (write(ctx->fd, writebuf, strlen(writebuf)) != + (int)strlen(writebuf)) { + LOG(PIL_CRIT, "%s: Error writing to %s : %s", + pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* suceeded! */ + return S_OK; + +} /* end RPSSendCommand() */ + +/* + * RPSReset - Reset (power-cycle) the given outlet id + */ +static int +RPSReset(struct pluginDevice* ctx, char unit_id, const char * rebootid) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid, + ctx->device); + return S_OOPS; + } + + /* send the "toggle power" command */ + SENDCMD(unit_id, 'T', 10); + + /* Expect "Plug 0 Off" */ + /* Note: If asked to control "*", the RPS10 will report all units it + * separately; however, we don't know how many, so we can only wait + * for the first unit to report something and then wait until the + * "Complete" */ + EXPECT(ctx->fd, WTItokPlug, 5); + if (Debug) { + LOG(PIL_DEBUG, "Got Plug\n"); + } + EXPECT(ctx->fd, WTItokOutlet, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got Outlet #\n"); + } + EXPECT(ctx->fd, WTItokOff, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got Off\n"); + } + EXPECT(ctx->fd, WTItokCRNL, 2); + LOG(PIL_INFO, "Host is being rebooted: %s", rebootid); + + /* Expect "Complete" */ + EXPECT(ctx->fd, WTItokComplete, 14); + if (Debug) { + LOG(PIL_DEBUG, "Got Complete\n"); + } + EXPECT(ctx->fd, WTItokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL\n"); + } + + return(S_OK); + +} /* end RPSReset() */ + + +#if defined(ST_POWERON) +/* + * RPSOn - Turn OFF the given outlet id + */ +static int +RPSOn(struct pluginDevice* ctx, char unit_id, const char * host) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid, + ctx->device); + return S_OOPS; + } + + /* send the "On" command */ + SENDCMD(unit_id, '1', 10); + + /* Expect "Plug 0 On" */ + EXPECT(ctx->fd, WTItokPlug, 5); + EXPECT(ctx->fd, WTItokOutlet, 2); + EXPECT(ctx->fd, WTItokOn, 2); + EXPECT(ctx->fd, WTItokCRNL, 2); + LOG(PIL_INFO, "Host is being turned on: %s", host); + + /* Expect "Complete" */ + EXPECT(ctx->fd, WTItokComplete, 5); + EXPECT(ctx->fd, WTItokCRNL, 2); + + return(S_OK); + +} /* end RPSOn() */ +#endif + + +#if defined(ST_POWEROFF) +/* + * RPSOff - Turn Off the given outlet id + */ +static int +RPSOff(struct pluginDevice* ctx, char unit_id, const char * host) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid, + ctx->device); + return S_OOPS; + } + + /* send the "Off" command */ + SENDCMD(unit_id, '0', 10); + + /* Expect "Plug 0 Off" */ + EXPECT(ctx->fd, WTItokPlug, 5); + EXPECT(ctx->fd, WTItokOutlet, 2); + EXPECT(ctx->fd, WTItokOff, 2); + EXPECT(ctx->fd, WTItokCRNL, 2); + LOG(PIL_INFO, "Host is being turned on: %s", host); + + /* Expect "Complete" */ + EXPECT(ctx->fd, WTItokComplete, 5); + EXPECT(ctx->fd, WTItokCRNL, 2); + + return(S_OK); + +} /* end RPSOff() */ +#endif + + +/* + * rps10_status - API entry point to probe the status of the stonith device + * (basically just "is it reachable and functional?", not the + * status of the individual outlets) + * + * Returns: + * S_OOPS - some error occured + * S_OK - if the stonith device is reachable and online. + */ +static int +rps10_status(StonithPlugin *s) +{ + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + if (RPSConnect(ctx) != S_OK) { + return(S_OOPS); + } + + /* The "connect" really does enough work to see if the + controller is alive... It verifies that it is returning + RPS-10 Ready + */ + + return(RPSDisconnect(ctx)); +} + +/* + * rps10_hostlist - API entry point to return the list of hosts + * for the devices on this WTI_RPS10 unit + * + * This type of device is configured from the config file, + * so we don't actually have to connect to figure this + * out, just peruse the 'ctx' structure. + * Returns: + * NULL on error + * a malloced array, terminated with a NULL, + * of null-terminated malloc'ed strings. + */ +static char ** +rps10_hostlist(StonithPlugin *s) +{ + char ** ret = NULL; /* list to return */ + int i; + int j; + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,NULL); + + ctx = (struct pluginDevice*) s; + + if (ctx->unit_count >= 1) { + ret = (char **)MALLOC((ctx->unit_count+1)*sizeof(char*)); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return ret; + } + ret[ctx->unit_count]=NULL; /* null terminate the array */ + for (i=0; i < ctx->unit_count; i++) { + ret[i] = STRDUP(ctx->controllers[i].node); + if (ret[i] == NULL) { + for(j=0; j<i; j++) { + FREE(ret[j]); + } + FREE(ret); ret = NULL; + break; + } + } /* end for each possible outlet */ + } /* end if any outlets are configured */ + return(ret); +} /* end si_hostlist() */ + +/* + * Parse the given configuration information, and stash + * it away... + * + * The format of <info> for this module is: + * <serial device> <remotenode> <outlet> [<remotenode> <outlet>] ... + * + * e.g. A machine named 'nodea' can kill a machine named 'nodeb' through + * a device attached to serial port /dev/ttyS0. + * A machine named 'nodeb' can kill machines 'nodea' and 'nodec' + * through a device attached to serial port /dev/ttyS1 (outlets 0 + * and 1 respectively) + * + * <assuming this is the heartbeat configuration syntax:> + * + * stonith nodea rps10 /dev/ttyS0 nodeb 0 + * stonith nodeb rps10 /dev/ttyS0 nodea 0 nodec 1 + * + * Another possible configuration is for 2 stonith devices + * accessible through 2 different serial ports on nodeb: + * + * stonith nodeb rps10 /dev/ttyS0 nodea 0 + * stonith nodeb rps10 /dev/ttyS1 nodec 0 + */ + +/* + * OOPS! + * + * Most of the large block of comments above is incorrect as far as this + * module is concerned. It is somewhat applicable to the heartbeat code, + * but not to this Stonith module. + * + * The format of parameter string for this module is: + * <serial device> <remotenode> <outlet> [<remotenode> <outlet>] ... + */ + +static int +RPS_parse_config_info(struct pluginDevice* ctx, const char * info) +{ + char *copy; + char *token; + char *outlet, *node; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* strtok() is nice to use to parse a string with + (other than it isn't threadsafe), but it is destructive, so + we're going to alloc our own private little copy for the + duration of this function. + */ + + copy = STRDUP(info); + if (!copy) { + LOG(PIL_CRIT, "out of memory"); + return S_OOPS; + } + + /* Grab the serial device */ + token = strtok (copy, " \t"); + + if (!token) { + LOG(PIL_CRIT, "%s: Can't find serial device on config line '%s'", + pluginid, info); + goto token_error; + } + + ctx->device = STRDUP(token); + if (!ctx->device) { + LOG(PIL_CRIT, "out of memory"); + goto token_error; + } + + /* Loop through the rest of the command line which should consist of */ + /* <nodename> <outlet> pairs */ + while ((node = strtok (NULL, " \t")) + && (outlet = strtok (NULL, " \t\n"))) { + char outlet_id; + + /* validate the outlet token */ + if ((sscanf (outlet, "%c", &outlet_id) != 1) + || !( ((outlet_id >= '0') && (outlet_id <= '9')) + || (outlet_id == '*') || (outlet_id == 'A') ) + ) { + LOG(PIL_CRIT + , "%s: the outlet_id %s must be between" + " 0 and 9 or '*' / 'A'", + pluginid, outlet); + goto token_error; + } + + if (outlet_id == 'A') { + /* Remap 'A' to '*'; in some configurations, + * a '*' can't be configured because it breaks + * scripts -- lmb */ + outlet_id = '*'; + } + + if (ctx->unit_count >= WTI_NUM_CONTROLLERS) { + LOG(PIL_CRIT, + "%s: Tried to configure too many controllers", + pluginid); + goto token_error; + } + + ctx->controllers[ctx->unit_count].node = STRDUP(node); + strdown(ctx->controllers[ctx->unit_count].node); + ctx->controllers[ctx->unit_count].outlet_id = outlet_id; + ctx->unit_count++; + + } + + /* free our private copy of the string we've been destructively + * parsing with strtok() + */ + FREE(copy); + return ((ctx->unit_count > 0) ? S_OK : S_BADCONFIG); + +token_error: + FREE(copy); + if (ctx->device) { + FREE(ctx->device); + ctx->device = NULL; + } + return(S_BADCONFIG); +} + + +/* + * dtrtoggle - toggle DTR on the serial port + * + * snarfed from minicom, sysdep1.c, a well known POSIX trick. + * + */ +static void dtrtoggle(int fd) { + struct termios tty, old; + int sec = 2; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + tcgetattr(fd, &tty); + tcgetattr(fd, &old); + cfsetospeed(&tty, B0); + cfsetispeed(&tty, B0); + tcsetattr(fd, TCSANOW, &tty); + if (sec>0) { + sleep(sec); + tcsetattr(fd, TCSANOW, &old); + } + + if (Debug) { + LOG(PIL_DEBUG, "dtrtoggle Complete (%s)\n", pluginid); + } +} + +/* + * RPSConnect - + * + * Connect to the given WTI_RPS10 device. + * Side Effects + * DTR on the serial port is toggled + * ctx->fd now contains a valid file descriptor to the serial port + * ??? LOCK THE SERIAL PORT ??? + * + * Returns + * S_OK on success + * S_OOPS on error + * S_TIMEOUT if the device did not respond + * + */ +static int +RPSConnect(struct pluginDevice * ctx) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Open the serial port if it isn't already open */ + if (ctx->fd < 0) { + struct termios tio; + + if (OurImports->TtyLock(ctx->device) < 0) { + LOG(PIL_CRIT, "%s: TtyLock failed.", pluginid); + return S_OOPS; + } + + ctx->fd = open (ctx->device, O_RDWR); + if (ctx->fd <0) { + LOG(PIL_CRIT, "%s: Can't open %s : %s", + pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* set the baudrate to 9600 8 - N - 1 */ + memset (&tio, 0, sizeof(tio)); + + /* ??? ALAN - the -tradtitional flag on gcc causes the + CRTSCTS constant to generate a warning, and warnings + are treated as errors, so I can't set this flag! - EZA ??? + + Hmmm. now that I look at the documentation, RTS + is just wired high on this device! we don't need it. + */ + /* tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD | CRTSCTS ;*/ + tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD ; + tio.c_lflag = ICANON; + + if (tcsetattr (ctx->fd, TCSANOW, &tio) < 0) { + LOG(PIL_CRIT, "%s: Can't set attributes %s : %s", + pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + /* flush all data to and fro the serial port before we start */ + if (tcflush (ctx->fd, TCIOFLUSH) < 0) { + LOG(PIL_CRIT, "%s: Can't flush %s : %s", + pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + + } + + /* Toggle DTR - this 'resets' the controller serial port interface + In minicom, try CTRL-A H to hangup and you can see this behavior. + */ + dtrtoggle(ctx->fd); + + /* Wait for the switch to respond with "RPS-10 Ready". + Emperically, this usually takes 5-10 seconds... + ... If this fails, this may be a hint that you got + a broken serial cable, which doesn't connect hardware + flow control. + */ + if (Debug) { + LOG(PIL_DEBUG, "Waiting for READY\n"); + } + EXPECT(ctx->fd, WTItokReady, 12); + if (Debug) { + LOG(PIL_DEBUG, "Got READY\n"); + } + EXPECT(ctx->fd, WTItokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL\n"); + } + + return(S_OK); +} + +static int +RPSDisconnect(struct pluginDevice * ctx) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd >= 0) { + /* Flush the serial port, we don't care what happens to the + * characters and failing to do this can cause close to hang. + */ + tcflush(ctx->fd, TCIOFLUSH); + close (ctx->fd); + if (ctx->device != NULL) { + OurImports->TtyUnlock(ctx->device); + } + } + ctx->fd = -1; + + return S_OK; +} + +/* + * RPSNametoOutlet - Map a hostname to an outlet on this stonith device. + * + * Returns: + * 0-9, * on success ( the outlet id on the RPS10 ) + * -1 on failure (host not found in the config file) + * + */ +static signed char +RPSNametoOutlet ( struct pluginDevice * ctx, const char * host ) +{ + int i=0; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* scan the controllers[] array to see if this host is there */ + for (i=0;i<ctx->unit_count;i++) { + /* return the outlet id */ + if ( ctx->controllers[i].node + && !strcasecmp(host, ctx->controllers[i].node)) { + /* found it! */ + break; + } + } + + if (i == ctx->unit_count) { + return -1; + } else { + return ctx->controllers[i].outlet_id; + } +} + + +/* + * rps10_reset - API call to Reset (reboot) the given host on + * this Stonith device. This involves toggling the power off + * and then on again, OR just calling the builtin reset command + * on the stonith device. + */ +static int +rps10_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = S_OK; + int lorc = S_OK; + signed char outlet_id = -1; + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + + if ((rc = RPSConnect(ctx)) != S_OK) { + return(rc); + } + + outlet_id = RPSNametoOutlet(ctx, host); + + if (outlet_id < 0) { + LOG(PIL_WARN, "%s: %s doesn't control host [%s]" + , pluginid, ctx->device, host ); + RPSDisconnect(ctx); + return(S_BADHOST); + } + + switch(request) { + +#if defined(ST_POWERON) + case ST_POWERON: + rc = RPSOn(ctx, outlet_id, host); + break; +#endif +#if defined(ST_POWEROFF) + case ST_POWEROFF: + rc = RPSOff(ctx, outlet_id, host); + break; +#endif + case ST_GENERIC_RESET: + rc = RPSReset(ctx, outlet_id, host); + break; + default: + rc = S_INVAL; + break; + } + + lorc = RPSDisconnect(ctx); + + return(rc != S_OK ? rc : lorc); +} + +/* + * Parse the information in the given string, + * and stash it away... + */ +static int +rps10_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* ctx; + StonithNamesToGet namestocopy [] = + { {ST_RPS10, NULL} + , {NULL, NULL} + }; + int rc=0; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + if (s->isconfigured) { + /* The module is already configured. */ + return(S_OOPS); + } + + ctx = (struct pluginDevice*) s; + + if((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK){ + LOG(PIL_DEBUG , "get all calues failed"); + return rc; + } + + rc = RPS_parse_config_info(ctx, namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + return rc; +} + +/* + * Return the Stonith plugin configuration parameter + * + */ +static const char * const * +rps10_get_confignames(StonithPlugin* p) +{ + static const char * Rps10Params[] = {ST_RPS10 ,NULL }; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + return Rps10Params; +} + +/* + * rps10_getinfo - API entry point to retrieve something from the handle + */ +static const char * +rps10_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* ctx; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + ctx = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ctx->idinfo; + break; + case ST_DEVICENAME: + ret = ctx->device; + break; + case ST_DEVICEDESCR: + ret = "Western Telematic Inc. (WTI) " + "Remote Power Switch - RPS-10M.\n"; + break; + case ST_DEVICEURL: + ret = "http://www.wti.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = rps10XML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * rps10_destroy - API entry point to destroy a WTI_RPS10 Stonith object. + */ +static void +rps10_destroy(StonithPlugin *s) +{ + struct pluginDevice* ctx; + int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + ctx = (struct pluginDevice *)s; + + ctx->pluginid = NOTwtiid; + + /* close the fd if open and set ctx->fd to invalid */ + RPSDisconnect(ctx); + + if (ctx->device != NULL) { + FREE(ctx->device); + ctx->device = NULL; + } + if (ctx->unit_count > 0) { + for (i = 0; i < ctx->unit_count; i++) { + if (ctx->controllers[i].node != NULL) { + FREE(ctx->controllers[i].node); + ctx->controllers[i].node = NULL; + } + } + } + FREE(ctx); +} + +/* + * rps10_new - API entry point called to create a new WTI_RPS10 Stonith device + * object. + */ +static StonithPlugin * +rps10_new(const char *subplugin) +{ + struct pluginDevice* ctx = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(ctx, 0, sizeof(*ctx)); + ctx->pluginid = pluginid; + ctx->fd = -1; + ctx->unit_count = 0; + ctx->device = NULL; + ctx->idinfo = DEVICE; + ctx->sp.s_ops = &rps10Ops; + + return &(ctx->sp); +} diff --git a/lib/plugins/stonith/ssh.c b/lib/plugins/stonith/ssh.c new file mode 100644 index 0000000..e90c199 --- /dev/null +++ b/lib/plugins/stonith/ssh.c @@ -0,0 +1,351 @@ +/* + * Stonith module for SSH Stonith device + * + * Copyright (c) 2001 SuSE Linux AG + * + * Authors: Joachim Gleissner <jg@suse.de>, Lars Marowsky-Brée <lmb@suse.de> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> + +#include <config.h> + +#define DEVICE "SSH STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN ssh +#define PIL_PLUGIN_S "ssh" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +static StonithPlugin * ssh_new(const char *); +static void ssh_destroy(StonithPlugin *); +static const char * const * ssh_get_confignames(StonithPlugin *); +static int ssh_set_config(StonithPlugin *, StonithNVpair*); +static const char * ssh_get_info(StonithPlugin * s, int InfoType); +static int ssh_status(StonithPlugin * ); +static int ssh_reset_req(StonithPlugin * s, int request +, const char * host); +static char ** ssh_hostlist(StonithPlugin *); + +static struct stonith_ops sshOps ={ + ssh_new, /* Create new STONITH object */ + ssh_destroy, /* Destroy STONITH object */ + ssh_get_info, /* Return STONITH info string */ + ssh_get_confignames, /* Return configuration parameters */ + ssh_set_config, /* set configuration */ + ssh_status, /* Return STONITH device status */ + ssh_reset_req, /* Request a reset */ + ssh_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &sshOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* uncomment this if you have an ssh that can do what it claims +#define SSH_COMMAND "ssh -q -x -o PasswordAuthentication=no StrictHostKeyChecking=no" +*/ +/* use this if you have the (broken) OpenSSH 2.1.1 */ +/* sunjd@cn.ibm.com added the option -f to temporily work around the block issue + * in which the child process always stay in 'system' call. Please FIX this. + * Additonally, this issue seems related to both of 2.6 kernel and stonithd. + */ +#define SSH_COMMAND "ssh -q -x -n -l root" + +/* We need to do a real hard reboot without syncing anything to simulate a + * power cut. + * We have to do it in the background, otherwise this command will not + * return. + */ +#define REBOOT_COMMAND "nohup sh -c '(sleep 2; nohup " REBOOT " " REBOOT_OPTIONS ") </dev/null >/dev/null 2>&1' &" +#undef REBOOT_COMMAND +#define REBOOT_COMMAND "echo 'sleep 2; " REBOOT " " REBOOT_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" +#define POWEROFF_COMMAND "echo 'sleep 2; " POWEROFF_CMD " " POWEROFF_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" + +#define MAX_PING_ATTEMPTS 15 + +/* + * SSH STONITH device + * + * I used the null device as template, so I guess there is missing + * some functionality. + * + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static const char * pluginid = "SSHDevice-Stonith"; +static const char * NOTpluginid = "SSH device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *sshXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +ssh_status(StonithPlugin *s) +{ + ERRIFWRONGDEV(s, S_OOPS); + + return system(NULL) ? S_OK : S_OOPS; +} + + +/* + * Return the list of hosts configured for this SSH device + */ + +static char ** +ssh_hostlist(StonithPlugin *s) +{ + struct pluginDevice* sd = (struct pluginDevice*)s; + + ERRIFWRONGDEV(s, NULL); + + if (sd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in %s", __FUNCTION__); + return(NULL); + } + + return OurImports->CopyHostList((const char * const *)sd->hostlist); +} + + +/* + * Reset the given host on this Stonith device. + */ +static int +ssh_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + char cmd[4096]; + int i, status = -1; + + ERRIFWRONGDEV(s, S_OOPS); + + if (request == ST_POWERON) { + LOG(PIL_CRIT, "%s not capable of power-on operation", DEVICE); + return S_INVAL; + } else if (request != ST_POWEROFF && request != ST_GENERIC_RESET) { + return S_INVAL; + } + + for (i = 0; i < sd->hostcount; i++) { + if (strcasecmp(host, sd->hostlist[i]) == 0) { + break; + } + } + + if (i >= sd->hostcount) { + LOG(PIL_CRIT, "%s doesn't control host [%s]" + , sd->idinfo, host); + return(S_BADHOST); + } + + LOG(PIL_INFO, "Initiating ssh-%s on host: %s" + , request == ST_POWEROFF ? "poweroff" : "reset", host); + + snprintf(cmd, sizeof(cmd)-1, "%s \"%s\" \"%s\"", SSH_COMMAND + , host + , request == ST_POWEROFF ? POWEROFF_COMMAND : REBOOT_COMMAND); + + status = system(cmd); + if (WIFEXITED(status) && 0 == WEXITSTATUS(status)) { + if (Debug) { + LOG(PIL_DEBUG, "checking whether %s stonith'd", host); + } + + snprintf(cmd, sizeof(cmd)-1 + , "ping -w1 -c1 %s >/dev/null 2>&1", host); + + for (i = 0; i < MAX_PING_ATTEMPTS; i++) { + status = system(cmd); + if (WIFEXITED(status) && 1 == WEXITSTATUS(status)) { + if (Debug) { + LOG(PIL_DEBUG, "unable to ping %s" + " after %d tries, stonith did work" + , host, i); + } + return S_OK; + } + sleep(1); + } + + LOG(PIL_CRIT, "still able to ping %s after %d tries, stonith" + " did not work", host, MAX_PING_ATTEMPTS); + return S_RESETFAIL; + }else{ + LOG(PIL_CRIT, "command %s failed", cmd); + return S_RESETFAIL; + } +} + +static const char * const * +ssh_get_confignames(StonithPlugin* p) +{ + static const char * SshParams[] = {ST_HOSTLIST, NULL }; + return SshParams; +} + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +ssh_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * hlist; + + ERRIFWRONGDEV(s,S_OOPS); + + if ((hlist = OurImports->GetValue(list, ST_HOSTLIST)) == NULL) { + return S_OOPS; + } + sd->hostlist = OurImports->StringToHostList(hlist); + if (sd->hostlist == NULL) { + LOG(PIL_CRIT, "out of memory"); + sd->hostcount = 0; + }else{ + for (sd->hostcount = 0; sd->hostlist[sd->hostcount] + ; sd->hostcount++) { + strdown(sd->hostlist[sd->hostcount]); + } + } + + return sd->hostcount ? S_OK : S_OOPS; +} + + +static const char * +ssh_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + + switch (reqtype) { + case ST_DEVICEID: + ret = sd->idinfo; + break; + + + case ST_DEVICENAME: + ret = "ssh STONITH device"; + break; + + + case ST_DEVICEDESCR: /* Description of device type */ + ret = "SSH-based host reset\n" + "Fine for testing, but not suitable for production!"; + break; + + + case ST_DEVICEURL: + ret = "http://openssh.org"; + break; + + + case ST_CONF_XML: /* XML metadata */ + ret = sshXML; + break; + + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * SSH Stonith destructor... + */ +static void +ssh_destroy(StonithPlugin *s) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + + VOIDERRIFWRONGDEV(s); + + sd->pluginid = NOTpluginid; + if (sd->hostlist) { + stonith_free_hostlist(sd->hostlist); + sd->hostlist = NULL; + } + sd->hostcount = -1; + FREE(sd); +} + +/* Create a new ssh Stonith device */ +static StonithPlugin* +ssh_new(const char *subplugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + sd->hostlist = NULL; + sd->hostcount = -1; + sd->idinfo = DEVICE; + sd->sp.s_ops = &sshOps; + return &(sd->sp); +} diff --git a/lib/plugins/stonith/stonith_config_xml.h b/lib/plugins/stonith/stonith_config_xml.h new file mode 100644 index 0000000..ff04ae9 --- /dev/null +++ b/lib/plugins/stonith/stonith_config_xml.h @@ -0,0 +1,157 @@ +/* + * stonith_config_xml.h: common macros easing the writing of config + * XML for STONITH plugins. Only a STONITH + * plugin should include this header! + * + * Copyright (C) International Business Machines Corp., 2005 + * Author: Dave Blaschke <debltc@us.ibm.com> + * Support: linux-ha@lists.linux-ha.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef _STONITH_CONFIG_XML_H +#define _STONITH_CONFIG_XML_H + +/* + * The generic constants for XML + */ + +/* <parameters>?</parameters> */ +#define XML_PARAMETERS_BEGIN "<parameters>" +#define XML_PARAMETERS_END "</parameters>" + +/* <parameter name="ipaddr" unique="?">?<content type="string" /></parameter> */ +#define XML_PARAMETER_BEGIN(name,type,req,uniq) \ + "<parameter name=\"" name "\" unique=\"" uniq "\" required=\"" req "\">" \ + "<content type=\"" type "\" />\n" +#define XML_PARAMETER_END "</parameter>\n" + +/* <shortdesc lang="en">?</shortdesc> */ +#define XML_PARM_SHORTDESC_BEGIN(lang) \ + "<shortdesc lang=\"" lang "\">\n" +#define XML_PARM_SHORTDESC_END "</shortdesc>\n" + +/* <longdesc lang="en">?</longdesc> */ +#define XML_PARM_LONGDESC_BEGIN(lang) \ + "<longdesc lang=\"" lang "\">\n" +#define XML_PARM_LONGDESC_END "</longdesc>\n" + +/* + * The short and long descriptions for the few standardized parameter names; + * these can be translated by appending different languages to these constants + * (must include XML_PARM_****DESC_BEGIN(), the translated description, and + * XML_PARM_****DESC_END for each language) + */ +#define XML_HOSTLIST_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Hostlist" \ + XML_PARM_SHORTDESC_END + +#define XML_HOSTLIST_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The list of hosts that the STONITH device controls" \ + XML_PARM_LONGDESC_END + +#define XML_IPADDR_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "IP Address" \ + XML_PARM_SHORTDESC_END + +#define XML_IPADDR_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The IP address of the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_LOGIN_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Login" \ + XML_PARM_SHORTDESC_END + +#define XML_LOGIN_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The username used for logging in to the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_PASSWD_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Password" \ + XML_PARM_SHORTDESC_END + +#define XML_PASSWD_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The password used for logging in to the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_COMMUNITY_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "SNMP Community" \ + XML_PARM_SHORTDESC_END + +#define XML_COMMUNITY_LONGDESC "" \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The SNMP community string associated with the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_TTYDEV_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "TTY Device" \ + XML_PARM_SHORTDESC_END + +#define XML_TTYDEV_LONGDESC "" \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The TTY device used for connecting to the STONITH device" \ + XML_PARM_LONGDESC_END + +/* + * Complete parameter descriptions for the few standardized parameter names + */ +#define XML_HOSTLIST_PARM \ + XML_PARAMETER_BEGIN(ST_HOSTLIST, "string", "1", "0") \ + XML_HOSTLIST_SHORTDESC \ + XML_HOSTLIST_LONGDESC \ + XML_PARAMETER_END + +#define XML_IPADDR_PARM \ + XML_PARAMETER_BEGIN(ST_IPADDR, "string", "1", "0") \ + XML_IPADDR_SHORTDESC \ + XML_IPADDR_LONGDESC \ + XML_PARAMETER_END + +#define XML_LOGIN_PARM \ + XML_PARAMETER_BEGIN(ST_LOGIN, "string", "1", "0") \ + XML_LOGIN_SHORTDESC \ + XML_LOGIN_LONGDESC \ + XML_PARAMETER_END + +#define XML_PASSWD_PARM \ + XML_PARAMETER_BEGIN(ST_PASSWD, "string", "1", "0") \ + XML_PASSWD_SHORTDESC \ + XML_PASSWD_LONGDESC \ + XML_PARAMETER_END + +#define XML_COMMUNITY_PARM \ + XML_PARAMETER_BEGIN(ST_COMMUNITY, "string", "1", "0") \ + XML_COMMUNITY_SHORTDESC \ + XML_COMMUNITY_LONGDESC \ + XML_PARAMETER_END + +#define XML_TTYDEV_PARM \ + XML_PARAMETER_BEGIN(ST_TTYDEV, "string", "1", "0") \ + XML_TTYDEV_SHORTDESC \ + XML_TTYDEV_LONGDESC \ + XML_PARAMETER_END + +#endif diff --git a/lib/plugins/stonith/stonith_expect_helpers.h b/lib/plugins/stonith/stonith_expect_helpers.h new file mode 100644 index 0000000..f9eaa19 --- /dev/null +++ b/lib/plugins/stonith/stonith_expect_helpers.h @@ -0,0 +1,120 @@ +/* + * stonith_expect_helpers.h: Some common expect defines. + * + * Copyright (C) 2004 Lars Marowsky-Bree <lmb@suse.de> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* This is still somewhat ugly. It needs to be included after the PILS + * definitions so that it can access them, but the code reduction seemed + * to justify this. Hopefully it can be made somewhat more elegant + * eventually. */ + +/* + * Many expect/telnet plugins use these defines and functions. + */ + +#define SEND(fd,s) { \ + size_t slen = strlen(s); \ + if (Debug) { \ + LOG(PIL_DEBUG \ + , "Sending [%s] (len %d)" \ + , (s) \ + , (int)slen); \ + } \ + if (write((fd), (s), slen) != slen) { \ + LOG(PIL_CRIT \ + , "%s: write failed" \ + , __FUNCTION__); \ + } \ + } + +#define EXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) \ + return(errno == ETIMEDOUT \ + ? S_TIMEOUT : S_OOPS); \ + } + +#define NULLEXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) \ + return(NULL); \ + } + +#define SNARF(fd,s, to) { \ + if (StonithScanLine(fd,to,(s),sizeof(s))\ + != S_OK){ \ + return(S_OOPS); \ + } \ + } + +#define NULLSNARF(fd,s, to){ \ + if (StonithScanLine(fd,to,(s),sizeof(s))\ + != S_OK) { \ + return(NULL); \ + } \ + } + +/* Look for any of the given patterns. We don't care which */ +static int +StonithLookFor(int fd, struct Etoken * tlist, int timeout) +{ + int rc; + char savebuf[512]; + + if ((rc = EXPECT_TOK(fd, tlist, timeout, savebuf, sizeof(savebuf) + , Debug)) < 0) { + LOG(PIL_CRIT, "Did not find string %s from " DEVICE "." + , tlist[0].string); + LOG(PIL_CRIT, "Received [%s]", savebuf); + } + return(rc); +} + +#ifndef DOESNT_USE_STONITHSCANLINE +/* Accept either a CR/NL or an NL/CR */ +static struct Etoken CRNL[] = { {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}}; + +static int +StonithScanLine(int fd, int timeout, char * buf, int max) +{ + if (EXPECT_TOK(fd, CRNL, timeout, buf, max, Debug) < 0) { + LOG(PIL_CRIT, "Could not read line from" DEVICE "."); + return(S_OOPS); + } + return(S_OK); +} +#endif + +#ifndef DOESNT_USE_STONITHKILLCOMM +static void +Stonithkillcomm(int *rdfd, int *wrfd, int *pid) +{ + if ((rdfd != NULL) && (*rdfd >= 0)) { + close(*rdfd); + *rdfd = -1; + } + if ((wrfd != NULL) && (*wrfd >= 0)) { + close(*wrfd); + *wrfd = -1; + } + if ((pid != NULL) && (*pid > 0)) { + STONITH_KILL(*pid, SIGKILL); + (void)waitpid(*pid, NULL, 0); + *pid = -1; + } +} +#endif diff --git a/lib/plugins/stonith/stonith_plugin_common.h b/lib/plugins/stonith/stonith_plugin_common.h new file mode 100644 index 0000000..dcdd7c8 --- /dev/null +++ b/lib/plugins/stonith/stonith_plugin_common.h @@ -0,0 +1,127 @@ +/* + * stonith_plugin_common.h: common macros easing the writing of STONITH + * plugins. Only a STONITH plugin should + * include this header! + * + * Copyright (C) 2004 Lars Marowsky-Bree <lmb@suse.de> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef _STONITH_PLUGIN_COMMON_H +#define _STONITH_PLUGIN_COMMON_H + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <libintl.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <ctype.h> +#include <string.h> +#include <fcntl.h> +#include <netdb.h> +#ifdef HAVE_TERMIO_H +# include <termio.h> +#endif +#ifdef HAVE_SYS_TERMIOS_H +#include <sys/termios.h> +#else +#ifdef HAVE_TERMIOS_H +#include <termios.h> +#endif +#endif +#include <glib.h> + + +#include <stonith/stonith.h> +#include <stonith/stonith_plugin.h> + +#define LOG(w...) PILCallLog(PluginImports->log, w) + +#define MALLOC PluginImports->alloc +#define REALLOC PluginImports->mrealloc +#define STRDUP PluginImports->mstrdup +#define FREE PluginImports->mfree +#define EXPECT_TOK OurImports->ExpectToken +#define STARTPROC OurImports->StartProcess + +#ifdef MALLOCT +# undef MALLOCT +#endif +#define ST_MALLOCT(t) ((t *)(MALLOC(sizeof(t)))) + +#define N_(text) (text) +#define _(text) dgettext(ST_TEXTDOMAIN, text) + +#define WHITESPACE " \t\n\r\f" + +#ifndef MIN +/* some macros */ +# define MIN( i, j ) ( i > j ? j : i ) +#endif + +#define REPLSTR(s,v) { \ + if ((s) != NULL) { \ + FREE(s); \ + (s)=NULL; \ + } \ + (s) = STRDUP(v); \ + if ((s) == NULL) { \ + PILCallLog(PluginImports->log, \ + PIL_CRIT, "out of memory"); \ + } \ + } + +#ifndef DEVICE +#define DEVICE "Dummy" +#endif + +#define PIL_PLUGINTYPE STONITH_TYPE +#define PIL_PLUGINTYPE_S STONITH_TYPE_S + +#define ISCORRECTDEV(i) ((i)!= NULL \ + && ((struct pluginDevice *)(i))->pluginid == pluginid) + +#define ERRIFWRONGDEV(s, retval) if (!ISCORRECTDEV(s)) { \ + LOG(PIL_CRIT, "%s: invalid argument", __FUNCTION__); \ + return(retval); \ + } + +#define VOIDERRIFWRONGDEV(s) if (!ISCORRECTDEV(s)) { \ + LOG(PIL_CRIT, "%s: invalid argument", __FUNCTION__); \ + return; \ + } + +#define ISCONFIGED(i) (i->isconfigured) + +#define ERRIFNOTCONFIGED(s,retval) ERRIFWRONGDEV(s,retval); \ + if (!ISCONFIGED(s)) { \ + LOG(PIL_CRIT, "%s: not configured", __FUNCTION__); \ + return(retval); \ + } + +#define VOIDERRIFNOTCONFIGED(s) VOIDERRIFWRONGDEV(s); \ + if (!ISCONFIGED(s)) { \ + LOG(PIL_CRIT, "%s: not configured", __FUNCTION__); \ + return; \ + } + +#endif + diff --git a/lib/plugins/stonith/stonith_signal.h b/lib/plugins/stonith/stonith_signal.h new file mode 100644 index 0000000..99513f5 --- /dev/null +++ b/lib/plugins/stonith/stonith_signal.h @@ -0,0 +1,68 @@ +/* + * stonith_signal.h: signal handling routines to be used by stonith + * plugin libraries + * + * Copyright (C) 2002 Horms <horms@verge.net.au> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef _STONITH_SIGNAL_H +#define _STONITH_SIGNAL_H + +#include <signal.h> +#include <sys/signal.h> + +int +stonith_signal_set_simple_handler(int sig, void (*handler)(int) +, struct sigaction *oldact); + +int +stonith_signal_set_simple_handler(int sig, void (*handler)(int) +, struct sigaction *oldact) +{ + struct sigaction sa; + sigset_t mask; + + (void)stonith_signal_set_simple_handler; + if(sigemptyset(&mask) < 0) { + return(-1); + } + + sa.sa_handler = handler; + sa.sa_mask = mask; + sa.sa_flags = 0; + + if(sigaction(sig, &sa, oldact) < 0) { + return(-1); + } + + return(0); +} + +#define STONITH_SIGNAL(_sig, _handler) \ + stonith_signal_set_simple_handler((_sig), (_handler), NULL) +#ifdef HAVE_SIGIGNORE +#define STONITH_IGNORE_SIG(_sig) \ + sigignore((_sig)) +#else +#define STONITH_IGNORE_SIG(_sig) \ + STONITH_SIGNAL((_sig), SIG_IGN) +#endif +#define STONITH_DEFAULT_SIG(_sig) STONITH_SIGNAL((_sig), SIG_DFL) + +#define STONITH_KILL(_pid, _sig) kill((_pid), (_sig)) + +#endif /* _STONITH_SIGNAL_H */ diff --git a/lib/plugins/stonith/suicide.c b/lib/plugins/stonith/suicide.c new file mode 100644 index 0000000..b9d1db4 --- /dev/null +++ b/lib/plugins/stonith/suicide.c @@ -0,0 +1,274 @@ +/* File: suicide.c + * Description: Stonith module for suicide + * + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <lha_internal.h> +#include <config.h> +#include <sys/utsname.h> + +#define DEVICE "Suicide STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN suicide +#define PIL_PLUGIN_S "suicide" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +static StonithPlugin * suicide_new(const char *); +static void suicide_destroy(StonithPlugin *); +static const char * const * suicide_get_confignames(StonithPlugin *); +static int suicide_set_config(StonithPlugin *, StonithNVpair*); +static const char * suicide_get_info(StonithPlugin * s, int InfoType); +static int suicide_status(StonithPlugin * ); +static int suicide_reset_req(StonithPlugin * s, int request + , const char * host); +static char ** suicide_hostlist(StonithPlugin *); + +static struct stonith_ops suicideOps ={ + suicide_new, /* Create new STONITH object */ + suicide_destroy, /* Destroy STONITH object */ + suicide_get_info, /* Return STONITH info string */ + suicide_get_confignames, /* Return configuration parameters */ + suicide_set_config, /* Set configuration */ + suicide_status, /* Return STONITH device status */ + suicide_reset_req, /* Request a reset */ + suicide_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &suicideOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#define REBOOT_COMMAND "nohup sh -c 'sleep 2; " REBOOT " " REBOOT_OPTIONS " </dev/null >/dev/null 2>&1' &" +#define POWEROFF_COMMAND "nohup sh -c 'sleep 2; " POWEROFF_CMD " " POWEROFF_OPTIONS " </dev/null >/dev/null 2>&1' &" +/* +#define REBOOT_COMMAND "echo 'sleep 2; " REBOOT " " REBOOT_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" +#define POWEROFF_COMMAND "echo 'sleep 2; " POWEROFF_CMD " " POWEROFF_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" +*/ + +/* + * Suicide STONITH device + */ +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; +}; + +static const char * pluginid = "SuicideDevice-Stonith"; +static const char * NOTpluginid = "Suicide device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *suicideXML = + XML_PARAMETERS_BEGIN + XML_PARAMETERS_END; + +static int +suicide_status(StonithPlugin *s) +{ + ERRIFWRONGDEV(s, S_OOPS); + + return S_OK; +} + +/* + * Return the list of hosts configured for this Suicide device + */ +static char ** +suicide_hostlist(StonithPlugin *s) +{ + char** ret = NULL; + struct utsname name; + + ERRIFWRONGDEV(s, NULL); + + if (uname(&name) == -1) { + LOG(PIL_CRIT, "uname error %d", errno); + return ret; + } + + ret = OurImports->StringToHostList(name.nodename); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return ret; + } + strdown(ret[0]); + + return ret; +} + +/* + * Suicide - reset or poweroff itself. + */ +static int +suicide_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = -1; + struct utsname name; + + ERRIFWRONGDEV(s, S_OOPS); + + if (request == ST_POWERON) { + LOG(PIL_CRIT, "%s not capable of power-on operation", DEVICE); + return S_INVAL; + } else if (request != ST_POWEROFF && request != ST_GENERIC_RESET) { + LOG(PIL_CRIT, "As for suicide virtual stonith device, " + "reset request=%d is not supported", request); + return S_INVAL; + } + + if (uname(&name) == -1) { + LOG(PIL_CRIT, "uname error %d", errno); + return S_RESETFAIL ; + } + + if (strcmp(name.nodename, host)) { + LOG(PIL_CRIT, "%s doesn't control host [%s]" + , name.nodename, host); + return S_RESETFAIL ; + } + + LOG(PIL_INFO, "Initiating suicide on host %s", host); + + rc = system( + request == ST_GENERIC_RESET ? REBOOT_COMMAND : POWEROFF_COMMAND); + + if (rc == 0) { + LOG(PIL_INFO, "Suicide stonith succeeded."); + return S_OK; + } else { + LOG(PIL_CRIT, "Suicide stonith failed."); + return S_RESETFAIL ; + } +} + +static const char * const * +suicide_get_confignames(StonithPlugin* p) +{ + /* Donnot need to initialize from external. */ + static const char * SuicideParams[] = { NULL }; + return SuicideParams; +} + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +suicide_set_config(StonithPlugin* s, StonithNVpair* list) +{ + ERRIFWRONGDEV(s,S_OOPS); + return S_OK; +} + +static const char * +suicide_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + sd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = sd->idinfo; + break; + + case ST_DEVICENAME: + ret = "suicide STONITH device"; + break; + + case ST_DEVICEDESCR: /* Description of device type */ + ret = "Virtual device to reboot/powerdown itself.\n"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = suicideXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Suicide Stonith destructor... + */ +static void +suicide_destroy(StonithPlugin *s) +{ + struct pluginDevice* sd; + + VOIDERRIFWRONGDEV(s); + + sd = (struct pluginDevice *)s; + + sd->pluginid = NOTpluginid; + FREE(sd); +} + +/* Create a new suicide Stonith device */ +static StonithPlugin* +suicide_new(const char * subplugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + sd->idinfo = DEVICE; + sd->sp.s_ops = &suicideOps; + return &(sd->sp); +} diff --git a/lib/plugins/stonith/vacm.c b/lib/plugins/stonith/vacm.c new file mode 100644 index 0000000..ce6d041 --- /dev/null +++ b/lib/plugins/stonith/vacm.c @@ -0,0 +1,485 @@ + +/****************************************************************************** +* +* Copyright 2000 Sistina Software, Inc. +* Tiny bits Copyright 2000 Alan Robertson <alanr@unix.sh> +* Tiny bits Copyright 2000 Zac Sprackett, VA Linux Systems +* Tiny bits Copyright 2005 International Business Machines +* Significantly Mangled by Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005 +* +* This is free software released under the GNU General Public License. +* There is no warranty for this software. See the file COPYING for +* details. +* +* See the file CONTRIBUTORS for a list of contributors. +* +* This file is maintained by: +* Michael C Tilstra <conrad@sistina.com> +* +* Becasue I have no device to test, now I just make it pass the compiling +* with vacm-2.0.5a. Please review before using. +* Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005 +* +* This module provides a driver for the VA Linux Cluster Manager. +* For more information on VACM, see http://vacm.sourceforge.net/ +* +* This module is rather poorly commented. But if you've read the +* VACM Manual, and looked at the code example they have, this +* should make pretty clean sense. (You obiviously should have +* looked at the other stonith source too) +* +*/ + +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define DEVICE "VA Linux Cluster Manager" + +#include "stonith_plugin_common.h" +#include "vacmclient_api.h" + +#define PIL_PLUGIN vacm +#define PIL_PLUGIN_S "vacm" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +static StonithPlugin * vacm_new(const char *); +static void vacm_destroy(StonithPlugin *); +static const char * const * vacm_get_confignames(StonithPlugin *); +static int vacm_set_config(StonithPlugin *, StonithNVpair *); +static const char * vacm_getinfo(StonithPlugin * s, int InfoType); +static int vacm_status(StonithPlugin * ); +static int vacm_reset_req(StonithPlugin * s, int request, const char * host); +static char ** vacm_hostlist(StonithPlugin *); + +static struct stonith_ops vacmOps ={ + vacm_new, /* Create new STONITH object */ + vacm_destroy, /* Destroy STONITH object */ + vacm_getinfo, /* Return STONITH info string */ + vacm_get_confignames, /* Return configuration parameters */ + vacm_set_config, /* Set configuration */ + vacm_status, /* Return STONITH device status */ + vacm_reset_req, /* Request a reset */ + vacm_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug); +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &vacmOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/*structs*/ +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + void *h; /* a handle to the nexxus. */ + char * nexxus; + char * user; + char * passwd; +}; + +#define ST_NEXXUS "nexxus" + +static const char * pluginid = "VACMDevice-Stonith"; +static const char * NOTpluginid = "VACM device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_NEXXUS_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_NEXXUS \ + XML_PARM_SHORTDESC_END + +#define XML_NEXXUS_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The Nexxus component of the VA Cluster Manager" \ + XML_PARM_LONGDESC_END + +#define XML_NEXXUS_PARM \ + XML_PARAMETER_BEGIN(ST_NEXXUS, "string", "1", "1") \ + XML_NEXXUS_SHORTDESC \ + XML_NEXXUS_LONGDESC \ + XML_PARAMETER_END + +static const char *vacmXML = + XML_PARAMETERS_BEGIN + XML_NEXXUS_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +/*funcs*/ +int +vacm_status(StonithPlugin *s) +{ + struct pluginDevice *sd; + char snd[] = "NEXXUS:VERSION"; + char *rcv, *tk; + int rcvlen; + + ERRIFWRONGDEV(s,S_OOPS); + sd = (struct pluginDevice*)s; + + /* If grabbing the nexxus version works, then the status must be ok. + * right? + */ + + api_nexxus_send_ipc(sd->h, snd, strlen(snd)+1); + while(1) { + if (api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + break; + } + if (!(tk = strtok(rcv,":"))) { /*NEXXUS*/ + break; + }else if (!(tk=strtok(NULL,":"))) { /* Job ID */ + break; + }else if (!(tk=strtok(NULL,":"))) { /* one of the below */ + break; + } else if ( !strcmp(tk, "JOB_COMPLETED")) { + free(rcv); + return S_OK; /* YEAH!! */ + }else if(!strcmp(tk, "JOB_STARTED")) { + free(rcv); + continue; + }else if(!strcmp(tk, "JOB_ERROR")) { + free(rcv); + break; + }else if(!strcmp(tk, "VERSION")) { + free(rcv); + continue; + } else { + LOG(PIL_CRIT, "Unexpected token \"%s\" in line \"%s\"\n" + , tk, rcv); + break; + } + } + + return S_OOPS; +} + +/* Better make sure the current group is correct. + * Can't think of a good way to do this. + */ +char ** +vacm_hostlist(StonithPlugin *s) +{ + struct pluginDevice *sd; + char snd[] = "NEXXUS:NODE_LIST"; + char *rcv,*tk; + int rcvlen; + char ** hlst=NULL; + int hacnt=0, hrcnt=0; +#define MSTEP 20 + + ERRIFWRONGDEV(s, NULL); + sd = (struct pluginDevice*)s; + + hlst = (char **)MALLOC(MSTEP * sizeof(char*)); + if (hlst == NULL) { + LOG(PIL_CRIT, "out of memory"); + return NULL; + } + hacnt=MSTEP; + + api_nexxus_send_ipc(sd->h, snd, strlen(snd)+1); + while(1) { + if(api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + goto HL_cleanup; + } + if(!(tk=strtok(rcv, ":"))) { /* NEXXUS */ + goto HL_cleanup; + }else if(!(tk=strtok(NULL,":"))) { /* Job ID */ + goto HL_cleanup; + }else if(!(tk=strtok(NULL,":"))) { /* JOB_* or NODELIST */ + goto HL_cleanup; + }else if( !strcmp(tk, "JOB_STARTED")) { + free(rcv); + continue; + }else if( !strcmp(tk, "JOB_COMPLETED")) { + free(rcv); + return hlst; + }else if( !strcmp(tk, "JOB_ERROR")) { + free(rcv); + break; + }else if( !strcmp(tk, "NODELIST")) { + if(!(tk = strtok(NULL,":"))) { /* group */ + goto HL_cleanup; + }else if((tk = strtok(NULL," \t\n\r"))) { /*Finally, a machine name.*/ + if( hrcnt >= (hacnt-1)) { /* grow array. */ + char **oldhlst = hlst; + hlst = (char **)REALLOC(hlst, (hacnt +MSTEP)*sizeof(char*)); + if( !hlst ) { + stonith_free_hostlist(oldhlst); + return NULL; + } + hacnt += MSTEP; + } + hlst[hrcnt] = STRDUP(tk); /* stuff the name. */ + hlst[hrcnt+1] = NULL; /* set next to NULL for looping */ + if (hlst[hrcnt] == NULL) { + stonith_free_hostlist(hlst); + return NULL; + } + strdown(hlst[hrcnt]); + hrcnt++; + } + }else { + /* WTF?! */ + LOG(PIL_CRIT, "Unexpected token \"%s\" in line \"%s\"\n",tk,rcv); + break; + } + } + +HL_cleanup: + stonith_free_hostlist(hlst); /* give the mem back */ + return NULL; +} + +#define SND_SIZE 256 +int +vacm_reset_req(StonithPlugin *s, int request, const char *host) +{ + struct pluginDevice *sd; + char snd[SND_SIZE]; /* god forbid its bigger than this */ + char *rcv, *tk; + int rcvlen; + + ERRIFWRONGDEV(s,S_OOPS); + sd = (struct pluginDevice*)s; + + switch(request) { +#ifdef ST_POWERON + case ST_POWERON: + snprintf(snd, SND_SIZE, "EMP:POWER_ON:%s", host); + break; +#endif /*ST_POWERON*/ +#ifdef ST_POWEROFF + case ST_POWEROFF: + snprintf(snd, SND_SIZE, "EMP:POWER_OFF:%s", host); + break; +#endif /*ST_POWEROFF*/ + case ST_GENERIC_RESET: + snprintf(snd, SND_SIZE, "EMP:POWER_CYCLE:%s", host); + break; + default: + return S_INVAL; + } + + api_nexxus_send_ipc(sd->h, snd, strlen(snd)+1); + while(1) { + if (api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + return S_RESETFAIL; + } + if (!(tk = strtok(rcv,":"))) { /*EMP*/ + break; + }else if (!(tk=strtok(NULL,":"))) { /* Job ID */ + break; + }else if (!(tk=strtok(NULL,":"))) { /* one of teh below */ + break; + } else if ( !strcmp(tk, "JOB_COMPLETED")) { + free(rcv); + return S_OK; + } else if(!strcmp(tk, "JOB_STARTED")) { + free(rcv); + continue; + } else if(!strcmp(tk, "JOB_ERROR")) { + free(rcv); + return S_RESETFAIL; + } else { + /* WTF?! */ + LOG(PIL_CRIT, "Unexpected token \"%s\" in line \"%s\"\n" + , tk, rcv); + break; + } + } + + return S_RESETFAIL; +} + +/* list => "nexxus:username:password" */ +static const char * const * +vacm_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_NEXXUS, ST_LOGIN, ST_PASSWD, NULL}; + return ret; +} + +static int +vacm_set_config(StonithPlugin *s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_NEXXUS, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + char *rcv; + int rcvlen; + + ERRIFWRONGDEV(s, S_OOPS); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->nexxus = namestocopy[0].s_value; + sd->user = namestocopy[1].s_value; + sd->passwd = namestocopy[2].s_value; + /* When to initialize the sd->h */ + + if (api_nexxus_connect(sd->nexxus, sd->user, sd->passwd, &sd->h)<0){ + return S_OOPS; + } + if (api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + return S_OOPS; + } + if (strcmp(rcv, "NEXXUS_READY")) { + rc = S_BADCONFIG; + }else{ + rc = S_OK; + } + free(rcv); + + return(rc); +} + +/* + * The "vacmconf:" is in the conffile so that one file could be used for + * multiple device configs. This module will only look at the first line + * that starts with this token. All other line are ignored. (and thus + * could contain configs for other modules.) + * + * I don't think any other stonith modules do this currently. + */ +const char * +vacm_getinfo(StonithPlugin *s, int reqtype) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + switch (reqtype) { + + case ST_DEVICEID: /* What type of device? */ + ret = sd->idinfo; + break; + + case ST_DEVICENAME: /* Which particular device? */ + ret = dgettext(ST_TEXTDOMAIN, "VACM"); + break; + + case ST_DEVICEDESCR: /* Description of dev type */ + ret = "A driver for the VA Linux Cluster Manager."; + break; + + case ST_DEVICEURL: /* VACM's web site */ + ret = "http://vacm.sourceforge.net/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = vacmXML; + break; + + default: + ret = NULL; + break; + } + + return ret; +} + +void +vacm_destroy(StonithPlugin *s) +{ + struct pluginDevice *sd; + + VOIDERRIFWRONGDEV(s); + sd = (struct pluginDevice*)s; + + if( sd->h ) { + api_nexxus_disconnect(sd->h); + } + + sd->pluginid = NOTpluginid; + if (sd->nexxus != NULL) { + FREE(sd->nexxus); + sd->nexxus = NULL; + } + if (sd->user != NULL) { + FREE(sd->user); + sd->user = NULL; + } + if (sd->passwd != NULL) { + FREE(sd->passwd); + sd->passwd = NULL; + } + + FREE(sd); +} + +static StonithPlugin * +vacm_new(const char *subplugin) +{ + struct pluginDevice *sd; + + sd = MALLOC(sizeof(struct pluginDevice)); + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->h = NULL; + sd->pluginid = pluginid; + sd->nexxus = NULL; + sd->user = NULL; + sd->passwd = NULL; + sd->idinfo = DEVICE; + sd->sp.s_ops = &vacmOps; + return &(sd->sp); /* same as "sd" */ +} diff --git a/lib/plugins/stonith/wti_mpc.c b/lib/plugins/stonith/wti_mpc.c new file mode 100644 index 0000000..548f91c --- /dev/null +++ b/lib/plugins/stonith/wti_mpc.c @@ -0,0 +1,856 @@ +/* + * Stonith module for WTI MPC (SNMP) + * Copyright (c) 2001 Andreas Piesk <a.piesk@gmx.net> + * Mangled by Sun Jiang Dong <sunjd@cn.ibm.com>, IBM, 2005 + * + * Modified for WTI MPC by Denis Chapligin <chollya@satgate.net>, SatGate, 2009 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version.* + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> + +/* device ID */ +#define DEVICE "WTI MPC" + +#include "stonith_plugin_common.h" +#undef FREE /* defined by snmp stuff */ + +#ifdef PACKAGE_BUGREPORT +#undef PACKAGE_BUGREPORT +#endif +#ifdef PACKAGE_NAME +#undef PACKAGE_NAME +#endif +#ifdef PACKAGE_STRING +#undef PACKAGE_STRING +#endif +#ifdef PACKAGE_TARNAME +#undef PACKAGE_TARNAME +#endif +#ifdef PACKAGE_VERSION +#undef PACKAGE_VERSION +#endif + +#ifdef HAVE_NET_SNMP_NET_SNMP_CONFIG_H +# include <net-snmp/net-snmp-config.h> +# include <net-snmp/net-snmp-includes.h> +# include <net-snmp/agent/net-snmp-agent-includes.h> +# define INIT_AGENT() init_master_agent() +#else +# include <ucd-snmp/ucd-snmp-config.h> +# include <ucd-snmp/ucd-snmp-includes.h> +# include <ucd-snmp/ucd-snmp-agent-includes.h> +# ifndef NETSNMP_DS_APPLICATION_ID +# define NETSNMP_DS_APPLICATION_ID DS_APPLICATION_ID +# endif +# ifndef NETSNMP_DS_AGENT_ROLE +# define NETSNMP_DS_AGENT_ROLE DS_AGENT_ROLE +# endif +# define netsnmp_ds_set_boolean ds_set_boolean +# define INIT_AGENT() init_master_agent(161, NULL, NULL) +#endif + +#define PIL_PLUGIN wti_mpc +#define PIL_PLUGIN_S "wti_mpc" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#define DEBUGCALL \ + if (Debug) { \ + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); \ + } + +static StonithPlugin * wti_mpc_new(const char *); +static void wti_mpc_destroy(StonithPlugin *); +static const char * const * wti_mpc_get_confignames(StonithPlugin *); +static int wti_mpc_set_config(StonithPlugin *, StonithNVpair *); +static const char * wti_mpc_getinfo(StonithPlugin * s, int InfoType); +static int wti_mpc_status(StonithPlugin * ); +static int wti_mpc_reset_req(StonithPlugin * s, int request, const char * host); +static char ** wti_mpc_hostlist(StonithPlugin *); + +static struct stonith_ops wti_mpcOps ={ + wti_mpc_new, /* Create new STONITH object */ + wti_mpc_destroy, /* Destroy STONITH object */ + wti_mpc_getinfo, /* Return STONITH info string */ + wti_mpc_get_confignames, /* Get configuration parameters */ + wti_mpc_set_config, /* Set configuration */ + wti_mpc_status, /* Return STONITH device status */ + wti_mpc_reset_req, /* Request a reset */ + wti_mpc_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + DEBUGCALL; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &wti_mpcOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * APCMaster tested with APC Masterswitch 9212 + */ + +/* outlet commands / status codes */ +#define OUTLET_ON 5 +#define OUTLET_OFF 6 +#define OUTLET_REBOOT 7 + +/* oids */ +#define OID_IDENT ".1.3.6.1.2.1.1.5.0" + +#define OID_GROUP_NAMES_V1 ".1.3.6.1.4.1.2634.3.1.3.1.2.%u" +#define OID_GROUP_STATE_V1 ".1.3.6.1.4.1.2634.3.1.3.1.3.%i" + +#define OID_GROUP_NAMES_V3 ".1.3.6.1.4.1.2634.3.100.300.1.2.%u" +#define OID_GROUP_STATE_V3 ".1.3.6.1.4.1.2634.3.100.300.1.3.%i" + +#define MAX_OUTLETS 128 + +/* + snmpset -c private -v1 172.16.0.32:161 + ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.1" i 1 + The last octet in the OID is the plug number. The value can + be 1 thru 8 because there are 8 power plugs on this device. + The integer that can be set is as follows: 1=on, 2=off, and + 3=reset +*/ + +/* own defines */ +#define MAX_STRING 128 +#define ST_PORT "port" +#define ST_MIBVERSION "mib-version" + +/* structur of stonith object */ +struct pluginDevice { + StonithPlugin sp; /* StonithPlugin object */ + const char* pluginid; /* id of object */ + const char* idinfo; /* type of device */ + struct snmp_session* sptr; /* != NULL->session created */ + char * hostname; /* masterswitch's hostname */ + /* or ip addr */ + int port; /* snmp port */ + int mib_version; /* mib version to use */ + char * community; /* snmp community (r/w) */ + int num_outlets; /* number of outlets */ +}; + +/* constant strings */ +static const char *pluginid = "WTI-MPC-Stonith"; +static const char *NOTpluginID = "WTI MPC device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_PORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PORT \ + XML_PARM_SHORTDESC_END + +#define XML_PORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The port number on which the SNMP server is running on the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_PORT_PARM \ + XML_PARAMETER_BEGIN(ST_PORT, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +#define XML_MIBVERSION_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_MIBVERSION \ + XML_PARM_SHORTDESC_END + +#define XML_MIBVERSION_LONGDESC \ + XML_MIBVERSION_LONGDESC_BEGIN("en") \ + "Version number of MPC MIB that we should use. Valid values are 1 (for 1.44 firmware) and 3 (for 1.62 firmware and later)" \ + XML_PARM_LONGDESC_END + +#define XML_MIBVERSION_PARM \ + XML_PARAMETER_BEGIN(ST_MIBVERSION, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +static const char *apcmastersnmpXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_PORT_PARM + XML_COMMUNITY_PARM + XML_MIBVERSION_PARM + XML_PARAMETERS_END; + +/* + * own prototypes + */ + +static void MPC_error(struct snmp_session *sptr, const char *fn +, const char *msg); +static struct snmp_session *MPC_open(char *hostname, int port +, char *community); +static void *MPC_read(struct snmp_session *sptr, const char *objname +, int type); +static int MPC_write(struct snmp_session *sptr, const char *objname +, char type, char *value); + +static void +MPC_error(struct snmp_session *sptr, const char *fn, const char *msg) +{ + int snmperr = 0; + int cliberr = 0; + char *errstr; + + snmp_error(sptr, &cliberr, &snmperr, &errstr); + LOG(PIL_CRIT + , "%s: %s (cliberr: %i / snmperr: %i / error: %s)." + , fn, msg, cliberr, snmperr, errstr); + free(errstr); +} + + +/* + * creates a snmp session + */ +static struct snmp_session * +MPC_open(char *hostname, int port, char *community) +{ + static struct snmp_session session; + struct snmp_session *sptr; + + DEBUGCALL; + + /* create session */ + snmp_sess_init(&session); + + /* fill session */ + session.peername = hostname; + session.version = SNMP_VERSION_1; + session.remote_port = port; + session.community = (u_char *)community; + session.community_len = strlen(community); + session.retries = 5; + session.timeout = 1000000; + + /* open session */ + sptr = snmp_open(&session); + + if (sptr == NULL) { + MPC_error(&session, __FUNCTION__, "cannot open snmp session"); + } + + /* return pointer to opened session */ + return (sptr); +} + +/* + * parse config + */ + +/* + * read value of given oid and return it as string + */ +static void * +MPC_read(struct snmp_session *sptr, const char *objname, int type) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct variable_list *vars; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + static char response_str[MAX_STRING]; + static int response_int; + + DEBUGCALL; + + /* convert objname into oid; return NULL if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (NULL); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_GET)) != NULL) { + + /* get-request have no values */ + snmp_add_null_var(pdu, name, namelen); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == SNMPERR_SUCCESS) { + + /* request succeed, got valid response ? */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* go through the returned vars */ + for (vars = resp->variables; vars; + vars = vars->next_variable) { + + /* return response as string */ + if ((vars->type == type) && (type == ASN_OCTET_STR)) { + memset(response_str, 0, MAX_STRING); + strncpy(response_str, (char *)vars->val.string, + MIN(vars->val_len, MAX_STRING)); + snmp_free_pdu(resp); + return ((void *) response_str); + } + /* return response as integer */ + if ((vars->type == type) && (type == ASN_INTEGER)) { + response_int = *vars->val.integer; + snmp_free_pdu(resp); + return ((void *) &response_int); + } + } + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + MPC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free repsonse pdu (necessary?) */ + snmp_free_pdu(resp); + }else{ + MPC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error: return nothing */ + return (NULL); +} + +/* + * write value of given oid + */ +static int +MPC_write(struct snmp_session *sptr, const char *objname, char type, + char *value) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + + DEBUGCALL; + + /* convert objname into oid; return FALSE if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (FALSE); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_SET)) != NULL) { + + /* add to be written value to pdu */ + snmp_add_var(pdu, name, namelen, type, value); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == STAT_SUCCESS) { + + /* go through the returned vars */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* request successful done */ + snmp_free_pdu(resp); + return (TRUE); + + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + MPC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free pdu (again: necessary?) */ + snmp_free_pdu(resp); + }else{ + MPC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error */ + return (FALSE); +} + +/* + * return the status for this device + */ + +static int +wti_mpc_status(StonithPlugin * s) +{ + struct pluginDevice *ad; + char *ident; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + if ((ident = MPC_read(ad->sptr, OID_IDENT, ASN_OCTET_STR)) == NULL) { + LOG(PIL_CRIT, "%s: cannot read ident.", __FUNCTION__); + return (S_ACCESS); + } + + /* status ok */ + return (S_OK); +} + +/* + * return the list of hosts configured for this device + */ + +static char ** +wti_mpc_hostlist(StonithPlugin * s) +{ + char **hl; + struct pluginDevice *ad; + int j, h, num_outlets; + char *outlet_name; + char objname[MAX_STRING]; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, NULL); + + ad = (struct pluginDevice *) s; + + /* allocate memory for array of up to NUM_OUTLETS strings */ + if ((hl = (char **)MALLOC((ad->num_outlets+1) * sizeof(char *))) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + /* clear hostlist array */ + memset(hl, 0, (ad->num_outlets + 1) * sizeof(char *)); + num_outlets = 0; + + /* read NUM_OUTLETS values and put them into hostlist array */ + for (j = 0; j < ad->num_outlets; ++j) { + + /* prepare objname */ + switch (ad->mib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V3,j+1); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V1,j+1); + break; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: using %s as group names oid", __FUNCTION__, objname); + } + + /* read outlet name */ + if ((outlet_name = MPC_read(ad->sptr, objname, ASN_OCTET_STR)) == + NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, j+1); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + + /* Check whether the host is already listed */ + for (h = 0; h < num_outlets; ++h) { + if (strcasecmp(hl[h],outlet_name) == 0) + break; + } + + if (h >= num_outlets) { + /* put outletname in hostlist */ + if (Debug) { + LOG(PIL_DEBUG, "%s: added %s to hostlist." + , __FUNCTION__, outlet_name); + } + + if ((hl[num_outlets] = STRDUP(outlet_name)) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + strdown(hl[num_outlets]); + num_outlets++; + } + } + + + if (Debug) { + LOG(PIL_DEBUG, "%s: %d unique hosts connected to %d outlets." + , __FUNCTION__, num_outlets, j); + } + /* return list */ + return (hl); +} + +/* + * reset the host + */ + +static int +wti_mpc_reset_req(StonithPlugin * s, int request, const char *host) +{ + struct pluginDevice *ad; + char objname[MAX_STRING]; + char value[MAX_STRING]; + char *outlet_name; + int req_oid = OUTLET_REBOOT; + int outlet; + int found_outlet=-1; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + /* read max. as->num_outlets values */ + for (outlet = 1; outlet <= ad->num_outlets; outlet++) { + + /* prepare objname */ + switch (ad->mib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V3,outlet); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V1,outlet); + break; + } + + /* read outlet name */ + if ((outlet_name = MPC_read(ad->sptr, objname, ASN_OCTET_STR)) + == NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + if (Debug) { + LOG(PIL_DEBUG, "%s: found outlet: %s.", __FUNCTION__, outlet_name); + } + + /* found one */ + if (strcasecmp(outlet_name, host) == 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: found %s at outlet %d." + , __FUNCTION__, host, outlet); + } + + /* Ok, stop iterating over host list */ + found_outlet=outlet; + break; + } + } + if (Debug) { + LOG(PIL_DEBUG, "%s: outlet: %i.", __FUNCTION__, outlet); + } + + /* host not found in outlet names */ + if (found_outlet == -1) { + LOG(PIL_CRIT, "%s: no active outlet for '%s'.", __FUNCTION__, host); + return (S_BADHOST); + } + + + /* choose the OID for the stonith request */ + switch (request) { + case ST_POWERON: + req_oid = OUTLET_ON; + break; + case ST_POWEROFF: + req_oid = OUTLET_OFF; + break; + case ST_GENERIC_RESET: + req_oid = OUTLET_REBOOT; + break; + default: break; + } + + /* Turn them all off */ + + /* prepare objnames */ + + switch (ad->mib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_STATE_V3,found_outlet); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_STATE_V1,found_outlet); + break; + } + + snprintf(value, MAX_STRING, "%i", req_oid); + + /* send reboot cmd */ + if (!MPC_write(ad->sptr, objname, 'i', value)) { + LOG(PIL_CRIT + , "%s: cannot send reboot command for outlet %d." + , __FUNCTION__, found_outlet); + return (S_RESETFAIL); + } + + return (S_OK); +} + +/* + * Get the configuration parameter names. + */ + +static const char * const * +wti_mpc_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_PORT, ST_COMMUNITY, ST_MIBVERSION, NULL}; + return ret; +} + +/* + * Set the configuration parameters. + */ + +static int +wti_mpc_set_config(StonithPlugin * s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + char * i; + int mo; + char objname[MAX_STRING]; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_PORT, NULL} + , {ST_COMMUNITY, NULL} + , {ST_MIBVERSION, NULL} + , {NULL, NULL} + }; + + DEBUGCALL; + ERRIFWRONGDEV(s,S_INVAL); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->hostname = namestocopy[0].s_value; + sd->port = atoi(namestocopy[1].s_value); + PluginImports->mfree(namestocopy[1].s_value); + sd->community = namestocopy[2].s_value; + sd->mib_version = atoi(namestocopy[3].s_value); + PluginImports->mfree(namestocopy[3].s_value); + + /* try to resolve the hostname/ip-address */ + if (gethostbyname(sd->hostname) != NULL) { + /* init snmp library */ + init_snmp("wti_mpc"); + + /* now try to get a snmp session */ + if ((sd->sptr = MPC_open(sd->hostname, sd->port, sd->community)) != NULL) { + + /* ok, get the number of groups from the mpc */ + sd->num_outlets=0; + /* We scan goup names table starting from 1 to MAX_OUTLETS */ + /* and increase num_outlet counter on every group entry with name */ + /* first entry without name is the mark of the end of the group table */ + for (mo=1;mo<MAX_OUTLETS;mo++) { + switch (sd->mib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V3,mo); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V1,mo); + break; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: used for groupTable retrieval: %s." + , __FUNCTION__, objname); + } + + if ((i = MPC_read(sd->sptr, objname, ASN_OCTET_STR)) == NULL) { + LOG(PIL_CRIT + , "%s: cannot read number of outlets." + , __FUNCTION__); + return (S_ACCESS); + } + if (strlen(i)) { + /* store the number of outlets */ + sd->num_outlets++; + } else { + break; + } + } + if (Debug) { + LOG(PIL_DEBUG, "%s: number of outlets: %i." + , __FUNCTION__, sd->num_outlets ); + } + + /* Everything went well */ + return (S_OK); + }else{ + LOG(PIL_CRIT, "%s: cannot create snmp session." + , __FUNCTION__); + } + }else{ + LOG(PIL_CRIT, "%s: cannot resolve hostname '%s', h_errno %d." + , __FUNCTION__, sd->hostname, h_errno); + } + + /* not a valid config */ + return (S_BADCONFIG); +} + +/* + * get info about the stonith device + */ + +static const char * +wti_mpc_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *ad; + const char *ret = NULL; + + DEBUGCALL; + + ERRIFWRONGDEV(s, NULL); + + ad = (struct pluginDevice *) s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ad->idinfo; + break; + + case ST_DEVICENAME: + ret = ad->hostname; + break; + + case ST_DEVICEDESCR: + ret = "WTI MPC (via SNMP)\n" + "The WTI MPC can accept multiple simultaneous SNMP clients"; + break; + + case ST_DEVICEURL: + ret = "http://www.wti.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = apcmastersnmpXML; + break; + + } + return ret; +} + + +/* + * APC StonithPlugin destructor... + */ + +static void +wti_mpc_destroy(StonithPlugin * s) +{ + struct pluginDevice *ad; + + DEBUGCALL; + + VOIDERRIFWRONGDEV(s); + + ad = (struct pluginDevice *) s; + + ad->pluginid = NOTpluginID; + + /* release snmp session */ + if (ad->sptr != NULL) { + snmp_close(ad->sptr); + ad->sptr = NULL; + } + + /* reset defaults */ + if (ad->hostname != NULL) { + PluginImports->mfree(ad->hostname); + ad->hostname = NULL; + } + if (ad->community != NULL) { + PluginImports->mfree(ad->community); + ad->community = NULL; + } + ad->num_outlets = 0; + + PluginImports->mfree(ad); +} + +/* + * Create a new APC StonithPlugin device. Too bad this function can't be + * static + */ + +static StonithPlugin * +wti_mpc_new(const char *subplugin) +{ + struct pluginDevice *ad = ST_MALLOCT(struct pluginDevice); + + DEBUGCALL; + + /* no memory for stonith-object */ + if (ad == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + + /* clear stonith-object */ + memset(ad, 0, sizeof(*ad)); + + /* set defaults */ + ad->pluginid = pluginid; + ad->sptr = NULL; + ad->hostname = NULL; + ad->community = NULL; + ad->mib_version=1; + ad->idinfo = DEVICE; + ad->sp.s_ops = &wti_mpcOps; + + /* return the object */ + return (&(ad->sp)); +} diff --git a/lib/plugins/stonith/wti_nps.c b/lib/plugins/stonith/wti_nps.c new file mode 100644 index 0000000..f0b81f7 --- /dev/null +++ b/lib/plugins/stonith/wti_nps.c @@ -0,0 +1,813 @@ +/* + * + * Copyright 2001 Mission Critical Linux, Inc. + * + * All Rights Reserved. + */ +/* + * Stonith module for WTI Network Power Switch Devices (NPS-xxx) + * Also supports the WTI Telnet Power Switch Devices (TPS-xxx) + * + * Copyright 2001 Mission Critical Linux, Inc. + * author: mike ledoux <mwl@mclinux.com> + * author: Todd Wheeling <wheeling@mclinux.com> + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * Further hurt by Lon <lhh@redhat.com>, Red Hat, 2005 + * + * Supported WTI devices: + * NPS-115 + * NPS-230 + * IPS-15 + * IPS-800 + * IPS-800-CE + * NBB-1600 + * NBB-1600-CE + * TPS-2 + * + * Based strongly on original code from baytech.c by Alan Robertson. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* Observations/Notes + * + * 1. The WTI Network Power Switch, unlike the BayTech network power switch, + * accpets only one (telnet) connection/session at a time. When one + * session is active, any subsequent attempt to connect to the NPS will + * result in a connection refused/closed failure. In a cluster environment + * or other environment utilizing polling/monitoring of the NPS + * (from multiple nodes), this can clearly cause problems. Obviously the + * more nodes and the shorter the polling interval, the more frequently such + * errors/collisions may occur. + * + * 2. We observed that on busy networks where there may be high occurances + * of broadcasts, the NPS became unresponsive. In some + * configurations this necessitated placing the power switch onto a + * private subnet. + */ + +#include <lha_internal.h> +#define DEVICE "WTI Network Power Switch" + +#define DOESNT_USE_STONITHKILLCOMM 1 + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN wti_nps +#define PIL_PLUGIN_S "wti_nps" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#define MAX_WTIPLUGINID 256 + +#include <pils/plugin.h> + +#include "stonith_signal.h" + +static StonithPlugin * wti_nps_new(const char *); +static void wti_nps_destroy(StonithPlugin *); +static const char * const * wti_nps_get_confignames(StonithPlugin *); +static int wti_nps_set_config(StonithPlugin * , StonithNVpair * ); +static const char * wti_nps_get_info(StonithPlugin * s, int InfoType); +static int wti_nps_status(StonithPlugin * ); +static int wti_nps_reset_req(StonithPlugin * s, int request, const char * host); +static char ** wti_nps_hostlist(StonithPlugin *); + +static struct stonith_ops wti_npsOps ={ + wti_nps_new, /* Create new STONITH object */ + wti_nps_destroy, /* Destroy STONITH object */ + wti_nps_get_info, /* Return STONITH info string */ + wti_nps_get_confignames,/* Return configration parameters */ + wti_nps_set_config, /* set configration */ + wti_nps_status, /* Return STONITH device status */ + wti_nps_reset_req, /* Request a reset */ + wti_nps_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +#include "stonith_expect_helpers.h" + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &wti_npsOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * I have a NPS-110. This code has been tested with this switch. + * (Tested with NPS-230 and TPS-2 by lmb) + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + pid_t pid; + int rdfd; + int wrfd; + char * device; + char * passwd; +}; + +static const char * pluginid = "WTINPS-Stonith"; +static const char * NOTnpsid = "WTINPS device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *wti_npsXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + + +/* + * Different expect strings that we get from the WTI + * Network Power Switch + */ + +#define WTINPSSTR " Power Switch" +#define WTINBBSTR "Boot Bar" + +static struct Etoken password[] = { {"Password:", 0, 0}, {NULL,0,0}}; +static struct Etoken Prompt[] = { {"PS>", 0, 0} + , {"IPS>", 0, 0} + , {"BB>", 0, 0} + , {NULL,0,0}}; +static struct Etoken LoginOK[] = { {WTINPSSTR, 0, 0} + , {WTINBBSTR, 0, 0} + , {"Invalid password", 1, 0} + , {NULL,0,0} }; +static struct Etoken Separator[] = { {"-----+", 0, 0} ,{NULL,0,0}}; + +/* We may get a notice about rebooting, or a request for confirmation */ +static struct Etoken Processing[] = { {"rocessing - please wait", 0, 0} + , {"(Y/N):", 1, 0} + , {NULL,0,0}}; + +static int NPS_connect_device(struct pluginDevice * nps); +static int NPSLogin(struct pluginDevice * nps); +static int NPSNametoOutlet(struct pluginDevice*, const char * name, char **outlets); +static int NPSReset(struct pluginDevice*, char * outlets, const char * rebootid); +static int NPSLogout(struct pluginDevice * nps); + +#if defined(ST_POWERON) && defined(ST_POWEROFF) +static int NPS_onoff(struct pluginDevice*, const char * outlets, const char * unitid +, int request); +#endif + +/* Attempt to login up to 20 times... */ +static int +NPSRobustLogin(struct pluginDevice * nps) +{ + int rc = S_OOPS; + int j = 0; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + for ( ; ; ) { + if (NPS_connect_device(nps) == S_OK) { + rc = NPSLogin(nps); + if (rc == S_OK) { + break; + } + } + if ((++j) == 20) { + break; + } + else { + sleep(1); + } + } + + return rc; +} + +/* Login to the WTI Network Power Switch (NPS) */ +static int +NPSLogin(struct pluginDevice * nps) +{ + char IDinfo[128]; + char * idptr = IDinfo; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Look for the unit type info */ + if (EXPECT_TOK(nps->rdfd, password, 2, IDinfo + , sizeof(IDinfo), Debug) < 0) { + LOG(PIL_CRIT, "No initial response from %s.", nps->idinfo); + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + idptr += strspn(idptr, WHITESPACE); + /* + * We should be looking at something like this: + * Enter Password: + */ + + SEND(nps->wrfd, nps->passwd); + SEND(nps->wrfd, "\r"); + /* Expect "Network Power Switch vX.YY" */ + + switch (StonithLookFor(nps->rdfd, LoginOK, 5)) { + + case 0: /* Good! */ + LOG(PIL_INFO, "Successful login to %s.", nps->idinfo); + break; + + case 1: /* Uh-oh - bad password */ + LOG(PIL_CRIT, "Invalid password for %s.", nps->idinfo); + return(S_ACCESS); + + default: + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + return(S_OK); +} + +/* Log out of the WTI NPS */ + +static int +NPSLogout(struct pluginDevice* nps) +{ + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Send "/h" help command and expect back prompt */ + /* + SEND(nps->wrfd, "/h\r"); + */ + /* Expect "PS>" */ + rc = StonithLookFor(nps->rdfd, Prompt, 5); + + /* "/x" is Logout, "/x,y" auto-confirms */ + SEND(nps->wrfd, "/x,y\r"); + + close(nps->wrfd); + close(nps->rdfd); + nps->wrfd = nps->rdfd = -1; + + return(rc >= 0 ? S_OK : (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS)); +} + +/* Reset (power-cycle) the given outlets */ +static int +NPSReset(struct pluginDevice* nps, char * outlets, const char * rebootid) +{ + char unum[32]; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Send "/h" help command and expect back prompt */ + SEND(nps->wrfd, "/h\r"); + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + /* Send REBOOT command for given outlets */ + snprintf(unum, sizeof(unum), "/BOOT %s,y\r", outlets); + SEND(nps->wrfd, unum); + + /* Expect "Processing "... or "(Y/N)" (if confirmation turned on) */ + + retry: + switch (StonithLookFor(nps->rdfd, Processing, 5)) { + case 0: /* Got "Processing" Do nothing */ + break; + + case 1: /* Got that annoying command confirmation :-( */ + SEND(nps->wrfd, "Y\r"); + goto retry; + + default: + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + LOG(PIL_INFO, "Host is being rebooted: %s", rebootid); + + /* Expect "PS>" */ + if (StonithLookFor(nps->rdfd, Prompt, 60) < 0) { + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + + /* All Right! Power is back on. Life is Good! */ + + LOG(PIL_INFO, "Power restored to host: %s", rebootid); + SEND(nps->wrfd, "/h\r"); + return(S_OK); +} + +#if defined(ST_POWERON) && defined(ST_POWEROFF) +static int +NPS_onoff(struct pluginDevice* nps, const char * outlets, const char * unitid, int req) +{ + char unum[32]; + + const char * onoff = (req == ST_POWERON ? "/On" : "/Off"); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Send "/h" help command and expect prompt back */ + SEND(nps->wrfd, "/h\r"); + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + /* Send ON/OFF command for given outlet */ + snprintf(unum, sizeof(unum), "%s %s,y\r", onoff, outlets); + SEND(nps->wrfd, unum); + + /* Expect "Processing"... or "(Y/N)" (if confirmation turned on) */ + + if (StonithLookFor(nps->rdfd, Processing, 5) == 1) { + /* They've turned on that annoying command confirmation :-( */ + SEND(nps->wrfd, "Y\r"); + } + EXPECT(nps->rdfd, Prompt, 60); + + /* All Right! Command done. Life is Good! */ + LOG(PIL_INFO, "Power to NPS outlet(s) %s turned %s", outlets, onoff); + + SEND(nps->wrfd, "/h\r"); + return(S_OK); +} +#endif /* defined(ST_POWERON) && defined(ST_POWEROFF) */ + +/* + * Map the given host name into an (AC) Outlet number on the power strip + */ + +static int +NPSNametoOutlet(struct pluginDevice* nps, const char * name, char **outlets) +{ + char NameMapping[128]; + int sockno; + char sockname[32]; + char buf[32]; + int left = 17; + int ret = -1; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if ((*outlets = (char *)MALLOC(left*sizeof(char))) == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(-1); + } + + strncpy(*outlets, "", left); + left = left - 1; /* ensure terminating '\0' */ + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(nps->wrfd, "/s\r"); + + /* Expect: "-----+" so we can skip over it... */ + EXPECT(nps->rdfd, Separator, 5); + + do { + NameMapping[0] = EOS; + SNARF(nps->rdfd, NameMapping, 5); + + if (sscanf(NameMapping + , "%d | %16c",&sockno, sockname) == 2) { + + char * last = sockname+16; + *last = EOS; + --last; + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (strncasecmp(name, sockname, 16) == 0) { + ret = sockno; + snprintf(buf, sizeof(buf), "%d ", sockno); + strncat(*outlets, buf, left); + left = left - strlen(buf); + } + } + } while (strlen(NameMapping) > 2 && left > 0); + + return(ret); +} + +static int +wti_nps_status(StonithPlugin *s) +{ + struct pluginDevice* nps; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + nps = (struct pluginDevice*) s; + + if ((rc = NPSRobustLogin(nps) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s.", nps->idinfo); + return(rc); + } + + /* Send "/h" help command and expect back prompt */ + SEND(nps->wrfd, "/h\r"); + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + return(NPSLogout(nps)); +} + +/* + * Return the list of hosts (outlet names) for the devices on this NPS unit + */ + +static char ** +wti_nps_hostlist(StonithPlugin *s) +{ + char NameMapping[128]; + char* NameList[64]; + unsigned int numnames = 0; + char ** ret = NULL; + struct pluginDevice* nps; + unsigned int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,NULL); + + nps = (struct pluginDevice*) s; + if (NPSRobustLogin(nps) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s.", nps->idinfo); + return(NULL); + } + + /* Expect "PS>" */ + NULLEXPECT(nps->rdfd, Prompt, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(nps->wrfd, "/s\r"); + + /* Expect: "-----" so we can skip over it... */ + NULLEXPECT(nps->rdfd, Separator, 5); + NULLEXPECT(nps->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + + do { + int sockno; + char sockname[64]; + NameMapping[0] = EOS; + NULLSNARF(nps->rdfd, NameMapping, 5); + if (sscanf(NameMapping + , "%d | %16c",&sockno, sockname) == 2) { + + char * last = sockname+16; + char * nm; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (numnames >= DIMOF(NameList)-1) { + break; + } + if (!strcmp(sockname,"(undefined)") || + !strcmp(sockname,"---")) { + /* lhh - skip undefined */ + continue; + } + if ((nm = STRDUP(sockname)) == NULL) { + goto out_of_memory; + } + strdown(nm); + NameList[numnames] = nm; + ++numnames; + NameList[numnames] = NULL; + } + } while (strlen(NameMapping) > 2); + + if (numnames >= 1) { + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + goto out_of_memory; + }else{ + memset(ret, 0, (numnames+1)*sizeof(char*)); + memcpy(ret, NameList, (numnames+1)*sizeof(char*)); + } + } + (void)NPSLogout(nps); + + return(ret); + +out_of_memory: + LOG(PIL_CRIT, "out of memory"); + for (i=0; i<numnames; i++) { + FREE(NameList[i]); + } + + return (NULL); +} + +/* + * Connect to the given NPS device. We should add serial support here + * eventually... + */ +static int +NPS_connect_device(struct pluginDevice * nps) +{ + int fd = OurImports->OpenStreamSocket(nps->device + , TELNET_PORT, TELNET_SERVICE); + + if (fd < 0) { + return(S_OOPS); + } + nps->rdfd = nps->wrfd = fd; + return(S_OK); +} + +/* + * Reset the given host on this Stonith device. + */ +static int +wti_nps_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = 0; + int lorc = 0; + struct pluginDevice* nps; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + nps = (struct pluginDevice*) s; + + if ((rc = NPSRobustLogin(nps)) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s.", nps->idinfo); + }else{ + char *outlets; + int noutlet; + + outlets = NULL; + noutlet = NPSNametoOutlet(nps, host, &outlets); + + if (noutlet < 1) { + LOG(PIL_WARN, "%s doesn't control host [%s]" + , nps->device, host); + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + return(S_BADHOST); + } + switch(request) { + +#if defined(ST_POWERON) && defined(ST_POWEROFF) + case ST_POWERON: + case ST_POWEROFF: + rc = NPS_onoff(nps, outlets, host, request); + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + break; +#endif + case ST_GENERIC_RESET: + rc = NPSReset(nps, outlets, host); + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + break; + default: + rc = S_INVAL; + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + break; + } + } + + lorc = NPSLogout(nps); + return(rc != S_OK ? rc : lorc); +} + +/* + * Parse the information in the given string, + * and stash it away... + */ +static int +wti_nps_set_config(StonithPlugin * s, StonithNVpair *list) +{ + struct pluginDevice* nps; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.\n", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + nps = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + nps->device = namestocopy[0].s_value; + nps->passwd = namestocopy[1].s_value; + return S_OK; +} + + +/* + * Return the Stonith plugin configuration parameter + * + */ +static const char * const * +wti_nps_get_confignames(StonithPlugin * p) +{ + static const char * names[] = { ST_IPADDR , ST_PASSWD , NULL}; + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + return names; +} + +/* + * Get info about the stonith device + * + */ +static const char * +wti_nps_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nps; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nps = (struct pluginDevice *)s; + + switch (reqtype) { + + case ST_DEVICEID: + ret = nps->idinfo; + break; + case ST_DEVICENAME: + ret = nps->device; + break; + case ST_DEVICEDESCR: + ret = "Western Telematic (WTI) Network Power Switch Devices (NPS-xxx)\n" + "Also supports the WTI Telnet Power Switch Devices (TPS-xxx)\n" + "NOTE: The WTI Network Power Switch, accepts only " + "one (telnet) connection/session at a time."; + break; + case ST_DEVICEURL: + ret = "http://www.wti.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = wti_npsXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * WTI NPS Stonith destructor... + */ +static void +wti_nps_destroy(StonithPlugin *s) +{ + struct pluginDevice* nps; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + nps = (struct pluginDevice *)s; + + nps->pluginid = NOTnpsid; + if (nps->rdfd >= 0) { + close(nps->rdfd); + nps->rdfd = -1; + } + if (nps->wrfd >= 0) { + close(nps->wrfd); + nps->wrfd = -1; + } + if (nps->device != NULL) { + FREE(nps->device); + nps->device = NULL; + } + if (nps->passwd != NULL) { + FREE(nps->passwd); + nps->passwd = NULL; + } + FREE(nps); +} + +/* Create a new BayTech Stonith device. */ + +static StonithPlugin * +wti_nps_new(const char *subplugin) +{ + struct pluginDevice* nps = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (nps == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nps, 0, sizeof(*nps)); + nps->pluginid = pluginid; + nps->pid = -1; + nps->rdfd = -1; + nps->wrfd = -1; + nps->device = NULL; + nps->passwd = NULL; + nps->idinfo = DEVICE; + nps->sp.s_ops = &wti_npsOps; + + return &(nps->sp); +} + diff --git a/lib/stonith/Makefile.am b/lib/stonith/Makefile.am new file mode 100644 index 0000000..429e1d3 --- /dev/null +++ b/lib/stonith/Makefile.am @@ -0,0 +1,54 @@ +# +# Stonith: Shoot The Node In The Head +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + +## include files + +## binaries +sbin_PROGRAMS = stonith meatclient + +stonith_SOURCES = main.c + +stonith_LDADD = libstonith.la $(top_builddir)/lib/pils/libpils.la $(GLIBLIB) \ + $(top_builddir)/lib/clplumbing/libplumb.la \ + $(top_builddir)/lib/clplumbing/libplumbgpl.la +stonith_LDFLAGS = @LIBADD_DL@ @LIBLTDL@ -export-dynamic @DLOPEN_FORCE_FLAGS@ @LIBADD_INTL@ + +meatclient_SOURCES = meatclient.c +meatclient_LDADD = $(GLIBLIB) libstonith.la + +## libraries + +lib_LTLIBRARIES = libstonith.la + +libstonith_la_SOURCES = expect.c stonith.c st_ttylock.c +libstonith_la_LDFLAGS = -version-info 1:0:0 +libstonith_la_LIBADD = $(top_builddir)/lib/pils/libpils.la \ + $(top_builddir)/replace/libreplace.la \ + $(GLIBLIB) + +helperdir = $(datadir)/$(PACKAGE_NAME) +helper_SCRIPTS = ha_log.sh + +EXTRA_DIST = $(helper_SCRIPTS) diff --git a/lib/stonith/README b/lib/stonith/README new file mode 100644 index 0000000..6b98ef9 --- /dev/null +++ b/lib/stonith/README @@ -0,0 +1,31 @@ +The STONITH module (a.k.a. STOMITH) provides an extensible interface +for remotely powering down a node in the cluster. The idea is quite simple: +When the software running on one machine wants to make sure another +machine in the cluster is not using a resource, pull the plug on the other +machine. It's simple and reliable, albiet admittedly brutal. + +Here's an example command line invocation used to power off a machine +named 'nodeb'. The parameters are dependent on the type of device you +are using for this capability. + +stonith -t rps10 -p "/dev/ttyS5 nodeb 0 " nodeb + +Currently supported devices: + + apcsmart: APCSmart (tested with 2 old 900XLI) + baytech: Baytech RPC5 + meatware: Alerts an operator to manually turn off a device. + nw_rpc100s: Micro Energetics Night/Ware RPC100S + rps10: Western Telematics RPS10 + vacm_stonith: VA Linux Cluster Manager (see README.vacm) + + +To see the parameter syntax for a module, run the 'stonith' command and omit the +-p parameter. For example: + +$ /usr/sbin/stonith -t rps10 test + +stonith: Invalid config file for rps10 device. +stonith: Config file syntax: <serial_device> <node> <outlet> [ <node> <outlet> [...] ] +All tokens are white-space delimited. +Blank lines and lines beginning with # are ignored diff --git a/lib/stonith/expect.c b/lib/stonith/expect.c new file mode 100644 index 0000000..bb1f818 --- /dev/null +++ b/lib/stonith/expect.c @@ -0,0 +1,539 @@ +/* + * Simple expect module for the STONITH library + * + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <syslog.h> +#include <sys/wait.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <sys/times.h> +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stonith/st_ttylock.h> +#include <clplumbing/longclock.h> +#define ENABLE_PIL_DEFS_PRIVATE +#include <pils/plugin.h> + +#ifdef _POSIX_PRIORITY_SCHEDULING +# include <sched.h> +#endif + +#include <stonith/stonith.h> +#include <stonith/stonith_plugin.h> + +extern PILPluginUniv* StonithPIsys; + +#define LOG(args...) PILCallLog(StonithPIsys->imports->log, args) +#define DEBUG(args...) LOG(PIL_DEBUG, args) +#undef DEBUG +#define DEBUG(args...) PILCallLog(StonithPIsys->imports->log, PIL_DEBUG, args) +#define MALLOC StonithPIsys->imports->alloc +#define REALLOC StonithPIsys->imports->mrealloc +#define STRDUP StonithPIsys->imports->mstrdup +#define FREE(p) {StonithPIsys->imports->mfree(p); (p) = NULL;} + +#ifdef TIMES_ALLOWS_NULL_PARAM +# define TIMES_PARAM NULL +#else + static struct tms dummy_longclock_tms_struct; +# define TIMES_PARAM &dummy_longclock_tms_struct +#endif + +static unsigned long +our_times(void) /* Make times(2) behave rationally on Linux */ +{ + clock_t ret; +#ifndef DISABLE_TIMES_KLUDGE + int save_errno = errno; + + /* + * This code copied from clplumbing/longclock.c to avoid + * making STONITH depend on clplumbing. See it for an explanation + */ + + errno = 0; +#endif /* DISABLE_TIMES_KLUDGE */ + + ret = times(TIMES_PARAM); + +#ifndef DISABLE_TIMES_KLUDGE + if (errno != 0) { + ret = (clock_t) (-errno); + } + errno = save_errno; +#endif /* DISABLE_TIMES_KLUDGE */ + return (unsigned long)ret; +} + +/* + * Look for ('expect') any of a series of tokens in the input + * Return the token type for the given token or -1 on error. + */ + +static int +ExpectToken(int fd, struct Etoken * toklist, int to_secs, char * savebuf +, int maxline, int Debug) +{ + unsigned long starttime; + unsigned long endtime; + int wraparound=0; + unsigned Hertz = sysconf(_SC_CLK_TCK); + int tickstousec = (1000000/Hertz); + unsigned long now; + unsigned long ticks; + int nchars = 1; /* reserve space for an EOS */ + struct timeval tv; + char * buf = savebuf; + + struct Etoken * this; + + /* Figure out when to give up. Handle lbolt wraparound */ + + starttime = our_times(); + ticks = (to_secs*Hertz); + endtime = starttime + ticks; + + if (endtime < starttime) { + wraparound = 1; + } + + if (buf) { + *buf = EOS; + } + + for (this=toklist; this->string; ++this) { + this->matchto = 0; + } + + + while (now = our_times(), + (wraparound && (now > starttime || now <= endtime)) + || (!wraparound && now <= endtime)) { + + fd_set infds; + char ch; + unsigned long timeleft; + int retval; + + timeleft = endtime - now; + + tv.tv_sec = timeleft / Hertz; + tv.tv_usec = (timeleft % Hertz) * tickstousec; + + if (tv.tv_sec == 0 && tv.tv_usec < tickstousec) { + /* Give 'em a little chance */ + tv.tv_usec = tickstousec; + } + + /* Watch our FD to see when it has input. */ + FD_ZERO(&infds); + FD_SET(fd, &infds); + + retval = select(fd+1, &infds, NULL, NULL, &tv); + if (retval <= 0) { + errno = ETIMEDOUT; + return(-1); + } + /* Whew! All that work just to read one character! */ + + if (read(fd, &ch, sizeof(ch)) <= 0) { + return(-1); + } + /* Save the text, if we can */ + if (buf && nchars < maxline-1) { + *buf = ch; + ++buf; + *buf = EOS; + ++nchars; + } + if (Debug > 1) { + DEBUG("Got '%c'", ch); + } + + /* See how this character matches our expect strings */ + + for (this=toklist; this->string; ++this) { + + if (ch == this->string[this->matchto]) { + + /* It matches the current token */ + + ++this->matchto; + if (this->string[this->matchto] == EOS){ + /* Hallelujah! We matched */ + if (Debug) { + DEBUG("Matched [%s] [%d]" + , this->string + , this->toktype); + if (savebuf) { + DEBUG("Saved [%s]" + , savebuf); + } + } + return(this->toktype); + } + }else{ + + /* It doesn't appear to match this token */ + + int curlen; + int nomatch=1; + /* + * If we already had a match (matchto is + * greater than zero), we look for a match + * of the tail of the pattern matched so far + * (with the current character) against the + * head of the pattern. + */ + + /* + * This is to make the string "aab" match + * the pattern "ab" correctly + * Painful, but nice to do it right. + */ + + for (curlen = (this->matchto) + ; nomatch && curlen >= 0 + ; --curlen) { + const char * tail; + tail=(this->string) + + this->matchto + - curlen; + + if (strncmp(this->string, tail + , curlen) == 0 + && this->string[curlen] == ch) { + + if (this->string[curlen+1]==EOS){ + /* We matched! */ + /* (can't happen?) */ + return(this->toktype); + } + this->matchto = curlen+1; + nomatch=0; + } + } + if (nomatch) { + this->matchto = 0; + } + } + } + } + errno = ETIMEDOUT; + return(-1); +} + +/* + * Start a process with its stdin and stdout redirected to pipes + * so the parent process can talk to it. + */ +static int +StartProcess(const char * cmd, int * readfd, int * writefd) +{ + pid_t pid; + int wrpipe[2]; /* The pipe the parent process writes to */ + /* (which the child process reads from) */ + int rdpipe[2]; /* The pipe the parent process reads from */ + /* (which the child process writes to) */ + + if (pipe(wrpipe) < 0) { + perror("cannot create pipe\n"); + return(-1); + } + if (pipe(rdpipe) < 0) { + perror("cannot create pipe\n"); + close(wrpipe[0]); + close(wrpipe[1]); + return(-1); + } + switch(pid=fork()) { + + case -1: perror("cannot StartProcess cmd"); + close(rdpipe[0]); + close(wrpipe[1]); + close(wrpipe[0]); + close(rdpipe[1]); + return(-1); + + case 0: /* We are the child */ + + /* Redirect stdin */ + close(0); + dup2(wrpipe[0], 0); + close(wrpipe[0]); + close(wrpipe[1]); + + /* Redirect stdout */ + close(1); + dup2(rdpipe[1], 1); + close(rdpipe[0]); + close(rdpipe[1]); +#if defined(SCHED_OTHER) && !defined(ON_DARWIN) + { + /* + * Try and (re)set our scheduling to "normal" + * Sometimes our callers run in soft + * real-time mode. The program we exec might + * not be very well behaved - this is bad for + * operation in high-priority (soft real-time) + * mode. In particular, telnet is prone to + * going into infinite loops when killed. + */ + struct sched_param sp; + memset(&sp, 0, sizeof(sp)); + sp.sched_priority = 0; + sched_setscheduler(0, SCHED_OTHER, &sp); + } +#endif + execlp("/bin/sh", "sh", "-c", cmd, (const char *)NULL); + perror("cannot exec shell!"); + exit(1); + + default: /* We are the parent */ + *readfd = rdpipe[0]; + close(rdpipe[1]); + + *writefd = wrpipe[1]; + close(wrpipe[0]); + return(pid); + } + /*NOTREACHED*/ + return(-1); +} + +static char ** +stonith_copy_hostlist(const char * const * hostlist) +{ + int hlleng = 1; + const char * const * here = hostlist; + char ** hret; + char ** ret; + + for (here = hostlist; *here; ++here) { + ++hlleng; + } + ret = (char**)MALLOC(hlleng * sizeof(char *)); + if (ret == NULL) { + return ret; + } + + hret = ret; + for (here = hostlist; *here; ++here,++hret) { + *hret = STRDUP(*here); + if (*hret == NULL) { + stonith_free_hostlist(ret); + return NULL; + } + } + *hret = NULL; + return ret; +} + +static char ** +StringToHostList(const char * s) +{ + const char * here; + int hlleng = 0; + char ** ret; + char ** hret; + const char * delims = " \t\n\f\r,"; + + /* Count the number of strings (words) in the result */ + here = s; + while (*here != EOS) { + /* skip delimiters */ + here += strspn(here, delims); + if (*here == EOS) { + break; + } + /* skip over substring proper... */ + here += strcspn(here, delims); + ++hlleng; + } + + + /* Malloc space for the result string pointers */ + ret = (char**)MALLOC((hlleng+1) * sizeof(char *)); + if (ret == NULL) { + return NULL; + } + + hret = ret; + here = s; + + /* Copy each substring into a separate string */ + while (*here != EOS) { + int slen; /* substring length */ + + /* skip delimiters */ + here += strspn(here, delims); + if (*here == EOS) { + break; + } + /* Compute substring length */ + slen = strcspn(here, delims); + *hret = MALLOC((slen+1) * sizeof(char)); + if (*hret == NULL) { + stonith_free_hostlist(hret); + return NULL; + } + /* Copy string (w/o EOS) */ + memcpy(*hret, here, slen); + /* Add EOS to result string */ + (*hret)[slen] = EOS; + strdown(*hret); + here += slen; + ++hret; + } + *hret = NULL; + return ret; +} + + +static const char * +GetValue(StonithNVpair* parameters, const char * name) +{ + while (parameters->s_name) { + if (strcmp(name, parameters->s_name) == 0) { + return parameters->s_value; + } + ++parameters; + } + return NULL; +} + +static int +CopyAllValues(StonithNamesToGet* output, StonithNVpair * input) +{ + int j; + int rc; + + for (j=0; output[j].s_name; ++j) { + const char * value = GetValue(input, output[j].s_name); + if (value == NULL) { + rc = S_INVAL; + output[j].s_value = NULL; + goto fail; + } + if ((output[j].s_value = STRDUP(value)) == NULL) { + rc = S_OOPS; + goto fail; + } + } + return S_OK; + +fail: + for (j=0; output[j].s_value; ++j) { + FREE(output[j].s_value); + } + return rc; +} + + +static int +OpenStreamSocket(const char * host, int port, const char * service) +{ + union s_un { + struct sockaddr_in si4; + struct sockaddr_in6 si6; + }sockun; + int sock; + int addrlen = -1; + + + memset(&sockun, 0, sizeof(sockun)); + + if (inet_pton(AF_INET, host, (void*)&sockun.si4.sin_addr) < 0) { + sockun.si4.sin_family = AF_INET; + }else if (inet_pton(AF_INET6, host, (void*)&sockun.si6.sin6_addr)<0){ + sockun.si6.sin6_family = AF_INET6; + }else{ + struct hostent* hostp = gethostbyname(host); + if (hostp == NULL) { + errno = EINVAL; + return -1; + } + sockun.si4.sin_family = hostp->h_addrtype; + memcpy(&sockun.si4.sin_addr, hostp->h_addr, hostp->h_length); + } + if ((sock = socket(sockun.si4.sin_family, SOCK_STREAM, 0)) < 0) { + return -1; + } + if (service != NULL) { + struct servent* se = getservbyname(service, "tcp"); + if (se != NULL) { + /* We convert it back later... */ + port = ntohs(se->s_port); + } + } + if (port <= 0) { + errno = EINVAL; + return -1; + } + port = htons(port); + if (sockun.si6.sin6_family == AF_INET6) { + sockun.si6.sin6_port = port; + addrlen = sizeof(sockun.si6); + }else if (sockun.si4.sin_family == AF_INET) { + sockun.si4.sin_port = port; + addrlen = sizeof(sockun.si4); + }else{ + errno = EINVAL; + return -1; + } + + if (connect(sock, (struct sockaddr*)(&sockun), addrlen)< 0){ + int save = errno; + perror("connect() failed"); + close(sock); + errno = save; + return -1; + } + return sock; +} + +StonithImports stonithimports = { + ExpectToken, + StartProcess, + OpenStreamSocket, + GetValue, + CopyAllValues, + StringToHostList, + stonith_copy_hostlist, + stonith_free_hostlist, + st_ttylock, + st_ttyunlock +}; diff --git a/lib/stonith/ha_log.sh b/lib/stonith/ha_log.sh new file mode 100755 index 0000000..73093f0 --- /dev/null +++ b/lib/stonith/ha_log.sh @@ -0,0 +1,114 @@ +#!/bin/sh +# +# +# ha_log.sh for stonith external plugins +# (equivalent to ocf_log in ocf-shellfuncs in resource-agents) +# +# Copyright (c) 2004 SUSE LINUX AG, Lars Marowsky-Brée +# All Rights Reserved. +# +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# Build version: @GLUE_BUILD_VERSION@ + +PROG=`basename $0` + +: ${HA_DATEFMT=+"%b %d %T"} +: ${HA_LOGD=yes} +: ${HA_LOGTAG=""} +: ${HA_LOGFACILITY=daemon} +: ${HA_LOGFILE=""} +: ${HA_DEBUGLOG=""} +: ${HA_debug="0"} + +hadate() { + date "+$HA_DATEFMT" +} + +level_pres() { + case "$1" in + crit) echo "CRIT";; + err|error) echo "ERROR";; + warn|warning) echo "WARN";; + notice) echo "notice";; + info) echo "info";; + debug) echo "debug";; + *) + ha_log err "$PROG: unrecognized loglevel: $1" + exit 1 + ;; + esac +} + +set_logtag() { + # add parent pid to the logtag + if [ "$HA_LOGTAG" ]; then + if [ -n "$CRM_meta_st_device_id" ]; then + HA_LOGTAG="$HA_LOGTAG($CRM_meta_st_device_id)[$PPID]" + else + HA_LOGTAG="$HA_LOGTAG[$PPID]" + fi + fi +} + +ha_log() { + loglevel=$1 + shift + prn_level=`level_pres $loglevel` + msg="$prn_level: $@" + + if [ "x$HA_debug" = "x0" -a "x$loglevel" = xdebug ] ; then + return 0 + fi + + set_logtag + + # if we're connected to a tty, then output to stderr + if tty >/dev/null; then + if [ "$HA_LOGTAG" ]; then + echo "$HA_LOGTAG: $msg" + else + echo "$msg" + fi >&2 + return 0 + fi + + [ "x$HA_LOGD" = "xyes" ] && + cat<<EOF | ha_logger -t "$HA_LOGTAG" && return 0 +$msg +EOF + + if [ -n "$HA_LOGFACILITY" -a "$HA_LOGFACILITY" != none ]; then + logger -t "$HA_LOGTAG" -p $HA_LOGFACILITY.$loglevel "$msg" + fi + dest=${HA_LOGFILE:-$HA_DEBUGLOG} + if [ -n "$dest" ]; then + msg="$prn_level: `hadate` $@" + echo "$HA_LOGTAG: $msg" >> $dest + fi +} + +if [ $# -lt 2 ]; then + ha_log err "$PROG: not enough arguments [$#]" + exit 1 +fi + +loglevel="$1" +shift 1 +msg="$*" + +ha_log "$loglevel" "$msg" diff --git a/lib/stonith/main.c b/lib/stonith/main.c new file mode 100644 index 0000000..44e099f --- /dev/null +++ b/lib/stonith/main.c @@ -0,0 +1,727 @@ +/* + * Stonith: simple test program for exercising the Stonith API code + * + * Copyright (C) 2000 Alan Robertson <alanr@unix.sh> + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <syslog.h> +#include <stonith/stonith.h> +#include <pils/plugin.h> +#include <clplumbing/cl_log.h> +#include <glib.h> +#include <libxml/entities.h> + +#define OPTIONS "c:F:p:t:T:EsnSlLmvhVd" +#define EQUAL '=' + +extern char * optarg; +extern int optind, opterr, optopt; + +static int debug = 0; + +#define LOG_TERMINAL 0 +#define LOG_CLLOG 1 +static int log_destination = LOG_TERMINAL; + +static const char META_TEMPLATE[] = +"<?xml version=\"1.0\"?>\n" +"<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n" +"<resource-agent name=\"%s\">\n" +"<version>1.0</version>\n" +"<longdesc lang=\"en\">\n" +"%s\n" +"</longdesc>\n" +"<shortdesc lang=\"en\">%s</shortdesc>\n" +"%s\n" +"<actions>\n" +"<action name=\"start\" timeout=\"20\" />\n" +"<action name=\"stop\" timeout=\"15\" />\n" +"<action name=\"status\" timeout=\"20\" />\n" +"<action name=\"monitor\" timeout=\"20\" interval=\"3600\" />\n" +"<action name=\"meta-data\" timeout=\"15\" />\n" +"</actions>\n" +"<special tag=\"heartbeat\">\n" +"<version>2.0</version>\n" +"</special>\n" +"</resource-agent>\n"; + +void version(void); +void usage(const char * cmd, int exit_status, const char * devtype); +void confhelp(const char * cmd, FILE* stream, const char * devtype); +void print_stonith_meta(Stonith * stonith_obj, const char *rsc_type); +void print_types(void); +void print_confignames(Stonith *s); + +void log_buf(int severity, char *buf); +void log_msg(int severity, const char * fmt, ...)G_GNUC_PRINTF(2,3); +void trans_log(int priority, const char * fmt, ...)G_GNUC_PRINTF(2,3); + +static int pil_loglevel_to_syslog_severity[] = { + /* Indices: <none>=0, PIL_FATAL=1, PIL_CRIT=2, PIL_WARN=3, + PIL_INFO=4, PIL_DEBUG=5 + */ + LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_WARNING, LOG_INFO, LOG_DEBUG + }; + +/* + * Note that we don't use the cl_log logging code because the STONITH + * command is intended to be shipped without the clplumbing libraries. + * + * :-( + * + * The stonith command has so far always been shipped along with + * the clplumbing library, so we'll use cl_log + * If that ever changes, we'll use something else + */ + +void +version() +{ + printf("stonith: %s (%s)\n", GLUE_VERSION, GLUE_BUILD_VERSION); + exit(0); +} + +void +usage(const char * cmd, int exit_status, const char * devtype) +{ + FILE *stream; + + stream = exit_status ? stderr : stdout; + + /* non-NULL devtype indicates help for specific device, so no usage */ + if (devtype == NULL) { + fprintf(stream, "usage:\n"); + fprintf(stream, "\t %s [-svh] " + "-L\n" + , cmd); + + fprintf(stream, "\t %s [-svh] " + "-t stonith-device-type " + "-n\n" + , cmd); + + fprintf(stream, "\t %s [-svh] " + "-t stonith-device-type " + "-m\n" + , cmd); + + fprintf(stream, "\t %s [-svh] " + "-t stonith-device-type " + "{-p stonith-device-parameters | " + "-F stonith-device-parameters-file | " + "-E | " + "name=value...} " + "[-c count] " + "-lS\n" + , cmd); + + fprintf(stream, "\t %s [-svh] " + "-t stonith-device-type " + "{-p stonith-device-parameters | " + "-F stonith-device-parameters-file | " + "-E | " + "name=value...} " + "[-c count] " + "-T {reset|on|off} nodename\n" + , cmd); + + fprintf(stream, "\nwhere:\n"); + fprintf(stream, "\t-L\tlist supported stonith device types\n"); + fprintf(stream, "\t-l\tlist hosts controlled by this stonith device\n"); + fprintf(stream, "\t-S\treport stonith device status\n"); + fprintf(stream, "\t-s\tsilent\n"); + fprintf(stream, "\t-v\tverbose\n"); + fprintf(stream, "\t-n\toutput the config names of stonith-device-parameters\n"); + fprintf(stream, "\t-m\tdisplay meta-data of the stonith device type\n"); + fprintf(stream, "\t-h\tdisplay detailed help message with stonith device description(s)\n"); + } + + if (exit_status == 0) { + confhelp(cmd, stream, devtype); + } + + exit(exit_status); +} + +/* Thanks to Lorn Kay <lorn_kay@hotmail.com> for the confhelp code */ +void +confhelp(const char * cmd, FILE* stream, const char * devtype) +{ + char ** typelist; + char ** this; + Stonith * s; + int devfound = 0; + + + /* non-NULL devtype indicates help for specific device, so no header */ + if (devtype == NULL) { + fprintf(stream + , "\nSTONITH -t device types and" + " associated configuration details:\n"); + } + + typelist = stonith_types(); + + if (typelist == NULL) { + fprintf(stderr, + "Failed to retrieve list of STONITH modules!\n"); + return; + } + for(this=typelist; *this && !devfound; ++this) { + const char * SwitchType = *this; + const char * cres; + const char * const * pnames; + + + if ((s = stonith_new(SwitchType)) == NULL) { + fprintf(stderr, "Invalid STONITH type %s(!)\n" + , SwitchType); + continue; + } + + if (devtype) { + if (strcmp(devtype, SwitchType)) { + continue; + } else { + devfound = 1; + } + } + + fprintf(stream, "\n\nSTONITH Device: %s - ", SwitchType); + + if ((cres = stonith_get_info(s, ST_DEVICEDESCR)) != NULL){ + fprintf(stream, "%s\n" + , cres); + } + + if ((cres = stonith_get_info(s, ST_DEVICEURL)) != NULL){ + fprintf(stream + , "For more information see %s\n" + , cres); + } + if (NULL == (pnames = stonith_get_confignames(s))) { + continue; + } + fprintf(stream + , "List of valid parameter names for %s STONITH device:\n" + , SwitchType); + for (;*pnames; ++pnames) { + fprintf(stream + , "\t%s\n", *pnames); + } + +#ifdef ST_CONFI_INFO_SYNTAX + fprintf(stream, "\nConfig info [-p] syntax for %s:\n\t%s\n" + , SwitchType, stonith_get_info(s, ST_CONF_INFO_SYNTAX)); +#else + fprintf(stream, "For Config info [-p] syntax" + ", give each of the above parameters in order as" + "\nthe -p value.\n" + "Arguments are separated by white space."); +#endif +#ifdef ST_CONFI_FILE_SYNTAX + fprintf(stream, "\nConfig file [-F] syntax for %s:\n\t%s\n" + , SwitchType, stonith->get_info(s, ST_CONF_FILE_SYNTAX)); +#else + fprintf(stream + , "\nConfig file [-F] syntax is the same as -p" + ", except # at the start of a line" + "\ndenotes a comment\n"); +#endif + + stonith_delete(s); s = NULL; + } + /* Note that the type list can't/shouldn't be freed */ + if (devtype && !devfound) { + fprintf(stderr, "Invalid device type: '%s'\n", devtype); + } + +} + +void +print_stonith_meta(Stonith * stonith_obj, const char *rsc_type) +{ + const char * meta_param = NULL; + const char * meta_longdesc = NULL; + const char * meta_shortdesc = NULL; + char *xml_meta_longdesc = NULL; + char *xml_meta_shortdesc = NULL; + static const char * no_parameter_info = "<!-- no value -->"; + + meta_longdesc = stonith_get_info(stonith_obj, ST_DEVICEDESCR); + if (meta_longdesc == NULL) { + fprintf(stderr, "stonithRA plugin: no long description"); + meta_longdesc = no_parameter_info; + } + xml_meta_longdesc = (char *)xmlEncodeEntitiesReentrant(NULL, (const unsigned char *)meta_longdesc); + + meta_shortdesc = stonith_get_info(stonith_obj, ST_DEVICEID); + if (meta_shortdesc == NULL) { + fprintf(stderr, "stonithRA plugin: no short description"); + meta_shortdesc = no_parameter_info; + } + xml_meta_shortdesc = (char *)xmlEncodeEntitiesReentrant(NULL, (const unsigned char *)meta_shortdesc); + + meta_param = stonith_get_info(stonith_obj, ST_CONF_XML); + if (meta_param == NULL) { + fprintf(stderr, "stonithRA plugin: no list of parameters"); + meta_param = no_parameter_info; + } + + printf(META_TEMPLATE, + rsc_type, xml_meta_longdesc, xml_meta_shortdesc, meta_param); + + xmlFree(xml_meta_longdesc); + xmlFree(xml_meta_shortdesc); +} + +#define MAXNVARG 50 + +void +print_types() +{ + char ** typelist; + + typelist = stonith_types(); + if (typelist == NULL) { + log_msg(LOG_ERR, "Could not list Stonith types."); + }else{ + char ** this; + + for(this=typelist; *this; ++this) { + printf("%s\n", *this); + } + } +} + +void +print_confignames(Stonith *s) +{ + const char * const * names; + int i; + + names = stonith_get_confignames(s); + + if (names != NULL) { + for (i=0; names[i]; ++i) { + printf("%s ", names[i]); + } + } + printf("\n"); +} + +void +log_buf(int severity, char *buf) +{ + if (severity == LOG_DEBUG && !debug) + return; + if (log_destination == LOG_TERMINAL) { + fprintf(stderr, "%s: %s\n", prio2str(severity),buf); + } else { + cl_log(severity, "%s", buf); + } +} + +void +log_msg(int severity, const char * fmt, ...) +{ + va_list ap; + char buf[MAXLINE]; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf)-1, fmt, ap); + va_end(ap); + log_buf(severity, buf); +} + +void +trans_log(int priority, const char * fmt, ...) +{ + int severity; + va_list ap; + char buf[MAXLINE]; + + severity = pil_loglevel_to_syslog_severity[ priority % sizeof + (pil_loglevel_to_syslog_severity) ]; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf)-1, fmt, ap); + va_end(ap); + log_buf(severity, buf); +} + +int +main(int argc, char** argv) +{ + char * cmdname; + int rc; + Stonith * s; + const char * SwitchType = NULL; + const char * optfile = NULL; + const char * parameters = NULL; + int reset_type = ST_GENERIC_RESET; + int verbose = 0; + int status = 0; + int silent = 0; + int listhosts = 0; + int listtypes = 0; + int listparanames = 0; + int params_from_env = 0; + + int c; + int errors = 0; + int argcount; + StonithNVpair nvargs[MAXNVARG]; + int nvcount=0; + int j; + int count = 1; + int help = 0; + int metadata = 0; + + /* The bladehpi stonith plugin makes use of openhpi which is + * threaded. The mix of memory allocation without thread + * initialization followed by g_thread_init followed by + * deallocating that memory results in segfault. Hence the + * following G_SLICE setting; see + * http://library.gnome.org/devel/glib/stable/glib-Memory-Slices.html#g-slice-alloc + */ + + setenv("G_SLICE", "always-malloc", 1); + + if ((cmdname = strrchr(argv[0], '/')) == NULL) { + cmdname = argv[0]; + }else{ + ++cmdname; + } + + + while ((c = getopt(argc, argv, OPTIONS)) != -1) { + switch(c) { + + case 'c': count = atoi(optarg); + if (count < 1) { + fprintf(stderr + , "bad count [%s]\n" + , optarg); + usage(cmdname, 1, NULL); + } + break; + + case 'd': debug++; + break; + + case 'F': optfile = optarg; + break; + + case 'E': params_from_env = 1; + break; + + case 'h': help++; + break; + + case 'm': metadata++; + break; + + case 'l': ++listhosts; + break; + + case 'L': ++listtypes; + break; + + case 'p': parameters = optarg; + break; + + case 's': ++silent; + break; + + case 'S': ++status; + break; + + case 't': SwitchType = optarg; + break; + + case 'T': if (strcmp(optarg, "on")== 0) { + reset_type = ST_POWERON; + }else if (strcmp(optarg, "off")== 0) { + reset_type = ST_POWEROFF; + }else if (strcmp(optarg, "reset")== 0) { + reset_type = ST_GENERIC_RESET; + }else{ + fprintf(stderr + , "bad reset type [%s]\n" + , optarg); + usage(cmdname, 1, NULL); + } + break; + + case 'n': ++listparanames; + break; + + case 'v': ++verbose; + break; + + case 'V': version(); + break; + + default: ++errors; + break; + } + } + + /* if we're invoked by stonithd, log through cl_log */ + if (!isatty(fileno(stdin))) { + log_destination = LOG_CLLOG; + cl_log_set_entity("stonith"); + cl_log_enable_stderr(debug?TRUE:FALSE); + cl_log_set_facility(HA_LOG_FACILITY); + + /* Use logd if it's enabled by heartbeat */ + cl_inherit_logging_environment(0); + } + + if (help && !errors) { + usage(cmdname, 0, SwitchType); + } + if (debug) { + PILpisysSetDebugLevel(debug); + setenv("HA_debug","2",0); + } + if ((optfile && parameters) || (optfile && params_from_env) + || (params_from_env && parameters)) { + fprintf(stderr + , "Please use just one of -F, -p, and -E options\n"); + usage(cmdname, 1, NULL); + } + + /* + * Process name=value arguments on command line... + */ + for (;optind < argc; ++optind) { + char * eqpos; + if ((eqpos=strchr(argv[optind], EQUAL)) == NULL) { + break; + } + if (parameters || optfile || params_from_env) { + fprintf(stderr + , "Cannot mix name=value and -p, -F, or -E " + "style arguments\n"); + usage(cmdname, 1, NULL); + } + if (nvcount >= MAXNVARG) { + fprintf(stderr + , "Too many name=value style arguments\n"); + exit(1); + } + nvargs[nvcount].s_name = argv[optind]; + *eqpos = EOS; + nvargs[nvcount].s_value = eqpos+1; + nvcount++; + } + nvargs[nvcount].s_name = NULL; + nvargs[nvcount].s_value = NULL; + + argcount = argc - optind; + + if (!(argcount == 1 || (argcount < 1 + && (status||listhosts||listtypes||listparanames||metadata)))) { + ++errors; + } + + if (errors) { + usage(cmdname, 1, NULL); + } + + if (listtypes) { + print_types(); + exit(0); + } + + if (SwitchType == NULL) { + log_msg(LOG_ERR,"Must specify device type (-t option)"); + usage(cmdname, 1, NULL); + } + s = stonith_new(SwitchType); + if (s == NULL) { + log_msg(LOG_ERR,"Invalid device type: '%s'", SwitchType); + exit(S_OOPS); + } + if (debug) { + stonith_set_debug(s, debug); + } + stonith_set_log(s, (PILLogFun)trans_log); + + if (!listparanames && !metadata && optfile == NULL && + parameters == NULL && !params_from_env && nvcount == 0) { + const char * const * names; + int needs_parms = 1; + + if (s != NULL && (names = stonith_get_confignames(s)) != NULL && names[0] == NULL) { + needs_parms = 0; + } + + if (needs_parms) { + fprintf(stderr + , "Must specify either -p option, -F option, -E option, or " + "name=value style arguments\n"); + if (s != NULL) { + stonith_delete(s); + } + usage(cmdname, 1, NULL); + } + } + + if (listparanames) { + print_confignames(s); + stonith_delete(s); + s=NULL; + exit(0); + } + + if (metadata) { + print_stonith_meta(s,SwitchType); + stonith_delete(s); + s=NULL; + exit(0); + } + + /* Old STONITH version 1 stuff... */ + if (optfile) { + /* Configure the Stonith object from a file */ + if ((rc=stonith_set_config_file(s, optfile)) != S_OK) { + log_msg(LOG_ERR + , "Invalid config file for %s device." + , SwitchType); +#if 0 + log_msg(LOG_INFO, "Config file syntax: %s" + , s->s_ops->getinfo(s, ST_CONF_FILE_SYNTAX)); +#endif + stonith_delete(s); s=NULL; + exit(S_BADCONFIG); + } + }else if (params_from_env) { + /* Configure Stonith object from the environment */ + StonithNVpair * pairs; + if ((pairs = stonith_env_to_NVpair(s)) == NULL) { + fprintf(stderr + , "Invalid config info for %s device.\n" + , SwitchType); + stonith_delete(s); s=NULL; + exit(1); + } + if ((rc = stonith_set_config(s, pairs)) != S_OK) { + fprintf(stderr + , "Invalid config info for %s device\n" + , SwitchType); + } + }else if (parameters) { + /* Configure Stonith object from the -p argument */ + StonithNVpair * pairs; + if ((pairs = stonith1_compat_string_to_NVpair + ( s, parameters)) == NULL) { + fprintf(stderr + , "Invalid STONITH -p parameter [%s]\n" + , parameters); + stonith_delete(s); s=NULL; + exit(1); + } + if ((rc = stonith_set_config(s, pairs)) != S_OK) { + fprintf(stderr + , "Invalid config info for %s device\n" + , SwitchType); + } + }else{ + /* + * Configure STONITH device using cmdline arguments... + */ + if ((rc = stonith_set_config(s, nvargs)) != S_OK) { + const char * const * names; + int j; + fprintf(stderr + , "Invalid config info for %s device\n" + , SwitchType); + + names = stonith_get_confignames(s); + + if (names != NULL) { + fprintf(stderr + , "Valid config names are:\n"); + + for (j=0; names[j]; ++j) { + fprintf(stderr + , "\t%s\n", names[j]); + } + } + stonith_delete(s); s=NULL; + exit(rc); + } + } + + + for (j=0; j < count; ++j) { + rc = S_OK; + + if (status) { + rc = stonith_get_status(s); + + if (!silent) { + if (rc == S_OK) { + log_msg((log_destination == LOG_TERMINAL) ? + LOG_INFO : LOG_DEBUG, + "%s device OK.", SwitchType); + }else{ + /* Uh-Oh */ + log_msg(LOG_ERR, "%s device not accessible." + , SwitchType); + } + } + } + + if (listhosts) { + char ** hostlist; + + hostlist = stonith_get_hostlist(s); + if (hostlist == NULL) { + log_msg(LOG_ERR, "Could not list hosts for %s." + , SwitchType); + rc = -1; + }else{ + char ** this; + + for(this=hostlist; *this; ++this) { + printf("%s\n", *this); + } + stonith_free_hostlist(hostlist); + } + } + + if (optind < argc) { + char *nodename; + nodename = g_strdup(argv[optind]); + strdown(nodename); + rc = stonith_req_reset(s, reset_type, nodename); + g_free(nodename); + } + } + stonith_delete(s); s = NULL; + return(rc); +} diff --git a/lib/stonith/meatclient.c b/lib/stonith/meatclient.c new file mode 100644 index 0000000..e95dc0e --- /dev/null +++ b/lib/stonith/meatclient.c @@ -0,0 +1,152 @@ +/* + * Stonith client for Human Operator Stonith device + * + * Copyright (c) 2001 Gregor Binder <gbinder@sysfive.com> + * + * This program is a rewrite of the "do_meatware" program by + * David C. Teigland <teigland@sistina.com> originally appeared + * in the GFS stomith meatware agent. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <ctype.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stonith/stonith.h> +#include <glib.h> + +#define OPTIONS "c:w" + +void usage(const char * cmd); + +void +usage(const char * cmd) +{ + fprintf(stderr, "usage: %s -c node [-w]\n", cmd); + exit(S_INVAL); +} + +extern char * optarg; +extern int optind, opterr, optopt; +int +main(int argc, char** argv) +{ + char * cmdname; + const char * meatpipe_pr = HA_VARRUNDIR "/meatware"; /* if you intend to + change this, modify + meatware.c as well */ + char * opthost = NULL; + int clearhost = 0; + + int c, argcount, waitmode = 0; + int errors = 0; + + if ((cmdname = strrchr(argv[0], '/')) == NULL) { + cmdname = argv[0]; + }else{ + ++cmdname; + } + + while ((c = getopt(argc, argv, OPTIONS)) != -1) { + switch(c) { + case 'c': opthost = optarg; + ++clearhost; + break; + case 'w': ++waitmode; + break; + default: ++errors; + break; + } + } + argcount = argc - optind; + if (!(argcount == 0) || !opthost) { + errors++; + } + + if (errors) { + usage(cmdname); + } + + strdown(opthost); + + if (clearhost) { + + int rc, fd; + char resp[3]; + + char line[256]; + char meatpipe[256]; + + gboolean waited=FALSE; + + snprintf(meatpipe, 256, "%s.%s", meatpipe_pr, opthost); + + while(1) { + fd = open(meatpipe, O_WRONLY | O_NONBLOCK); + if (fd >= 0) + break; + if (!waitmode || (errno != ENOENT && errno != ENXIO)) { + if (waited) printf("\n"); + snprintf(line, sizeof(line) + , "Meatware_IPC failed: %s", meatpipe); + perror(line); + exit(S_BADHOST); + } + printf("."); fflush(stdout); waited=TRUE; + sleep(1); + } + if (waited) printf("\n"); + + printf("\nWARNING!\n\n" + "If node \"%s\" has not been manually power-cycled or " + "disconnected from all shared resources and networks, " + "data on shared disks may become corrupted and " + "migrated services might not work as expected.\n" + "Please verify that the name or address above " + "corresponds to the node you just rebooted.\n\n" + "PROCEED? [yN] ", opthost); + + rc = scanf("%s", resp); + + if (rc == 0 || rc == EOF || tolower(resp[0] != 'y')) { + printf("Meatware_client: operation canceled.\n"); + exit(S_INVAL); + } + + sprintf(line, "meatware reply %s", opthost); + + rc = write(fd, line, 256); + + if (rc < 0) { + sprintf(line, "Meatware_IPC failed: %s", meatpipe); + perror(line); + exit(S_OOPS); + } + + printf("Meatware_client: reset confirmed.\n"); + } + + exit(S_OK); +} diff --git a/lib/stonith/st_ttylock.c b/lib/stonith/st_ttylock.c new file mode 100644 index 0000000..adc918d --- /dev/null +++ b/lib/stonith/st_ttylock.c @@ -0,0 +1,225 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <lha_internal.h> + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <ctype.h> +#include <clplumbing/cl_signal.h> +#include <stonith/st_ttylock.h> + +/* + * The following information is from the Filesystem Hierarchy Standard + * version 2.1 dated 12 April, 2000. + * + * 5.6 /var/lock : Lock files + * Lock files should be stored within the /var/lock directory structure. + * Device lock files, such as the serial device lock files that were originally + * found in either /usr/spool/locks or /usr/spool/uucp, must now be stored in + * /var/lock. The naming convention which must be used is LCK.. followed by + * the base name of the device file. For example, to lock /dev/cua0 the file + * LCK..cua0 would be created. + * + * The format used for device lock files must be the HDB UUCP lock file format. + * The HDB format is to store the process identifier (PID) as a ten byte + * ASCII decimal number, with a trailing newline. For example, if process 1230 + * holds a lock file, it would contain the eleven characters: space, space, + * space, space, space, space, one, two, three, zero, and newline. + * Then, anything wishing to use /dev/cua0 can read the lock file and act + * accordingly (all locks in /var/lock should be world-readable). + * + * + * PERMISSIONS NOTE: + * Different linux distributions set the mode of the lock directory differently + * Any process which wants to create lock files must have write permissions + * on HA_VARLOCKDIR (probably /var/lock). For things like the heartbeat API + * code, this may mean allowing the uid of the processes that use this API + * to join group uucp, or making the binaries setgid to uucp. + */ + +#define DEVDIR "/dev/" +#define DEVLEN (sizeof(DEVDIR)-1) + +static void raw_device (const char *dev, char *dest_name, size_t size); +static int DoLock(const char * prefix, const char *lockname); +static int DoUnlock(const char * prefix, const char *lockname); + +/* The code in this file originally written by Guenther Thomsen */ +/* Somewhat mangled by Alan Robertson */ + +/* + * Lock a tty (using lock files, see linux `man 2 open` close to O_EXCL) + * serial_device has to be _the complete path_, i.e. including '/dev/' to the + * special file, which denotes the tty to lock -tho + * return 0 on success, + * -1 if device is locked (lockfile exists and isn't stale), + * -2 for temporarily failure, try again, + * other negative value, if something unexpected happend (failure anyway) + */ + +static void +raw_device (const char *serial_device, char *dest_name, size_t size) +{ + char* dp = dest_name; + const char* sp = serial_device+DEVLEN; + const char* dpend = dp + size - 1; + + while (*sp != '\0' && dp < dpend) { + if (isalnum((unsigned int)*sp)) + *dp++ = *sp; + sp++; + } + *dp = EOS; +} + +int +st_ttylock(const char *serial_device) +{ + char rawname[64]; + + if (serial_device == NULL) { + errno = EFAULT; + return -3; + } + raw_device (serial_device, rawname, sizeof(rawname)); + return(DoLock("LCK..", rawname)); +} + +/* + * Unlock a tty (remove its lockfile) + * do we need to check, if its (still) ours? No, IMHO, if someone else + * locked our line, it's his fault -tho + * returns 0 on success + * <0 if some failure occured + */ + +int +st_ttyunlock(const char *serial_device) +{ + char rawname[64]; + + if (serial_device == NULL) { + errno = EFAULT; + return -3; + } + + raw_device (serial_device, rawname, sizeof(rawname)); + return(DoUnlock("LCK..", rawname)); +} + +/* This is what the FHS standard specifies for the size of our lock file */ +#define LOCKSTRLEN 11 + +static int +DoLock(const char * prefix, const char *lockname) +{ + char lf_name[256], tf_name[256], buf[LOCKSTRLEN+1]; + int fd; + long pid, mypid; + int rc; + struct stat sbuf; + + mypid = (unsigned long) getpid(); + + snprintf(lf_name, sizeof(lf_name), "%s/%s%s" + , HA_VARLOCKDIR, prefix, lockname); + + snprintf(tf_name, sizeof(tf_name), "%s/tmp%lu-%s" + , HA_VARLOCKDIR, mypid, lockname); + + if ((fd = open(lf_name, O_RDONLY)) >= 0) { + if (fstat(fd, &sbuf) >= 0 && sbuf.st_size < LOCKSTRLEN) { + sleep(1); /* if someone was about to create one, + * give'm a sec to do so + * Though if they follow our protocol, + * this won't happen. They should really + * put the pid in, then link, not the + * other way around. + */ + } + if (read(fd, buf, sizeof(buf)) < 1) { + /* lockfile empty -> rm it and go on */; + } else { + if (sscanf(buf, "%lu", &pid) < 1) { + /* lockfile screwed up -> rm it and go on */ + } else { + if (pid > 1 && ((long)getpid() != pid) + && ((CL_KILL((pid_t)pid, 0) >= 0) + || errno != ESRCH)) { + /* tty is locked by existing (not + * necessarily running) process + * -> give up */ + close(fd); + return -1; + } else { + /* stale lockfile -> rm it and go on */ + } + } + } + unlink(lf_name); + } + if ((fd = open(tf_name, O_CREAT | O_WRONLY | O_EXCL, 0644)) < 0) { + /* Hmmh, why did we fail? Anyway, nothing we can do about it */ + return -3; + } + + /* Slight overkill with the %*d format ;-) */ + snprintf(buf, sizeof(buf), "%*lu\n", LOCKSTRLEN-1, mypid); + + if (write(fd, buf, LOCKSTRLEN) != LOCKSTRLEN) { + /* Again, nothing we can do about this */ + return -3; + } + close(fd); + + switch (link(tf_name, lf_name)) { + case 0: + if (stat(tf_name, &sbuf) < 0) { + /* something weird happened */ + rc = -3; + break; + } + if (sbuf.st_nlink < 2) { + /* somehow, it didn't get through - NFS trouble? */ + rc = -2; + break; + } + rc = 0; + break; + case EEXIST: + rc = -1; + break; + default: + rc = -3; + } + unlink(tf_name); + return rc; +} + +static int +DoUnlock(const char * prefix, const char *lockname) +{ + char lf_name[256]; + + snprintf(lf_name, sizeof(lf_name), "%s/%s%s", HA_VARLOCKDIR + , prefix, lockname); + return unlink(lf_name); +} diff --git a/lib/stonith/stonith.c b/lib/stonith/stonith.c new file mode 100644 index 0000000..4ced8c7 --- /dev/null +++ b/lib/stonith/stonith.c @@ -0,0 +1,636 @@ +/* + * Stonith API infrastructure. + * + * Copyright (c) 2000 Alan Robertson <alanr@unix.sh> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <lha_internal.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <syslog.h> +#include <sys/wait.h> +#include <sys/param.h> +#include <dlfcn.h> +#include <dirent.h> +#include <glib.h> +#define ENABLE_PIL_DEFS_PRIVATE +#include <pils/plugin.h> +#include <pils/generic.h> +#include <stonith/stonith.h> +#include <stonith/stonith_plugin.h> + + +#define MALLOC StonithPIsys->imports->alloc +#ifdef MALLOCT +# undef MALLOCT +#endif +#define MALLOCT(t) (t*)(MALLOC(sizeof(t))) +#define REALLOC StonithPIsys->imports->mrealloc +#define STRDUP StonithPIsys->imports->mstrdup +#define FREE(p) {StonithPIsys->imports->mfree(p); (p) = NULL;} + +#define LOG(args...) PILCallLog(StonithPIsys->imports->log, args) + +#define EXTPINAME_S "external" +#define RHCSPINAME_S "rhcs" + +PILPluginUniv* StonithPIsys = NULL; +static GHashTable* Splugins = NULL; +static int init_pluginsys(void); +extern StonithImports stonithimports; + +static PILGenericIfMgmtRqst Reqs[] = +{ + {STONITH_TYPE_S, &Splugins, &stonithimports, NULL, NULL}, + {NULL, NULL, NULL, NULL, NULL} +}; + +void PILpisysSetDebugLevel(int); +/* Initialize the plugin system... */ +static int +init_pluginsys(void) { + + if (StonithPIsys) { + return TRUE; + } + + + /* PILpisysSetDebugLevel(10); */ + StonithPIsys = NewPILPluginUniv(STONITH_MODULES); + + if (StonithPIsys) { + int rc = PILLoadPlugin(StonithPIsys, PI_IFMANAGER, "generic", Reqs); + if (rc != PIL_OK) { + fprintf(stderr, "generic plugin load failed: %d\n", rc); + DelPILPluginUniv(StonithPIsys); + StonithPIsys = NULL; + } + /*PILSetDebugLevel(StonithPIsys, PI_IFMANAGER, "generic", 10);*/ + }else{ + fprintf(stderr, "pi univ creation failed\n"); + } + return StonithPIsys != NULL; +} + +/* + * Create a new Stonith object of the requested type. + */ + +Stonith * +stonith_new(const char * type) +{ + StonithPlugin * sp = NULL; + struct stonith_ops* ops = NULL; + char * key; + char * subplugin; + char * typecopy; + + + if (!init_pluginsys()) { + return NULL; + } + + if ((typecopy = STRDUP(type)) == NULL) { + return NULL; + } + + if (((subplugin = strchr(typecopy, '/')) != NULL) && + (strncmp(EXTPINAME_S, typecopy, strlen(EXTPINAME_S)) == 0 || + strncmp(RHCSPINAME_S, typecopy, strlen(RHCSPINAME_S)) == 0)) { + *subplugin++ = 0; /* make two strings */ + } + + /* Look and see if it's already loaded... */ + + if (g_hash_table_lookup_extended(Splugins, typecopy + , (gpointer)&key, (gpointer)&ops)) { + /* Yes! Increment reference count */ + PILIncrIFRefCount(StonithPIsys, STONITH_TYPE_S, typecopy, 1); + + }else{ /* No. Try and load it... */ + if (PILLoadPlugin(StonithPIsys, STONITH_TYPE_S, typecopy, NULL) + != PIL_OK) { + FREE(typecopy); + return NULL; + } + + /* Look up the plugin in the Splugins table */ + if (!g_hash_table_lookup_extended(Splugins, typecopy + , (void*)&key, (void*)&ops)) { + /* OOPS! didn't find it(!?!)... */ + PILIncrIFRefCount(StonithPIsys, STONITH_TYPE_S + , typecopy, -1); + FREE(typecopy); + return NULL; + } + } + + if (ops != NULL) { + sp = ops->new((const char *)(subplugin)); + if (sp != NULL) { + sp->s.stype = STRDUP(typecopy); + } + } + + FREE(typecopy); + return sp ? (&sp->s) : NULL; +} + +static int +qsort_string_cmp(const void *a, const void *b) +{ + return(strcmp(*(const char * const *)a, *(const char * const *)b)); +} + +/* + * Return list of STONITH types valid in stonith_new() + */ + +static char ** +get_plugin_list(const char *pltype) +{ + char ** typelist = NULL; + const char * const *extPI; + const char * const *p; + int numextPI, i; + Stonith * ext; + + /* let the external plugin return a list */ + if ((ext = stonith_new(pltype)) == NULL) { + LOG(PIL_CRIT, "Cannot create new external " + "plugin object"); + return NULL; + } + if ((extPI = stonith_get_confignames(ext)) == NULL) { + /* don't complain if rhcs plugins are not installed */ + if (strcmp(pltype, "rhcs")) + LOG(PIL_INFO, "Cannot get %s plugin subplugins", pltype); + stonith_delete(ext); + return NULL; + } + + /* count the external plugins */ + for (numextPI = 0, p = extPI; *p; p++, numextPI++); + + typelist = (char **) + MALLOC((numextPI+1)*sizeof(char *)); + if (typelist == NULL) { + LOG(PIL_CRIT, "Out of memory"); + stonith_delete(ext); + return NULL; + } + + memset(typelist, 0, (numextPI + 1)*sizeof(char *)); + + /* copy external plugins */ + for (i = 0; i < numextPI; i++) { + int len = strlen(pltype) + + strlen(extPI[i]) + 2; + typelist[i] = MALLOC(len); + if (typelist[i] == NULL) { + LOG(PIL_CRIT, "Out of memory"); + stonith_delete(ext); + goto err; + } + snprintf(typelist[i], len, "%s/%s" + , pltype, extPI[i]); + } + + stonith_delete(ext); + + /* sort the list of plugin names */ + qsort(typelist, numextPI, sizeof(char *), qsort_string_cmp); + + return typelist; +err: + stonith_free_hostlist(typelist); + return NULL; +} + +char ** +stonith_types(void) +{ + int i, j, cur=0, rl_size, sub_pl = 0; + static char ** rl = NULL; + char ** new_list, **sub_list = NULL; + + if (!init_pluginsys()) { + return NULL; + } + + new_list = PILListPlugins(StonithPIsys, STONITH_TYPE_S, NULL); + if (new_list == NULL) { + return NULL; + } + for (i=0; new_list[i]; ++i) + ; /* count */ + rl_size = i+1; + + rl = (char**)MALLOC(rl_size * sizeof(char *)); + if (rl == NULL) { + LOG(PIL_CRIT, "Out of memory"); + goto types_exit; + } + + for (i=0; new_list[i]; ++i) { + /* look for 'external' and 'rhcs' plugins */ + if (strcmp(new_list[i], EXTPINAME_S) == 0) { + sub_list = get_plugin_list(EXTPINAME_S); + sub_pl = 1; + } else if (strcmp(new_list[i], RHCSPINAME_S) == 0) { + sub_list = get_plugin_list(RHCSPINAME_S); + sub_pl = 1; + } + if (sub_pl) { + if (sub_list) { + for (j=0; sub_list[j]; ++j) + ; /* count */ + rl_size += j; + rl = (char**)REALLOC(rl, rl_size*sizeof(char *)); + for (j=0; sub_list[j]; ++j) { + rl[cur++] = sub_list[j]; + } + FREE(sub_list); + sub_list = NULL; + } + sub_pl = 0; + } else { + rl[cur] = STRDUP(new_list[i]); + if (rl[cur] == NULL) { + LOG(PIL_CRIT, "Out of memory"); + goto types_exit_mem; + } + cur++; + } + } + + rl[cur] = NULL; + goto types_exit; + +types_exit_mem: + stonith_free_hostlist(rl); + rl = NULL; +types_exit: + PILFreePluginList(new_list); + return rl; +} + +/* Destroy the STONITH object... */ + +void +stonith_delete(Stonith *s) +{ + StonithPlugin* sp = (StonithPlugin*)s; + + if (sp && sp->s_ops) { + char * st = sp->s.stype; + sp->s_ops->destroy(sp); + PILIncrIFRefCount(StonithPIsys, STONITH_TYPE_S, st, -1); + /* destroy should not free it */ + FREE(st); + } +} + +const char * const * +stonith_get_confignames(Stonith* s) +{ + StonithPlugin* sp = (StonithPlugin*)s; + + if (sp && sp->s_ops) { + return sp->s_ops->get_confignames(sp); + } + return NULL; +} + +const char* +stonith_get_info(Stonith* s, int infotype) +{ + StonithPlugin* sp = (StonithPlugin*)s; + + if (sp && sp->s_ops) { + return sp->s_ops->get_info(sp, infotype); + } + return NULL; + +} + +void +stonith_set_debug (Stonith* s, int debuglevel) +{ + StonithPlugin* sp = (StonithPlugin*)s; + if (StonithPIsys == NULL) { + return; + } + PILSetDebugLevel(StonithPIsys, STONITH_TYPE_S, sp->s.stype, debuglevel); +} + +void +stonith_set_log(Stonith* s, PILLogFun logfun) +{ + if (StonithPIsys == NULL) { + return; + } + PilPluginUnivSetLog(StonithPIsys, logfun); +} + +int +stonith_set_config(Stonith* s, StonithNVpair* list) +{ + StonithPlugin* sp = (StonithPlugin*)s; + + if (sp && sp->s_ops) { + int rc = sp->s_ops->set_config(sp, list); + if (rc == S_OK) { + sp->isconfigured = TRUE; + } + return rc; + } + return S_INVAL; +} + +/* + * FIXME: We really ought to support files with name=value type syntax + * on each line... + * + */ +int +stonith_set_config_file(Stonith* s, const char * configname) +{ + FILE * cfgfile; + + char line[1024]; + + if ((cfgfile = fopen(configname, "r")) == NULL) { + LOG(PIL_CRIT, "Cannot open %s", configname); + return(S_BADCONFIG); + } + while (fgets(line, sizeof(line), cfgfile) != NULL){ + int len; + + if (*line == '#' || *line == '\n' || *line == EOS) { + continue; + } + + /*remove the new line in the end*/ + len = strnlen(line, sizeof(line)-1); + if (line[len-1] == '\n'){ + line[len-1] = '\0'; + }else{ + line[len] = '\0'; + } + + fclose(cfgfile); + return stonith_set_config_info(s, line); + } + fclose(cfgfile); + return S_BADCONFIG; +} + +int +stonith_set_config_info(Stonith* s, const char * info) +{ + StonithNVpair* cinfo; + int rc; + cinfo = stonith1_compat_string_to_NVpair(s, info); + if (cinfo == NULL) { + return S_BADCONFIG; + } + rc = stonith_set_config(s, cinfo); + free_NVpair(cinfo); cinfo = NULL; + return rc; +} + +char** +stonith_get_hostlist(Stonith* s) +{ + StonithPlugin* sp = (StonithPlugin*)s; + if (sp && sp->s_ops && sp->isconfigured) { + return sp->s_ops->get_hostlist(sp); + } + return NULL; +} + +void +stonith_free_hostlist(char** hostlist) +{ + char ** here; + + for (here=hostlist; *here; ++here) { + FREE(*here); + } + FREE(hostlist); +} + +int +stonith_get_status(Stonith* s) +{ + StonithPlugin* sp = (StonithPlugin*)s; + if (sp && sp->s_ops && sp->isconfigured) { + return sp->s_ops->get_status(sp); + } + return S_INVAL; +} + +void +strdown(char *str) +{ + while( *str ) { + if( isupper(*str) ) + *str = tolower(*str); + str++; + } +} + +int +stonith_req_reset(Stonith* s, int operation, const char* node) +{ + StonithPlugin* sp = (StonithPlugin*)s; + if (sp && sp->s_ops && sp->isconfigured) { + char* nodecopy = STRDUP(node); + int rc; + if (nodecopy == NULL) { + return S_OOPS; + } + strdown(nodecopy); + + rc = sp->s_ops->req_reset(sp, operation, nodecopy); + FREE(nodecopy); + return rc; + } + return S_INVAL; +} +/* Stonith 1 compatibility: Convert a string to an NVpair set */ +StonithNVpair* +stonith1_compat_string_to_NVpair(Stonith* s, const char * str) +{ + /* We make some assumptions that the order of parameters in the + * result from stonith_get_confignames() matches that which + * was required from a Stonith1 module. + * Everything after the last delimiter is passed along as part of + * the final argument - white space and all... + */ + const char * const * config_names; + int n_names; + int j; + const char * delims = " \t\n\r\f"; + StonithNVpair* ret; + + if ((config_names = stonith_get_confignames(s)) == NULL) { + return NULL; + } + for (n_names=0; config_names[n_names] != NULL; ++n_names) { + /* Just count */; + } + ret = (StonithNVpair*) (MALLOC((n_names+1)*sizeof(StonithNVpair))); + if (ret == NULL) { + return NULL; + } + memset(ret, 0, (n_names+1)*sizeof(StonithNVpair)); + for (j=0; j < n_names; ++j) { + size_t len; + if ((ret[j].s_name = STRDUP(config_names[j])) == NULL) { + goto freeandexit; + } + ret[j].s_value = NULL; + str += strspn(str, delims); + if (*str == EOS) { + goto freeandexit; + } + if (j == (n_names -1)) { + len = strlen(str); + }else{ + len = strcspn(str, delims); + } + if ((ret[j].s_value = MALLOC((len+1)*sizeof(char))) == NULL) { + goto freeandexit; + } + memcpy(ret[j].s_value, str, len); + ret[j].s_value[len] = EOS; + str += len; + } + ret[j].s_name = NULL; + return ret; +freeandexit: + free_NVpair(ret); ret = NULL; + return NULL; +} + +StonithNVpair* +stonith_env_to_NVpair(Stonith* s) +{ + /* Read the config names values from the environment */ + const char * const * config_names; + int n_names; + int j; + StonithNVpair* ret; + + if ((config_names = stonith_get_confignames(s)) == NULL) { + return NULL; + } + for (n_names=0; config_names[n_names] != NULL; ++n_names) { + /* Just count */; + } + ret = (StonithNVpair*) (MALLOC((n_names+1)*sizeof(StonithNVpair))); + if (ret == NULL) { + return NULL; + } + memset(ret, 0, (n_names+1)*sizeof(StonithNVpair)); + for (j=0; j < n_names; ++j) { + char *env_value; + if ((ret[j].s_name = STRDUP(config_names[j])) == NULL) { + goto freeandexit; + } + env_value = getenv(config_names[j]); + if (env_value) { + if ((ret[j].s_value = STRDUP(env_value)) == NULL) { + goto freeandexit; + } + } else { + ret[j].s_value = NULL; + } + } + ret[j].s_name = NULL; + return ret; +freeandexit: + free_NVpair(ret); ret = NULL; + return NULL; +} + +static int NVcur = -1; +static int NVmax = -1; +static gboolean NVerr = FALSE; + +static void +stonith_walk_ghash(gpointer key, gpointer value, gpointer user_data) +{ + StonithNVpair* u = user_data; + + if (NVcur <= NVmax && !NVerr) { + u[NVcur].s_name = STRDUP(key); + u[NVcur].s_value = STRDUP(value); + if (u[NVcur].s_name == NULL || u[NVcur].s_value == NULL) { + /* Memory allocation error */ + NVerr = TRUE; + return; + } + ++NVcur; + }else{ + NVerr = TRUE; + } +} + + +StonithNVpair* +stonith_ghash_to_NVpair(GHashTable* stringtable) +{ + int hsize = g_hash_table_size(stringtable); + StonithNVpair* ret; + + if ((ret = (StonithNVpair*)MALLOC(sizeof(StonithNVpair)*(hsize+1))) == NULL) { + return NULL; + } + NVmax = hsize; + NVcur = 0; + ret[hsize].s_name = NULL; + ret[hsize].s_value = NULL; + g_hash_table_foreach(stringtable, stonith_walk_ghash, ret); + NVmax = NVcur = -1; + if (NVerr) { + free_NVpair(ret); + ret = NULL; + } + return ret; +} + +void +free_NVpair(StonithNVpair* nv) +{ + StonithNVpair* this; + + if (nv == NULL) { + return; + } + for (this=nv; this->s_name; ++this) { + FREE(this->s_name); + if (this->s_value) { + FREE(this->s_value); + } + } + FREE(nv); +} diff --git a/logd/Makefile.am b/logd/Makefile.am new file mode 100644 index 0000000..7ac75e2 --- /dev/null +++ b/logd/Makefile.am @@ -0,0 +1,56 @@ +# +# hbclient library: Linux-HA heartbeat code +# +# Copyright (C) 2001 Michael Moerz +# Copyright (C) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + +halibdir = $(libdir)/@HB_PKG@ +ha_sbindir = $(sbindir) + +LIBRT = @LIBRT@ +AM_CFLAGS = @CFLAGS@ + +initddir = @INITDIR@ + +## binary progs +ha_sbin_PROGRAMS = ha_logger +halib_PROGRAMS = ha_logd logtest + +ha_logd_SOURCES = ha_logd.c +ha_logd_LDADD = $(top_builddir)/lib/clplumbing/libplumb.la \ + $(top_builddir)/lib/clplumbing/libplumbgpl.la + +# $(top_builddir)/lib/apphb/libapphb.la + +ha_logger_SOURCES = ha_logger.c +ha_logger_LDADD = $(top_builddir)/lib/clplumbing/libplumb.la + +logtest_SOURCES = logtest.c +logtest_LDADD = $(top_builddir)/lib/clplumbing/libplumb.la + +if HAVE_SYSTEMD +systemdsystemunit_DATA = \ + logd.service +else +initd_SCRIPTS = logd +endif diff --git a/logd/ha_logd.c b/logd/ha_logd.c new file mode 100644 index 0000000..c98b9d7 --- /dev/null +++ b/logd/ha_logd.c @@ -0,0 +1,1085 @@ +/* + * ha_logd.c logging daemon + * + * Copyright (C) 2004 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <lha_internal.h> +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/ipc.h> +#include <clplumbing/GSource.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <clplumbing/loggingdaemon.h> +#include <netinet/in.h> +#include <clplumbing/lsb_exitcodes.h> +#include <sys/types.h> +#include <signal.h> +#include <errno.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <stdarg.h> +#include <clplumbing/Gmain_timeout.h> +#include <clplumbing/coredumps.h> +#include <clplumbing/setproctitle.h> +#include <clplumbing/cl_signal.h> +#include <clplumbing/cl_misc.h> +#include <sys/wait.h> +#include <clplumbing/cl_pidfile.h> +#include <clplumbing/cl_syslog.h> + +/*two processes involved + 1. parent process which reads messages from all client channels + and writes them to the child process + + 2. the child process which reads messages from the parent process through IPC + and writes them to syslog/disk + + I call the parent process READ process, and the child process WRITE one, + for convenience. + +*/ + + + +#define DEFAULT_CFG_FILE HA_SYSCONFDIR "/logd.cf" +#define LOGD_PIDFILE HA_VARRUNDIR "/logd.pid" + +#define FD_STDIN 0 +#define FD_STDOUT 1 + +#define FD_STDERR 2 + + +#define WRITE_PROC_CHAN 0 +#define READ_PROC_CHAN 1 +#define LOGD_QUEUE_LEN 128 + +#define EOS '\0' +#define nullchk(a) ((a) ? (a) : "<null>") + +static const int logd_keepalive_ms = 1000; +static const int logd_warntime_ms = 5000; +static const int logd_deadtime_ms = 10000; +static gboolean verbose = FALSE; +static pid_t write_process_pid; +static IPC_Channel *chanspair[2]; +static gboolean stop_reading = FALSE; +static gboolean needs_shutdown = FALSE; + +static struct { + char debugfile[MAXLINE]; + char logfile[MAXLINE]; + char entity[MAXENTITY]; + char syslogprefix[MAXENTITY]; + int log_facility; + mode_t logmode; + gboolean syslogfmtmsgs; +} logd_config = + { + .debugfile = "", + .logfile = "", + .entity = "logd", + .syslogprefix = "", + .log_facility = HA_LOG_FACILITY, + .logmode = 0644, + .syslogfmtmsgs = FALSE + }; + +static void logd_log(const char * fmt, ...) G_GNUC_PRINTF(1,2); +static int set_debugfile(const char* option); +static int set_logfile(const char* option); +static int set_facility(const char * value); +static int set_entity(const char * option); +static int set_syslogprefix(const char * option); +static int set_sendqlen(const char * option); +static int set_recvqlen(const char * option); +static int set_logmode(const char * option); +static int set_syslogfmtmsgs(const char * option); + + +static char* cmdname = NULL; + + +static struct directive { + const char* name; + int (*add_func)(const char*); +} Directives[] = { + {"debugfile", set_debugfile}, + {"logfile", set_logfile}, + {"logfacility", set_facility}, + {"entity", set_entity}, + {"syslogprefix",set_syslogprefix}, + {"sendqlen", set_sendqlen}, + {"recvqlen", set_recvqlen}, + {"logmode", set_logmode}, + {"syslogmsgfmt",set_syslogfmtmsgs} +}; + +static void +logd_log( const char * fmt, ...) +{ + char buf[MAXLINE]; + va_list ap; + + buf[MAXLINE-1] = EOS; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf)-1, fmt, ap); + va_end(ap); + + fprintf(stderr, "%s", buf); + + return; +} + +static int +set_debugfile(const char* option) +{ + if (!option){ + logd_config.debugfile[0] = EOS; + return FALSE; + } + + cl_log(LOG_INFO, "setting debug file to %s", option); + strncpy(logd_config.debugfile, option, MAXLINE); + return TRUE; +} +static int +set_logfile(const char* option) +{ + if (!option){ + logd_config.logfile[0] = EOS; + return FALSE; + } + cl_log(LOG_INFO, "setting log file to %s", option); + strncpy(logd_config.logfile, option, MAXLINE); + return TRUE; +} + +/* set syslog facility config variable */ +static int +set_facility(const char * value) +{ + int i; + + i = cl_syslogfac_str2int(value); + if (i >= 0) { + cl_log(LOG_INFO, "setting log facility to %s", value); + logd_config.log_facility = i; + return(TRUE); + } + else { + return(FALSE); + } +} + +static int +set_entity(const char * option) +{ + if (!option){ + logd_config.entity[0] = EOS; + return FALSE; + } + strncpy(logd_config.entity, option, MAXENTITY); + logd_config.entity[MAXENTITY-1] = '\0'; + if (strlen(option) >= MAXENTITY) + cl_log(LOG_WARNING, "setting entity to %s (truncated from %s)", + logd_config.entity, option); + else + cl_log(LOG_INFO, "setting entity to %s", logd_config.entity); + return TRUE; + +} + +static int +set_syslogprefix(const char * option) +{ + if (!option){ + logd_config.syslogprefix[0] = EOS; + return FALSE; + } + strncpy(logd_config.syslogprefix, option, MAXENTITY); + logd_config.syslogprefix[MAXENTITY-1] = '\0'; + if (strlen(option) >= MAXENTITY) + cl_log(LOG_WARNING, + "setting syslogprefix to %s (truncated from %s)", + logd_config.syslogprefix, option); + else + cl_log(LOG_INFO, + "setting syslogprefix to %s", + logd_config.syslogprefix); + return TRUE; + +} + +static int +set_sendqlen(const char * option) +{ + int length; + + if (!option){ + cl_log(LOG_ERR, "NULL send queue length"); + return FALSE; + } + + length = atoi(option); + if (length < 0){ + cl_log(LOG_ERR, "negative send queue length"); + return FALSE; + } + + cl_log(LOG_INFO, "setting send queue length to %d", length); + chanspair[READ_PROC_CHAN]->ops->set_send_qlen(chanspair[READ_PROC_CHAN], + length); + + return TRUE; + +} + +static int +set_recvqlen(const char * option) +{ + int length; + + if (!option){ + cl_log(LOG_ERR, "NULL recv queue length"); + return FALSE; + } + + length = atoi(option); + if (length < 0){ + cl_log(LOG_ERR, "negative recv queue length"); + return FALSE; + } + + cl_log(LOG_INFO, "setting recv queue length to %d", length); + chanspair[WRITE_PROC_CHAN]->ops->set_recv_qlen(chanspair[WRITE_PROC_CHAN], + length); + + return TRUE; + +} + +static int +set_logmode(const char * option) +{ + unsigned long mode; + char * endptr; + if (!option){ + cl_log(LOG_ERR, "NULL logmode parameter"); + return FALSE; + } + mode = strtoul(option, &endptr, 8); + if (*endptr != EOS) { + cl_log(LOG_ERR, "Invalid log mode [%s]", option); + return FALSE; + } + if (*option != '0') { + /* Whine if mode doesn't start with '0' */ + cl_log(LOG_WARNING, "Log mode [%s] assumed to be octal" + , option); + } + logd_config.logmode = (mode_t)mode; + return TRUE; +} +static int +set_syslogfmtmsgs(const char * option) +{ + gboolean dosyslogfmt; + + if (cl_str_to_boolean(option, &dosyslogfmt) == HA_OK) { + cl_log_enable_syslog_filefmt(dosyslogfmt); + }else{ + return FALSE; + } + return TRUE; +} + + +typedef struct { + char app_name[MAXENTITY]; + pid_t pid; + gid_t gid; + uid_t uid; + + IPC_Channel* chan; + IPC_Channel* logchan; + GCHSource* g_src; +}ha_logd_client_t; + +static GList* logd_client_list = NULL; + +static IPC_Message* +getIPCmsg(IPC_Channel* ch) +{ + + int rc; + IPC_Message* ipcmsg; + + /* FIXME: Should we block here?? */ + rc = ch->ops->waitin(ch); + + switch(rc) { + default: + case IPC_FAIL: + cl_log(LOG_ERR, "getIPCmsg: waitin failure\n"); + return NULL; + + case IPC_BROKEN: + sleep(1); + return NULL; + + case IPC_INTR: + return NULL; + + case IPC_OK: + break; + } + + ipcmsg = NULL; + rc = ch->ops->recv(ch, &ipcmsg); + if (rc != IPC_OK) { + return NULL; + } + + return ipcmsg; + +} + +/* Flow control all clients off */ +static void +logd_suspend_clients(IPC_Channel* notused1, gpointer notused2) +{ + GList * gl; + + stop_reading = TRUE; + for (gl=g_list_first(logd_client_list); gl != NULL + ; gl = g_list_next(gl)) { + ha_logd_client_t* client = gl->data; + if (client && client->g_src) { + G_main_IPC_Channel_pause(client->g_src); + }else if (client) { + cl_log(LOG_ERR, "Could not suspend client [%s] pid %d" + , nullchk(client->app_name), client->pid); + }else{ + cl_log(LOG_ERR, "%s: Could not suspend NULL client", + __FUNCTION__); + } + } +} + +/* Resume input from clients - Flow control all clients back on */ +static void +logd_resume_clients(IPC_Channel* notused1, gpointer notused2) +{ + GList * gl; + + stop_reading = FALSE; + for (gl=g_list_first(logd_client_list); gl != NULL + ; gl = g_list_next(gl)) { + ha_logd_client_t* client = gl->data; + if (client && client->g_src) { + G_main_IPC_Channel_resume(client->g_src); + }else if (client) { + cl_log(LOG_ERR, "Could not resume client [%s] pid %d" + , nullchk(client->app_name), client->pid); + }else{ + cl_log(LOG_ERR, "%s: Could not suspend NULL client", + __FUNCTION__); + } + } +} + +static gboolean +on_receive_cmd (IPC_Channel* ch, gpointer user_data) +{ + IPC_Message* ipcmsg; + ha_logd_client_t* client = (ha_logd_client_t*)user_data; + IPC_Channel* logchan= client->logchan; + + + if (!ch->ops->is_message_pending(ch)) { + goto getout; + } + + ipcmsg = getIPCmsg(ch); + if (ipcmsg == NULL){ + if (IPC_ISRCONN(ch)) { + cl_log(LOG_ERR, "%s: read error on connected channel [%s:%d]" + , __FUNCTION__, client->app_name, client->pid); + } + return FALSE; + } + + if( ipcmsg->msg_body && ipcmsg->msg_len > 0 ){ + + if (client->app_name[0] == '\0'){ + LogDaemonMsgHdr* logmsghdr; + logmsghdr = (LogDaemonMsgHdr*) ipcmsg->msg_body; + strncpy(client->app_name, logmsghdr->entity, MAXENTITY); + } + + if (!IPC_ISWCONN(logchan)){ + cl_log(LOG_ERR + , "%s: channel to write process disconnected" + , __FUNCTION__); + return FALSE; + } + if (logchan->ops->send(logchan, ipcmsg) != IPC_OK){ + cl_log(LOG_ERR + , "%s: forwarding msg from [%s:%d] to" + " write process failed" + , __FUNCTION__ + , client->app_name, client->pid); + cl_log(LOG_ERR, "queue too small? (max=%ld, current len =%ld)", + (long)logchan->send_queue->max_qlen, + (long)logchan->send_queue->current_qlen); + return TRUE; + } + + }else { + cl_log(LOG_ERR, "on_receive_cmd:" + " invalid ipcmsg\n"); + } + + getout: + return TRUE; +} + +static void +on_remove_client (gpointer user_data) +{ + + logd_client_list = g_list_remove(logd_client_list, user_data); + if (user_data){ + free(user_data); + } + return; +} + + + +/* + *GLoop Message Handlers + */ +static gboolean +on_connect_cmd (IPC_Channel* ch, gpointer user_data) +{ + ha_logd_client_t* client = NULL; + + /* check paremeters */ + if (NULL == ch) { + cl_log(LOG_ERR, "on_connect_cmd: channel is null"); + return TRUE; + } + /* create new client */ + if (NULL == (client = malloc(sizeof(ha_logd_client_t)))) { + return FALSE; + } + memset(client, 0, sizeof(ha_logd_client_t)); + client->pid = ch->farside_pid; + client->chan = ch; + client->logchan = (IPC_Channel*)user_data; + client->g_src = G_main_add_IPC_Channel(G_PRIORITY_DEFAULT, + ch, FALSE, on_receive_cmd, + (gpointer)client, + on_remove_client); + if (client->g_src == NULL){ + cl_log(LOG_ERR, "add the client to main loop failed"); + free(client); + return TRUE; + } + if (stop_reading){ + G_main_IPC_Channel_pause(client->g_src); + } + + logd_client_list = g_list_append(logd_client_list, client); + + + return TRUE; +} + + + +static void +logd_make_daemon(gboolean daemonize) +{ + long pid; + + if (daemonize) { + if (daemon(0,0)) { + fprintf(stderr, "%s: could not start daemon\n" + , cmdname); + perror("fork"); + exit(LSB_EXIT_GENERIC); + } + } + + if (cl_lock_pidfile(LOGD_PIDFILE) < 0 ){ + pid = cl_read_pidfile(LOGD_PIDFILE); + if (pid > 0) + fprintf(stderr, "%s: already running [pid %ld].\n", + cmdname, pid); + else + fprintf(stderr, "%s: problem creating pid file %s\n", + cmdname, LOGD_PIDFILE); + exit(LSB_EXIT_OK); + } + + if (daemonize || !verbose){ + cl_log_enable_stderr(FALSE); + } + + if (daemonize){ + mode_t mask; + /* + * Some sample umask calculations: + * + * logmode = 0644 + * + * (~0644)&0777 = 0133 + * (0133 & ~0111) = 0022 + * => umask will be 022 (the expected result) + * + * logmode = 0600 + * (~0600)&0777 = 0177 + * (0177 & ~0111) = 0066 + */ + mask = (mode_t)(((~logd_config.logmode) & 0777) & (~0111)); + umask(mask); + } +} + + + +static void +logd_stop(void) +{ + + long running_logd_pid = cl_read_pidfile(LOGD_PIDFILE); + int err; + + if (running_logd_pid < 0) { + fprintf(stderr, "ha_logd already stopped.\n"); + cl_log(LOG_INFO, "ha_logd already stopped."); + exit(LSB_EXIT_OK); + } + + cl_log(LOG_DEBUG, "Stopping ha_logd with pid %ld", running_logd_pid); + if (kill((pid_t)running_logd_pid, SIGTERM) >= 0) { + /* Wait for the running logd to die */ + cl_log(LOG_INFO, "Waiting for pid=%ld to exit", + running_logd_pid); + alarm(0); + do { + sleep(1); + }while (kill((pid_t)running_logd_pid, 0) >= 0); + } + err = errno; + + if(errno == ESRCH) { + cl_log(LOG_INFO, "Pid %ld exited", running_logd_pid); + exit(LSB_EXIT_OK); + } else { + cl_perror("Pid %ld not killed", running_logd_pid); + exit((err == EPERM || err == EACCES) + ? LSB_EXIT_EPERM + : LSB_EXIT_GENERIC); + } + +} + + +static int +get_dir_index(const char* directive) +{ + int j; + for(j=0; j < DIMOF(Directives); j++){ + if (0 == strcasecmp(directive, Directives[j].name)){ + return j; + } + } + return -1; +} + + +/* Adapted from parse_config in config.c */ +static gboolean +parse_config(const char* cfgfile) +{ + FILE* f; + char buf[MAXLINE]; + char* bp; + char* cp; + char directive[MAXLINE]; + int dirlength; + int optionlength; + char option[MAXLINE]; + int dir_index; + + gboolean ret = TRUE; + + if ((f = fopen(cfgfile, "r")) == NULL){ + cl_log(LOG_WARNING, "Cannot open config file [%s]", cfgfile); + return(FALSE); + } + + while(fgets(buf, MAXLINE, f) != NULL){ + bp = buf; + /* Skip over white space*/ + bp += strspn(bp, " \t\n\r\f"); + + /* comments */ + if ((cp = strchr(bp, '#')) != NULL){ + *cp = EOS; + } + + if (*bp == EOS){ + continue; + } + + dirlength = strcspn(bp, " \t\n\f\r"); + strncpy(directive, bp, dirlength); + directive[dirlength] = EOS; + + if ((dir_index = get_dir_index(directive)) == -1){ + fprintf(stderr, "Illegal directive [%s] in %s\n" + , directive, cfgfile); + ret = FALSE; + continue; + } + + bp += dirlength; + + /* skip delimiters */ + bp += strspn(bp, " ,\t\n\f\r"); + + /* Set option */ + optionlength = strcspn(bp, " ,\t\n\f\r"); + strncpy(option, bp, optionlength); + option[optionlength] = EOS; + if (!(*Directives[dir_index].add_func)(option)) { + ret = FALSE; + } + }/*while*/ + fclose(f); + return ret; +} + +static gboolean +logd_term_action(int sig, gpointer userdata) +{ + GList *log_iter = logd_client_list; + GMainLoop *mainloop = (GMainLoop*)userdata; + ha_logd_client_t *client = NULL; + + cl_log(LOG_DEBUG, "logd_term_action: received SIGTERM"); + if (mainloop == NULL){ + cl_log(LOG_ERR, "logd_term_action: invalid arguments"); + return FALSE; + } + + stop_reading = TRUE; + + while(log_iter != NULL) { + client = log_iter->data; + log_iter = log_iter->next; + + cl_log(LOG_DEBUG, "logd_term_action:" + " waiting for %d messages to be read for process %s", + (int)client->logchan->send_queue->current_qlen, + client->app_name); + + client->logchan->ops->waitout(client->logchan); + } + + cl_log(LOG_DEBUG, "logd_term_action" + ": waiting for %d messages to be read by write process" + , (int)chanspair[WRITE_PROC_CHAN]->send_queue->current_qlen); + chanspair[WRITE_PROC_CHAN]->ops->waitout(chanspair[WRITE_PROC_CHAN]); + + cl_log(LOG_DEBUG, "logd_term_action: sending SIGTERM to write process"); + if (CL_KILL(write_process_pid, SIGTERM) >= 0){ + + pid_t pid; + pid = wait4(write_process_pid, NULL, 0, NULL); + if (pid < 0){ + cl_log(LOG_ERR, "wait4 for write process failed"); + } + + } + + g_main_quit(mainloop); + + return TRUE; +} + +/* + * Handle SIGHUP to re-open log files + */ +static gboolean +logd_hup_action(int sig, gpointer userdata) +{ + cl_log_close_log_files(); + if (write_process_pid) + /* do we want to propagate the HUP, + * or do we assume that it was a killall anyways? */ + CL_KILL(write_process_pid, SIGHUP); + else + cl_log(LOG_INFO, "SIGHUP received, re-opened log files"); + return TRUE; +} + +static void +read_msg_process(IPC_Channel* chan) +{ + GHashTable* conn_cmd_attrs; + IPC_WaitConnection* conn_cmd = NULL; + char path[] = "path"; + char socketpath[] = HA_LOGDAEMON_IPC; + GMainLoop* mainloop; + + + + mainloop = g_main_new(FALSE); + + G_main_add_SignalHandler(G_PRIORITY_HIGH, SIGTERM, + logd_term_action,mainloop, NULL); + + conn_cmd_attrs = g_hash_table_new(g_str_hash, g_str_equal); + + g_hash_table_insert(conn_cmd_attrs, path, socketpath); + + conn_cmd = ipc_wait_conn_constructor(IPC_ANYTYPE, conn_cmd_attrs); + g_hash_table_destroy(conn_cmd_attrs); + + if (conn_cmd == NULL){ + fprintf(stderr, "ERROR: create waiting connection failed"); + exit(1); + } + + /*Create a source to handle new connect rquests for command*/ + G_main_add_IPC_WaitConnection( G_PRIORITY_HIGH, conn_cmd, NULL, FALSE + , on_connect_cmd, chan, NULL); + chan->ops->set_high_flow_callback(chan, logd_suspend_clients, NULL); + chan->ops->set_low_flow_callback(chan, logd_resume_clients, NULL); + chan->high_flow_mark = chan->send_queue->max_qlen; + chan->low_flow_mark = (chan->send_queue->max_qlen*3)/4; + + G_main_add_IPC_Channel(G_PRIORITY_DEFAULT, chan, FALSE,NULL,NULL,NULL); + + G_main_add_SignalHandler(G_PRIORITY_DEFAULT, SIGHUP, + logd_hup_action, mainloop, NULL); + g_main_run(mainloop); + + return; +} + +static gboolean +direct_log(IPC_Channel* ch, gpointer user_data) +{ + IPC_Message* ipcmsg; + GMainLoop* loop; + int pri = LOG_DEBUG + 1; + + loop =(GMainLoop*)user_data; + + while(ch->ops->is_message_pending(ch)){ + if (ch->ch_status == IPC_DISCONNECT){ + cl_log(LOG_ERR, "read channel is disconnected:" + "something very wrong happened"); + return FALSE; + } + + ipcmsg = getIPCmsg(ch); + if (ipcmsg == NULL){ + return TRUE; + } + + if( ipcmsg->msg_body + && ipcmsg->msg_len > 0 ){ + LogDaemonMsgHdr *logmsghdr; + LogDaemonMsgHdr copy; + char *msgtext; + + logmsghdr = (LogDaemonMsgHdr*) ipcmsg->msg_body; + /* this copy nonsense is here because apparently ia64 + * complained about "unaligned memory access. */ +#define COPYFIELD(copy, msg, field) memcpy(((u_char*)©.field), ((u_char*)&msg->field), sizeof(copy.field)) + COPYFIELD(copy, logmsghdr, use_pri_str); + COPYFIELD(copy, logmsghdr, entity); + COPYFIELD(copy, logmsghdr, entity_pid); + COPYFIELD(copy, logmsghdr, timestamp); + COPYFIELD(copy, logmsghdr, priority); + /* Don't want to copy the following message text */ + + msgtext = (char *)logmsghdr + sizeof(LogDaemonMsgHdr); + cl_direct_log(copy.priority, msgtext + , copy.use_pri_str + , copy.entity, copy.entity_pid + , copy.timestamp); + + if (copy.priority < pri) + pri = copy.priority; + + (void)logd_log; +/* + if (verbose){ + logd_log("%s[%d]: %s %s\n", + logmsg->entity[0]=='\0'? + "unknown": copy.entity, + copy.entity_pid, + ha_timestamp(copy.timestamp), + msgtext); + } + */ + if (ipcmsg->msg_done){ + ipcmsg->msg_done(ipcmsg); + } + } + } + /* current message backlog processed, + * about to return to mainloop, + * fflush and potentially fsync stuff */ + cl_log_do_fflush(pri <= LOG_ERR); + + if(needs_shutdown) { + cl_log(LOG_INFO, "Exiting write process"); + g_main_quit(loop); + return FALSE; + } + return TRUE; +} + +static gboolean +logd_term_write_action(int sig, gpointer userdata) +{ + /* as a side-effect, the log message makes sure we enter direct_log() + * one last time (so we always exit) + */ + needs_shutdown = TRUE; + cl_log(LOG_INFO, "logd_term_write_action: received SIGTERM"); + cl_log(LOG_DEBUG, "Writing out %d messages then quitting", + (int)chanspair[WRITE_PROC_CHAN]->recv_queue->current_qlen); + + direct_log(chanspair[WRITE_PROC_CHAN], userdata); + + return TRUE; +} + +static void +write_msg_process(IPC_Channel* readchan) +{ + + GMainLoop* mainloop; + IPC_Channel* ch = readchan; + + + mainloop = g_main_new(FALSE); + + G_main_add_IPC_Channel(G_PRIORITY_DEFAULT, + ch, FALSE, + direct_log, mainloop, NULL); + + G_main_add_SignalHandler(G_PRIORITY_HIGH, SIGTERM, + logd_term_write_action, mainloop, NULL); + + G_main_add_SignalHandler(G_PRIORITY_DEFAULT, SIGHUP, + logd_hup_action, mainloop, NULL); + + g_main_run(mainloop); + +} + + + + + + +static void +usage(void) +{ + printf("usage: \n" + "%s [options]\n\n" + "options: \n" + "-d make the program a daemon\n" + "-k stop the logging daemon if it is already running\n" + "-s return logging daemon status \n" + "-c use this config file\n" + "-v verbosely print debug messages" + "-h print out this message\n\n", + cmdname); + + return; +} +int +main(int argc, char** argv, char** envp) +{ + + int c; + gboolean daemonize = FALSE; + gboolean stop_logd = FALSE; + gboolean ask_status= FALSE; + const char* cfgfile = NULL; + pid_t pid; + + cmdname = argv[0]; + while ((c = getopt(argc, argv, "c:dksvh")) != -1){ + + switch(c){ + + case 'd': /* daemonize */ + daemonize = TRUE; + break; + case 'k': /* stop */ + stop_logd = TRUE; + break; + case 's': /* status */ + ask_status = TRUE; + break; + case 'c': /* config file*/ + cfgfile = optarg; + break; + case 'v': + verbose = TRUE; + break; + case 'h': /*help message */ + default: + usage(); + exit(1); + } + + } + + set_ipc_time_debug_flag(FALSE); + cl_log_set_uselogd(FALSE); + + if (!cfgfile && access(DEFAULT_CFG_FILE, F_OK) == 0) { + cfgfile = DEFAULT_CFG_FILE; + } + + + /* default one set to "logd" + * by setting facility, we enable syslog + */ + cl_log_enable_stderr(TRUE); + cl_log_set_entity(logd_config.entity); + cl_log_set_facility(logd_config.log_facility); + + + if (ask_status){ + long pid; + + if( (pid = cl_read_pidfile(LOGD_PIDFILE)) > 0 ){ + printf("logging daemon is running [pid = %ld].\n", pid); + exit(LSB_EXIT_OK); + }else{ + if (pid == - LSB_STATUS_VAR_PID) { + printf("logging daemon is stopped: %s exists.\n" + , LOGD_PIDFILE); + }else{ + printf("logging daemon is stopped.\n"); + } + } + exit(-pid); + + } + if (stop_logd){ + logd_stop(); + exit(LSB_EXIT_OK); + } + + logd_make_daemon(daemonize); + + + if (ipc_channel_pair(chanspair) != IPC_OK){ + cl_perror("cannot create channel pair IPC"); + return -1; + } + + + if (cfgfile && !parse_config(cfgfile)) { + FILE* f; + if ((f = fopen(cfgfile, "r")) != NULL){ + fclose(f); + cl_log(LOG_ERR, "Config file [%s] is incorrect." + , cfgfile); + exit(LSB_EXIT_NOTCONFIGED); + } + } + + if (strlen(logd_config.debugfile) > 0) { + cl_log_set_debugfile(logd_config.debugfile); + } + if (strlen(logd_config.logfile) > 0) { + cl_log_set_logfile(logd_config.logfile); + } + cl_log_set_syslogprefix(logd_config.syslogprefix); + cl_log_set_entity(logd_config.entity); + cl_log_set_facility(logd_config.log_facility); + + cl_log(LOG_INFO, "logd started with %s.", + cfgfile ? cfgfile : "default configuration"); + + if (cl_enable_coredumps(TRUE) < 0){ + cl_log(LOG_ERR, "enabling core dump failed"); + } + cl_cdtocoredir(); + + + + + chanspair[WRITE_PROC_CHAN]->ops->set_recv_qlen(chanspair[WRITE_PROC_CHAN], + LOGD_QUEUE_LEN); + + chanspair[READ_PROC_CHAN]->ops->set_send_qlen(chanspair[READ_PROC_CHAN], + LOGD_QUEUE_LEN); + + if (init_set_proc_title(argc, argv, envp) < 0) { + cl_log(LOG_ERR, "Allocation of proc title failed."); + return -1; + } + + switch(pid = fork()){ + case -1: + cl_perror("Can't fork child process!"); + return -1; + case 0: + /*child*/ + cl_log_use_buffered_io(1); + set_proc_title("ha_logd: write process"); + write_msg_process(chanspair[WRITE_PROC_CHAN]); + break; + default: + /*parent*/ + set_proc_title("ha_logd: read process"); + write_process_pid = pid; + /* we don't expect to log anything in the parent. */ + cl_log_close_log_files(); + + read_msg_process(chanspair[READ_PROC_CHAN]); + break; + } + return 0; +} + + + + diff --git a/logd/ha_logger.1 b/logd/ha_logger.1 new file mode 100644 index 0000000..db7f939 --- /dev/null +++ b/logd/ha_logger.1 @@ -0,0 +1,38 @@ +.TH HA_LOGGER 1 "5th Nov 2004" +.SH NAME +.B ha_logger +\- Log a message to files/syslog through the HA Logging Daemon +.SH SYNOPSIS +.B ha_logger +.RI "[-D ha-log/ha-debug] [-t tag] [message ...]" +.P +.SH DESCRIPTION +.B ha_logger +is used to log a message to files/syslog through the HA Logging Daemon +.PP +.IP "\fB-D\fP \fIha-log/ha-debug\fP" +Log the message to different files. Ha-log will log the message to +the log file and the debug file, while ha-debug will log the message +to the debug file only. +.IP "\fB-t\fP \fItag\fP" +Mark every line in the log with the specified +.IP \fBmessage\fP +The message you want to log on. +.PP +.SH SEE ALSO +ha_logd(8) heartbeat(8) + +.SH DOCUMENTATION +More information may be found at +.UR http://linux-ha.org/wiki +http://linux-ha.org/wiki +.UE + +.SH AUTHORS +.nf + Guochun Shi <gshi@ncsa.uiuc.edu> + Alan Robertson <alanr@unix.sh> + Lars Marowsky-Bree <lmb@suse.de> +.fi + + diff --git a/logd/ha_logger.c b/logd/ha_logger.c new file mode 100644 index 0000000..5e2f9ef --- /dev/null +++ b/logd/ha_logger.c @@ -0,0 +1,142 @@ +/* + * ha_logger.c utility to log a message to the logging daemon + * + * Copyright (C) 2004 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <lha_internal.h> +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/ipc.h> +#include <clplumbing/GSource.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <clplumbing/loggingdaemon.h> +#include <syslog.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <errno.h> +#include <netinet/in.h> + +#define EXIT_OK 0 +#define EXIT_FAIL 1 + +int LogToDaemon(int priority, const char * buf, int bstrlen, gboolean use_pri_str); +void cl_log(int priority, const char * fmt, ...) G_GNUC_PRINTF(2,3); +static void +usage(void) +{ + printf("usage: " + "ha_logger [-t tag] [-D <ha-log/ha-debug>] [message]\n"); + return; +} +#define BUFSIZE 1024 +int +main(int argc, char** argv) +{ + int priority; + char* entity = NULL; + int c; + char buf[BUFSIZE]; + const char* logtype = "ha-log"; + + + while (( c =getopt(argc, argv,"t:D:h")) != -1){ + switch(c){ + + case 't': + entity = optarg; + break; + case 'D': + logtype=optarg; + break; + case 'h': + usage(); + exit(1); + default: + usage(); + exit(1); + } + + } + + if(!cl_log_test_logd()){ + fprintf(stderr, "logd is not running"); + return EXIT_FAIL; + } + + argc -=optind; + argv += optind; + + if (entity != NULL){ + cl_log_set_entity(entity); + } + + if (strcmp(logtype, "ha-log") == 0){ + priority = LOG_INFO; + } else if (strcmp(logtype, "ha-debug") == 0){ + priority = LOG_DEBUG; + }else{ + goto err_exit; + } + + if (argc > 0){ + register char *p; + + for (p = *argv; *argv; argv++, p = *argv) { + while (strlen(p) > BUFSIZE-1) { + memcpy(buf, p, BUFSIZE-1); + *(buf+BUFSIZE-1) = '\0'; + if (LogToDaemon(priority,buf, + BUFSIZE,FALSE) != HA_OK){ + return EXIT_FAIL; + } + p += BUFSIZE-1; + } + if (LogToDaemon(priority,p, + strnlen(p, BUFSIZE),FALSE) != HA_OK){ + return EXIT_FAIL; + } + } + return EXIT_OK; + }else { + while (fgets(buf, sizeof(buf), stdin) != NULL) { + /* glibc is buggy and adds an additional newline, + so we have to remove it here until glibc is fixed */ + int len = strlen(buf); + + if (len > 0 && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + if (LogToDaemon(priority, buf,strlen(buf), FALSE) == HA_OK){ + continue; + }else { + return EXIT_FAIL; + } + } + + return EXIT_OK; + } + + err_exit: + usage(); + return(1); + +} + diff --git a/logd/logd.cf b/logd/logd.cf new file mode 100644 index 0000000..cd8ccc3 --- /dev/null +++ b/logd/logd.cf @@ -0,0 +1,66 @@ +# File to write debug messages to +# Default: /var/log/ha-debug +#debugfile /var/log/ha-debug + +# +# +# File to write other messages to +# Default: /var/log/ha-log +#logfile /var/log/ha-log + +# +# +# Octal file permission to create the log files with +# Default: 0644 +#logmode 0640 + + +# +# +# Facility to use for syslog()/logger +# (set to 'none' to disable syslog logging) +# Default: daemon +#logfacility daemon + + +# Entity to be shown at beginning of a message +# generated by the logging daemon itself +# Default: "logd" +#entity logd + + +# Entity to be shown at beginning of _every_ message +# passed to syslog (not to log files). +# +# Intended for easier filtering, or safe blacklisting. +# You can filter on logfacility and this prefix. +# +# Message format changes like this: +# -Nov 18 11:30:31 soda logtest: [21366]: info: total message dropped: 0 +# +Nov 18 11:30:31 soda common-prefix: logtest[21366]: info: total message dropped: 0 +# +# Default: none (disabled) +#syslogprefix linux-ha + + +# Do we register to apphbd +# Default: no +#useapphbd no + +# There are two processes running for logging daemon +# 1. parent process which reads messages from all client channels +# and writes them to the child process +# +# 2. the child process which reads messages from the parent process through IPC +# and writes them to syslog/disk + + +# set the send queue length from the parent process to the child process +# +#sendqlen 256 + +# set the recv queue length in child process +# +#recvqlen 256 + + diff --git a/logd/logd.in b/logd/logd.in new file mode 100755 index 0000000..41d60c5 --- /dev/null +++ b/logd/logd.in @@ -0,0 +1,101 @@ +#!/bin/sh +# +# +# logd Start logd (non-blocking log service) +# +# Author: Dejan Muhamedagic <dmuhamedagic@suse.de> +# (After the heartbeat init script) +# License: GNU General Public License (GPL) +# +# This script works correctly under SuSE, Debian, +# Conectiva, Red Hat and a few others. Please let me know if it +# doesn't work under your distribution, and we'll fix it. +# We don't hate anyone, and like for everyone to use +# our software, no matter what OS or distribution you're using. +# +# chkconfig: 2345 19 21 +# description: Startup script logd service. +# processname: ha_logd +# pidfile: @localstatedir@/run/logd.pid +# config: @sysconfdir@/logd.cf +# +### BEGIN INIT INFO +# Description: ha_logd is a non-blocking logging daemon. +# It can log messages either to a file or through syslog +# daemon. +# Short-Description: ha_logd logging daemon +# Provides: ha_logd +# Required-Start: $network $syslog $remote_fs +# Required-Stop: $network $syslog $remote_fs +# X-Start-Before: heartbeat openais corosync +# Default-Start: 3 5 +# Default-Stop: 0 1 6 +### END INIT INFO + +LOGD_CFG=@sysconfdir@/logd.cf +LOGD_OPT="" +[ -f "$LOGD_CFG" ] && LOGD_OPT="-c $LOGD_CFG" +LOGD_BIN="@libdir@/@HB_PKG@/ha_logd" + +if [ ! -f $LOGD_BIN ]; then + echo -n "ha_logd not installed." + exit 5 +fi + +StartLogd() { + echo -n "Starting ha_logd: " + $LOGD_BIN -s >/dev/null 2>&1 + if [ $? -eq 0 ]; then + echo "logd is already running" + return 0 + fi + + $LOGD_BIN -d $LOGD_OPT >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "starting logd failed" + exit 1 + fi + echo "ok" + exit 0 +} + +StopLogd() { + echo -n "Stopping ha_logd: " + + $LOGD_BIN -s >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "logd is already stopped" + return 0 + fi + + $LOGD_BIN -k >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "stopping logd failed" + exit 1 + fi + echo "stopped" + exit 0 +} + +StatusLogd() { + $LOGD_BIN -s + exit $? +} + +case "$1" in + start) StartLogd ;; + status) StatusLogd ;; + stop) StopLogd ;; + restart) + sleeptime=1 + $0 stop && sleep $sleeptime && $0 start + echo + ;; + try-restart) + $0 status && $0 restart + ;; + *) + echo "Usage: $0 {start|stop|status|restart}" + exit 1 +esac + diff --git a/logd/logd.service.in b/logd/logd.service.in new file mode 100644 index 0000000..9783547 --- /dev/null +++ b/logd/logd.service.in @@ -0,0 +1,13 @@ +[Unit] +Description=ha_logd logging daemon +Before=pacemaker.service +PartOf=pacemaker.service + +[Service] +ExecStart=@libdir@/@HB_PKG@/ha_logd -c @sysconfdir@/logd.cf +ExecStartPre=/bin/rm -f @HA_VARRUNDIR@/logd.pid +ExecStopPost=/bin/rm -f @HA_VARRUNDIR@/logd.pid +PIDFile=@HA_VARRUNDIR@/logd.pid + +[Install] +WantedBy=multi-user.target diff --git a/logd/logtest.c b/logd/logtest.c new file mode 100644 index 0000000..11e4014 --- /dev/null +++ b/logd/logtest.c @@ -0,0 +1,129 @@ +/* + * ha_logger.c utility to log a message to the logging daemon + * + * Copyright (C) 2004 Guochun Shi <gshi@ncsa.uiuc.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <lha_internal.h> +#include <glib.h> +#include <clplumbing/cl_log.h> +#include <clplumbing/ipc.h> +#include <clplumbing/GSource.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <clplumbing/loggingdaemon.h> +#include <syslog.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <errno.h> +#include <netinet/in.h> + +#define EXIT_OK 0 +#define EXIT_FAIL 1 +#define MAXMSGSIZE (1048*4) + +int LogToDaemon(int priority, const char * buf, int bstrlen, gboolean use_pri_str); +extern IPC_Channel * get_log_chan(void); + +static GMainLoop* loop; + +static gboolean +send_log_msg(gpointer data) +{ + static int count = 0; + char msgstring[MAXMSGSIZE]; + int priority; + static int dropmsg = 0; + long maxcount = (long) data; + IPC_Channel* chan = get_log_chan(); + + + if (chan == NULL){ + cl_log(LOG_ERR, "logging channel is NULL"); + g_main_quit(loop); + return FALSE; + + } + if (count >= maxcount){ + cl_log(LOG_INFO, "total message dropped: %d", dropmsg); + g_main_quit(loop); + return FALSE; + } + + if (chan->send_queue->current_qlen + == chan->send_queue->max_qlen){ + return TRUE; + } + + + priority = LOG_INFO; + msgstring[0]=0; + + snprintf(msgstring, sizeof(msgstring),"Message %d", count++); + fprintf(stderr, "sending %s\n", msgstring); + if (LogToDaemon(priority, msgstring,MAXMSGSIZE, FALSE) != HA_OK){ + printf("sending out messge %d failed\n", count); + dropmsg++; + } + + + + return TRUE; +} + + +static void +usage(char* prog) +{ + printf("Usage:%s <num_of_messages>\n", prog); + return; +} + +int +main(int argc, char** argv) +{ + + long maxcount; + + if (argc < 2){ + usage(argv[0]); + return 1; + } + + maxcount = atoi(argv[1]); + + cl_log_set_entity("logtest"); + cl_log_set_facility(HA_LOG_FACILITY); + cl_log_set_uselogd(TRUE); + + if(!cl_log_test_logd()){ + return EXIT_FAIL; + } + + cl_log_set_logd_channel_source(NULL, NULL); + + g_idle_add(send_log_msg, (gpointer)maxcount); + + + loop = g_main_loop_new(NULL, FALSE); + g_main_run(loop); + return(1); + +} + diff --git a/lrm/Makefile.am b/lrm/Makefile.am new file mode 100644 index 0000000..78a92c4 --- /dev/null +++ b/lrm/Makefile.am @@ -0,0 +1,20 @@ +# Author: Sun Jiang Dong <sunjd@cn.ibm.com> +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +SUBDIRS = lrmd admin test diff --git a/lrm/admin/Makefile.am b/lrm/admin/Makefile.am new file mode 100644 index 0000000..a92cd72 --- /dev/null +++ b/lrm/admin/Makefile.am @@ -0,0 +1,40 @@ +# +# Author: Sun Jiang Dong <sunjd@cn.ibm.com> +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + +halibdir = $(libdir)/@HB_PKG@ +COMMONLIBS = $(top_builddir)/lib/clplumbing/libplumb.la $(GLIBLIB) +LRM_DIR = lrm +sbin_PROGRAMS = lrmadmin +sbin_SCRIPTS = cibsecret +lrmadmin_SOURCES = lrmadmin.c +lrmadmin_LDFLAGS = $(COMMONLIBS) +lrmadmin_LDADD = $(top_builddir)/lib/$(LRM_DIR)/liblrm.la +lrmadmin_DEPENDENCIES = $(top_builddir)/lib/$(LRM_DIR)/liblrm.la + +if BUILD_HELP +man8_MANS = $(sbin_PROGRAMS:%=%.8) +%.8: % + echo Creating $@ + chmod a+x $< + help2man --output $@ --no-info --section 8 --name "Part of the Linux-HA project" $(top_builddir)/lrm/admin/$< +endif diff --git a/lrm/admin/cibsecret.in b/lrm/admin/cibsecret.in new file mode 100755 index 0000000..5255cdd --- /dev/null +++ b/lrm/admin/cibsecret.in @@ -0,0 +1,350 @@ +#!/bin/sh + +# Copyright (C) 2011 Dejan Muhamedagic <dmuhamedagic@suse.de> +# +# 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.1 of the License, or (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# WARNING: +# +# The CIB secrets interface and implementation is still being +# discussed, it may change + +# +# cibsecret: manage the secrets directory /var/lib/heartbeat/lrm/secrets +# +# secrets are ascii files, holding just one value per file: +# /var/lib/heartbeat/lrm/secrets/<rsc>/<param> +# +# NB: this program depends on utillib.sh +# + +. @OCF_ROOT_DIR@/lib/heartbeat/ocf-shellfuncs + +HA_NOARCHBIN=@datadir@/@PACKAGE_NAME@ + +. $HA_NOARCHBIN/utillib.sh + +LRM_CIBSECRETS=$HA_VARLIB/lrm/secrets + +PROG=`basename $0` +SSH_OPTS="-o StrictHostKeyChecking=no" + +usage() { + cat<<EOF +usage: $PROG [-C] <command> <parameters> + +-C: don't read/write the CIB + +command: set | delete | stash | unstash | get | check | sync + + set <rsc> <param> <value> + get <rsc> <param> + check <rsc> <param> + stash <rsc> <param> (if not -C) + unstash <rsc> <param> (if not -C) + delete <rsc> <param> + sync + +stash/unstash: move the parameter from/to the CIB (if you already + have the parameter set in the CIB). + +set/delete: add/remove a parameter from the local file. + +get: display the parameter from the local file. + +check: verify MD5 hash of the parameter from the local file and the CIB. + +sync: copy $LRM_CIBSECRETS to other nodes. + +Examples: + + $PROG set ipmi_node1 passwd SecreT_PASS + $PROG stash ipmi_node1 passwd + $PROG get ipmi_node1 passwd + $PROG check ipmi_node1 passwd + $PROG sync +EOF + exit $1 +} +fatal() { + echo "ERROR: $*" + exit 1 +} +warn() { + echo "WARNING: $*" +} +info() { + echo "INFO: $*" +} + +check_env() { + which md5sum >/dev/null 2>&1 || + fatal "please install md5sum to run $PROG" + if which pssh >/dev/null 2>&1; then + rsh=pssh_fun + rcp=pscp_fun + elif which pdsh >/dev/null 2>&1; then + rsh=pdsh_fun + rcp=pdcp_fun + elif which ssh >/dev/null 2>&1; then + rsh=ssh_fun + rcp=scp_fun + else + fatal "please install pssh, pdsh, or ssh to run $PROG" + fi + ps -ef | grep '[c]rmd' >/dev/null || + fatal "pacemaker not running? $PROG needs pacemaker" +} + +get_other_nodes() { + crm_node -l | awk '{print $2}' | grep -v `uname -n` +} +check_down_nodes() { + local n down_nodes + down_nodes=`(for n; do echo $n; done) | sort | uniq -u` + if [ -n "$down_nodes" ]; then + if [ `echo $down_nodes | wc -w` = 1 ]; then + warn "node $down_nodes is down" + warn "you'll need to update it using $PROG sync later" + else + warn "nodes `echo $down_nodes` are down" + warn "you'll need to update them using $PROG sync later" + fi + fi +} + +pssh_fun() { + pssh -qi -H "$nodes" -x "$SSH_OPTS" $* +} +pscp_fun() { + pscp -q -H "$nodes" -x "-pr" -x "$SSH_OPTS" $* +} +pdsh_fun() { + local pdsh_nodes=`echo $nodes | tr ' ' ','` + export PDSH_SSH_ARGS_APPEND="$SSH_OPTS" + pdsh -w $pdsh_nodes $* +} +pdcp_fun() { + local pdsh_nodes=`echo $nodes | tr ' ' ','` + export PDSH_SSH_ARGS_APPEND="$SSH_OPTS" + pdcp -pr -w $pdsh_nodes $* +} +ssh_fun() { + local h + for h in $nodes; do + ssh $SSH_OPTS $h $* || return + done +} +scp_fun() { + local h src="$1" dest=$2 + for h in $nodes; do + scp -pr -q $SSH_OPTS $src $h:$dest || return + done +} +# TODO: this procedure should be replaced with csync2 +# provided that csync2 has already been configured +sync_files() { + local crm_nodes=`get_other_nodes` + local nodes=`get_live_nodes $crm_nodes` + check_down_nodes $nodes $crm_nodes + [ "$nodes" = "" ] && { + info "no other nodes live" + return + } + info "syncing $LRM_CIBSECRETS to `echo $nodes` ..." + $rsh rm -rf $LRM_CIBSECRETS && + $rsh mkdir -p `dirname $LRM_CIBSECRETS` && + $rcp $LRM_CIBSECRETS `dirname $LRM_CIBSECRETS` +} +sync_one() { + local f=$1 f_all="$1 $1.sign" + local crm_nodes=`get_other_nodes` + local nodes=`get_live_nodes $crm_nodes` + check_down_nodes $nodes $crm_nodes + [ "$nodes" = "" ] && { + info "no other nodes live" + return + } + info "syncing $f to `echo $nodes` ..." + $rsh mkdir -p `dirname $f` && + if [ -f "$f" ]; then + $rcp "$f_all" `dirname $f` + else + $rsh rm -f $f_all + fi +} + +is_secret() { + # assume that the secret is in the CIB if we cannot talk to + # cib + [ "$NO_CRM" ] || + test "$1" = "$MAGIC" +} +check_cib_rsc() { + local rsc=$1 output + output=`$NO_CRM crm_resource -r $rsc -W >/dev/null 2>&1` || + fatal "resource $rsc doesn't exist: $output" +} +get_cib_param() { + local rsc=$1 param=$2 + check_cib_rsc $rsc + $NO_CRM crm_resource -r $rsc -g $param 2>/dev/null +} +set_cib_param() { + local rsc=$1 param=$2 value=$3 + check_cib_rsc $rsc + $NO_CRM crm_resource -r $rsc -p $param -v "$value" 2>/dev/null +} +remove_cib_param() { + local rsc=$1 param=$2 + check_cib_rsc $rsc + $NO_CRM crm_resource -r $rsc -d $param 2>/dev/null +} + +localfiles() { + local cmd=$1 + local rsc=$2 param=$3 value=$4 + local local_file=$LRM_CIBSECRETS/$rsc/$param + case $cmd in + "get") + cat $local_file 2>/dev/null + true + ;; + "getsum") + cat $local_file.sign 2>/dev/null + true + ;; + "set") + local md5sum + md5sum=`printf $value | md5sum` || + fatal "md5sum failed to produce hash for resource $rsc parameter $param" + md5sum=`echo $md5sum | awk '{print $1}'` + mkdir -p `dirname $local_file` && + echo $value > $local_file && + echo $md5sum > $local_file.sign && + sync_one $local_file + ;; + "remove") + rm -f $local_file + sync_one $local_file + ;; + *) + # not reached, this is local interface + ;; + esac +} +get_local_param() { + local rsc=$1 param=$2 + localfiles get $rsc $param +} +set_local_param() { + local rsc=$1 param=$2 value=$3 + localfiles set $rsc $param $value +} +remove_local_param() { + local rsc=$1 param=$2 + localfiles remove $rsc $param +} + +cibsecret_set() { + local value=$1 + + if [ -z "$NO_CRM" ]; then + [ "$current" -a "$current" != "$MAGIC" -a "$current" != "$value" ] && + fatal "CIB value <$current> different for $rsc parameter $param; please delete it first" + fi + set_local_param $rsc $param $value && + set_cib_param $rsc $param "$MAGIC" +} + +cibsecret_check() { + local md5sum local_md5sum + is_secret "$current" || + fatal "resource $rsc parameter $param not set as secret, nothing to check" + local_md5sum=`localfiles getsum $rsc $param` + [ "$local_md5sum" ] || + fatal "no MD5 hash for resource $rsc parameter $param" + md5sum=`printf "$current_local" | md5sum | awk '{print $1}'` + [ "$md5sum" = "$local_md5sum" ] || + fatal "MD5 hash mismatch for resource $rsc parameter $param" +} + +cibsecret_get() { + cibsecret_check + echo "$current_local" +} + +cibsecret_delete() { + remove_local_param $rsc $param && + remove_cib_param $rsc $param +} + +cibsecret_stash() { + [ "$NO_CRM" ] && + fatal "no access to Pacemaker, stash not supported" + [ "$current" = "" ] && + fatal "nothing to stash for resource $rsc parameter $param" + is_secret "$current" && + fatal "resource $rsc parameter $param already set as secret, nothing to stash" + cibsecret_set "$current" +} + +cibsecret_unstash() { + [ "$NO_CRM" ] && + fatal "no access to Pacemaker, unstash not supported" + [ "$current_local" = "" ] && + fatal "nothing to unstash for resource $rsc parameter $param" + is_secret "$current" || + warn "resource $rsc parameter $param not set as secret, but we have local value so proceeding anyway" + remove_local_param $rsc $param && + set_cib_param $rsc $param $current_local +} + +cibsecret_sync() { + sync_files +} + +check_env + +MAGIC="lrm://" +umask 0077 + +if [ "$1" = "-C" ]; then + NO_CRM=':' + shift 1 +fi + +cmd=$1 +rsc=$2 +param=$3 +value=$4 + +case "$cmd" in + set) [ $# -ne 4 ] && usage 1;; + get) [ $# -ne 3 ] && usage 1;; + check) [ $# -ne 3 ] && usage 1;; + stash) [ $# -ne 3 ] && usage 1;; + unstash) [ $# -ne 3 ] && usage 1;; + delete) [ $# -ne 3 ] && usage 1;; + sync) [ $# -ne 1 ] && usage 1;; + *) usage 1; +esac + +# we'll need these two often +current=`get_cib_param $rsc $param` +current_local=`get_local_param $rsc $param` + +cibsecret_$cmd $value diff --git a/lrm/admin/lrmadmin.c b/lrm/admin/lrmadmin.c new file mode 100644 index 0000000..27f37bf --- /dev/null +++ b/lrm/admin/lrmadmin.c @@ -0,0 +1,1129 @@ +/* File: lrmadmin.c + * Description: A adminstration tool for Local Resource Manager + * + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * Todo: security verification + * + * 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.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <lha_internal.h> + +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> +#include <stdlib.h> +#include <time.h> +#ifndef __USE_GNU +#define __USE_GNU +/* For strnlen protype */ +#include <string.h> +#undef __USE_GNU +#else +#include <string.h> +#endif +#include <errno.h> +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#endif /* HAVE_GETOPT_H */ +#include <clplumbing/cl_log.h> +#include <lrm/lrm_api.h> +#include <lrm/lrm_msg.h> +#include <lrm/raexec.h> +#include <clplumbing/lsb_exitcodes.h> +#include <clplumbing/GSource.h> +#include <clplumbing/Gmain_timeout.h> + +static const char *optstring = "A:D:X:dE:F:dg:p:M:O:P:c:S:LI:CT:n:hv"; + +#ifdef HAVE_GETOPT_H +static struct option long_options[] = { + {"daemon", 0, NULL, 'd'}, + {"executera", 1, NULL, 'E'}, + {"flush", 1, NULL, 'F'}, + {"state", 1, NULL, 'S'}, + {"listall", 0, NULL, 'L'}, + {"information", 1, NULL, 'I'}, + {"add", 1, NULL, 'A'}, + {"delete", 1, NULL, 'D'}, + {"fail", 1, NULL, 'X'}, + {"raclass_supported", 1, NULL, 'C'}, + {"ratype_supported", 1, NULL, 'T'}, + {"all_type_metadata", 1, NULL, 'O'}, + {"metadata", 1, NULL, 'M'}, + {"provider", 1, NULL, 'P'}, + {"set_lrmd_param", 1, NULL, 'p'}, + {"get_lrmd_param", 1, NULL, 'g'}, + {"help", 0, NULL, 'h'}, + {"version", 0, NULL, 'v'}, + {NULL, 0, NULL, 0} +}; +#endif /* HAVE_GETOPT_H */ + +static GMainLoop *mainloop; +static const char *lrmadmin_name = "lrmadmin"; +static const char *fake_name; +/* 20 is the length limit for a argv[x] */ +static const int ARGVI_MAX_LEN = 48; + +typedef enum { + ERROR_OPTION = -1, + NULL_OP, + DAEMON_OP, + EXECUTE_RA, + FLUSH, + RSC_STATE, + LIST_ALLRSC, + INF_RSC, + SET_PARAM, + GET_PARAM, + ADD_RSC, + DEL_RSC, + FAIL_RSC, + RACLASS_SUPPORTED, + RATYPE_SUPPORTED, + RA_METADATA, + RA_PROVIDER, + ALL_RA_METADATA, + HELP +} lrmadmin_cmd_t; + +#define nullcheck(p) ((p) ? (p) : "<null>") +static const char * status_msg[6] = { + "pending", /* LRM_OP_PENDING */ + "succeed", /* LRM_OP_DONE */ + "cancelled", /* LRM_OP_CANCELLED */ + "timeout", /* LRM_OP_TIMEOUT */ + "not Supported", /* LRM_OP_NOTSUPPORTED */ + "failed due to an error" /* LRM_OP_ERROR */ +}; + +static const char * rc_msg[] = { + "unknown error", + "no ra", + "ok", + "unknown error", + "invalid parameter", + "unimplement feature", + "insufficient priority", + "not installed", + "not configured", + "not running", + "running master", + "failed master", + "invalid rc", + /* For status command only */ + "daemon dead1", + "daemon dead2", + "daemon stopped", + "status unknow" +}; + + +static gboolean QUIT_GETOPT = FALSE; +static lrmadmin_cmd_t lrmadmin_cmd = NULL_OP; +static gboolean ASYN_OPS = FALSE; +static int call_id = 0; +static int TIMEOUT = -1; /* the unit is ms */ + +static const char *simple_help_screen = +"lrmadmin -d,--daemon\n" +" -A,--add <rscid> <raclass> <ratype> <provider|NULL> [<rsc_params_list>]\n" +" -D,--delete <rscid>\n" +" -F,--flush <rscid>\n" +" -X,--fail <rscid> [<fail_rc> [<fail_reason>]]\n" +" -E,--execute <rscid> <operator> <timeout> <interval> <target_rc|EVERYTIME|CHANGED> [<operator_parameters_list>]\n" +" -S,--state <rscid> [-n <fake_name>]\n" +" -L,--listall\n" +" -I,--information <rsc_id>\n" +" -C,--raclass_supported\n" +" -T,--ratype_supported <raclass>\n" +" -O,--all metadata of this class <raclass>\n" +" -M,--metadata <raclass> <ratype> <provider|NULL>\n" +" -P,--provider <raclass> <ratype>\n" +" -p,--set_lrmd_param <name> <value>\n" +" -g,--get_lrmd_param <name>\n" +" -v,--version\n" +" -h,--help\n"; + +#define OPTION_OBSCURE_CHECK \ + if ( lrmadmin_cmd != NULL_OP ) { \ + cl_log(LOG_ERR,"Obscure options."); \ + return -1; \ + } + +/* the begin of the internal used function list */ +static int resource_operation(ll_lrm_t * lrmd, char *rsc_id, + int argc, int optind, char * argv[]); +static int add_resource(ll_lrm_t * lrmd, char *rsc_id, + int argc, int optind, char * argv[]); +static int fail_resource(ll_lrm_t * lrmd, char *rsc_id, int optc, char *opts[]); +static int get_param(ll_lrm_t * lrmd, int argc, int optind, char * argv[]); +static int set_param(ll_lrm_t * lrmd, int argc, int optind, char * argv[]); +static int transfer_cmd_params(int amount, int start, char * argv[], + const char * class, GHashTable ** params_ht); +static void g_print_stringitem_and_free(gpointer data, gpointer user_data); +static void g_print_rainfo_item_and_free(gpointer data, gpointer user_data); +static void g_print_ops(gpointer data, gpointer user_data); +static void g_get_rsc_description(gpointer data, gpointer user_data); +static void g_print_meta(gpointer key, gpointer value, gpointer user_data); + +static void print_rsc_inf(lrm_rsc_t * lrmrsc); +static char * params_hashtable_to_str(const char * class, GHashTable * ht); +static void free_stritem_of_hashtable(gpointer key, gpointer value, + gpointer user_data); +static void ocf_params_hash_to_str(gpointer key, gpointer value, + gpointer user_data); +static void normal_params_hash_to_str(gpointer key, gpointer value, + gpointer user_data); +static lrm_rsc_t * get_lrm_rsc(ll_lrm_t * lrmd, char * rscid); + +static int ra_metadata(ll_lrm_t * lrmd, int argc, int optind, char * argv[]); +static int ra_provider(ll_lrm_t * lrmd, int argc, int optind, char * argv[]); +static gboolean lrmd_output_dispatch(IPC_Channel* notused, gpointer user_data); +static gboolean lrm_op_timeout(gpointer data); + +/* the end of the internal used function list */ + +static void lrm_op_done_callback(lrm_op_t* op); + +static int ret_value; +int main(int argc, char **argv) +{ + int option_char; + char rscid_arg_tmp[RID_LEN]; + ll_lrm_t* lrmd; + lrm_rsc_t * lrm_rsc; + GList *raclass_list = NULL, + *ratype_list = NULL, + *rscid_list; + GHashTable *all_meta = NULL; + char raclass[20]; + const char * login_name = lrmadmin_name; + + /* Prevent getopt_long to print error message on stderr itself */ + /*opterr = 0; */ + + if (argc == 1) { + printf("%s",simple_help_screen); + return 0; + } + + cl_log_set_entity(lrmadmin_name); + cl_log_enable_stderr(TRUE); + cl_log_set_facility(LOG_USER); + + memset(rscid_arg_tmp, '\0', RID_LEN); + memset(raclass, '\0', 20); + do { +#ifdef HAVE_GETOPT_H + option_char = getopt_long (argc, argv, optstring, + long_options, NULL); +#else + option_char = getopt (argc, argv, optstring); +#endif + + if (option_char == -1) { + break; + } + + switch (option_char) { + case 'd': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = DAEMON_OP; + QUIT_GETOPT = TRUE; + break; + + case 'A': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = ADD_RSC; + strncpy(rscid_arg_tmp, optarg, RID_LEN-1); + break; + + case 'D': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = DEL_RSC; + strncpy(rscid_arg_tmp, optarg, RID_LEN-1); + break; + + case 'X': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = FAIL_RSC; + strncpy(rscid_arg_tmp, optarg, RID_LEN-1); + break; + + case 'C': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = RACLASS_SUPPORTED; + break; + + case 'T': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = RATYPE_SUPPORTED; + if (optarg) { + strncpy(raclass, optarg, 19); + } + break; + + case 'O': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = ALL_RA_METADATA; + if (optarg) { + strncpy(raclass, optarg, 19); + } + break; + + case 'F': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = FLUSH; + strncpy(rscid_arg_tmp, optarg, RID_LEN-1); + break; + + case 'E': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = EXECUTE_RA; + strncpy(rscid_arg_tmp, optarg, RID_LEN-1); + break; + + case 'M': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = RA_METADATA; + break; + + case 'P': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = RA_PROVIDER; + break; + + case 'S': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = RSC_STATE; + strncpy(rscid_arg_tmp, optarg, RID_LEN-1); + break; + + case 'L': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = LIST_ALLRSC; + break; + + case 'I': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = INF_RSC; + strncpy(rscid_arg_tmp, optarg, RID_LEN-1); + break; + + case 'p': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = SET_PARAM; + break; + + case 'g': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = GET_PARAM; + break; + + case 'n': + if (optarg) { + fake_name = optarg; + } + break; + + case 'v': + printf("%s\n",GLUE_VERSION); + return 0; + case 'h': + OPTION_OBSCURE_CHECK + printf("%s",simple_help_screen); + return 0; + + case '?': + /* cl_log(LOG_ERR,"There is a unrecognized + option %s", optarg); + */ + printf("%s", simple_help_screen); + return -1; + + default: + cl_log(LOG_ERR,"getopt returned character" + " code %c.", option_char); + return -1; + } + } while (!QUIT_GETOPT); + + lrmd = ll_lrm_new("lrm"); + + if (NULL == lrmd) { + cl_log(LOG_ERR,"ll_lrm_new returned NULL."); + return -2; + } + + lrmd->lrm_ops->set_lrm_callback(lrmd, lrm_op_done_callback); + + if (fake_name != NULL) { + login_name = fake_name; + } + if (lrmd->lrm_ops->signon(lrmd, login_name) != 1) { /* != HA_OK */ + printf("lrmd is not running.\n"); + if (lrmadmin_cmd == DAEMON_OP) { + return LSB_STATUS_STOPPED; + } else { + cl_log(LOG_WARNING,"Can't connect to lrmd!"); + return -2; + } + } + + if (lrmadmin_cmd == DAEMON_OP) { + printf("lrmd is stopped.\n"); + lrmd->lrm_ops->signoff(lrmd); + return 0; + } + + switch (lrmadmin_cmd) { + case EXECUTE_RA: + call_id = resource_operation(lrmd, rscid_arg_tmp, argc, optind, argv); + if (call_id < 0) { + if ( call_id == -2 ) { + cl_log(LOG_ERR, "Failed to operate " + "resource %s due to parameter error." + , argv[optind]); + ret_value = -3; + } + if ( call_id == -1 ) { + cl_log(LOG_WARNING, "Failed! No such " + "resource %s.", argv[optind]); + ret_value = -2; + } + else { + cl_log(LOG_ERR, "Failed to operate " + "resource %s due to unknown error." + , argv[optind]); + ret_value = -3; + } + ASYN_OPS = FALSE; + } else { + /* Return value: HA_OK = 1 Or HA_FAIL = 0 */ + if ( call_id == 0 ) { + cl_log(LOG_ERR, "Resource operation " + "failed." ); + ret_value = -3; + ASYN_OPS = FALSE; + } else { + ASYN_OPS = TRUE; + } + } + break; + + case RA_METADATA: + ra_metadata(lrmd, argc, optind, argv); + ASYN_OPS = FALSE; + break; + case RA_PROVIDER: + ra_provider(lrmd, argc, optind, argv); + ASYN_OPS = FALSE; + break; + + case SET_PARAM: + set_param(lrmd, argc, optind, argv); + ASYN_OPS = FALSE; + break; + + case GET_PARAM: + get_param(lrmd, argc, optind, argv); + ASYN_OPS = FALSE; + break; + + case ADD_RSC: + if (add_resource(lrmd, rscid_arg_tmp, argc, optind, argv) == 0) { + printf("Succeeded in adding this resource.\n"); + } else { + printf("Failed to add this resource.\n"); + ret_value = -3; + } + ASYN_OPS = FALSE; + break; + + case DEL_RSC: + /* Return value: HA_OK = 1 Or HA_FAIL = 0 */ + if (lrmd->lrm_ops->delete_rsc(lrmd, rscid_arg_tmp)==1) { + printf("Succeeded in deleting this resource.\n"); + } else { + printf("Failed to delete this resource.\n"); + ret_value = -3; + } + ASYN_OPS = FALSE; + break; + + case FAIL_RSC: + /* Return value: HA_OK = 1 Or HA_FAIL = 0 */ + if (fail_resource(lrmd, rscid_arg_tmp, + argc-optind, argv+optind) == 1) + { + printf("Succeeded in failing the resource.\n"); + } else { + printf("Failed to fail the resource.\n"); + ret_value = -3; + } + ASYN_OPS = FALSE; + break; + + case FLUSH: + lrm_rsc = get_lrm_rsc(lrmd, rscid_arg_tmp); + if (!(lrm_rsc)) { + ret_value = -3; + } else { + /* Return value: HA_OK = 1 Or HA_FAIL = 0 */ + if (lrm_rsc->ops->flush_ops(lrm_rsc) == 1 ) { + printf("Succeeded in flushing.\n"); + } else { + printf("Failed to flush.\n"); + ret_value = -3; + } + lrm_free_rsc(lrm_rsc); + } + + ASYN_OPS = FALSE; + break; + + case RACLASS_SUPPORTED: + raclass_list = lrmd->lrm_ops-> + get_rsc_class_supported(lrmd); + printf("There are %d RA classes supported:\n", + g_list_length(raclass_list)); + if (raclass_list) { + g_list_foreach(raclass_list, g_print_stringitem_and_free, + NULL); + g_list_free(raclass_list); + ret_value = LSB_EXIT_OK; + } else { + printf("No RA classes found!\n"); + ret_value = -3; + } + + ASYN_OPS = FALSE; + break; + + case RATYPE_SUPPORTED: + ratype_list = lrmd->lrm_ops-> + get_rsc_type_supported(lrmd, raclass); + printf("There are %d RAs:\n", g_list_length(ratype_list)); + if (ratype_list) { + g_list_foreach(ratype_list, g_print_rainfo_item_and_free, + NULL); + g_list_free(ratype_list); + } + + ASYN_OPS = FALSE; + break; + case ALL_RA_METADATA: + all_meta = lrmd->lrm_ops->get_all_type_metadata(lrmd, raclass); + if (all_meta) { + g_hash_table_foreach(all_meta, g_print_meta, NULL); + g_hash_table_destroy(all_meta); + } + ASYN_OPS = FALSE; + break; + case LIST_ALLRSC: + rscid_list = lrmd->lrm_ops->get_all_rscs(lrmd); + if (rscid_list) { + g_list_foreach(rscid_list, g_get_rsc_description + , lrmd); + g_list_free(rscid_list); + } else + printf("Currently no resources are managed by " + "LRM.\n"); + + ASYN_OPS = FALSE; + break; + + case INF_RSC: + lrm_rsc = get_lrm_rsc(lrmd, rscid_arg_tmp); + if (!(lrm_rsc)) { + ret_value = -3; + } else { + print_rsc_inf(lrm_rsc); + lrm_free_rsc(lrm_rsc); + } + + ASYN_OPS = FALSE; + break; + + case RSC_STATE: + lrm_rsc = get_lrm_rsc(lrmd, rscid_arg_tmp); + if (!(lrm_rsc)) { + ret_value = -3; + } else { + state_flag_t cur_state = LRM_RSC_IDLE; + GList * ops_queue; + ops_queue = lrm_rsc->ops->get_cur_state(lrm_rsc, + &cur_state); + printf("resource state:%s\n", + cur_state==LRM_RSC_IDLE? + "LRM_RSC_IDLE":"LRM_RSC_BUSY"); + + printf("The resource %d operations' " + "information:\n" + , g_list_length(ops_queue)); + if (ops_queue) { + g_list_foreach(ops_queue, + g_print_ops, + NULL); + lrm_free_op_list(ops_queue); + } + lrm_free_rsc(lrm_rsc); + } + + ASYN_OPS = FALSE; + break; + + + default: + fprintf(stderr, "Option %c is not supported yet.\n", + option_char); + ret_value = -1; + ASYN_OPS = FALSE; + break; + } + + if (ASYN_OPS) { + G_main_add_IPC_Channel(G_PRIORITY_LOW, lrmd->lrm_ops->ipcchan(lrmd), + FALSE, lrmd_output_dispatch, lrmd, NULL); + if (TIMEOUT > 0) { + Gmain_timeout_add(TIMEOUT, lrm_op_timeout, &ret_value); + } + + mainloop = g_main_new(FALSE); + printf( "Waiting for lrmd to callback...\n"); + g_main_run(mainloop); + } + + lrmd->lrm_ops->signoff(lrmd); + return ret_value; +} + +static gboolean +lrm_op_timeout(gpointer data) +{ + int * idata = data; + + printf("ERROR: This operation has timed out - no result from lrmd.\n"); + + *idata = -5; + g_main_quit(mainloop); + return FALSE; +} + +static gboolean +lrmd_output_dispatch(IPC_Channel* notused, gpointer user_data) +{ + ll_lrm_t *lrm = (ll_lrm_t*)user_data; + lrm->lrm_ops->rcvmsg(lrm, FALSE); + + g_main_quit(mainloop); + return TRUE; +} + +static void +lrm_op_done_callback(lrm_op_t* op) +{ + if (!op) { + cl_log(LOG_ERR, "In callback function, op is NULL pointer."); + ret_value = -3; + return; + } + + printf("----------------operation--------------\n"); + printf("type:%s\n", op->op_type); + if ( (0 == STRNCMP_CONST(op->op_type, "status") + || 0 == STRNCMP_CONST(op->op_type, "monitor")) && (op->rc == 7) ) { + printf("operation status:%s\n", status_msg[LRM_OP_DONE-LRM_OP_PENDING]); + printf("op_status: %d\n", LRM_OP_DONE); + } else { + printf("operation status:%s\n", status_msg[(op->op_status + - LRM_OP_PENDING) % DIMOF(status_msg)]); + printf("op_status: %d\n", op->op_status); + } + printf("return code: %d\n", op->rc); + printf("output data: \n%s\n", (op->output ? op->output : "[null]")); + printf("---------------------------------------\n\n"); + ret_value = op->rc; +} + +static int +resource_operation(ll_lrm_t * lrmd, char *rsc_id, int argc, int optind, char * argv[]) +{ + GHashTable * params_ht = NULL; + lrm_op_t op = lrm_zero_op; + lrm_rsc_t * lrm_rsc; + int call_id; + + if ((argc - optind) < 3) { + cl_log(LOG_ERR,"Not enough parameters."); + return -2; + } + + lrm_rsc = lrmd->lrm_ops->get_rsc(lrmd, rsc_id); + if (!lrm_rsc) { + return -1; + } + + op.op_type = argv[optind]; + op.timeout = atoi(argv[optind+1]); + + /* When op.timeout!=0, plus additional 1s. Or lrmadmin may time out before + the normal operation result returned from lrmd. This may be redudant, + but harmless. */ + if (0 < op.timeout ) { + TIMEOUT = op.timeout + 1000; + } + op.interval = atoi(argv[optind+2]); + op.user_data = NULL; + op.user_data_len = 0; + if (0 == strcmp(argv[optind+3], "EVERYTIME")) { + op.target_rc = EVERYTIME; + } + else + if (0 == strcmp(argv[optind+3], "CHANGED")) { + op.target_rc = CHANGED; + } + else { + op.target_rc = atoi(argv[optind+3]); + } + + if ((argc - optind) > 3) { + if (0 > transfer_cmd_params(argc, optind+4, argv, + lrm_rsc->class, ¶ms_ht) ) { + return -2; + } + } + op.params = params_ht; + + call_id = lrm_rsc->ops->perform_op(lrm_rsc, &op); + lrm_free_rsc(lrm_rsc); + if (params_ht) { + g_hash_table_foreach(params_ht, free_stritem_of_hashtable, NULL); + g_hash_table_destroy(params_ht); + } + return call_id; +} +static int +ra_metadata(ll_lrm_t * lrmd, int argc, int optind, char * argv[]) +{ + const char * class = argv[optind-1]; + const char * type = argv[optind]; + const char * provider = argv[optind+1]; + char* metadata; + + if(argc < 5) { + cl_log(LOG_ERR,"Not enough parameters."); + return -2; + } + + if (0 == strncmp(provider,"NULL",strlen("NULL"))) { + provider=NULL; + } + + metadata = lrmd->lrm_ops->get_rsc_type_metadata(lrmd, class, type, provider); + if (NULL!=metadata) { + printf ("%s\n", metadata); + g_free (metadata); + } + return 0; +} + +static int +ra_provider(ll_lrm_t * lrmd, int argc, int optind, char * argv[]) +{ + const char * class = argv[optind-1]; + const char * type = argv[optind]; + GList* providers = NULL; + GList* provider = NULL; + + if(argc < 4) { + cl_log(LOG_ERR,"Not enough parameters."); + return -2; + } + + providers = lrmd->lrm_ops->get_rsc_provider_supported(lrmd,class,type); + + while (NULL != (provider = g_list_first(providers))) { + printf("%s\n",(char*)provider->data); + g_free(provider->data); + providers = g_list_remove(providers, provider->data); + } + g_list_free(providers); + return 0; +} + +static int +add_resource(ll_lrm_t * lrmd, char *rsc_id, int argc, int optind, char * argv[]) +{ + const char * class = argv[optind]; + const char * type = argv[optind+1]; + const char * provider = argv[optind+2]; + GHashTable * params_ht = NULL; + int tmp_ret; + + if ((argc - optind) < 3) { + cl_log(LOG_ERR,"Not enough parameters."); + return -2; + } + + if (0 == strncmp(provider, "NULL", strlen("NULL"))) { + provider=NULL; + } + + /* delete Hashtable */ + if ((argc - optind) > 3) { + if ( 0 > transfer_cmd_params(argc, optind+3, argv, class, + ¶ms_ht) ) { + return -1; + } + } + + tmp_ret = lrmd->lrm_ops->add_rsc(lrmd, rsc_id, class, + type, provider, params_ht); + + /*delete params_ht*/ + if (params_ht) { + g_hash_table_foreach(params_ht, free_stritem_of_hashtable, NULL); + g_hash_table_destroy(params_ht); + } + + return (tmp_ret ? 0 : -1); /* tmp_ret is HA_OK=1 or HA_FAIL=0 */ +} + +static int +fail_resource(ll_lrm_t * lrmd, char *rsc_id, int optc, char *opts[]) +{ + int fail_rc = 0; + const char * reason = NULL; + + if (optc > 2) { + cl_log(LOG_ERR,"Bad usage."); + return -2; + } + + if (optc >= 1) + fail_rc = atoi(opts[0]); + if (optc == 2) + reason = opts[1]; + + return lrmd->lrm_ops->fail_rsc(lrmd, rsc_id, fail_rc, reason); +} + +static int +get_param(ll_lrm_t * lrmd, int argc, int optind, char * argv[]) +{ + const char *name = argv[optind-1]; + char *value; + + if ((argc - optind) != 0) { + cl_log(LOG_ERR,"Bad usage."); + return -2; + } + value = lrmd->lrm_ops->get_lrmd_param(lrmd, name); + printf("%s: %s\n", name, value); + return 0; +} + +static int +set_param(ll_lrm_t * lrmd, int argc, int optind, char * argv[]) +{ + const char *name = argv[optind-1]; + const char *value = argv[optind]; + + if ((argc - optind) != 1) { + cl_log(LOG_ERR,"Bad usage."); + return -2; + } + return lrmd->lrm_ops->set_lrmd_param(lrmd, name, value); +} + +static int +transfer_cmd_params(int amount, int start, char * argv[], const char * class, +GHashTable ** params_ht) +{ + int i, len_tmp; + char * delimit, * key, * value; + char buffer[21]; + + if (amount < start) { + return -1; + } + + if ( strncmp("ocf", class, strlen("ocf"))==0 + || strncmp("stonith", class, strlen("stonith"))==0) { + *params_ht = g_hash_table_new(g_str_hash, g_str_equal); + + for (i=start; i<amount; i++) { + delimit = strchr(argv[i], '='); + if (!delimit) { + cl_log(LOG_ERR, "Parameter %s is invalid for " + "the OCF standard.", argv[i]); + goto error_return; /* Have to */ + } + + len_tmp = strnlen(delimit+1, MAX_PARAM_LEN) + 1; + value = g_new(gchar, len_tmp); + strncpy(value, delimit+1, len_tmp); + + len_tmp = strnlen(argv[i], MAX_PARAM_LEN) - strnlen(delimit, MAX_PARAM_LEN); + key = g_new(gchar, len_tmp+1); + key[len_tmp] = '\0'; + strncpy(key, argv[i], len_tmp); + + g_hash_table_insert(*params_ht, key, value); + } + } else if ( strncmp("lsb", class, strlen("lsb")) == 0 + || strncmp("heartbeat", class, strlen("heartbeat")) == 0 ) { + + /* Pay attention: for parameter ordring issue */ + *params_ht = g_hash_table_new(g_str_hash, g_str_equal); + + memset(buffer, '0', 21); + for (i=start; i<amount; i++) { + snprintf(buffer, 20, "%d", i-start+1); + g_hash_table_insert( *params_ht, g_strdup(buffer), + g_strdup(argv[i])); + /* printf("index: %d value: %s \n", i-start+1, argv[i]); */ + } + } else { + fprintf(stderr, "Not supported resource agent class.\n"); + return -1; + } + + return 0; + +error_return: + if (*params_ht) { + g_hash_table_foreach(*params_ht, free_stritem_of_hashtable, NULL); + g_hash_table_destroy(*params_ht); + *params_ht = NULL; + } + return -1; +} + +static char * +params_hashtable_to_str(const char * class, GHashTable * ht) +{ + int i,ht_size; + gchar * params_str = NULL; + GString * gstr_tmp; + gchar * tmp_str; + + if (!ht) { + return NULL; + } + + if ( strncmp("ocf", class, strlen("ocf")) == 0 + || strncmp("stonith", class, strlen("stonith")) == 0) { + gstr_tmp = g_string_new(""); + g_hash_table_foreach(ht, ocf_params_hash_to_str, &gstr_tmp); + params_str = g_new(gchar, gstr_tmp->len+1); + strncpy(params_str, gstr_tmp->str, gstr_tmp->len+1); + g_string_free(gstr_tmp, TRUE); + } else if ( strncmp("lsb", class, strlen("lsb")) == 0 + || strncmp("heartbeat", class, strlen("heartbeat")) == 0 ) { + ht_size = g_hash_table_size(ht); + if (ht_size == 0) { + return NULL; + } + tmp_str = g_new(gchar, ht_size*ARGVI_MAX_LEN); + memset(tmp_str, ' ', ht_size*ARGVI_MAX_LEN); + tmp_str[ht_size*ARGVI_MAX_LEN-1] = '\0'; + g_hash_table_foreach(ht, normal_params_hash_to_str, &tmp_str); + gstr_tmp = g_string_new(""); + for (i=0; i< ht_size; i++) { + gstr_tmp = g_string_append(gstr_tmp + , tmp_str + i*ARGVI_MAX_LEN ); + gstr_tmp = g_string_append(gstr_tmp, " "); + } + params_str = g_new(gchar, gstr_tmp->len+1); + strncpy(params_str, gstr_tmp->str, gstr_tmp->len+1); + g_string_free(gstr_tmp, TRUE); + } else { + fprintf(stderr, "Not supported resource agent class.\n"); + } + + return params_str; +} + +static void +g_print_stringitem_and_free(gpointer data, gpointer user_data) +{ + printf("%s\n", (char*)data); + g_free(data); +} + +static void +g_print_rainfo_item_and_free(gpointer data, gpointer user_data) +{ + printf("%s\n", (char *)data); + g_free(data); +} + + +static void +g_print_ops(gpointer data, gpointer user_data) +{ + lrm_op_t* op = (lrm_op_t*)data; + GString * param_gstr; + time_t run_at=0, rcchange_at=0; + + if (NULL == op) { + cl_log(LOG_ERR, "%s:%d: op==NULL" + , __FUNCTION__, __LINE__); + return; + } + + param_gstr = g_string_new(""); + g_hash_table_foreach(op->params, ocf_params_hash_to_str, ¶m_gstr); + + if( op->t_run ) + run_at=(time_t)op->t_run; + if( op->t_rcchange ) + rcchange_at=(time_t)op->t_rcchange; + printf(" operation '%s' [call_id=%d]:\n" + " start_delay=%d, interval=%d, timeout=%d, app_name=%s\n" + " rc=%d (%s), op_status=%d (%s)\n" + , nullcheck(op->op_type), op->call_id + , op->start_delay, op->interval, op->timeout + , nullcheck(op->app_name), op->rc + , rc_msg[(op->rc-EXECRA_EXEC_UNKNOWN_ERROR) % DIMOF(rc_msg)] + , op->op_status + , status_msg[(op->op_status-LRM_OP_PENDING) % DIMOF(status_msg)] + ); + if( op->t_run || op->t_rcchange ) + printf(" run at: %s" + " last rc change at: %s" + " queue time: %lums, exec time: %lums\n" + , op->t_run ? ctime(&run_at) : "N/A\n" + , op->t_rcchange ? ctime(&rcchange_at) : "N/A\n" + , op->queue_time, op->exec_time + ); + printf(" parameters: %s\n", param_gstr->str); + g_string_free(param_gstr, TRUE); +} + +static void +g_get_rsc_description(gpointer data, gpointer user_data) +{ + ll_lrm_t* lrmd = (ll_lrm_t *)user_data; + lrm_rsc_t * lrm_rsc; + char rsc_id_tmp[RID_LEN]; + + if (!(user_data)) { + return; + } + + memset(rsc_id_tmp, '\0', RID_LEN); + strncpy(rsc_id_tmp, data, RID_LEN-1); + + lrm_rsc = lrmd->lrm_ops->get_rsc(lrmd, rsc_id_tmp); + if (lrm_rsc) { + print_rsc_inf(lrm_rsc); + lrm_free_rsc(lrm_rsc); + } else + cl_log(LOG_ERR, "Invalid resource id: %s.", + rsc_id_tmp); + + g_free(data); +} +static void +g_print_meta(gpointer key, gpointer value, gpointer user_data) +{ + printf("%s\n", (const char*)key); + printf("%s\n", (const char*)value); +} +static void +print_rsc_inf(lrm_rsc_t * lrm_rsc) +{ + char rscid_str_tmp[RID_LEN]; + char * tmp = NULL; + + if (!lrm_rsc) { + return; + } + + rscid_str_tmp[RID_LEN-1] = '\0'; + strncpy(rscid_str_tmp, lrm_rsc->id, RID_LEN-1); + printf("\nResource ID:%s\n", rscid_str_tmp); + printf("Resource agent class:%s\n", lrm_rsc->class); + printf("Resource agent type:%s\n", lrm_rsc->type); + printf("Resource agent provider:%s\n" + , lrm_rsc->provider?lrm_rsc->provider:"default"); + + if (lrm_rsc->params) { + tmp = params_hashtable_to_str(lrm_rsc->class, + lrm_rsc->params); + printf("Resource agent parameters:%s\n" + , (tmp == NULL) ? "No parameter" : tmp); + if (tmp != NULL) { + g_free(tmp); + } + } +} + +static void +free_stritem_of_hashtable(gpointer key, gpointer value, gpointer user_data) +{ + /*printf("key=%s value=%s\n", (char *)key, (char *)value);*/ + g_free(key); + g_free(value); +} + +static void +ocf_params_hash_to_str(gpointer key, gpointer value, gpointer user_data) +{ + GString * gstr_tmp = *(GString **)user_data; + gstr_tmp = g_string_append(gstr_tmp, (char*)key); + gstr_tmp = g_string_append(gstr_tmp, "="); + gstr_tmp = g_string_append(gstr_tmp, (char *)value); + gstr_tmp = g_string_append(gstr_tmp, " "); +} + +static void +normal_params_hash_to_str(gpointer key, gpointer value, gpointer user_data) +{ + gint key_int; + + gchar * str_tmp = *(gchar **) user_data; + if (str_tmp == NULL ) { + return; + } + + key_int = atoi((char *)key) - 1; + if( key_int < 0 ) { + return; + } + strncpy(str_tmp + key_int * ARGVI_MAX_LEN, (char*)value, + ARGVI_MAX_LEN - 1); +} + +static lrm_rsc_t * +get_lrm_rsc(ll_lrm_t * lrmd, char * rscid) +{ + char uuid_str_tmp[RID_LEN]; + lrm_rsc_t * lrm_rsc; + lrm_rsc = lrmd->lrm_ops->get_rsc(lrmd, rscid); + if (!(lrm_rsc)) { + uuid_str_tmp[RID_LEN-1] = '\0'; + strncpy(uuid_str_tmp, rscid, RID_LEN-1); + cl_log(LOG_ERR,"Resource %s does not exist.", uuid_str_tmp); + } + return lrm_rsc; +} + diff --git a/lrm/admin/lrmadmin.txt b/lrm/admin/lrmadmin.txt new file mode 100644 index 0000000..739aa70 --- /dev/null +++ b/lrm/admin/lrmadmin.txt @@ -0,0 +1,60 @@ +# LRM Admin Command-line Interface +# It's a draft +NAME + lrmadmin - Local Resource Manager Commander-line Daministrator Tools + +SYNOPSIS +lrmadmin {-d|--daemon} + {-A|--add} <rscid> <rsc_class> <rsc_type> [<rsc_params_list>] + {-D|--delete} <rscid> + {-F|--flush} <rscid> + {-E|--execute} <rscid> <operator> <timeout> [<operator_parameters_list>] + {-M|--monitor} -s <rscid> <operator> <timeout> <interval> + [<operator_parameters_list>] + {-M|--monitor} {-g|-c} <rscid> + {-S|--status} <rscid> + {-L|--listall} + {-I|--information} <rsc_id> + {-R|--rasupported} + {-h|--help} + +Detailed Explanation for Options + +Lrmd daemon options + {-d|--daemon} +# -s The status of lrmd: running or not running +# -r Reset lrmd (?) + +Resource options + {-A|--add} <rscid> <rsc_class> <rsc_type> [<rsc_params_list>] + Add a resource. + + {-D|--delete} <rscid> + Delete a resource + + {-E|--execute} <rscid> <operator> <timeout> [<operator_parameters_list>] + Let resource agent to performance the operation + + {-F|--flush} <rscid> + Clear all pending operation on the resource agnency + + {-M|--monitor} {-s|-g} <rscid> <interval> + -s rscname Set a monitors on the resource agent + -g Get the information about the monitors on the + resource agent + + {-S|--status} <rscid> + Get the status of current resource agent + + {-L|--listall} + List all available resource agent + + {-I|--information} <rsc_id> + List the information about a resource + + {-R|--rasupported} + List the support types of resource agent such as OCF and etc. + +Other options + {-h|--help} + Display the help screen diff --git a/lrm/lrmd/Makefile.am b/lrm/lrmd/Makefile.am new file mode 100644 index 0000000..3680928 --- /dev/null +++ b/lrm/lrmd/Makefile.am @@ -0,0 +1,42 @@ +# +# Author: Sun Jiang Dong <sunjd@cn.ibm.com> +# Copyright (c) 2002 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir) -I$(top_srcdir) + +halibdir = $(libdir)/@HB_PKG@ + +COMMONLIBS = $(top_builddir)/lib/clplumbing/libplumb.la \ + $(GLIBLIB) +# $(top_builddir)/lib/apphb/libapphb.la + +halib_PROGRAMS = lrmd + +lrmd_SOURCES = lrmd.c audit.c cib_secrets.c lrmd_fdecl.h lrmd.h + +lrmd_LDFLAGS = $(top_builddir)/lib/lrm/liblrm.la \ + $(COMMONLIBS) @LIBLTDL@ \ + $(top_builddir)/lib/pils/libpils.la + +noinst_HEADERS = lrmd_fdecl.h lrmd.h + +# make lrmd's owner as hacluster:haclient? diff --git a/lrm/lrmd/audit.c b/lrm/lrmd/audit.c new file mode 100644 index 0000000..ec92dad --- /dev/null +++ b/lrm/lrmd/audit.c @@ -0,0 +1,191 @@ +/* + * Audit lrmd global data structures + * + * Author: Dejan Muhamedagic <dejan@suse.de> + * Copyright (c) 2007 Novell GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <lha_internal.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <dirent.h> +#include <pwd.h> +#include <time.h> + +#include <glib.h> +#include <pils/plugin.h> +#include <pils/generic.h> +#include <clplumbing/GSource.h> +#include <clplumbing/lsb_exitcodes.h> +#include <clplumbing/cl_signal.h> +#include <clplumbing/proctrack.h> +#include <clplumbing/coredumps.h> +#include <clplumbing/uids.h> +#include <clplumbing/Gmain_timeout.h> +#include <clplumbing/cl_pidfile.h> +#include <ha_msg.h> +#ifdef ENABLE_APPHB +# include <apphb.h> +#endif + +#include <lrm/lrm_api.h> +#include <lrm/lrm_msg.h> +#include <lrm/raexec.h> +#include <lrmd.h> + +#ifdef DOLRMAUDITS + +extern GHashTable* clients; +extern GHashTable* resources; + +#define ptr_bad(level,p,item,text) \ + lrmd_log(level,"LRMAUDIT: 0x%lx unallocated pointer for: %s(%s)", \ + (unsigned long)p,item,text); +#define ptr_null(level,item,text) \ + lrmd_log(level,"LRMAUDIT: pointer null for: %s(%s)", \ + item,text); + +/* NB: this macro contains return */ +#define ret_on_null(p,item,text) do { \ + if( !p ) { \ + ptr_bad(LOG_INFO,p,item,text); \ + return; \ + } \ +} while(0) +#define log_on_null(p,item,text) do { \ + if( !p ) { \ + ptr_null(LOG_INFO,item,text); \ + } \ +} while(0) + +void +lrmd_audit(const char *function, int line) +{ + lrmd_log(LOG_DEBUG, "LRMAUDIT: in %s:%d",function,line); +#ifdef LRMAUDIT_CLIENTS + audit_clients(); +#endif +#ifdef LRMAUDIT_RESOURCES + audit_resources(); +#endif +} + +void +audit_clients() +{ + g_hash_table_foreach(clients, on_client, NULL); +} + +void +audit_resources() +{ + g_hash_table_foreach(resources, on_resource, NULL); +} + +void +audit_ops(GList* rsc_ops, lrmd_rsc_t* rsc, const char *desc) +{ + GList *oplist; + + for( oplist = g_list_first(rsc_ops); + oplist; oplist = g_list_next(oplist) ) + { + on_op(oplist->data, rsc, desc); + } +} + +void +on_client(gpointer key, gpointer value, gpointer user_data) +{ + lrmd_client_t * client = (lrmd_client_t*)value; + + ret_on_null(client,"","client"); + log_on_null(client->app_name,"","app_name"); + log_on_null(client->ch_cmd,client->app_name,"ch_cmd"); + log_on_null(client->ch_cbk,client->app_name,"ch_cbk"); + log_on_null(client->g_src,client->app_name,"g_src"); + log_on_null(client->g_src_cbk,client->app_name,"g_src_cbk"); +} + +void +on_resource(gpointer key, gpointer value, gpointer user_data) +{ + lrmd_rsc_t* rsc = (lrmd_rsc_t*)value; + + ret_on_null(rsc,"","rsc"); + ret_on_null(rsc->id,"","id"); + log_on_null(rsc->type,rsc->id,"type"); + log_on_null(rsc->class,rsc->id,"class"); + log_on_null(rsc->provider,rsc->id,"provider"); + /*log_on_null(rsc->params,rsc->id,"params");*/ + log_on_null(rsc->last_op_table,rsc->id,"last_op_table"); + log_on_null(rsc->last_op_done,rsc->id,"last_op_done"); + audit_ops(rsc->op_list,rsc,"op_list"); + audit_ops(rsc->repeat_op_list,rsc,"repeat_op_list"); +} + +void +on_op(lrmd_op_t *op, lrmd_rsc_t* rsc, const char *desc) +{ + ret_on_null(op,rsc->id,desc); + log_on_null(op->rsc_id,rsc->id,"rsc_id"); + if( strcmp(op->rsc_id,rsc->id) ) { + lrmd_log(LOG_ERR,"LRMAUDIT: rsc %s, op %s " + "op->rsc_id does not match rsc->id", + rsc->id,small_op_info(op)); + } + log_on_null(op->msg,small_op_info(op),"msg"); + if( op->rapop ) { + if( op->rapop->lrmd_op != op ) { + lrmd_log(LOG_ERR, + "LRMAUDIT: rsc %s, op %s: rapop->lrmd_op does not match op", + rsc->id,small_op_info(op)); + } + if( strcmp(op->rapop->rsc_id,op->rsc_id) ) { + lrmd_log(LOG_ERR, + "LRMAUDIT: rsc %s, op %s rapop->rsc_id does not match op->rsc_id", + rsc->id,small_op_info(op)); + } + on_ra_pipe_op(op->rapop,op,"rapop"); + } +} + +void +on_ra_pipe_op(ra_pipe_op_t *rapop, lrmd_op_t *op, const char *desc) +{ + ret_on_null(rapop,small_op_info(op),desc); + log_on_null(rapop->ra_stdout_gsource,small_op_info(op),"ra_stdout_gsource"); + log_on_null(rapop->ra_stderr_gsource,small_op_info(op),"ra_stderr_gsource"); + log_on_null(rapop->rsc_id,small_op_info(op),"rsc_id"); + log_on_null(rapop->op_type,small_op_info(op),"op_type"); + log_on_null(rapop->rsc_class,small_op_info(op),"rsc_class"); + if( strcmp(op->rsc_id,rapop->rsc_id) ) { + lrmd_log(LOG_ERR,"LRMAUDIT: %s: rapop->rsc_id " + "does not match op_rsc->id", + small_op_info(op)); + } +} + +#endif /*DOLRMAUDITS*/ diff --git a/lrm/lrmd/cib_secrets.c b/lrm/lrmd/cib_secrets.c new file mode 100644 index 0000000..612ffdb --- /dev/null +++ b/lrm/lrmd/cib_secrets.c @@ -0,0 +1,205 @@ +/* + * cib_secrets.c + * + * Author: Dejan Muhamedagic <dejan@suse.de> + * Copyright (c) 2011 SUSE, Attachmate + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <lha_internal.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <time.h> + +#include <glib.h> +#include <pils/plugin.h> +#include <pils/generic.h> +#include <clplumbing/GSource.h> +#include <clplumbing/lsb_exitcodes.h> +#include <clplumbing/cl_signal.h> +#include <clplumbing/proctrack.h> +#include <clplumbing/coredumps.h> +#include <clplumbing/uids.h> +#include <clplumbing/Gmain_timeout.h> +#include <clplumbing/cl_pidfile.h> +#include <clplumbing/realtime.h> +#include <clplumbing/md5.h> +#include <ha_msg.h> + +#include <lrm/lrm_api.h> +#include <lrm/lrm_msg.h> + +#include <lrmd.h> + +int replace_secret_params(char *rsc_id, GHashTable* params); +static int is_magic_value(char *p); +static int check_md5_hash(char *hash, char *value); +static void add_secret_params(gpointer key, gpointer value, gpointer user_data); +static char *read_local_file(char *local_file); + +#define MAGIC "lrm://" + +static int +is_magic_value(char *p) +{ + return !strcmp(p, MAGIC); +} + +#define MD5LEN 16 +static int +check_md5_hash(char *hash, char *value) +{ + int i; + char hash2[2*MD5LEN+1]; + unsigned char binary[MD5LEN+1]; + + MD5((unsigned char *)value, strlen(value), binary); + for (i = 0; i < MD5LEN; i++) + sprintf(hash2+2*i, "%02x", binary[i]); + hash2[2*i] = '\0'; + lrmd_debug2(LOG_DEBUG + , "%s:%d: hash: %s, calculated hash: %s" + , __FUNCTION__, __LINE__, hash, hash2); + return !strcmp(hash, hash2); +} + +static char * +read_local_file(char *local_file) +{ + FILE *fp = fopen(local_file, "r"); + char buf[MAX_VALUE_LEN+1]; + char *p; + + if (!fp) { + if (errno != ENOENT) { + cl_perror("%s:%d: cannot open %s" + , __FUNCTION__, __LINE__, local_file); + } + return NULL; + } + if (!fgets(buf, MAX_VALUE_LEN, fp)) { + cl_perror("%s:%d: cannot read %s" + , __FUNCTION__, __LINE__, local_file); + return NULL; + } + /* strip white space */ + for (p = buf+strlen(buf)-1; p >= buf && isspace(*p); p--) + ; + *(p+1) = '\0'; + return g_strdup(buf); +} + +/* + * returns 0 on success or no replacements necessary + * returns -1 if replacement failed for whatever reasone + */ + +int +replace_secret_params(char *rsc_id, GHashTable* params) +{ + char local_file[FILENAME_MAX+1], *start_pname; + char hash_file[FILENAME_MAX+1], *hash; + GList *secret_params = NULL, *l; + char *key, *pvalue, *secret_value; + int rc = 0; + + /* secret_params could be cached with the resource; + * there are also parameters sent with operations + * which cannot be cached + */ + g_hash_table_foreach(params, add_secret_params, &secret_params); + if (!secret_params) /* none found? */ + return 0; + + lrmd_debug(LOG_DEBUG + , "%s:%d: replace secret parameters for resource %s" + , __FUNCTION__, __LINE__, rsc_id); + if (snprintf(local_file, FILENAME_MAX, + LRM_CIBSECRETS "/%s/", rsc_id) > FILENAME_MAX) { + lrmd_log(LOG_ERR + , "%s:%d: filename size exceeded for resource %s" + , __FUNCTION__, __LINE__, rsc_id); + return -1; + } + start_pname = local_file + strlen(local_file); + + for (l = g_list_first(secret_params); l; l = g_list_next(l)) { + key = (char *)(l->data); + pvalue = g_hash_table_lookup(params, key); + if (!pvalue) { /* this cannot really happen */ + lrmd_log(LOG_ERR + , "%s:%d: odd, no parameter %s for rsc %s found now" + , __FUNCTION__, __LINE__, key, rsc_id); + continue; + } + if ((strlen(key) + strlen(local_file)) >= FILENAME_MAX-2) { + lrmd_log(LOG_ERR + , "%s:%d: parameter name %s too big" + , __FUNCTION__, __LINE__, key); + rc = -1; + continue; + } + strcpy(start_pname, key); + secret_value = read_local_file(local_file); + if (!secret_value) { + lrmd_log(LOG_ERR + , "%s:%d: secret for rsc %s parameter %s " + "not found in " LRM_CIBSECRETS + , __FUNCTION__, __LINE__, rsc_id, key); + rc = -1; + continue; + } + strcpy(hash_file, local_file); + if (strlen(hash_file) + 5 > FILENAME_MAX) { + lrmd_log(LOG_ERR + , "%s:%d: cannot build such a long name " + "for the sign file: %s.sign" + , __FUNCTION__, __LINE__, hash_file); + } else { + strncat(hash_file, ".sign", 5); + hash = read_local_file(hash_file); + if (!check_md5_hash(hash, secret_value)) { + lrmd_log(LOG_ERR + , "%s:%d: md5 sum for rsc %s parameter %s " + "does not match" + , __FUNCTION__, __LINE__, rsc_id, key); + g_free(secret_value); + g_free(hash); + rc = -1; + continue; + } + g_free(hash); + } + g_hash_table_replace(params, g_strdup(key), secret_value); + } + g_list_free(secret_params); + return rc; +} + +static void +add_secret_params(gpointer key, gpointer value, gpointer user_data) +{ + GList **lp = (GList **)user_data; + + if (is_magic_value((char *)value)) + *lp = g_list_append(*lp, (char *)key); +} diff --git a/lrm/lrmd/lrmd.c b/lrm/lrmd/lrmd.c new file mode 100644 index 0000000..385096b --- /dev/null +++ b/lrm/lrmd/lrmd.c @@ -0,0 +1,4053 @@ +/* + * Local Resource Manager Daemon + * + * Author: Huang Zhen <zhenhltc@cn.ibm.com> + * Partly contributed by Andrew Beekhof <andrew@beekhof.net> + * Copyright (c) 2004 International Business Machines + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <lha_internal.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <dirent.h> +#include <pwd.h> +#include <time.h> +#include <sched.h> + +#include <glib.h> +#include <pils/plugin.h> +#include <pils/generic.h> +#include <clplumbing/GSource.h> +#include <clplumbing/lsb_exitcodes.h> +#include <clplumbing/cl_signal.h> +#include <clplumbing/proctrack.h> +#include <clplumbing/coredumps.h> +#include <clplumbing/uids.h> +#include <clplumbing/Gmain_timeout.h> +#include <clplumbing/cl_pidfile.h> +#include <clplumbing/realtime.h> +#include <ha_msg.h> +#ifdef ENABLE_APPHB +# include <apphb.h> +#endif +/* #include <hb_api.h> */ + +#include <lrm/lrm_api.h> +#include <lrm/lrm_msg.h> +#include <lrm/raexec.h> + +#include <lrmd.h> +#include <lrmd_fdecl.h> + +static gboolean in_alloc_dump = FALSE; + +ProcTrack_ops ManagedChildTrackOps = { + on_ra_proc_finished, + on_ra_proc_registered, + on_ra_proc_query_name +}; + +/* msg dispatch table */ +typedef int (*msg_handler)(lrmd_client_t* client, struct ha_msg* msg); +struct msg_map +{ + const char *msg_type; + int reply_time; + msg_handler handler; + int min_priv; /* minimum privileges required */ +}; + +/* + * two ways to handle replies: + * REPLY_NOW: pack whatever the handler returned and send it + * NO_MSG: the handler will send the reply itself + */ +#define REPLY_NOW 0 +#define NO_MSG 1 +#define send_msg_now(p) \ + (p->reply_time==REPLY_NOW) + +struct msg_map msg_maps[] = { + {REGISTER, REPLY_NOW, on_msg_register, 0}, + {GETRSCCLASSES, NO_MSG, on_msg_get_rsc_classes, 0}, + {GETRSCTYPES, NO_MSG, on_msg_get_rsc_types, 0}, + {GETPROVIDERS, NO_MSG, on_msg_get_rsc_providers, 0}, + {ADDRSC, REPLY_NOW, on_msg_add_rsc, PRIV_ADMIN}, + {GETRSC, NO_MSG, on_msg_get_rsc, PRIV_ADMIN}, + {GETLASTOP, NO_MSG, on_msg_get_last_op, PRIV_ADMIN}, + {GETALLRCSES, NO_MSG, on_msg_get_all, PRIV_ADMIN}, + {DELRSC, REPLY_NOW, on_msg_del_rsc, PRIV_ADMIN}, + {FAILRSC, REPLY_NOW, on_msg_fail_rsc, PRIV_ADMIN}, + {PERFORMOP, REPLY_NOW, on_msg_perform_op, PRIV_ADMIN}, + {FLUSHOPS, REPLY_NOW, on_msg_flush_all, PRIV_ADMIN}, + {CANCELOP, REPLY_NOW, on_msg_cancel_op, PRIV_ADMIN}, + {GETRSCSTATE, NO_MSG, on_msg_get_state, PRIV_ADMIN}, + {GETRSCMETA, NO_MSG, on_msg_get_metadata, 0}, + {SETLRMDPARAM, REPLY_NOW, on_msg_set_lrmd_param, PRIV_ADMIN}, + {GETLRMDPARAM, NO_MSG, on_msg_get_lrmd_param, 0}, +}; +#define MSG_NR sizeof(msg_maps)/sizeof(struct msg_map) + +GHashTable* clients = NULL; /* a GHashTable indexed by pid */ +GHashTable* resources = NULL; /* a GHashTable indexed by rsc_id */ + +static GMainLoop* mainloop = NULL; +static int call_id = 1; +static const char* lrm_system_name = "lrmd"; +static GHashTable * RAExecFuncs = NULL; +static GList* ra_class_list = NULL; +static gboolean shutdown_in_progress = FALSE; +static unsigned long apphb_interval = 2000; /* Millisecond */ +static gboolean reg_to_apphbd = FALSE; +static int max_child_count = 4; +static int retry_interval = 1000; /* Millisecond */ +static int child_count = 0; +static IPC_Auth * auth = NULL; + +static struct { + int opcount; + int clientcount; + int rsccount; +}lrm_objectstats; + +/* define indexes into logmsg_ctrl_defs */ +#define OP_STAYED_TOO_LONG 0 +static struct logspam logmsg_ctrl_defs[] = { + { "operation stayed too long in the queue", + 10, 60, 120, /* max 10 messages in 60s, then delay for 120s */ + "configuration advice: reduce operation contention " + "either by increasing lrmd max_children or by increasing intervals " + "of monitor operations" + }, +}; + +#define set_fd_opts(fd,opts) do { \ + int flag; \ + if ((flag = fcntl(fd, F_GETFL)) >= 0) { \ + if (fcntl(fd, F_SETFL, flag|opts) < 0) { \ + cl_perror("%s::%d: fcntl", __FUNCTION__ \ + , __LINE__); \ + } \ + } else { \ + cl_perror("%s::%d: fcntl", __FUNCTION__, __LINE__); \ + } \ + } while(0) + +static ra_pipe_op_t * +ra_pipe_op_new(int child_stdout, int child_stderr, lrmd_op_t * lrmd_op) +{ + ra_pipe_op_t * rapop; + lrmd_rsc_t* rsc = NULL; + + if ( NULL == lrmd_op ) { + lrmd_log(LOG_WARNING + , "%s:%d: lrmd_op==NULL, no need to malloc ra_pipe_op" + , __FUNCTION__, __LINE__); + return NULL; + } + rapop = calloc(sizeof(ra_pipe_op_t), 1); + if ( rapop == NULL) { + lrmd_log(LOG_ERR, "%s:%d out of memory" + , __FUNCTION__, __LINE__); + return NULL; + } + rapop->first_line_read = FALSE; + + /* + * No any obviouse proof of lrmd hang in pipe read yet. + * Bug 475 may be a duplicate of bug 499. + * Anyway, via test, it's proved that NOBLOCK read will + * obviously reduce the RA execution time (bug 553). + */ + /* Let the read operation be NONBLOCK */ + set_fd_opts(child_stdout,O_NONBLOCK); + set_fd_opts(child_stderr,O_NONBLOCK); + + /* there's so much code duplication here */ + rapop->ra_stdout_fd = child_stdout; + if (rapop->ra_stdout_fd <= STDERR_FILENO) { + lrmd_log(LOG_ERR, "%s: invalid stdout fd [%d]" + , __FUNCTION__, rapop->ra_stdout_fd); + } + rapop->ra_stdout_gsource = G_main_add_fd(G_PRIORITY_HIGH + , child_stdout, FALSE, handle_pipe_ra_stdout + , rapop, destroy_pipe_ra_stdout); + + rapop->ra_stderr_fd = child_stderr; + if (rapop->ra_stderr_fd <= STDERR_FILENO) { + lrmd_log(LOG_ERR, "%s: invalid stderr fd [%d]" + , __FUNCTION__, rapop->ra_stderr_fd); + } + rapop->ra_stderr_gsource = G_main_add_fd(G_PRIORITY_HIGH + , child_stderr, FALSE, handle_pipe_ra_stderr + , rapop, destroy_pipe_ra_stderr); + + rapop->lrmd_op = lrmd_op; + + rapop->op_type = strdup(ha_msg_value(lrmd_op->msg, F_LRM_OP)); + rapop->rsc_id = strdup(lrmd_op->rsc_id); + rsc = lookup_rsc(lrmd_op->rsc_id); + if (rsc == NULL) { + lrmd_debug(LOG_WARNING + , "%s::%d: the rsc (id=%s) does not exist" + , __FUNCTION__, __LINE__, lrmd_op->rsc_id); + rapop->rsc_class = NULL; + } else { + rapop->rsc_class = strdup(rsc->class); + } + + return rapop; +} + +static void +ra_pipe_op_destroy(ra_pipe_op_t * rapop) +{ + CHECK_ALLOCATED(rapop, "ra_pipe_op", ); + + if ( NULL != rapop->ra_stdout_gsource) { + G_main_del_fd(rapop->ra_stdout_gsource); + rapop->ra_stdout_gsource = NULL; + } + + if ( NULL != rapop->ra_stderr_gsource) { + G_main_del_fd(rapop->ra_stderr_gsource); + rapop->ra_stderr_gsource = NULL; + } + + if (rapop->ra_stdout_fd >= STDERR_FILENO) { + close(rapop->ra_stdout_fd); + rapop->ra_stdout_fd = -1; + }else if (rapop->ra_stdout_fd >= 0) { + lrmd_log(LOG_ERR, "%s: invalid stdout fd %d" + , __FUNCTION__, rapop->ra_stdout_fd); + } + if (rapop->ra_stderr_fd >= STDERR_FILENO) { + close(rapop->ra_stderr_fd); + rapop->ra_stderr_fd = -1; + }else if (rapop->ra_stderr_fd >= 0) { + lrmd_log(LOG_ERR, "%s: invalid stderr fd %d" + , __FUNCTION__, rapop->ra_stderr_fd); + } + rapop->first_line_read = FALSE; + + free(rapop->rsc_id); + free(rapop->op_type); + rapop->op_type = NULL; + free(rapop->rsc_class); + rapop->rsc_class = NULL; + + if (rapop->lrmd_op != NULL) { + rapop->lrmd_op->rapop = NULL; + rapop->lrmd_op = NULL; + } + + free(rapop); +} + +static void +lrmd_op_destroy(lrmd_op_t* op) +{ + CHECK_ALLOCATED(op, "op", ); + --lrm_objectstats.opcount; + + if (op->exec_pid > 1) { + lrmd_log(LOG_CRIT + , "%s: lingering operation process %d, op %s" + , __FUNCTION__, op->exec_pid, small_op_info(op)); + return; + } + lrmd_debug2(LOG_DEBUG, "%s: free the %s with address %p" + ,__FUNCTION__, op_info(op), op); + ha_msg_del(op->msg); + op->msg = NULL; + if( op->rsc_id ) { + free(op->rsc_id); + op->rsc_id = NULL; + } + op->exec_pid = 0; + if ( op->rapop != NULL ) { + op->rapop->lrmd_op = NULL; + op->rapop = NULL; + } + op->first_line_ra_stdout[0] = EOS; + + if( op->repeat_timeout_tag ) { + Gmain_timeout_remove(op->repeat_timeout_tag); + } + free(op); +} + +static lrmd_op_t* +lrmd_op_new(void) +{ + lrmd_op_t* op = (lrmd_op_t*)calloc(sizeof(lrmd_op_t),1); + + if (op == NULL) { + lrmd_log(LOG_ERR, "lrmd_op_new(): out of memory when " + "calloc a lrmd_op_t."); + return NULL; + } + op->rsc_id = NULL; + op->msg = NULL; + op->exec_pid = -1; + op->repeat_timeout_tag = 0; + op->rapop = NULL; + op->first_line_ra_stdout[0] = EOS; + op->t_recv = time_longclock(); + op->t_perform = zero_longclock; + op->t_done = zero_longclock; + op->t_rcchange = zero_longclock; + op->t_lastlogmsg = zero_longclock; + + memset(op->killseq, 0, sizeof(op->killseq)); + ++lrm_objectstats.opcount; + return op; +} + +static lrmd_op_t* +lrmd_op_copy(const lrmd_op_t* op) +{ + lrmd_op_t* ret; + + ret = lrmd_op_new(); + if (NULL == ret) { + return NULL; + } + /* Do a "shallow" copy */ + *ret = *op; + /* + * Some things, like timer ids and child pids are duplicated here + * but can be destroyed in one copy, but kept intact + * in the other, to later be destroyed. + * This isn't a complete disaster, since the timer ids aren't + * pointers, but it's still untidy at the least. + * Be sure and care of this situation when using this function. + */ + /* Do a "deep" copy of the message structure */ + ret->rapop = NULL; + ret->msg = ha_msg_copy(op->msg); + ret->rsc_id = strdup(op->rsc_id); + ret->rapop = NULL; + ret->first_line_ra_stdout[0] = EOS; + ret->repeat_timeout_tag = 0; + ret->exec_pid = -1; + ret->t_recv = op->t_recv; + ret->t_perform = op->t_perform; + ret->t_done = op->t_done; + ret->t_rcchange = op->t_rcchange; + ret->is_copy = TRUE; + ret->is_cancelled = FALSE; + ret->weight = op->weight; + return ret; +} + +static +const char * +op_status_to_str(int op_status) +{ + static char whatwasthat[25]; + switch (op_status) { + case LRM_OP_DONE: + return "LRM_OP_DONE"; + case LRM_OP_CANCELLED: + return "LRM_OP_CANCELLED"; + case LRM_OP_TIMEOUT: + return "LRM_OP_TIMEOUT"; + case LRM_OP_NOTSUPPORTED: + return "LRM_OP_NOTSUPPORTED"; + case -1: + return "N/A (-1)"; + default: + break; + } + snprintf(whatwasthat, sizeof(whatwasthat), "UNDEFINED STATUS: %d?", op_status); + return whatwasthat; +} +static +const char * +op_target_rc_to_str(int target) +{ + static char whatwasthat[25]; + switch (target) { + case EVERYTIME: + return "EVERYTIME"; + case CHANGED: + return "CHANGED"; + default: + break; + } + snprintf(whatwasthat, sizeof(whatwasthat) + ,"UNDEFINED TARGET_RC: %d", target); + return whatwasthat; +} + +/* + * We need a separate function to dump out operations for + * debugging. Then we wouldn't have to have the code for this + * inline. In particular, we could then call this from on_op_done() + * which would shorten and simplify that code - which could use + * the help :-) + */ + + +/* Debug oriented funtions */ +static gboolean debug_level_adjust(int nsig, gpointer user_data); + +static void +lrmd_op_dump(const lrmd_op_t* op, const char * text) +{ + int op_status = -1; + int target_rc = -1; + const char * pidstat; + longclock_t now = time_longclock(); + + CHECK_ALLOCATED(op, "op", ); + if (op->exec_pid < 1 + || ((kill(op->exec_pid, 0) < 0) && ESRCH == errno)) { + pidstat = "not running"; + }else{ + pidstat = "running"; + } + ha_msg_value_int(op->msg, F_LRM_OPSTATUS, &op_status); + ha_msg_value_int(op->msg, F_LRM_TARGETRC, &target_rc); + lrmd_debug(LOG_DEBUG + , "%s: lrmd_op: %s status: %s, target_rc=%s, client pid %d call_id" + ": %d, child pid: %d (%s) %s %s" + , text, op_info(op), op_status_to_str(op_status) + , op_target_rc_to_str(target_rc) + , op->client_id, op->call_id, op->exec_pid, pidstat + , (op->is_copy ? "copy" : "original") + , (op->is_cancelled ? "cancelled" : "")); + lrmd_debug(LOG_DEBUG + , "%s: lrmd_op2: rt_tag: %d, interval: %d, delay: %d" + , text, op->repeat_timeout_tag + , op->interval, op->delay); + lrmd_debug(LOG_DEBUG + , "%s: lrmd_op3: t_recv: %ldms, t_add: %ldms" + ", t_perform: %ldms, t_done: %ldms, t_rcchange: %ldms" + , text, tm2age(op->t_recv), tm2age(op->t_addtolist) + , tm2age(op->t_perform), tm2age(op->t_done), tm2age(op->t_rcchange)); + lrmd_rsc_dump(op->rsc_id, text); +} + + +static void +lrmd_client_destroy(lrmd_client_t* client) +{ + CHECK_ALLOCATED(client, "client", ); + + --lrm_objectstats.clientcount; + /* + * Delete direct references to this client + * and repeating operations it might have scheduled + */ + unregister_client(client); + if (client->app_name) { + free(client->app_name); + client->app_name = NULL; + } + free(client); +} + +static lrmd_client_t* +lrmd_client_new(void) +{ + lrmd_client_t* client; + client = calloc(sizeof(lrmd_client_t), 1); + if (client == NULL) { + lrmd_log(LOG_ERR, "lrmd_client_new(): out of memory when " + "calloc lrmd_client_t."); + return NULL; + } + client->g_src = NULL; + client->g_src_cbk = NULL; + ++lrm_objectstats.clientcount; + return client; +} +static void +lrmd_client_dump(gpointer key, gpointer value, gpointer user_data) +{ + lrmd_client_t * client = (lrmd_client_t*)value; + CHECK_ALLOCATED(client, "client", ); + if(!client) { + return; + } + + lrmd_debug(LOG_DEBUG, "client name: %s, client pid: %d" + ", client uid: %d, gid: %d, last request: %s" + ", last op in: %s, lastop out: %s" + ", last op rc: %s" + , lrm_str(client->app_name) + , client->pid + , client->uid, client->gid + , client->lastrequest + , ctime(&client->lastreqstart) + , ctime(&client->lastreqend) + , ctime(&client->lastrcsent) + ); + if (!client->ch_cmd) { + lrmd_debug(LOG_DEBUG, "NULL client ch_cmd in %s()", __FUNCTION__); + }else{ + lrmd_debug(LOG_DEBUG + , "Command channel status: %d, read queue addr: %p, write queue addr: %p" + , client->ch_cmd->ch_status + , client->ch_cmd->recv_queue + , client->ch_cmd->send_queue ); + + if (client->ch_cmd->recv_queue && client->ch_cmd->send_queue) { + lrmd_debug(LOG_DEBUG, "read Qlen: %ld, write Qlen: %ld" + , (long)client->ch_cmd->recv_queue->current_qlen + , (long)client->ch_cmd->send_queue->current_qlen); + } + } + if (!client->ch_cbk) { + lrmd_debug(LOG_DEBUG, "NULL client ch_cbk in %s()", __FUNCTION__); + }else{ + lrmd_debug(LOG_DEBUG + , "Callback channel status: %d, read Qlen: %ld, write Qlen: %ld" + , client->ch_cbk->ch_status + , (long)client->ch_cbk->recv_queue->current_qlen + , (long)client->ch_cbk->send_queue->current_qlen); + } +} +static void +lrmd_dump_all_clients(void) +{ + static gboolean incall = FALSE; + + if (incall) { + return; + } + + incall = TRUE; + + lrmd_debug(LOG_DEBUG, "%d clients connected to lrmd" + , g_hash_table_size(clients)); + + g_hash_table_foreach(clients, lrmd_client_dump, NULL); + incall = FALSE; +} + +static void +lrmd_rsc_destroy(lrmd_rsc_t* rsc) +{ + LRMAUDIT(); + CHECK_ALLOCATED(rsc, "resource", ); + --lrm_objectstats.rsccount; + if( rsc->op_list || rsc->repeat_op_list ) { + lrmd_log(LOG_ERR, "%s: refusing to remove resource %s" + " which is still holding operations" + , __FUNCTION__, lrm_str(rsc->id)); + return; + } else { + lrmd_debug(LOG_DEBUG, "%s: removing resource %s" + , __FUNCTION__, lrm_str(rsc->id)); + } + g_hash_table_remove(resources, rsc->id); + if (rsc->id) { + free(rsc->id); + rsc->id = NULL; + } + if (rsc->type) { + free(rsc->type); + rsc->type = NULL; + } + if (rsc->class) { + free(rsc->class); + rsc->class = NULL; + } + if (rsc->provider) { + free(rsc->provider); + rsc->provider = NULL; + } + if (NULL != rsc->params) { + free_str_table(rsc->params); + rsc->params = NULL; + } + if (rsc->last_op_table) { + g_hash_table_foreach_remove(rsc->last_op_table + , free_str_hash_pair, NULL); + g_hash_table_destroy(rsc->last_op_table); + rsc->last_op_table = NULL; + } + if (rsc->last_op_done) { + lrmd_op_destroy(rsc->last_op_done); + rsc->last_op_done = NULL; + } + + if (rsc->delay_timeout > 0) { + Gmain_timeout_remove(rsc->delay_timeout); + rsc->delay_timeout = (guint)0; + } + + free(rsc); + LRMAUDIT(); +} + +static lrmd_rsc_t* +lrmd_rsc_new(const char * id, struct ha_msg* msg) +{ + lrmd_rsc_t* rsc; + rsc = (lrmd_rsc_t *)calloc(sizeof(lrmd_rsc_t),1); + if (rsc == NULL) { + lrmd_log(LOG_ERR, "%s: out of memory when calloc " + "a lrmd_rsc_t", __FUNCTION__); + return NULL; + } + rsc->delay_timeout = (guint)0; + if (id) { + rsc->id = strdup(id); + } + if (msg) { + rsc->type = strdup(ha_msg_value(msg, F_LRM_RTYPE)); + rsc->class = strdup(ha_msg_value(msg, F_LRM_RCLASS)); + if (NULL == ha_msg_value(msg, F_LRM_RPROVIDER)) { + lrmd_log(LOG_NOTICE, "%s(): No %s field in message" + , __FUNCTION__, F_LRM_RPROVIDER); + }else{ + rsc->provider = strdup(ha_msg_value(msg, F_LRM_RPROVIDER)); + if (rsc->provider == NULL) { + goto errout; + } + } + if (rsc->id == NULL + || rsc->type == NULL + || rsc->class == NULL) { + goto errout; + } + } + g_hash_table_insert(resources, strdup(id), rsc); + ++lrm_objectstats.rsccount; + return rsc; +errout: + lrmd_rsc_destroy(rsc); /* violated property */ /* Or so BEAM thinks :-) */ + rsc = NULL; + return rsc; +} + +static void +dump_op(gpointer key, gpointer val, gpointer data) +{ + lrmd_op_t* lrmd_op = (lrmd_op_t*) val; + + lrmd_op_dump(lrmd_op, "rsc->last_op_table"); +} +static void +dump_op_table(gpointer key, gpointer val, gpointer data) +{ + GHashTable* table = (GHashTable*) val; + + g_hash_table_foreach(table, dump_op, data); +} +static void +lrmd_rsc_dump(char* rsc_id, const char * text) +{ + static gboolean incall = FALSE; + GList* oplist; + lrmd_rsc_t* rsc=NULL; + + if( rsc_id ) { + rsc = lookup_rsc(rsc_id); + } else { + lrmd_debug(LOG_INFO + , "%s:%d: the rsc_id is NULL" + , __FUNCTION__, __LINE__); + return; + } + CHECK_ALLOCATED(rsc, "rsc", ); + if(!rsc) { + return; + } + + /* Avoid infinite recursion loops... */ + if (incall) { + return; + } + incall = TRUE; + /* TODO: Dump params and last_op_table FIXME */ + + lrmd_debug(LOG_DEBUG, "%s: BEGIN resource dump", text); + lrmd_debug(LOG_DEBUG, "%s: resource %s/%s/%s/%s" + , text + , lrm_str(rsc->id) + , lrm_str(rsc->type) + , lrm_str(rsc->class) + , lrm_str(rsc->provider)); + + lrmd_debug(LOG_DEBUG, "%s: rsc->op_list...", text); + for(oplist = g_list_first(rsc->op_list); oplist; + oplist = g_list_next(oplist)) { + lrmd_op_dump(oplist->data, "rsc->op_list"); + } + + lrmd_debug(LOG_DEBUG, "%s: rsc->repeat_op_list...", text); + for(oplist = g_list_first(rsc->repeat_op_list); oplist; + oplist=g_list_next(oplist)) { + lrmd_op_dump(oplist->data, "rsc->repeat_op_list"); + } + + if (rsc->last_op_done != NULL) { + lrmd_debug(LOG_DEBUG, "%s: rsc->last_op_done...", text); + lrmd_op_dump(rsc->last_op_done, "rsc->last_op_done"); + } + else { + lrmd_debug(LOG_DEBUG, "%s: rsc->last_op_done==NULL", text); + } + if (rsc->last_op_table) { + g_hash_table_foreach(rsc->last_op_table,dump_op_table,NULL); + } + else { + lrmd_debug(LOG_DEBUG, "%s: rsc->last_op_table==NULL", text); + } + lrmd_debug(LOG_DEBUG, "%s: END resource dump", text); + incall = FALSE; +}; +static void +dump_id_rsc_pair(gpointer key, gpointer value, gpointer user_data) +{ + char* rid = (char*)key; + char* text = (char*)user_data; + lrmd_rsc_dump(rid,text); +} +static void +lrmd_dump_all_resources(void) +{ + static gboolean incall = FALSE; + char text[]= "lrmd_dump_all_resources"; + if (incall) { + return; + } + incall = TRUE; + + lrmd_debug(LOG_DEBUG, "%d resources are managed by lrmd" + , g_hash_table_size(resources)); + g_hash_table_foreach(resources, dump_id_rsc_pair, text); + incall = FALSE; +} + + +#if 0 +static void +lrm_debug_running_op(lrmd_op_t* op, const char * text) +{ + char cmd[256]; + lrmd_op_dump(op, text); + CHECK_ALLOCATED(op, "op", ); + if (op->exec_pid >= 1) { + /* This really ought to use our logger + * So... it might not get forwarded to the central machine + * if you're testing with CTS -- FIXME!!! + */ + snprintf(cmd, sizeof(cmd) + , "ps -l -f -s %d | logger -p daemon.info -t 'T/O PS:'" + , op->exec_pid); + lrmd_debug(LOG_DEBUG, "Running [%s]", cmd); + if (system(cmd) != 0) { + lrmd_log(LOG_ERR, "Running [%s] failed", cmd); + } + snprintf(cmd, sizeof(cmd) + , "ps axww | logger -p daemon.info -t 't/o ps:'"); + lrmd_debug(LOG_DEBUG, "Running [%s]", cmd); + if (system(cmd) != 0) { + lrmd_log(LOG_ERR, "Running [%s] failed", cmd); + } + } +} +#endif +int +main(int argc, char ** argv) +{ + int req_restart = TRUE; + int req_status = FALSE; + int req_stop = FALSE; + + int argerr = 0; + int flag; + + while ((flag = getopt(argc, argv, OPTARGS)) != EOF) { + switch(flag) { + case 'h': /* Help message */ + usage(lrm_system_name, LSB_EXIT_OK); + break; + case 'v': /* Debug mode, more logs*/ + ++debug_level; + break; + case 's': /* Status */ + req_status = TRUE; + break; + case 'k': /* Stop (kill) */ + req_stop = TRUE; + break; + case 'r': /* Restart */ + req_restart = TRUE; + break; + /* Register to apphbd then monitored by it */ + case 'm': + reg_to_apphbd = TRUE; + break; + case 'i': /* Get apphb interval */ + if (optarg) { + apphb_interval = atoi(optarg); + } + break; + default: + ++argerr; + break; + } + } + + if (optind > argc) { + ++argerr; + } + + if (argerr) { + usage(lrm_system_name, LSB_EXIT_GENERIC); + } + + cl_log_set_entity(lrm_system_name); + cl_log_enable_stderr(debug_level?TRUE:FALSE); + cl_log_set_facility(HA_LOG_FACILITY); + + /* Use logd if it's enabled by heartbeat */ + cl_inherit_logging_environment(0); + + if (req_status){ + return init_status(PID_FILE, lrm_system_name); + } + + if (req_stop){ + return init_stop(PID_FILE); + } + + if (req_restart) { + init_stop(PID_FILE); + } + + return init_start(); +} + +int +init_status(const char *pid_file, const char *client_name) +{ + long pid = cl_read_pidfile(pid_file); + + if (pid > 0) { + fprintf(stderr, "%s is running [pid: %ld]\n" + , client_name, pid); + return LSB_STATUS_OK; + } + fprintf(stderr, "%s is stopped.\n", client_name); + return LSB_STATUS_STOPPED; +} + +int +init_stop(const char *pid_file) +{ + long pid; + int rc = LSB_EXIT_OK; + + + + if (pid_file == NULL) { + lrmd_log(LOG_ERR, "No pid file specified to kill process"); + return LSB_EXIT_GENERIC; + } + pid = cl_read_pidfile(pid_file); + + if (pid > 0) { + if (CL_KILL((pid_t)pid, SIGTERM) < 0) { + rc = (errno == EPERM + ? LSB_EXIT_EPERM : LSB_EXIT_GENERIC); + fprintf(stderr, "Cannot kill pid %ld\n", pid); + }else{ + lrmd_log(LOG_INFO, + "Signal sent to pid=%ld," + " waiting for process to exit", + pid); + + while (CL_PID_EXISTS(pid)) { + sleep(1); + } + } + } + return rc; +} + +static const char usagemsg[] = "[-srkhv]\n\ts: status\n\tr: restart" + "\n\tk: kill\n\tm: register to apphbd\n\ti: the interval of apphb\n\t" + "h: help\n\tv: debug\n"; + +void +usage(const char* cmd, int exit_status) +{ + FILE* stream; + + stream = exit_status ? stderr : stdout; + + fprintf(stream, "usage: %s %s", cmd, usagemsg); + fflush(stream); + + exit(exit_status); +} +/* + * In design, the lrmd should not know the meaning of operation type + * and the meaning of rc. This function is just for logging. + */ +static void +warning_on_active_rsc(gpointer key, gpointer value, gpointer user_data) +{ + int op_status, rc; + const char* op_type; + + lrmd_rsc_t* rsc = (lrmd_rsc_t*)value; + if (rsc->last_op_done != NULL) { + if (HA_OK != ha_msg_value_int(rsc->last_op_done->msg + , F_LRM_OPSTATUS, &op_status)) { + lrmd_debug(LOG_WARNING + ,"resource %s is left in UNKNOWN status." \ + "(last op done is damaged..)" + ,rsc->id); + return; + } + op_type = ha_msg_value(rsc->last_op_done->msg, F_LRM_OP); + if (op_status != LRM_OP_DONE) { + lrmd_debug(LOG_WARNING + ,"resource %s is left in UNKNOWN status." \ + "(last op %s finished without LRM_OP_DONE status.)" + ,rsc->id, op_type); + return; + } + if (HA_OK != ha_msg_value_int(rsc->last_op_done->msg + , F_LRM_RC, &rc)) { + lrmd_debug(LOG_WARNING + ,"resource %s is left in UNKNOWN status." \ + "(last op done is damaged..)" + ,rsc->id); + return; + } + if((rc == 0) && + (STRNCMP_CONST(op_type,"start") ==0 + ||STRNCMP_CONST(op_type,"monitor") ==0 + ||STRNCMP_CONST(op_type,"status") ==0)) { + lrmd_debug(LOG_WARNING + ,"resource %s is left in RUNNING status." \ + "(last op %s finished with rc 0.)" + ,rsc->id, op_type); + return; + } + if ((rc !=0 ) && + (STRNCMP_CONST(op_type,"start") ==0 + ||STRNCMP_CONST(op_type,"stop") ==0)) { + lrmd_debug(LOG_WARNING + ,"resource %s is left in UNKNOWN status." \ + "(last op %s finished with rc %d.)" + ,rsc->id, op_type, rc); + return; + } + } +} + +static gboolean +lrm_shutdown(void) +{ + lrmd_log(LOG_INFO,"lrmd is shutting down"); + if (mainloop != NULL && g_main_is_running(mainloop)) { + g_hash_table_foreach(resources, warning_on_active_rsc, NULL); + g_main_quit(mainloop); + }else { + exit(LSB_EXIT_OK); + } + return FALSE; +} +static void +has_pending_op(gpointer key, gpointer value, gpointer user_data) +{ + lrmd_rsc_t* rsc = (lrmd_rsc_t*)value; + int* result = (int*)user_data; + if (rsc->op_list != NULL) { + *result = TRUE; + } +} +static gboolean +can_shutdown() +{ + int has_ops = FALSE; + g_hash_table_foreach(resources, has_pending_op, &has_ops); + + return !has_ops; +} +gboolean +sigterm_action(int nsig, gpointer user_data) +{ + shutdown_in_progress = TRUE; + + if (can_shutdown()) { + lrm_shutdown(); + } else { + lrmd_log(LOG_INFO, "sigterm_action: shutdown postponed, some operations are still running"); + } + return TRUE; +} + +static void +register_pid(gboolean do_fork, + gboolean (*shutdown)(int nsig, gpointer userdata)) +{ + int j; + + umask(022); + + for (j=0; j < 3; ++j) { + close(j); + (void)open("/dev/null", j == 0 ? O_RDONLY : O_WRONLY); + } + CL_IGNORE_SIG(SIGINT); + CL_IGNORE_SIG(SIGHUP); + CL_DEFAULT_SIG(SIGPIPE); + G_main_add_SignalHandler(G_PRIORITY_HIGH, SIGTERM + , shutdown, NULL, NULL); + cl_signal_set_interrupt(SIGTERM, 1); + cl_signal_set_interrupt(SIGCHLD, 1); + /* At least they are harmless, I think. ;-) */ + cl_signal_set_interrupt(SIGINT, 0); + cl_signal_set_interrupt(SIGHUP, 0); +} + +static int +init_using_apphb(void) +{ +#ifdef ENABLE_APPHB + char lrmd_instance[40]; + + if (reg_to_apphbd == FALSE) { + return -1; + } + + snprintf(lrmd_instance, sizeof(lrmd_instance), "%s_%ld" + , lrm_system_name, (long)getpid()); + if (apphb_register(lrm_system_name, lrmd_instance) != 0) { + lrmd_log(LOG_ERR, "Failed when trying to register to apphbd."); + lrmd_log(LOG_ERR, "Maybe apphbd is not running. Quit."); + return -1; + } + lrmd_log(LOG_INFO, "Registered to apphbd."); + + apphb_setinterval(apphb_interval); + apphb_setwarn(apphb_interval*APPHB_WARNTIME_FACTOR); + + Gmain_timeout_add(apphb_interval - APPHB_INTVL_DETLA, emit_apphb, NULL); +#endif + return 0; +} + +static gboolean +emit_apphb(gpointer data) +{ +#ifdef ENABLE_APPHB + if (reg_to_apphbd == FALSE) { + return FALSE; + } + + if (apphb_hb() != 0) { + lrmd_log(LOG_ERR, "emit_apphb: Failed to emit an apphb."); + reg_to_apphbd = FALSE; + return FALSE; + }; +#endif + return TRUE; +} + +static void +calc_max_children() +{ +#ifdef _SC_NPROCESSORS_ONLN + int nprocs; + + nprocs = sysconf(_SC_NPROCESSORS_ONLN); + if( nprocs < 1 ) { + lrmd_log(LOG_WARNING, "%s: couldn't get the number of processors" + , __FUNCTION__); + } else { + if( nprocs/2 > max_child_count ) { + max_child_count = nprocs/2; + } + lrmd_log(LOG_INFO, "max-children set to %d " + "(%d processors online)", max_child_count, nprocs); + return; + } +#else + lrmd_log(LOG_WARNING, "%s: cannot get the number of processors " + "on this platform", __FUNCTION__); +#endif + lrmd_log(LOG_INFO, "max-children set to %d", max_child_count); +} + +/* main loop of the daemon*/ +int +init_start () +{ + DIR* dir = NULL; + PILPluginUniv * PluginLoadingSystem = NULL; + struct dirent* subdir; + char* dot = NULL; + char* ra_name = NULL; + int len; + IPC_WaitConnection* conn_cmd = NULL; + IPC_WaitConnection* conn_cbk = NULL; + + GHashTable* conn_cmd_attrs; + GHashTable* conn_cbk_attrs; + + char path[] = IPC_PATH_ATTR; + char cmd_path[] = LRM_CMDPATH; + char cbk_path[] = LRM_CALLBACKPATH; + + PILGenericIfMgmtRqst RegisterRqsts[]= { + {"RAExec", &RAExecFuncs, NULL, NULL, NULL}, + { NULL, NULL, NULL, NULL, NULL} }; + + if( getenv("LRMD_MAX_CHILDREN") ) { + set_lrmd_param("max-children", getenv("LRMD_MAX_CHILDREN")); + } else { + calc_max_children(); + } + + qsort(msg_maps, MSG_NR, sizeof(struct msg_map), msg_type_cmp); + + if (cl_lock_pidfile(PID_FILE) < 0) { + lrmd_log(LOG_ERR, "already running: [pid %d].", cl_read_pidfile(PID_FILE)); + lrmd_log(LOG_ERR, "Startup aborted (already running). Shutting down."); + exit(100); + } + + register_pid(FALSE, sigterm_action); + + /* load RA plugins */ + PluginLoadingSystem = NewPILPluginUniv (HA_PLUGIN_DIR); + PILLoadPlugin(PluginLoadingSystem, "InterfaceMgr", "generic", + &RegisterRqsts); + + /* + * FIXME!!! + * Much of the code through the end of the next loop is + * unnecessary - The plugin system will do this for you quite + * nicely. And, it does it portably, too... + */ + + dir = opendir(LRM_PLUGIN_DIR); + if (NULL == dir) { + lrmd_log(LOG_ERR, "main: can not open RA plugin dir "LRM_PLUGIN_DIR); + lrmd_log(LOG_ERR, "Startup aborted (no RA plugin). Shutting down."); + exit(100); + } + + while ( NULL != (subdir = readdir(dir))) { + /* skip . and .. */ + if ( '.' == subdir->d_name[0]) { + continue; + } + /* skip the other type files */ + if (NULL == strstr(subdir->d_name, ".so")) { + continue; + } + /* remove the ".so" */ + dot = strchr(subdir->d_name,'.'); + if (NULL != dot) { + len = (int)(dot - subdir->d_name); + ra_name = g_strndup(subdir->d_name,len); + } + else { + ra_name = g_strdup(subdir->d_name); + } + PILLoadPlugin(PluginLoadingSystem , "RAExec", ra_name, NULL); + ra_class_list = g_list_append(ra_class_list,ra_name); + } + closedir(dir); dir = NULL; /* Don't forget to close 'dir' */ + + /* + *create the waiting connections + *one for register the client, + *the other is for create the callback channel + */ + + /*Create a waiting connection to accept command connect from client*/ + conn_cmd_attrs = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(conn_cmd_attrs, path, cmd_path); + conn_cmd = ipc_wait_conn_constructor(IPC_ANYTYPE, conn_cmd_attrs); + g_hash_table_destroy(conn_cmd_attrs); + if (NULL == conn_cmd) { + lrmd_log(LOG_ERR, + "main: can not create wait connection for command."); + lrmd_log(LOG_ERR, "Startup aborted (can't create comm channel). Shutting down."); + + exit(100); + } + + /*Create a source to handle new connect rquests for command*/ + G_main_add_IPC_WaitConnection( G_PRIORITY_HIGH, conn_cmd, NULL, FALSE, + on_connect_cmd, conn_cmd, NULL); + + /* auth is static, but used when clients register */ + auth = ipc_str_to_auth(ADMIN_UIDS, strlen(ADMIN_UIDS), "", 0); + + /* + * Create a waiting connection to accept the callback connect from client + */ + conn_cbk_attrs = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(conn_cbk_attrs, path, cbk_path); + conn_cbk = ipc_wait_conn_constructor( IPC_ANYTYPE, conn_cbk_attrs); + g_hash_table_destroy(conn_cbk_attrs); + + if (NULL == conn_cbk) { + lrmd_log(LOG_ERR, + "main: can not create wait connection for callback."); + lrmd_log(LOG_ERR, "Startup aborted (can't create comm channel). Shutting down."); + exit(100); + } + + /*Create a source to handle new connect rquests for callback*/ + G_main_add_IPC_WaitConnection( G_PRIORITY_HIGH, conn_cbk, NULL, FALSE, + on_connect_cbk, conn_cbk, NULL); + + /* our child signal handling involves calls with + * unpredictable timing; so we raise the limit to + * reduce the number of warnings + */ + set_sigchld_proctrack(G_PRIORITY_HIGH,10*DEFAULT_MAXDISPATCHTIME); + + lrmd_log(LOG_INFO, "enabling coredumps"); + /* Although lrmd can count on the parent to enable coredump, still + * set it here for test, when start manually. + */ + cl_cdtocoredir(); + cl_enable_coredumps(TRUE); + + /* Allow us to always take a "secure" core dump + * We might have STONITH logins and passwords, etc. in our address + * space - so we need to make sure it's only readable by root. + * Calling this function accomplishes that. + */ + cl_set_all_coredump_signal_handlers(); + if( drop_privs(0, 0) ) { /* become "nobody" */ + lrmd_log(LOG_WARNING,"%s: failed to drop privileges: %s" + , __FUNCTION__, strerror(errno)); + } + + /* + * Add the signal handler for SIGUSR1, SIGUSR2. + * They are used to change the debug level. + */ + G_main_add_SignalHandler(G_PRIORITY_HIGH, SIGUSR1, + debug_level_adjust, NULL, NULL); + G_main_add_SignalHandler(G_PRIORITY_HIGH, SIGUSR2, + debug_level_adjust, NULL, NULL); + + /* + * alloc memory for client table and resource table + */ + clients = g_hash_table_new(g_int_hash, g_int_equal); + if (clients == NULL) { + cl_log(LOG_ERR, "can not new hash table clients"); + exit(100); + } + resources = g_hash_table_new_full(g_str_hash + , g_str_equal, free, NULL); + if (resources == NULL) { + cl_log(LOG_ERR, "can not new hash table resources"); + exit(100); + } + + /*Create the mainloop and run it*/ + mainloop = g_main_new(FALSE); + lrmd_debug(LOG_DEBUG, "main: run the loop..."); + lrmd_log(LOG_INFO, "Started."); + + /* apphb initializing */ + init_using_apphb(); + emit_apphb(NULL); /* Avoid warning */ + + g_main_run(mainloop); + + emit_apphb(NULL); + if (reg_to_apphbd == TRUE) { +#ifdef ENABLE_APPHB + apphb_unregister(); +#endif + reg_to_apphbd = FALSE; + } + + if( return_to_orig_privs() ) { + cl_perror("%s: failed to raise privileges", __FUNCTION__); + } + conn_cmd->ops->destroy(conn_cmd); + conn_cmd = NULL; + + conn_cbk->ops->destroy(conn_cbk); + conn_cbk = NULL; + + ipc_destroy_auth(auth); + if (cl_unlock_pidfile(PID_FILE) == 0) { + lrmd_debug(LOG_DEBUG, "[%s] stopped", lrm_system_name); + } + return 0; +} + +/* + *GLoop Message Handlers + */ +gboolean +on_connect_cmd (IPC_Channel* ch, gpointer user_data) +{ + lrmd_client_t* client = NULL; + + /* check paremeters */ + if (NULL == ch) { + lrmd_log(LOG_ERR, "on_connect_cmd: channel is null"); + return TRUE; + } + /* create new client */ + /* the register will be finished in on_msg_register */ + client = lrmd_client_new(); + if (client == NULL) { + return TRUE; + } + client->app_name = NULL; + client->ch_cmd = ch; + client->g_src = G_main_add_IPC_Channel(G_PRIORITY_DEFAULT, + ch, FALSE, on_receive_cmd, (gpointer)client, + on_remove_client); + + + return TRUE; +} + +gboolean +on_connect_cbk (IPC_Channel* ch, gpointer user_data) +{ + /*client connect for create the second channel for call back*/ + pid_t pid; + const char* type = NULL; + struct ha_msg* msg = NULL; + lrmd_client_t* client = NULL; + + if (NULL == ch) { + lrmd_log(LOG_ERR, "on_connect_cbk: channel is null"); + return TRUE; + } + + /* Isn't this kind of a tight timing assumption ?? + * This operation is non-blocking -- IIRC + * Maybe this should be moved to the input dispatch function + * for this channel when we make a GSource from it. + * FIXME + */ + + /*get the message, ends up in socket_waitin */ + msg = msgfromIPC_noauth(ch); + if (NULL == msg) { + lrmd_log(LOG_ERR, "on_connect_cbk: can not receive msg"); + return TRUE; + } + + /*check if it is a register message*/ + type = ha_msg_value(msg, F_LRM_TYPE); + if (0 != STRNCMP_CONST(type, REGISTER)) { + lrmd_log(LOG_ERR, "on_connect_cbk: received a message which is " + "not known by lrmd."); + ha_msg_del(msg); + send_ret_msg(ch, HA_FAIL); + return TRUE; + } + + /*get the pid of client */ + if (HA_OK != ha_msg_value_int(msg, F_LRM_PID, &pid)) { + lrmd_log(LOG_ERR, "on_connect_cbk: can not get pid from the " + "message."); + ha_msg_del(msg); + send_ret_msg(ch, HA_FAIL); + return TRUE; + } + ha_msg_del(msg); + + /*get the client in the client list*/ + client = lookup_client(pid); + if (NULL == client) { + lrmd_log(LOG_ERR, "on_connect_cbk: donnot find the client " + "[pid:%d] in internal client list. ", pid); + send_ret_msg(ch, HA_FAIL); + return TRUE; + } + if (client->ch_cbk != NULL) { + client->ch_cbk->ops->destroy(client->ch_cbk); + client->ch_cbk = NULL; + } + client->g_src_cbk = G_main_add_IPC_Channel(G_PRIORITY_DEFAULT + , ch, FALSE,NULL,NULL,NULL); + + /*fill the channel of callback field*/ + client->ch_cbk = ch; + send_ret_msg(ch, HA_OK); + return TRUE; +} + +int +msg_type_cmp(const void *p1, const void *p2) +{ + + return strncmp( + ((const struct msg_map *)p1)->msg_type, + ((const struct msg_map *)p2)->msg_type, + MAX_MSGTYPELEN); +} + +gboolean +on_receive_cmd (IPC_Channel* ch, gpointer user_data) +{ + struct msg_map *msgmap_p, in_type; + lrmd_client_t* client = NULL; + struct ha_msg* msg = NULL; + char *msg_s; + int ret = FALSE; + + client = (lrmd_client_t*)user_data; + + if (IPC_DISCONNECT == ch->ch_status) { + lrmd_debug(LOG_DEBUG, + "on_receive_cmd: the IPC to client [pid:%d] disconnected." + , client->pid); + return FALSE; + } + + if (!ch->ops->is_message_pending(ch)) { + lrmd_debug(LOG_DEBUG, "on_receive_cmd: no pending message in IPC " + "channel."); + return TRUE; + } + + + /*get the message */ + msg = msgfromIPC(ch, 0); + if (NULL == msg) { + lrmd_log(LOG_ERR, "on_receive_cmd: can not receive messages."); + return TRUE; + } + + if (TRUE == shutdown_in_progress ) { + send_ret_msg(ch,HA_FAIL); + ha_msg_del(msg); + lrmd_log(LOG_INFO, "%s: new requests denied," \ + " we're about to shutdown", __FUNCTION__); + return TRUE; + } + + /*dispatch the message*/ + in_type.msg_type = ha_msg_value(msg, F_LRM_TYPE); + if( !in_type.msg_type ) { + LOG_FAILED_TO_GET_FIELD(F_LRM_TYPE); + ha_msg_del(msg); + return TRUE; + } + msg_s = msg2string(msg); + if( msg_s ) { + lrmd_debug2(LOG_DEBUG,"dumping request: %s",msg_s); + free(msg_s); + } + + if (!(msgmap_p = bsearch(&in_type, msg_maps, + MSG_NR, sizeof(struct msg_map), msg_type_cmp) + )) { + + lrmd_log(LOG_ERR, "on_receive_cmd: received an unknown msg"); + } else { + if( !client->app_name && msgmap_p->handler != on_msg_register ) { + ha_msg_del(msg); + lrmd_log(LOG_ERR, "%s: the client needs to register first", __FUNCTION__); + return FALSE; + } + + if( client->priv_lvl < msgmap_p->min_priv ) { + ha_msg_del(msg); + lrmd_log(LOG_ERR, "%s: insufficient privileges for %s (pid %d)" + , __FUNCTION__ + , client->app_name, client->pid); + return FALSE; + } + strncpy(client->lastrequest, in_type.msg_type, sizeof(client->lastrequest)); + client->lastrequest[sizeof(client->lastrequest)-1]='\0'; + client->lastreqstart = time(NULL); + /*call the handler of the message*/ + ret = msgmap_p->handler(client, msg); + client->lastreqend = time(NULL); + + /*return rc to client if need*/ + if (send_msg_now(msgmap_p)) { + send_ret_msg(ch, ret); + client->lastrcsent = time(NULL); + } + } + + /*delete the msg*/ + ha_msg_del(msg); + + return ret; +} +static void +remove_repeat_op_from_client(gpointer key, gpointer value, gpointer user_data) +{ + lrmd_rsc_t* rsc = (lrmd_rsc_t*)value; + pid_t pid = GPOINTER_TO_UINT(user_data); /* pointer cast as int */ + + (void)flush_all(&(rsc->repeat_op_list),pid); +} + +/* Remove all direct pointer references to 'client' before destroying it */ +static int +unregister_client(lrmd_client_t* client) +{ + CHECK_ALLOCATED(client, "client", HA_FAIL); + + if (NULL == lookup_client(client->pid)) { + lrmd_log(LOG_ERR,"%s: can not find client %s [pid %d] when try " + "to unregister it." + , __FUNCTION__ + , client->app_name, client->pid); + return HA_FAIL; + } + + /* Search all resources for repeating ops this client owns */ + g_hash_table_foreach(resources + , remove_repeat_op_from_client, GUINT_TO_POINTER(client->pid)); + + /* Remove from clients */ + g_hash_table_remove(clients, (gpointer)&client->pid); + + lrmd_debug(LOG_DEBUG, "%s: client %s [pid:%d] is unregistered" + , __FUNCTION__ + , client->app_name + , client->pid); + return HA_OK; +} + +void +on_remove_client (gpointer user_data) +{ + lrmd_client_t* client = (lrmd_client_t*) user_data; + + CHECK_ALLOCATED(client, "client", ); + if (client->g_src != NULL) { + G_main_del_IPC_Channel(client->g_src); + } + if (client->g_src_cbk != NULL) { + G_main_del_IPC_Channel(client->g_src_cbk); + } + lrmd_client_destroy(client); + +} + + +/* This function called when its time to run a repeating operation now */ +/* Move op from repeat queue to running queue */ +gboolean +on_repeat_op_readytorun(gpointer data) +{ + lrmd_op_t* op = NULL; + lrmd_rsc_t* rsc = NULL; + + LRMAUDIT(); + op = (lrmd_op_t*)data; + CHECK_ALLOCATED(op, "op", FALSE ); + + if (op->exec_pid == 0) { + lrmd_log(LOG_ERR, "%s: exec_pid is 0 (internal error)" + , __FUNCTION__); + return FALSE; + } + + lrmd_debug2(LOG_DEBUG + , "%s: remove operation %s from the repeat operation list and " + "add it to the operation list" + , __FUNCTION__, op_info(op)); + + if( op->rsc_id ) { + rsc = lookup_rsc(op->rsc_id); + } else { + lrmd_debug(LOG_INFO + , "%s: the rsc_id in op %s is NULL" + , __FUNCTION__, op_info(op)); + return FALSE; + } + + rsc->repeat_op_list = g_list_remove(rsc->repeat_op_list, op); + if (op->repeat_timeout_tag != 0) { + op->repeat_timeout_tag = (guint)0; + } + + op->exec_pid = -1; + + if (!shutdown_in_progress) { + add_op_to_runlist(rsc,op); + } + perform_op(rsc); + + LRMAUDIT(); + return FALSE; +} + +/*LRM Message Handlers*/ +int +on_msg_register(lrmd_client_t* client, struct ha_msg* msg) +{ + lrmd_client_t* exist = NULL; + const char* app_name = NULL; + + CHECK_ALLOCATED(msg, "register message", HA_FAIL); + + app_name = ha_msg_value(msg, F_LRM_APP); + if (NULL == app_name) { + lrmd_log(LOG_ERR, "on_msg_register: no app_name in " + "the ha message."); + return HA_FAIL; + } + client->app_name = strdup(app_name); + + return_on_no_int_value(msg, F_LRM_PID, &client->pid); + return_on_no_int_value(msg, F_LRM_GID, (int *)&client->gid); + return_on_no_int_value(msg, F_LRM_UID, (int *)&client->uid); + + exist = lookup_client(client->pid); + if (NULL != exist) { + g_hash_table_remove(clients, (gpointer)&client->pid); + on_remove_client(exist); + lrmd_log(LOG_NOTICE, + "on_msg_register: the client [pid:%d] already exists in " + "internal client list, let remove it at first." + , client->pid); + } + + /* everybody can connect, but only certain UIDs can perform + * administrative actions + */ + if( client->ch_cmd->ops->verify_auth(client->ch_cmd, auth) == IPC_OK ) + client->priv_lvl = PRIV_ADMIN; + else + client->priv_lvl = 0; + + g_hash_table_insert(clients, (gpointer)&client->pid, client); + lrmd_debug(LOG_DEBUG, "on_msg_register:client %s [%d] registered" + , client->app_name + , client->pid); + + return HA_OK; +} + +int +on_msg_get_rsc_classes(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + lrmd_debug2(LOG_DEBUG + , "on_msg_get_rsc_classes:client [%d] wants to get rsc classes" + , client->pid); + + ret = create_lrm_ret(HA_OK, 4); + CHECK_RETURN_OF_CREATE_LRM_RET; + + cl_msg_add_list(ret,F_LRM_RCLASS,ra_class_list); + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, + "on_msg_get_rsc_classes: cannot send the ret mesage"); + } + ha_msg_del(ret); + + return HA_OK; +} + +int +on_msg_get_rsc_types(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + struct RAExecOps * RAExec = NULL; + GList* types = NULL; + GList* type; + const char* rclass = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + ret = create_lrm_ret(HA_OK,5); + CHECK_RETURN_OF_CREATE_LRM_RET; + + rclass = ha_msg_value(msg, F_LRM_RCLASS); + if (rclass == NULL) { + lrmd_log(LOG_ERR, "on_msg_get_rsc_types: cannot get the " + "resource class field from the message."); + send_ret_msg(client->ch_cmd, HA_FAIL); + return HA_FAIL; + } + + lrmd_debug2(LOG_DEBUG, "on_msg_get_rsc_types: the client [pid:%d] " + "wants to get resource types of resource class %s" + , client->pid, rclass); + + RAExec = g_hash_table_lookup(RAExecFuncs,rclass); + + if (NULL == RAExec) { + lrmd_log(LOG_NOTICE, "on_msg_get_rsc_types: can not find this " + "RA class %s.", rclass); + } else { + if (0 <= RAExec->get_resource_list(&types) && types != NULL) { + cl_msg_add_list(ret, F_LRM_RTYPES, types); + while (NULL != (type = g_list_first(types))) { + types = g_list_remove_link(types, type); + g_free(type->data); + g_list_free_1(type); + } + g_list_free(types); + } + } + + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, + "on_msg_get_rsc_types: can not send the ret message."); + } + ha_msg_del(ret); + + return HA_OK; +} + +int +on_msg_get_rsc_providers(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + struct RAExecOps * RAExec = NULL; + GList* providers = NULL; + GList* provider = NULL; + const char* rclass = NULL; + const char* rtype = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + ret = create_lrm_ret(HA_OK,5); + CHECK_RETURN_OF_CREATE_LRM_RET; + + rclass = ha_msg_value(msg, F_LRM_RCLASS); + rtype = ha_msg_value(msg, F_LRM_RTYPE); + if( !rclass || !rtype ) { + lrmd_log(LOG_NOTICE + , "%s: could not retrieve resource class or type" + , __FUNCTION__); + send_ret_msg(client->ch_cmd, HA_FAIL); + return HA_FAIL; + } + + lrmd_debug2(LOG_DEBUG + , "%s: the client [%d] wants to get rsc privider of %s::%s" + , __FUNCTION__ + , client->pid + , rclass + , rtype); + + RAExec = g_hash_table_lookup(RAExecFuncs, rclass); + + if (NULL == RAExec) { + lrmd_log(LOG_NOTICE + , "%s: can not find the class %s." + , __FUNCTION__ + , rclass); + } + else { + if (0 <= RAExec->get_provider_list(rtype, &providers)) { + if (providers != NULL) { + cl_msg_add_list(ret, F_LRM_RPROVIDERS, providers); + } + while (NULL != (provider = g_list_first(providers))) { + providers = g_list_remove_link(providers, provider); + g_free(provider->data); + g_list_free_1(provider); + } + g_list_free(providers); + } + } + + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, + "on_msg_get_rsc_providers: can not send the ret msg"); + } + ha_msg_del(ret); + + return HA_OK; +} + +int +on_msg_get_metadata(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + struct RAExecOps * RAExec = NULL; + const char* rtype = NULL; + const char* rclass = NULL; + const char* provider = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + rtype = ha_msg_value(msg, F_LRM_RTYPE); + rclass = ha_msg_value(msg, F_LRM_RCLASS); + provider = ha_msg_value(msg, F_LRM_RPROVIDER); + + lrmd_debug2(LOG_DEBUG + , "%s: the client [pid:%d] wants to get rsc metadata of %s::%s::%s." + , __FUNCTION__ + , client->pid + , lrm_str(rclass) + , lrm_str(provider) + , lrm_str(rtype)); + + ret = create_lrm_ret(HA_OK, 5); + CHECK_RETURN_OF_CREATE_LRM_RET; + + RAExec = g_hash_table_lookup(RAExecFuncs,rclass); + if (NULL == RAExec) { + lrmd_log(LOG_NOTICE + , "%s: can not find the class %s." + , __FUNCTION__ + , rclass); + } + else { + char* meta = RAExec->get_resource_meta(rtype,provider); + if (NULL != meta && strlen(meta) > 0) { + if (HA_OK != ha_msg_add(ret,F_LRM_METADATA, meta)) { + LOG_FAILED_TO_ADD_FIELD("metadata"); + } + g_free(meta); + } + else { + lrmd_log(LOG_WARNING + , "%s: empty metadata for %s::%s::%s." + , __FUNCTION__ + , lrm_str(rclass) + , lrm_str(provider) + , lrm_str(rtype)); + ha_msg_mod_int(ret, F_LRM_RET, HA_FAIL); + } + } + + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, + "on_msg_get_metadata: can not send the ret msg"); + } + ha_msg_del(ret); + + return HA_OK; +} +static void +add_rid_to_msg(gpointer key, gpointer value, gpointer user_data) +{ + char* rid = (char*)key; + struct ha_msg* msg = (struct ha_msg*)user_data; + if (HA_OK != cl_msg_list_add_string(msg,F_LRM_RID,rid)) { + LOG_FAILED_TO_ADD_FIELD("resource id"); + } +} +int +on_msg_get_all(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + lrmd_debug2(LOG_DEBUG + , "on_msg_get_all:client [%d] want to get all rsc information." + , client->pid); + + ret = create_lrm_ret(HA_OK, g_hash_table_size(resources) + 1); + CHECK_RETURN_OF_CREATE_LRM_RET; + + g_hash_table_foreach(resources, add_rid_to_msg, ret); + + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, "on_msg_get_all: can not send the ret msg"); + } + ha_msg_del(ret); + + return HA_OK; +} +int +on_msg_get_rsc(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + lrmd_rsc_t* rsc = NULL; + const char* id = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + id = ha_msg_value(msg, F_LRM_RID); + + lrmd_debug2(LOG_DEBUG + , "on_msg_get_rsc: the client [pid:%d] wants to get " + "the information of the resource [rsc_id: %s]" + , client->pid, lrmd_nullcheck(id)); + + rsc = lookup_rsc_by_msg(msg); + if (NULL == rsc) { + lrmd_debug2(LOG_DEBUG + , "on_msg_get_rsc: no rsc with id %s." + , lrmd_nullcheck(id)); + ret = create_lrm_ret(HA_FAIL, 1); + CHECK_RETURN_OF_CREATE_LRM_RET; + } + else { + ret = create_lrm_ret(HA_OK, 5); + CHECK_RETURN_OF_CREATE_LRM_RET; + + if (HA_OK != ha_msg_add(ret, F_LRM_RID, rsc->id) + || HA_OK != ha_msg_add(ret, F_LRM_RTYPE, rsc->type) + || HA_OK != ha_msg_add(ret, F_LRM_RCLASS, rsc->class)) { + ha_msg_del(ret); + lrmd_log(LOG_ERR, + "on_msg_get_rsc: failed to add fields to msg."); + return HA_FAIL; + } + if( rsc->provider ) { + if (HA_OK != ha_msg_add(ret, F_LRM_RPROVIDER, + rsc->provider)) { + ha_msg_del(ret); + LOG_FAILED_TO_ADD_FIELD("provider"); + return HA_FAIL; + } + } + + if ( rsc->params && + HA_OK!=ha_msg_add_str_table(ret,F_LRM_PARAM,rsc->params)) { + ha_msg_del(ret); + LOG_FAILED_TO_ADD_FIELD("parameter"); + return HA_FAIL; + } + + } + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, "on_msg_get_rsc: can not send the ret msg"); + } + ha_msg_del(ret); + + return HA_OK; +} + +int +on_msg_get_last_op(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + const char* op_type = NULL; + lrmd_rsc_t* rsc = NULL; + const char* rid = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + rid = ha_msg_value(msg, F_LRM_RID); + op_type = ha_msg_value(msg, F_LRM_OP); + + lrmd_debug2(LOG_DEBUG + , "on_msg_get_last_op:client %s[%d] want to get the information " + "regarding last %s op on %s" + , client->app_name, client->pid + , lrmd_nullcheck(op_type), lrmd_nullcheck(rid)); + + rsc = lookup_rsc_by_msg(msg); + if (NULL != rsc && NULL != op_type) { + GHashTable* table = g_hash_table_lookup(rsc->last_op_table + , client->app_name); + if (NULL != table ) { + lrmd_op_t* op = g_hash_table_lookup(table, op_type); + if (NULL != op) { + lrmd_debug(LOG_DEBUG + , "%s: will return op %s" + , __FUNCTION__ + , op_type); + + ret = op_to_msg(op); + if (NULL == ret) { + lrmd_log(LOG_ERR + , "%s: can't create a message with op_to_msg." + , __FUNCTION__); + + } else + if (HA_OK != ha_msg_add_int(ret + , F_LRM_OPCNT, 1)) { + LOG_FAILED_TO_ADD_FIELD("operation count"); + } + } + } + } + + if (NULL == ret) { + lrmd_log(LOG_ERR + , "%s: return ha_msg ret is null, will re-create it again." + , __FUNCTION__); + ret = create_lrm_ret(HA_OK, 1); + CHECK_RETURN_OF_CREATE_LRM_RET; + + if (HA_OK != ha_msg_add_int(ret, F_LRM_OPCNT, 0)) { + LOG_FAILED_TO_ADD_FIELD("operation count"); + } + + } + + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, "on_msg_get_last_op: can not send the ret msg"); + } + ha_msg_del(ret); + + return HA_OK; +} + +int +on_msg_del_rsc(lrmd_client_t* client, struct ha_msg* msg) +{ + lrmd_rsc_t* rsc = NULL; + const char* id = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + id = ha_msg_value(msg, F_LRM_RID); + lrmd_debug2(LOG_DEBUG + , "%s: client [%d] wants to delete rsc %s" + , __FUNCTION__, client->pid, lrmd_nullcheck(id)); + + rsc = lookup_rsc_by_msg(msg); + if (NULL == rsc) { + lrmd_log(LOG_ERR, "%s: no rsc with id %s.",__FUNCTION__,id); + return -1; + } + LRMAUDIT(); + (void)flush_all(&(rsc->repeat_op_list),0); + if( flush_all(&(rsc->op_list),0) ) { + set_rsc_removal_pending(rsc); + lrmd_log(LOG_INFO, "resource %s busy, removal pending", rsc->id); + LRMAUDIT(); + return HA_RSCBUSY; /* resource is busy, removal delayed */ + } + lrmd_rsc_destroy(rsc); + LRMAUDIT(); + return HA_OK; +} + +static int +prepare_failmsg(struct ha_msg* msg, int fail_rc, const char *fail_reason) +{ + call_id++; /* use the next id */ + if (HA_OK != ha_msg_mod(msg,F_LRM_OP,ASYNC_OP_NAME) + || HA_OK != ha_msg_add(msg,F_LRM_FAIL_REASON,fail_reason) + || HA_OK != ha_msg_mod_int(msg,F_LRM_ASYNCMON_RC,fail_rc) + || HA_OK != ha_msg_mod_int(msg,F_LRM_RC,fail_rc) + || HA_OK != ha_msg_mod_int(msg,F_LRM_OPSTATUS,(int)LRM_OP_DONE) + || HA_OK != ha_msg_mod_int(msg,F_LRM_CALLID,call_id) + || HA_OK != ha_msg_mod_int(msg,F_LRM_TIMEOUT,0) + || HA_OK != ha_msg_mod_int(msg,F_LRM_INTERVAL,0) + || HA_OK != ha_msg_mod_int(msg,F_LRM_TARGETRC,EVERYTIME) + || HA_OK != ha_msg_mod_int(msg,F_LRM_DELAY,0) + ) { + lrmd_log(LOG_ERR,"%s:%d: cannot add field to a message" + , __FUNCTION__, __LINE__); + return 1; + } + return 0; +} + +static void +async_notify(gpointer key, gpointer val, gpointer data) +{ + struct ha_msg* msg = (struct ha_msg*)data; + lrmd_client_t* client; + + client = lookup_client_by_name((char *)key); + if (!client) { + lrmd_log(LOG_INFO, + "%s: client %s not found, probably signed out", __FUNCTION__, (char *)key); + } else { + send_msg(msg, client); + } +} + +int +on_msg_fail_rsc(lrmd_client_t* client, struct ha_msg* msg) +{ + lrmd_rsc_t* rsc; + const char* id; + int fail_rc = -1; + const char *fail_reason; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + id = ha_msg_value(msg, F_LRM_RID); + lrmd_debug2(LOG_DEBUG + , "%s: client [%d] wants to fail rsc %s" + , __FUNCTION__, client->pid, lrmd_nullcheck(id)); + + rsc = lookup_rsc_by_msg(msg); + if (!rsc) { + lrmd_log(LOG_ERR, "%s: no resource with id %s." + , __FUNCTION__, lrmd_nullcheck(id)); + return HA_FAIL; + } + fail_reason = ha_msg_value(msg,F_LRM_FAIL_REASON); + if (!fail_reason || *fail_reason == '\0') { + fail_reason = DEFAULT_FAIL_REASON; + } + if (HA_OK != ha_msg_value_int(msg,F_LRM_ASYNCMON_RC,&fail_rc) || fail_rc <= 0) { + fail_rc = DEFAULT_FAIL_RC; + } + if (prepare_failmsg(msg,fail_rc,fail_reason)) + return HA_FAIL; + lrmd_log(LOG_WARNING + , "received asynchronous failure for rsc %s (rc: %d, reason: %s)" + , lrmd_nullcheck(id), fail_rc, fail_reason); + /* notify all clients from last_op table about the failure */ + if (rsc->last_op_table) { + g_hash_table_foreach(rsc->last_op_table,async_notify,msg); + } else { + lrmd_log(LOG_INFO + , "rsc to be failed %s had no operations so far", lrmd_nullcheck(id)); + send_msg(msg, client); + } + return HA_OK; +} + +static gboolean +free_str_hash_pair(gpointer key, gpointer value, gpointer user_data) +{ + GHashTable* table = (GHashTable*) value; + free(key); + g_hash_table_foreach_remove(table, free_str_op_pair, NULL); + g_hash_table_destroy(table); + return TRUE; +} + +static gboolean +free_str_op_pair(gpointer key, gpointer value, gpointer user_data) +{ + lrmd_op_t* op = (lrmd_op_t*)value; + + if (NULL == op) { + lrmd_log(LOG_ERR, "%s(): NULL op in op_pair(%s)" , __FUNCTION__ + , (const char *)key); + }else{ + lrmd_op_destroy(op); + } + return TRUE; +} + +int +on_msg_add_rsc(lrmd_client_t* client, struct ha_msg* msg) +{ + GList* node; + gboolean ra_type_exist = FALSE; + char* class = NULL; + lrmd_rsc_t* rsc = NULL; + const char* id = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + return_on_no_value(msg, F_LRM_RID,id); + + lrmd_debug(LOG_DEBUG + , "on_msg_add_rsc:client [%d] adds resource %s" + , client->pid, lrmd_nullcheck(id)); + + if (RID_LEN <= strlen(id)) { + lrmd_log(LOG_ERR, "on_msg_add_rsc: rsc_id is too long."); + return HA_FAIL; + } + + if (NULL != lookup_rsc(id)) { + lrmd_log(LOG_ERR, "on_msg_add_rsc: same id resource exists."); + return HA_FAIL; + } + + LRMAUDIT(); + rsc = lrmd_rsc_new(id, msg); + if (rsc == NULL) { + return HA_FAIL; + } + + ra_type_exist = FALSE; + for(node=g_list_first(ra_class_list); NULL!=node; node=g_list_next(node)){ + class = (char*)node->data; + if (0 == strncmp(class, rsc->class, MAX_CLASSNAMELEN)) { + ra_type_exist = TRUE; + break; + } + } + if (!ra_type_exist) { + lrmd_log(LOG_ERR + , "on_msg_add_rsc: RA class [%s] does not exist." + , rsc->class); + lrmd_rsc_destroy(rsc); + rsc = NULL; + LRMAUDIT(); + return HA_FAIL; + } + + rsc->last_op_done = NULL; + rsc->params = ha_msg_value_str_table(msg,F_LRM_PARAM); + rsc->last_op_table = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(resources, strdup(rsc->id), rsc); + + LRMAUDIT(); + return HA_OK; +} + +static int +cancel_op(GList** listp,int cancel_op_id) +{ + GList* node = NULL; + lrmd_op_t* op = NULL; + int rc = HA_FAIL; + + for( node = g_list_first(*listp) + ; node; node = g_list_next(node) ) { + op = (lrmd_op_t*)node->data; + if( op->call_id == cancel_op_id ) { + lrmd_log(LOG_INFO + ,"%s: %s cancelled" + , __FUNCTION__, op_info(op)); + rc = flush_op(op); + if( rc != HA_RSCBUSY && rc != HA_FAIL ) { + notify_client(op); /* send notification now */ + *listp = g_list_remove(*listp, op); + remove_op_history(op); + lrmd_op_destroy(op); + } + return rc; + } + } + return rc; +} + +int +on_msg_cancel_op(lrmd_client_t* client, struct ha_msg* msg) +{ + lrmd_rsc_t* rsc = NULL; + int cancel_op_id = 0; + int op_cancelled = HA_OK; + + LRMAUDIT(); + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + rsc = lookup_rsc_by_msg(msg); + if (NULL == rsc) { + lrmd_log(LOG_ERR, + "%s: no resource with such id.", __FUNCTION__); + return HA_FAIL; + } + + return_on_no_int_value(msg, F_LRM_CALLID, &cancel_op_id); + + lrmd_debug2(LOG_DEBUG + , "%s:client [pid:%d] cancel the operation [callid:%d]" + , __FUNCTION__ + , client->pid + , cancel_op_id); + + if( cancel_op(&(rsc->repeat_op_list), cancel_op_id) != HA_OK ) { + op_cancelled = cancel_op(&(rsc->op_list), cancel_op_id); + } + if( op_cancelled == HA_FAIL ) { + lrmd_log(LOG_INFO, "%s: no operation with id %d", + __FUNCTION__, cancel_op_id); + } else if( op_cancelled == HA_RSCBUSY ) { + lrmd_log(LOG_INFO, "%s: operation %d running, cancel pending", + __FUNCTION__, cancel_op_id); + } else { + lrmd_debug(LOG_DEBUG, "%s: operation %d cancelled", + __FUNCTION__, cancel_op_id); + } + LRMAUDIT(); + return op_cancelled; +} + +static gboolean +flush_all(GList** listp, int client_pid) +{ + GList* node = NULL; + lrmd_op_t* op = NULL; + gboolean rsc_busy = FALSE; + + node = g_list_first(*listp); + while( node ) { + op = (lrmd_op_t*)node->data; + if (client_pid && op->client_id != client_pid) { + node = g_list_next(node); + continue; /* not the client's operation */ + } + if( flush_op(op) == HA_RSCBUSY ) { + rsc_busy = TRUE; + node = g_list_next(node); + } else if (!client_pid || op->client_id == client_pid) { + node = *listp = g_list_remove(*listp, op); + remove_op_history(op); + lrmd_op_destroy(op); + } else { + node = g_list_next(node); + } + } + return rsc_busy; +} + +int +on_msg_flush_all(lrmd_client_t* client, struct ha_msg* msg) +{ + lrmd_rsc_t* rsc = NULL; + const char* id = NULL; + + LRMAUDIT(); + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + return_on_no_value(msg, F_LRM_RID,id); + rsc = lookup_rsc_by_msg(msg); + if (NULL == rsc) { + lrmd_log(LOG_ERR, + "%s: no resource with id %s.", __FUNCTION__,id); + LRMAUDIT(); + return -1; + } + + /* when a flush request arrived, flush all pending ops */ + lrmd_debug2(LOG_DEBUG + , "%s:client [%d] flush operations" + , __FUNCTION__, client->pid); + (void)flush_all(&(rsc->repeat_op_list),0); + if( flush_all(&(rsc->op_list),0) ) { + set_rsc_flushing_ops(rsc); /* resource busy */ + lrmd_log(LOG_INFO, "resource %s busy, all flush pending", rsc->id); + LRMAUDIT(); + return HA_RSCBUSY; + } + LRMAUDIT(); + return HA_OK; +} + +int +on_msg_perform_op(lrmd_client_t* client, struct ha_msg* msg) +{ + lrmd_rsc_t* rsc = NULL; + lrmd_op_t* op; + const char* id = NULL; + int timeout = 0; + int interval = 0; + int delay = 0; + + LRMAUDIT(); + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + return_on_no_value(msg, F_LRM_RID,id); + return_on_no_int_value(msg, F_LRM_INTERVAL, &interval); + return_on_no_int_value(msg, F_LRM_TIMEOUT, &timeout); + return_on_no_int_value(msg, F_LRM_DELAY, &delay); + + rsc = lookup_rsc_by_msg(msg); + if (NULL == rsc) { + lrmd_log(LOG_ERR, + "%s: no resource with such id.", __FUNCTION__); + return -1; + } + if( rsc_frozen(rsc) ) { + lrmd_log(LOG_NOTICE, "%s: resource %s is frozen, " + "no ops can run.", __FUNCTION__, rsc->id); + return -1; + } + + call_id++; + if( !(rsc->id) ) { + lrmd_debug(LOG_ERR + , "%s:%d: the resource id is NULL" + , __FUNCTION__, __LINE__); + return -1; + } + if (HA_OK != ha_msg_add_int(msg, F_LRM_CALLID, call_id)) { + LOG_FAILED_TO_ADD_FIELD("callid"); + return -1; + } + if (HA_OK !=ha_msg_mod(msg, F_LRM_APP, client->app_name)) { + LOG_FAILED_TO_ADD_FIELD("app_name"); + return -1; + } + + op = lrmd_op_new(); + if (op == NULL) { + return -1; + } + op->call_id = call_id; + op->client_id = client->pid; + op->rsc_id = strdup(rsc->id); + op->interval = interval; + op->delay = delay; + op->weight = no_child_count(rsc) ? 0 : 1; + + op->msg = ha_msg_copy(msg); + + if( ha_msg_value_int(msg,F_LRM_COPYPARAMS,&op->copyparams) == HA_OK + && op->copyparams ) { + lrmd_debug(LOG_DEBUG + , "%s:%d: copying parameters for rsc %s" + , __FUNCTION__, __LINE__,rsc->id); + if (rsc->params) { + free_str_table(rsc->params); + } + rsc->params = ha_msg_value_str_table(msg, F_LRM_PARAM); + } + + lrmd_debug2(LOG_DEBUG + , "%s: client [%d] want to add an operation %s on resource %s." + , __FUNCTION__ + , client->pid + , op_info(op) + , NULL!=op->rsc_id ? op->rsc_id : "#EMPTY#"); + + if ( 0 < op->delay ) { + op->repeat_timeout_tag = Gmain_timeout_add(op->delay + ,on_repeat_op_readytorun, op); + rsc->repeat_op_list = + g_list_append (rsc->repeat_op_list, op); + lrmd_debug(LOG_DEBUG + , "%s: an operation %s is added to the repeat " + "operation list for delay execution" + , __FUNCTION__ + , op_info(op)); + } else { + lrmd_debug(LOG_DEBUG + , "%s: add an operation %s to the operation list." + , __FUNCTION__ + , op_info(op)); + add_op_to_runlist(rsc,op); + } + + perform_op(rsc); + + LRMAUDIT(); + return call_id; +} + +static void +send_last_op(gpointer key, gpointer value, gpointer user_data) +{ + IPC_Channel* ch = NULL; + lrmd_op_t* op = NULL; + struct ha_msg* msg = NULL; + + ch = (IPC_Channel*)user_data; + op = (lrmd_op_t*)value; + msg = op_to_msg(op); + if (msg == NULL) { + lrmd_log(LOG_ERR, "send_last_op: failed to convert an operation " + "information to a ha_msg."); + return; + } + if (HA_OK != msg2ipcchan(msg, ch)) { + lrmd_log(LOG_ERR, "send_last_op: can not send a message."); + } + ha_msg_del(msg); +} + +int +on_msg_get_state(lrmd_client_t* client, struct ha_msg* msg) +{ + int op_count = 0; + lrmd_rsc_t* rsc = NULL; + GList* node; + struct ha_msg* ret = NULL; + lrmd_op_t* op = NULL; + struct ha_msg* op_msg = NULL; + const char* id = NULL; + GHashTable* last_ops = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + id = ha_msg_value(msg,F_LRM_RID); + lrmd_debug2(LOG_DEBUG + , "%s: client [%d] want to get the state of resource %s" + , __FUNCTION__, client->pid, lrmd_nullcheck(id)); + + rsc = lookup_rsc_by_msg(msg); + if (NULL == rsc) { + lrmd_log(LOG_ERR, "on_msg_get_state: no resource with id %s." + , lrmd_nullcheck(id)); + send_ret_msg(client->ch_cmd, HA_FAIL); + return HA_FAIL; + } + + ret = ha_msg_new(5); + if (NULL == ret) { + lrmd_log(LOG_ERR, "on_msg_get_state: can't create a ha_msg."); + return HA_FAIL; + } + /* add the F_LRM_STATE field */ + if (HA_OK != ha_msg_add_int(ret, F_LRM_STATE + , rsc->op_list ? LRM_RSC_BUSY : LRM_RSC_IDLE)) { + LOG_FAILED_TO_ADD_FIELD("state"); + ha_msg_del(ret); + return HA_FAIL; + } + lrmd_debug(LOG_DEBUG + , "on_msg_get_state:state of rsc %s is %s" + , lrmd_nullcheck(id) + , rsc->op_list ? "LRM_RSC_BUSY" : "LRM_RSC_IDLE" ); + /* calculate the count of ops being returned */ + last_ops = g_hash_table_lookup(rsc->last_op_table, client->app_name); + if (last_ops == NULL) { + op_count = g_list_length(rsc->op_list) + + g_list_length(rsc->repeat_op_list); + } + else { + op_count = g_hash_table_size(last_ops) + + g_list_length(rsc->op_list) + + g_list_length(rsc->repeat_op_list); + } + /* add the count of ops being returned */ + if (HA_OK != ha_msg_add_int(ret, F_LRM_OPCNT, op_count)) { + LOG_FAILED_TO_ADD_FIELD("operation count"); + ha_msg_del(ret); + return HA_FAIL; + } + /* send the first message to client */ + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, + "on_msg_get_state: can not send the ret message."); + ha_msg_del(ret); + return HA_FAIL; + } + ha_msg_del(ret); + + /* send the ops in last ops table */ + if(last_ops != NULL) { + g_hash_table_foreach(last_ops, send_last_op, client->ch_cmd); + } + + /* send the ops in op list */ + for(node = g_list_first(rsc->op_list) + ; NULL != node; node = g_list_next(node)){ + op = (lrmd_op_t*)node->data; + op_msg = op_to_msg(op); + if (NULL == op_msg) { + lrmd_log(LOG_ERR, + "on_msg_get_state: failed to make a message " + "from a operation: %s", op_info(op)); + continue; + } + if (HA_OK != msg2ipcchan(op_msg, client->ch_cmd)) { + lrmd_log(LOG_ERR, + "on_msg_get_state: failed to send a message."); + } + ha_msg_del(op_msg); + } + + /* send the ops in repeat op list */ + for(node = g_list_first(rsc->repeat_op_list) + ; NULL != node; node = g_list_next(node)){ + op = (lrmd_op_t*)node->data; + op_msg = op_to_msg(op); + if (NULL == op_msg) { + lrmd_log(LOG_ERR, + "on_msg_get_state: failed to make a message " + "from a operation: %s", op_info(op)); + continue; + } + if (HA_OK != msg2ipcchan(op_msg, client->ch_cmd)) { + lrmd_log(LOG_ERR, + "on_msg_get_state: failed to send a message."); + } + ha_msg_del(op_msg); + } + return HA_OK; +} + +#define safe_len(s) (s ? strlen(s) : 0) + +static char * +lrm_concat(const char *prefix, const char *suffix, char join) +{ + int len = 2; + char *new_str = NULL; + len += safe_len(prefix); + len += safe_len(suffix); + + new_str = malloc(sizeof(char)*len); + if (NULL == new_str) { + lrmd_log(LOG_ERR,"%s:%d: malloc failed" + , __FUNCTION__, __LINE__); + return NULL; + } + + memset(new_str, 0, len); + sprintf(new_str, "%s%c%s", prefix?prefix:"", join, suffix?suffix:""); + new_str[len-1] = 0; + return new_str; +} + +/* /////////////////////op functions////////////////////// */ + +#define mk_op_id(op,id) do { \ + const char *op_type = ha_msg_value(op->msg, F_LRM_OP); \ + const char *op_interval = ha_msg_value(op->msg, F_LRM_INTERVAL); \ + id = lrm_concat(op_type, op_interval, '_'); \ +} while(0) + +/* find the last operation for the client + * replace it with the new one (if requested) + */ +static void +replace_last_op(lrmd_client_t* client, lrmd_rsc_t* rsc, lrmd_op_t* op) +{ + char *op_hash_key; + GHashTable *client_last_op; + lrmd_op_t *old_op, *new_op; + + if (!client || !rsc || !op) + return; + client_last_op = g_hash_table_lookup(rsc->last_op_table, client->app_name); + if (!client_last_op) { + lrmd_debug2(LOG_DEBUG + , "%s: new last op table for client %s" + , __FUNCTION__, client->app_name); + client_last_op = g_hash_table_new_full( g_str_hash + , g_str_equal, free, NULL); + g_hash_table_insert(rsc->last_op_table + , (gpointer)strdup(client->app_name) + , (gpointer)client_last_op); + } + mk_op_id(op,op_hash_key); + old_op = (lrmd_op_t*)g_hash_table_lookup(client_last_op, op_hash_key); + + /* make a copy of op and insert it into client_last_op */ + if (!(new_op = lrmd_op_copy(op))) { + lrmd_log(LOG_ERR, "%s:%d out of memory" + , __FUNCTION__, __LINE__); + } + if (old_op) { + lrmd_debug2(LOG_DEBUG + , "%s: replace last op %s for client %s" + , __FUNCTION__, op_hash_key, client->app_name); + g_hash_table_replace(client_last_op,op_hash_key,(gpointer)new_op); + lrmd_op_destroy(old_op); + } else { + lrmd_debug2(LOG_DEBUG + , "%s: add last op %s for client %s" + , __FUNCTION__, op_hash_key, client->app_name); + g_hash_table_insert(client_last_op,op_hash_key,(gpointer)new_op); + } +} + +static int +record_op_completion(lrmd_rsc_t* rsc, lrmd_op_t* op) +{ + lrmd_client_t* client; + + LRMAUDIT(); + /*save the op in the last op finished*/ + if (rsc->last_op_done != NULL) { + lrmd_op_destroy(rsc->last_op_done); + } + if (!(rsc->last_op_done = lrmd_op_copy(op))) { + lrmd_log(LOG_ERR, "%s:%d out of memory" + , __FUNCTION__, __LINE__); + return 1; + } + rsc->last_op_done->repeat_timeout_tag = (guint)0; + + client = lookup_client(op->client_id); + if (!client) { + lrmd_log(LOG_INFO, "%s: cannot record %s: the client is gone" + , __FUNCTION__, small_op_info(op)); + LRMAUDIT(); + return 1; + } + /* insert (or replace) the new op in last_op_table for the client */ + replace_last_op(client,rsc,op); + LRMAUDIT(); + return 0; +} + +static void +to_repeatlist(lrmd_rsc_t* rsc, lrmd_op_t* op) +{ + lrmd_op_t *repeat_op; + + if (!(repeat_op = lrmd_op_copy(op))) { + lrmd_log(LOG_ERR, "%s:%d out of memory" + , __FUNCTION__, __LINE__); + } + reset_timestamps(repeat_op); + repeat_op->is_copy = FALSE; + repeat_op->repeat_timeout_tag = + Gmain_timeout_add(op->interval, + on_repeat_op_readytorun, repeat_op); + rsc->repeat_op_list = + g_list_append (rsc->repeat_op_list, repeat_op); + lrmd_debug2(LOG_DEBUG + , "%s: repeat %s is added to repeat op list to wait" + , __FUNCTION__, op_info(op)); +} + +static void +remove_op_history(lrmd_op_t* op) +{ + lrmd_client_t* client = lookup_client(op->client_id); + lrmd_rsc_t* rsc = NULL; + char *op_id, *last_op_id; + lrmd_op_t* old_op = NULL; + GHashTable* client_last_op = NULL; + + LRMAUDIT(); + if( !(rsc = lookup_rsc(op->rsc_id)) ) { + return; + } + lrmd_debug2(LOG_DEBUG, "%s: remove history of the op %s" + ,__FUNCTION__, op_info(op)); + mk_op_id(op,op_id); + if (rsc->last_op_done != NULL ) { + mk_op_id(rsc->last_op_done,last_op_id); + if( !strcmp(op_id,last_op_id) ) { + lrmd_debug2(LOG_DEBUG, "%s: remove history of the last op done %s" + ,__FUNCTION__, op_info(rsc->last_op_done)); + lrmd_op_destroy(rsc->last_op_done); + rsc->last_op_done = NULL; + } + free(last_op_id); + } + if( client && + (client_last_op = g_hash_table_lookup(rsc->last_op_table + , client->app_name)) ) { + lrmd_debug2(LOG_DEBUG, "%s: found client %s in the last op table" + ,__FUNCTION__, client->app_name); + old_op = g_hash_table_lookup(client_last_op, op_id); + if (old_op) { + g_hash_table_remove(client_last_op, op_id); + lrmd_debug2(LOG_DEBUG, "%s: remove history of the client's last %s" + ,__FUNCTION__, op_info(old_op)); + lrmd_op_destroy(old_op); + } + } + free(op_id); + LRMAUDIT(); +} + +static void +add_op_to_runlist(lrmd_rsc_t* rsc, lrmd_op_t* op) +{ + op->t_addtolist = time_longclock(); + rsc->op_list = g_list_append(rsc->op_list, op); + if (g_list_length(rsc->op_list) >= 4) { + lrmd_log(LOG_WARNING + , "operations list for %s is suspiciously" + " long [%d]" + , rsc->id + , g_list_length(rsc->op_list)); + lrmd_rsc_dump(rsc->id, "rsc->op_list: too many ops"); + } +} + +/* 1. this function sends a message to the client: + * a) on operation instance exit using the callback channel + * b) in case a client requested that operation to be cancelled, + * using the command channel + * c) in case a client requested a resource removal or flushing + * all ops and this is the last operation that finished, again + * using the command channel + * 2. if the op was not cancelled: + * a) it is copied to the last_op_done field of rsc + * b) if it's a repeating op, it is put in the repeat_op_list + * c) the outcome is recorded for future reference + * 3. op is destroyed and removed from the op_list + */ +int +on_op_done(lrmd_rsc_t* rsc, lrmd_op_t* op) +{ + int rc = HA_OK; + int target_rc, last_rc, op_rc; + int rc_changed; + op_status_t op_status; + + LRMAUDIT(); + CHECK_ALLOCATED(op, "op", HA_FAIL ); + if (op->exec_pid == 0) { + lrmd_log(LOG_ERR, "%s: op->exec_pid == 0",__FUNCTION__); + return HA_FAIL; + } + op->t_done = time_longclock(); + + if (debug_level >= 2) { + lrmd_debug(LOG_DEBUG, "%s: %s",__FUNCTION__, op_info(op)); + lrmd_op_dump(op, __FUNCTION__); + } + + return_on_no_int_value(op->msg,F_LRM_TARGETRC,&target_rc); + return_on_no_int_value(op->msg,F_LRM_OPSTATUS,(int *)&op_status); + + last_rc = op_rc = -1; /* set all rc to -1 */ + ha_msg_value_int(op->msg,F_LRM_RC,&op_rc); + ha_msg_value_int(op->msg,F_LRM_LASTRC,&last_rc); + rc_changed = ( + op_status == LRM_OP_DONE + && op_rc != -1 + && ((last_rc == -1) || (last_rc != op_rc)) + ); + if (rc_changed) { + if (HA_OK != ha_msg_mod_int(op->msg, F_LRM_LASTRC, op_rc)) { + lrmd_log(LOG_ERR,"%s: cannot save status to msg",__FUNCTION__); + return HA_FAIL; + } + op->t_rcchange = op->t_perform; + } + if (store_timestamps(op)) + return HA_FAIL; + + /* remove the op from op_list */ + rsc->op_list = g_list_remove(rsc->op_list,op); + lrmd_debug2(LOG_DEBUG + , "%s:%s is removed from op list" + , __FUNCTION__, op_info(op)); + + if (!op->is_cancelled) { + if( !record_op_completion(rsc,op) ) { /*record the outcome of the op */ + if (op->interval) /* copy op to the repeat list */ + to_repeatlist(rsc,op); + } + } else { + if (HA_OK != ha_msg_mod_int(op->msg,F_LRM_OPSTATUS,(int)LRM_OP_CANCELLED)) { + LOG_FAILED_TO_ADD_FIELD(F_LRM_OPSTATUS); + return HA_FAIL; + } + op_status = LRM_OP_CANCELLED; + remove_op_history(op); + } + + if (rsc_removal_pending(rsc)) { + if (HA_OK != ha_msg_add_int(op->msg,F_LRM_RSCDELETED,1)) { + LOG_FAILED_TO_ADD_FIELD(F_LRM_RSCDELETED); + } + } + if (op_status != LRM_OP_DONE + || (op_rc == -1) + || (op_rc == target_rc) + || (target_rc == EVERYTIME) + || ((target_rc == CHANGED) && rc_changed) + || rsc_removal_pending(rsc) + ) { + notify_client(op); + } + lrmd_op_destroy(op); + if( !rsc->op_list ) { + if( rsc_removal_pending(rsc) ) { + lrmd_log(LOG_INFO, "late removal of resource %s", rsc->id); + lrmd_rsc_destroy(rsc); + rc = -1; /* let the caller know that the rsc is gone */ + } else { + rsc_reset_state(rsc); + } + } + LRMAUDIT(); + if (shutdown_in_progress && can_shutdown()) { + lrm_shutdown(); + } + return rc; +} + +/* + * an operation is flushed only in case there is + * no process running initiated by this operation + * NB: the caller has to destroy the operation itself + */ +int +flush_op(lrmd_op_t* op) +{ + CHECK_ALLOCATED(op, "op", HA_FAIL ); + if (op->exec_pid == 0) { + lrmd_debug(LOG_ERR, "%s: op->exec_pid == 0",__FUNCTION__); + return HA_FAIL; + } + + if (HA_OK != ha_msg_mod_int(op->msg, F_LRM_RC, HA_FAIL)) { + LOG_FAILED_TO_ADD_FIELD("F_LRM_RC"); + return HA_FAIL; + } + + if( op->exec_pid == -1 ) { + if (HA_OK != ha_msg_mod_int(op->msg,F_LRM_OPSTATUS,(int)LRM_OP_CANCELLED)){ + LOG_FAILED_TO_ADD_FIELD("opstatus"); + return HA_FAIL; + } + return HA_OK; + } else { + op->is_cancelled = TRUE; /* mark the op as cancelled */ + lrmd_log(LOG_INFO, "%s: process for %s still " + "running, flush delayed" + ,__FUNCTION__,small_op_info(op)); + return HA_RSCBUSY; + } +} + +/* Resume the execution of ops of the resource */ +static gboolean +rsc_execution_freeze_timeout(gpointer data) +{ + lrmd_rsc_t* rsc = (lrmd_rsc_t*)data; + + if (rsc == NULL) { + return FALSE; + } + + if (rsc->delay_timeout > 0) { + rsc->delay_timeout = (guint)0; + } + + perform_op(rsc); + + return FALSE; +} + +/* this function gets the first op in the rsc op list and execute it*/ +int +perform_op(lrmd_rsc_t* rsc) +{ + GList* node = NULL; + lrmd_op_t* op = NULL; + + LRMAUDIT(); + CHECK_ALLOCATED(rsc, "resource", HA_FAIL); + if (shutdown_in_progress && can_shutdown()) { + lrm_shutdown(); + } + + if (rsc_frozen(rsc)) { + lrmd_log(LOG_INFO,"%s: resource %s is frozen, " + "no ops allowed to run" + , __FUNCTION__, rsc->id); + return HA_OK; + } + + if (NULL == rsc->op_list) { + lrmd_debug2(LOG_DEBUG,"%s: no op to perform?", __FUNCTION__); + return HA_OK; + } + + node = g_list_first(rsc->op_list); + while (NULL != node) { + op = node->data; + if (-1 != op->exec_pid) { + if (!g_list_next(node)) { + /* this is the only operation, no need to do + * anything further */ + break; + } + lrmd_log(LOG_INFO, "%s:%d: %s for rsc is already running." + , __FUNCTION__, __LINE__, op_info(op)); + if( rsc->delay_timeout > 0 ) { + lrmd_log(LOG_INFO + , "%s:%d: operations on resource %s already delayed" + , __FUNCTION__, __LINE__, lrm_str(rsc->id)); + } else { + lrmd_log(LOG_INFO + , "%s:%d: postponing " + "all ops on resource %s by %d ms" + , __FUNCTION__, __LINE__ + , lrm_str(rsc->id), retry_interval); + rsc->delay_timeout = Gmain_timeout_add(retry_interval + , rsc_execution_freeze_timeout, rsc); + } + break; + } + if (op->weight && child_count >= max_child_count) { + if ((int)rsc->delay_timeout > 0) { + lrmd_log(LOG_INFO + , "%s:%d: max_child_count (%d) reached and operations on resource %s already delayed" + , __FUNCTION__, __LINE__, max_child_count, lrm_str(rsc->id)); + } else { + lrmd_debug(LOG_NOTICE + , "max_child_count (%d) reached, postponing " + "execution of %s by %d ms" + , max_child_count, op_info(op), retry_interval); + rsc->delay_timeout = Gmain_timeout_add(retry_interval + , rsc_execution_freeze_timeout, rsc); + } + break; + } + + if (HA_OK != perform_ra_op(op)) { + lrmd_log(LOG_ERR + , "unable to perform_ra_op on %s" + , op_info(op)); + if (HA_OK != ha_msg_add_int(op->msg, F_LRM_OPSTATUS, + LRM_OP_ERROR)) { + LOG_FAILED_TO_ADD_FIELD("opstatus"); + } + on_op_done(rsc,op); + node = g_list_first(rsc->op_list); + } + else { + break; + } + } + + LRMAUDIT(); + return HA_OK; +} + +static int +store_timestamps(lrmd_op_t* op) +{ + struct ha_msg* msg = op->msg; + longclock_t now = time_longclock(), /* tm2unix() needs this */ + exec_time = zero_longclock, + queue_time = zero_longclock; + + if (op->t_perform) { + queue_time = + longclockto_ms(sub_longclock(op->t_perform,op->t_addtolist)); + if (op->t_done) { + exec_time = + longclockto_ms(sub_longclock(op->t_done,op->t_perform)); + } + } + if ((HA_OK!=ha_msg_mod_ul(msg,F_LRM_T_RUN,tm2unix(op->t_perform))) + || (HA_OK!=ha_msg_mod_ul(msg,F_LRM_T_RCCHANGE,tm2unix(op->t_rcchange))) + || (HA_OK!=ha_msg_mod_ul(msg,F_LRM_EXEC_TIME,exec_time)) + || (HA_OK!=ha_msg_mod_ul(msg,F_LRM_QUEUE_TIME,queue_time)) + ) { + lrmd_log(LOG_ERR,"%s: can not save timestamps to msg",__FUNCTION__); + return 1; + } + return 0; +} + +static void +reset_timestamps(lrmd_op_t* op) +{ + op->t_perform = zero_longclock; + op->t_done = zero_longclock; + cl_msg_remove(op->msg, F_LRM_T_RUN); + cl_msg_remove(op->msg, F_LRM_T_RCCHANGE); + cl_msg_remove(op->msg, F_LRM_EXEC_TIME); + cl_msg_remove(op->msg, F_LRM_QUEUE_TIME); +} + +struct ha_msg* +op_to_msg(lrmd_op_t* op) +{ + struct ha_msg* msg = NULL; + + CHECK_ALLOCATED(op, "op", NULL); + if (op->exec_pid == 0) { + lrmd_log(LOG_ERR, "%s: op->exec_pid is 0",__FUNCTION__); + return NULL; + } + msg = ha_msg_copy(op->msg); + if (NULL == msg) { + lrmd_log(LOG_ERR,"%s: can not copy the msg",__FUNCTION__); + return NULL; + } + if ((HA_OK!=ha_msg_mod_int(msg,F_LRM_CALLID,op->call_id))) { + lrmd_log(LOG_ERR,"%s: can not save F_LRM_CALLID to msg",__FUNCTION__); + ha_msg_del(msg); + msg = NULL; + } + return msg; +} + +/* //////////////////////////////RA wrap funcs/////////////////////////////////// */ +int +perform_ra_op(lrmd_op_t* op) +{ + int stdout_fd[2]; + int stderr_fd[2]; + pid_t pid; + int timeout; + struct RAExecOps * RAExec = NULL; + const char* op_type = NULL; + GHashTable* params = NULL; + GHashTable* op_params = NULL; + lrmd_rsc_t* rsc = NULL; + ra_pipe_op_t * rapop; + + LRMAUDIT(); + CHECK_ALLOCATED(op, "op", HA_FAIL); + rsc = (lrmd_rsc_t*)lookup_rsc(op->rsc_id); + CHECK_ALLOCATED(rsc, "rsc", HA_FAIL); + + if ( pipe(stdout_fd) < 0 ) { + cl_perror("%s::%d: pipe", __FUNCTION__, __LINE__); + } + + if ( pipe(stderr_fd) < 0 ) { + cl_perror("%s::%d: pipe", __FUNCTION__, __LINE__); + } + + if (op->exec_pid == 0) { + lrmd_log(LOG_ERR, "%s::%d: op->exec_pid == 0.", __FUNCTION__, __LINE__); + return HA_FAIL; + } + + op_type = ha_msg_value(op->msg, F_LRM_OP); + op->t_perform = time_longclock(); + check_queue_duration(op); + + if(HA_OK != ha_msg_value_int(op->msg, F_LRM_TIMEOUT, &timeout)){ + timeout = 0; + lrmd_log(LOG_ERR,"%s::%d: failed to get timeout for %s" + , __FUNCTION__, __LINE__, small_op_info(op)); + } + + if( return_to_orig_privs() ) { + cl_perror("%s::%d: failed to raise privileges" + , __FUNCTION__, __LINE__); + } + switch(pid=fork()) { + case -1: + cl_perror("%s::%d: fork", __FUNCTION__, __LINE__); + close(stdout_fd[0]); + close(stdout_fd[1]); + close(stderr_fd[0]); + close(stderr_fd[1]); + if( return_to_dropped_privs() ) { + cl_perror("%s::%d: failed to drop privileges" + , __FUNCTION__, __LINE__); + } + return HA_FAIL; + + default: /* Parent */ + child_count += op->weight; + NewTrackedProc(pid, 1 + , debug_level ? + ((op->interval && !is_logmsg_due(op)) ? PT_LOGNORMAL : PT_LOGVERBOSE) : PT_LOGNONE + , op, &ManagedChildTrackOps); + + if (!op->interval || is_logmsg_due(op)) { /* log non-repeating ops */ + lrmd_log(LOG_INFO,"rsc:%s %s[%d] (pid %d)", + rsc->id,probe_str(op,op_type),op->call_id,pid); + } else { + lrmd_debug(LOG_DEBUG,"rsc:%s %s[%d] (pid %d)", + rsc->id,op_type,op->call_id,pid); + } + close(stdout_fd[1]); + close(stderr_fd[1]); + rapop = ra_pipe_op_new(stdout_fd[0], stderr_fd[0], op); + op->rapop = rapop; + op->exec_pid = pid; + if (0 < timeout ) { + + /* Wait 'timeout' ms then send SIGTERM */ + /* allow for extra 15 seconds for stonith, + * because stonithd handles its children with the + * same timeout; in this case the lrmd child + * should never timeout, but return the timeout + * reported by stonithd + */ + op->killseq[0].mstimeout = timeout + + (!strcmp(rsc->class,"stonith") ? 15000 : 0); + op->killseq[0].signalno = SIGTERM; + + /* Wait 5 seconds then send SIGKILL */ + op->killseq[1].mstimeout = 5000; + op->killseq[1].signalno = SIGKILL; + + /* Wait 5 more seconds then moan and complain */ + op->killseq[2].mstimeout = 5000; + op->killseq[2].signalno = 0; + + SetTrackedProcTimeouts(pid, op->killseq); + } + if( return_to_dropped_privs() ) { + lrmd_log(LOG_WARNING,"%s::%d: failed to drop privileges: %s" + , __FUNCTION__, __LINE__, strerror(errno)); + } + + if ( rapop == NULL) { + return HA_FAIL; + } + LRMAUDIT(); + return HA_OK; + + case 0: /* Child */ +#ifdef DEFAULT_REALTIME_POLICY + if (sched_getscheduler(0) != SCHED_OTHER) { + struct sched_param sp; + lrmd_debug(LOG_DEBUG, + "perform_ra_op: resetting scheduler class to SCHED_OTHER"); + sp.sched_priority = 0; + if (sched_setscheduler(0, SCHED_OTHER, &sp) == -1) + cl_perror("%s::%d: sched_setscheduler", + __FUNCTION__, __LINE__); + } +#endif + /* Man: The call setpgrp() is equivalent to setpgid(0,0) + * _and_ compiles on BSD variants too + * need to investigate if it works the same too. + */ + setpgid(0,0); + close(stdout_fd[0]); + close(stderr_fd[0]); + if (STDOUT_FILENO != stdout_fd[1]) { + if (dup2(stdout_fd[1], STDOUT_FILENO)!=STDOUT_FILENO) { + cl_perror("%s::%d: dup2" + , __FUNCTION__, __LINE__); + } + close(stdout_fd[1]); + } + if (STDERR_FILENO != stderr_fd[1]) { + if (dup2(stderr_fd[1], STDERR_FILENO)!=STDERR_FILENO) { + cl_perror("%s::%d: dup2", __FUNCTION__, __LINE__); + } + close(stderr_fd[1]); + } + RAExec = g_hash_table_lookup(RAExecFuncs,rsc->class); + if (NULL == RAExec) { + close(stdout_fd[1]); + close(stderr_fd[1]); + lrmd_log(LOG_ERR,"%s::%d: can't find RAExec for class %s" + , __FUNCTION__, __LINE__, rsc->class); + exit(EXECRA_EXEC_UNKNOWN_ERROR); + } + + /*should we use logging daemon or not in script*/ + setenv(HALOGD, cl_log_get_uselogd()?"yes":"no",1); + + /* Name of the resource and some others also + * need to be passed in. Maybe pass through the + * entire lrm_op_t too? */ + lrmd_debug2(LOG_DEBUG + , "perform_ra_op:calling RA plugin to perform %s, pid: [%d]" + , op_info(op), getpid()); + + op_params = ha_msg_value_str_table(op->msg, F_LRM_PARAM); + params = merge_str_tables(rsc->params,op_params); + if (op_params) { + free_str_table(op_params); + op_params = NULL; + } + + if (replace_secret_params(rsc->id, params) < 0) { + /* replacing secrets failed! */ + if (!strcmp(op_type,"stop")) { + /* don't fail on stop! */ + lrmd_log(LOG_INFO + , "%s:%d: proceeding with the stop operation for %s" + , __FUNCTION__, __LINE__, rsc->id); + } else { + lrmd_log(LOG_ERR + , "%s:%d: failed to get secrets for %s, " + "considering resource not configured" + , __FUNCTION__, __LINE__, rsc->id); + exit(EXECRA_NOT_CONFIGURED); + } + } + RAExec->execra (rsc->id, + rsc->type, + rsc->provider, + op_type, + timeout, + params); + + /* execra should never return. */ + exit(EXECRA_EXEC_UNKNOWN_ERROR); + + } + lrmd_log(LOG_ERR, "perform_ra_op: end(impossible)."); + return HA_OK; +} + +static void +on_ra_proc_registered(ProcTrack* p) +{ +} + +/* Handle one of our ra child processes finished*/ +static void +on_ra_proc_finished(ProcTrack* p, int status, int signo, int exitcode +, int waslogged) +{ + lrmd_op_t* op = NULL; + lrmd_rsc_t* rsc = NULL; + struct RAExecOps * RAExec = NULL; + const char* op_type; + int rc = EXECRA_EXEC_UNKNOWN_ERROR; + int ret; + int op_status; + + LRMAUDIT(); + + CHECK_ALLOCATED(p, "ProcTrack p", ); + op = proctrack_data(p); + + child_count -= op->weight; + if (child_count < 0) { + lrmd_log(LOG_ERR, "%s:%d: child count is less than zero: %d" + , __FUNCTION__, __LINE__, child_count); + child_count = 0; + } + + lrmd_debug2(LOG_DEBUG, "on_ra_proc_finished: accessing the op whose " + "address is %p", op); + CHECK_ALLOCATED(op, "op", ); + if (op->exec_pid == 0) { + lrmd_log(LOG_ERR, "on_ra_proc_finished: the op was freed."); + dump_data_for_debug(); + return; + } + RemoveTrackedProcTimeouts(op->exec_pid); + op->exec_pid = -1; + + rsc = lookup_rsc(op->rsc_id); + if (rsc == NULL) { + lrmd_log(LOG_ERR, "%s: the rsc (id=%s) does not exist" + , __FUNCTION__, lrm_str(op->rsc_id)); + lrmd_op_dump(op, __FUNCTION__); + lrmd_dump_all_resources(); + /* delete the op */ + lrmd_op_destroy(op); + reset_proctrack_data(p); + LRMAUDIT(); + return; + } + + RAExec = g_hash_table_lookup(RAExecFuncs,rsc->class); + if (NULL == RAExec) { + lrmd_log(LOG_ERR,"on_ra_proc_finished: can not find RAExec for" + " resource class <%s>", rsc->class); + dump_data_for_debug(); + return; + } + + op_type = ha_msg_value(op->msg, F_LRM_OP); + + if ( (NULL == strchr(op->first_line_ra_stdout, '\n')) + && (0==STRNCMP_CONST(rsc->class, "heartbeat")) + && ( (0==STRNCMP_CONST(op_type, "monitor")) + ||(0==STRNCMP_CONST(op_type, "status"))) ) { + if ( ( op->rapop != NULL ) + && (op->rapop->ra_stdout_fd >= 0) ) { + handle_pipe_ra_stdout(op->rapop->ra_stdout_fd + , op->rapop); + } else { + lrmd_log(LOG_WARNING, "There is something wrong: the " + "first line isn't read in. Maybe the heartbeat " + "does not ouput string correctly for status " + "operation. Or the code (myself) is wrong."); + } + } + + if( signo ) { + if( proctrack_timedout(p) ) { + lrmd_log(LOG_WARNING, "%s: pid %d timed out" + , small_op_info(op), proctrack_pid(p)); + op_status = LRM_OP_TIMEOUT; + } else { + op_status = LRM_OP_ERROR; + } + } else { + rc = RAExec->map_ra_retvalue(exitcode, op_type + , op->first_line_ra_stdout); + if (!op->interval || is_logmsg_due(op) || debug_level > 0) { /* log non-repeating ops */ + if (rc == exitcode) { + lrmd_log(LOG_INFO + , "%s: pid %d exited with" + " return code %d", small_op_info(op), proctrack_pid(p), rc); + }else{ + lrmd_log(LOG_INFO + , "%s: pid %d exited with" + " return code %d (mapped from %d)" + , small_op_info(op), proctrack_pid(p), rc, exitcode); + } + } + if (EXECRA_EXEC_UNKNOWN_ERROR == rc || EXECRA_NO_RA == rc) { + op_status = LRM_OP_ERROR; + lrmd_log(LOG_CRIT + , "on_ra_proc_finished: the exit code indicates a problem."); + } else { + op_status = LRM_OP_DONE; + } + } + if (op->interval && is_logmsg_due(op)) { + op->t_lastlogmsg = time_longclock(); + } + if (HA_OK != + ha_msg_mod_int(op->msg, F_LRM_OPSTATUS, op_status)) { + LOG_FAILED_TO_ADD_FIELD("opstatus"); + return ; + } + if (HA_OK != ha_msg_mod_int(op->msg, F_LRM_RC, rc)) { + LOG_FAILED_TO_ADD_FIELD("F_LRM_RC"); + return ; + } + + if ( 0 < strlen(op->first_line_ra_stdout) ) { + if (NULL != cl_get_string(op->msg, F_LRM_DATA)) { + cl_msg_remove(op->msg, F_LRM_DATA); + } + ret = ha_msg_add(op->msg, F_LRM_DATA, op->first_line_ra_stdout); + if (HA_OK != ret) { + LOG_FAILED_TO_ADD_FIELD("data"); + } + } + + if (on_op_done(rsc,op) >= 0) { + perform_op(rsc); + } + reset_proctrack_data(p); + LRMAUDIT(); +} + +/* Handle the death of one of our managed child processes */ +static const char * +on_ra_proc_query_name(ProcTrack* p) +{ + static char proc_name[MAX_PROC_NAME]; + lrmd_op_t* op = NULL; + lrmd_rsc_t* rsc = NULL; + const char* op_type = NULL; + + LRMAUDIT(); + op = (lrmd_op_t*)(proctrack_data(p)); + if (NULL == op || op->exec_pid == 0) { + return "*unknown*"; + } + + op_type = ha_msg_value(op->msg, F_LRM_OP); + rsc = lookup_rsc(op->rsc_id); + if (rsc == NULL) { + snprintf(proc_name + , MAX_PROC_NAME + , "unknown rsc(%s):%s maybe deleted" + , op->rsc_id, op_type); + }else { + snprintf(proc_name, MAX_PROC_NAME, "%s:%s", rsc->id, op_type); + } + LRMAUDIT(); + return proc_name; +} + +static int +get_lrmd_param(const char *name, char *value, int maxstring) +{ + if (!name) { + lrmd_log(LOG_ERR, "%s: empty name", __FUNCTION__); + return HA_FAIL; + } + if (!strcmp(name,"max-children")) { + snprintf(value, maxstring, "%d", max_child_count); + return HA_OK; + } else { + lrmd_log(LOG_ERR, "%s: unknown lrmd parameter %s", __FUNCTION__, name); + return HA_FAIL; + } +} + +static int +set_lrmd_param(const char *name, const char *value) +{ + int ival; + + if (!name) { + lrmd_log(LOG_ERR, "%s: empty name", __FUNCTION__); + return HA_FAIL; + } + if (!value) { + lrmd_log(LOG_ERR, "%s: empty value", __FUNCTION__); + return HA_FAIL; + } + if (!strcmp(name,"max-children")) { + ival = atoi(value); + if (ival <= 0) { + lrmd_log(LOG_ERR, "%s: invalid value for lrmd parameter %s" + , __FUNCTION__, name); + return HA_FAIL; + } else if (ival == max_child_count) { + lrmd_log(LOG_INFO, "max-children already set to %d", ival); + return HA_OK; + } + lrmd_log(LOG_INFO, "setting max-children to %d", ival); + max_child_count = ival; + return HA_OK; + } else { + lrmd_log(LOG_ERR, "%s: unknown lrmd parameter %s" + , __FUNCTION__, name); + return HA_FAIL; + } +} + +int +on_msg_set_lrmd_param(lrmd_client_t* client, struct ha_msg* msg) +{ + const char *name, *value; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + name = ha_msg_value(msg,F_LRM_LRMD_PARAM_NAME); + value = ha_msg_value(msg,F_LRM_LRMD_PARAM_VAL); + if (!name || !value) { + lrmd_log(LOG_ERR, "%s: no parameter defined" + , __FUNCTION__); + return HA_FAIL; + } + return set_lrmd_param(name,value); +} + +int +on_msg_get_lrmd_param(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + const char *name; + char value[MAX_NAME_LEN]; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + ret = create_lrm_ret(HA_OK, 1); + CHECK_RETURN_OF_CREATE_LRM_RET; + + name = ha_msg_value(msg,F_LRM_LRMD_PARAM_NAME); + if (get_lrmd_param(name, value, MAX_NAME_LEN) != HA_OK) { + return HA_FAIL; + } + if (HA_OK != ha_msg_add(ret, F_LRM_LRMD_PARAM_VAL, value)) { + ha_msg_del(ret); + LOG_FAILED_TO_ADD_FIELD(F_LRM_LRMD_PARAM_VAL); + return HA_FAIL; + } + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, "%s: can not send the ret msg",__FUNCTION__); + } + ha_msg_del(ret); + return HA_OK; +} + + +/* /////////////////Util Functions////////////////////////////////////////////// */ +int +send_ret_msg (IPC_Channel* ch, int ret) +{ + struct ha_msg* msg = NULL; + + msg = create_lrm_ret(ret, 1); + CHECK_RETURN_OF_CREATE_LRM_RET; + + if (HA_OK != msg2ipcchan(msg, ch)) { + lrmd_log(LOG_ERR, "send_ret_msg: can not send the ret msg"); + } + ha_msg_del(msg); + return HA_OK; +} + +static void +send_cbk_msg(struct ha_msg* msg, lrmd_client_t* client) +{ + if (!client) { + lrmd_log(LOG_WARNING, + "%s: zero client", __FUNCTION__); + return; + } + if (!client->ch_cbk) { + lrmd_log(LOG_WARNING, + "%s: callback channel is null", __FUNCTION__); + } else if (HA_OK != msg2ipcchan(msg, client->ch_cbk)) { + lrmd_log(LOG_WARNING, + "%s: can not send the ret msg", __FUNCTION__); + } +} + +static void +send_msg(struct ha_msg* msg, lrmd_client_t* client) +{ + if (!client) { + lrmd_log(LOG_WARNING, + "%s: zero client", __FUNCTION__); + return; + } + if (HA_OK != ha_msg_mod(msg,F_LRM_APP,client->app_name)) { + lrmd_log(LOG_ERR,"%s:%d: cannot add field to a message" + , __FUNCTION__, __LINE__); + return; + } + send_cbk_msg(msg, client); +} + +void +notify_client(lrmd_op_t* op) +{ + lrmd_client_t* client = lookup_client(op->client_id); + + if (client) { + /* send the result to client */ + send_cbk_msg(op->msg, client); + } else { + lrmd_log(LOG_WARNING + , "%s: client for the operation %s does not exist" + " and client requested notification." + , __FUNCTION__, op_info(op)); + } +} + +lrmd_client_t* +lookup_client (pid_t pid) +{ + return (lrmd_client_t*) g_hash_table_lookup(clients, &pid); +} + +static gboolean +client_cmp_name(gpointer key, gpointer val, gpointer app_name) +{ + return strcmp(((lrmd_client_t*)val)->app_name,(char *)app_name) ? + FALSE : TRUE; +} + +static lrmd_client_t* +lookup_client_by_name(char *app_name) +{ + return (lrmd_client_t*)g_hash_table_find(clients,client_cmp_name,app_name); +} + +lrmd_rsc_t* +lookup_rsc (const char* rid) +{ + return rid ? + (lrmd_rsc_t*)g_hash_table_lookup(resources, rid) : + NULL; +} + +lrmd_rsc_t* +lookup_rsc_by_msg (struct ha_msg* msg) +{ + const char* id = NULL; + lrmd_rsc_t* rsc = NULL; + + CHECK_ALLOCATED(msg, "msg", NULL); + id = ha_msg_value(msg, F_LRM_RID); + if (id == NULL) { + lrmd_log(LOG_ERR, "lookup_rsc_by_msg: got a NULL resource id."); + return NULL; + } + if (RID_LEN <= strnlen(id, RID_LEN+2)) { + lrmd_log(LOG_ERR, "lookup_rsc_by_msg: resource id is too long."); + return NULL; + } + rsc = lookup_rsc(id); + return rsc; +} + +static void +destroy_pipe_ra_stdout(gpointer user_data) +{ + ra_pipe_op_t * rapop = (ra_pipe_op_t *)user_data; + + CHECK_ALLOCATED(rapop, "ra_pipe_op",); + if (rapop->ra_stderr_fd < 0) { + ra_pipe_op_destroy(rapop); + } +} + +static void +destroy_pipe_ra_stderr(gpointer user_data) +{ + ra_pipe_op_t * rapop = (ra_pipe_op_t *)user_data; + + CHECK_ALLOCATED(rapop, "ra_pipe_op",); + if (rapop->ra_stdout_fd < 0) { + ra_pipe_op_destroy(rapop); + } +} + +static gboolean +handle_pipe_ra_stdout(int fd, gpointer user_data) +{ + gboolean rc = TRUE; + ra_pipe_op_t * rapop = (ra_pipe_op_t *)user_data; + char * data = NULL; + lrmd_op_t* lrmd_op = NULL; + + CHECK_ALLOCATED(rapop, "ra_pipe_op", FALSE); + + if (rapop->lrmd_op == NULL) { + lrmd_debug2(LOG_DEBUG, "%s:%d: Unallocated lrmd_op 0x%lx!!" + , __FUNCTION__, __LINE__ + , (unsigned long)rapop->lrmd_op); + } else { + lrmd_op = rapop->lrmd_op; + } + + if (fd <= STDERR_FILENO) { + lrmd_log(LOG_CRIT, "%s:%d: Attempt to read from " + "closed/invalid file descriptor %d." + , __FUNCTION__, __LINE__, fd); + return FALSE; + } + + if (0 != read_pipe(fd, &data, rapop)) { + /* error or reach the EOF */ + if (fd > STDERR_FILENO) { + close(fd); + if (fd == rapop->ra_stdout_fd) { + rapop->ra_stdout_fd = -1; + } + } + if ( NULL != rapop->ra_stdout_gsource) { + /* + * Returning FALSE will trigger ipc code to release + * the GFDSource, so donn't release it here. + */ + rapop->ra_stdout_gsource = NULL; + } + rc = FALSE; + } + + if ( data!=NULL ) { + if ( (0==STRNCMP_CONST(rapop->op_type, "meta-data")) + ||(0==STRNCMP_CONST(rapop->op_type, "monitor")) + ||(0==STRNCMP_CONST(rapop->op_type, "status")) ) { + lrmd_debug(LOG_DEBUG, "RA output: (%s:%s:stdout) %s" + , lrm_str(rapop->rsc_id), rapop->op_type, data); + } else { + lrmd_log(LOG_INFO, "RA output: (%s:%s:stdout) %s" + , lrm_str(rapop->rsc_id), rapop->op_type, data); + } + + /* + * This code isn't good enough, it produces erratic and hard-to + * read messages in the logs. But this does not affect the + * function correctness, since the first line output is ensured + * to be collected into the buffer completely. + * Anyway, the meta-data (which is _many_ lines long) can be + * handled by another function, see raexec.h + */ + if ( (rapop->first_line_read == FALSE) + && (0==STRNCMP_CONST(rapop->rsc_class, "heartbeat")) + && ( lrmd_op != NULL ) + && ( (0==STRNCMP_CONST(rapop->op_type, "monitor")) + ||(0==STRNCMP_CONST(rapop->op_type, "status")) )) { + if (lrmd_op != NULL) { + strncat(lrmd_op->first_line_ra_stdout, data + , sizeof(lrmd_op->first_line_ra_stdout) - + strlen(lrmd_op->first_line_ra_stdout)-1); + if (strchr(lrmd_op->first_line_ra_stdout, '\n') + != NULL) { + rapop->first_line_read = TRUE; + } + } else { + lrmd_log(LOG_CRIT + , "Before read the first line, the RA " + "execution child quitted and waited."); + } + } + + g_free(data); + } + + return rc; +} + +static gboolean +handle_pipe_ra_stderr(int fd, gpointer user_data) +{ + gboolean rc = TRUE; + char * data = NULL; + ra_pipe_op_t * rapop = (ra_pipe_op_t *)user_data; + + CHECK_ALLOCATED(rapop, "ra_pipe_op", FALSE); + + if (fd <= STDERR_FILENO) { + lrmd_log(LOG_CRIT, "%s:%d: Attempt to read from " + " closed/invalid file descriptor %d." + , __FUNCTION__, __LINE__, fd); + return FALSE; + } + + if (0 != read_pipe(fd, &data, rapop)) { + /* error or reach the EOF */ + if (fd > STDERR_FILENO) { + close(fd); + if (fd == rapop->ra_stderr_fd) { + rapop->ra_stderr_fd = -1; + } + } + if ( NULL != rapop->ra_stderr_gsource) { + /* + * G_main_del_fd will trigger + * destroy_pipe_ra_stderr + * ra_pipe_op_destroy + * + * Returning FALSE will trigger ipc code to release + * the GFDSource, so donn't release it here. + */ + rapop->ra_stderr_gsource = NULL; + } + rc = FALSE; + } + + if (data!=NULL) { + lrmd_log(LOG_INFO, "RA output: (%s:%s:stderr) %s" + , lrm_str(rapop->rsc_id), probe_str(rapop->lrmd_op,rapop->op_type), data); + g_free(data); + } + + return rc; +} + +int +read_pipe(int fd, char ** data, void * user_data) +{ + const int BUFFLEN = 81; + char buffer[BUFFLEN]; + int readlen; + GString * gstr_tmp; + int rc = 0; + lrmd_op_t * op = NULL; + ra_pipe_op_t * rapop = (ra_pipe_op_t *)user_data; + + lrmd_debug3(LOG_DEBUG, "%s begin.", __FUNCTION__); + + CHECK_ALLOCATED(rapop, "ra_pipe_op", FALSE); + + op = (lrmd_op_t *)rapop->lrmd_op; + if (NULL == op) { + lrmd_debug2(LOG_DEBUG, "%s:%d: Unallocated lrmd_op 0x%lx!!" + , __FUNCTION__, __LINE__ + , (unsigned long)op); + } + + *data = NULL; + gstr_tmp = g_string_new(""); + + do { + errno = 0; + readlen = read(fd, buffer, BUFFLEN - 1); + if (NULL == op) { + lrmd_debug2(LOG_NOTICE + , "read's ret: %d when lrmd_op finished" + , readlen); + } + if ( readlen > 0 ) { + buffer[readlen] = EOS; + g_string_append(gstr_tmp, buffer); + } + } while (readlen == BUFFLEN - 1 || errno == EINTR); + + if (errno == EINTR || errno == EAGAIN) { + errno = 0; + } + + /* Reach the EOF */ + if (readlen == 0) { + rc = -1; + } + + if ((readlen < 0) && (errno !=0)) { + rc = -1; + switch (errno) { + default: + cl_perror("%s:%d read error: fd %d errno=%d" + , __FUNCTION__, __LINE__ + , fd, errno); + if (NULL != op) { + lrmd_op_dump(op, "op w/bad errno"); + } else { + lrmd_log(LOG_NOTICE + , "%s::%d: lrmd_op has been freed" + , __FUNCTION__, __LINE__); + } + break; + + case EBADF: + lrmd_log(LOG_CRIT + , "%s:%d" + " Attempt to read from closed file descriptor %d." + , __FUNCTION__, __LINE__, fd); + if (NULL != op) { + lrmd_op_dump(op, "op w/bad errno"); + } else { + lrmd_log(LOG_NOTICE + , "%s::%d: lrmd_op has been freed" + , __FUNCTION__, __LINE__); + } + break; + } + } + + if ( gstr_tmp->len == 0 ) { + g_string_free(gstr_tmp, TRUE); + } else { + *data = gstr_tmp->str; + g_string_free(gstr_tmp, FALSE); + } + + lrmd_debug3(LOG_DEBUG, "%s end.", __FUNCTION__); + return rc; +} + + +static gboolean +debug_level_adjust(int nsig, gpointer user_data) +{ + char s[16]; + + switch (nsig) { + case SIGUSR1: + debug_level++; + dump_data_for_debug(); + break; + + case SIGUSR2: + dump_data_for_debug(); + debug_level--; + if (debug_level < 0) { + debug_level = 0; + } + break; + + default: + lrmd_log(LOG_WARNING, "debug_level_adjust: Received an " + "unexpected signal(%d). Something wrong?.",nsig); + } + + snprintf(s, sizeof(s), "%d", debug_level); + setenv(HADEBUGVAL, s, 1); + return TRUE; +} + +static void +dump_data_for_debug(void) +{ + lrmd_debug(LOG_DEBUG, "begin to dump internal data for debugging."); + lrmd_dump_all_clients(); + lrmd_dump_all_resources(); + lrmd_debug(LOG_DEBUG, "end to dump internal data for debugging."); +} + +const char* +gen_op_info(const lrmd_op_t* op, gboolean add_params) +{ + static char info[512]; + lrmd_rsc_t* rsc = NULL; + const char * op_type; + GString * param_gstr; + GHashTable* op_params = NULL; + + if (NULL == op) { + lrmd_log(LOG_ERR, "%s:%d: op==NULL" + , __FUNCTION__, __LINE__); + return NULL; + } + rsc = lookup_rsc(op->rsc_id); + op_type = ha_msg_value(op->msg, F_LRM_OP); + + if (rsc == NULL) { + snprintf(info,sizeof(info) + ,"operation %s[%d] on unknown rsc(maybe deleted) for client %d" + ,lrm_str(op_type) + ,op->call_id ,op->client_id); + + }else{ + if (op->exec_pid > 1) { + snprintf(info, sizeof(info) + ,"operation %s[%d] with pid %d on %s for client %d" + ,lrm_str(op_type), op->call_id, op->exec_pid, lrm_str(rsc->id) + ,op->client_id); + } else { + snprintf(info, sizeof(info) + ,"operation %s[%d] on %s for client %d" + ,lrm_str(op_type), op->call_id, lrm_str(rsc->id) + ,op->client_id); + } + + if( add_params ) { + param_gstr = g_string_new(""); + op_params = ha_msg_value_str_table(op->msg, F_LRM_PARAM); + hash_to_str(op_params, param_gstr); + if (op_params) { + free_str_table(op_params); + op_params = NULL; + } + + snprintf(info+strlen(info), sizeof(info)-strlen(info) + ,", its parameters: %s",param_gstr->str); + + g_string_free(param_gstr, TRUE); + } + } + return info; +} + +static void +hash_to_str(GHashTable * params , GString * str) +{ + if (params) { + g_hash_table_foreach(params, hash_to_str_foreach, str); + } +} + +static void +hash_to_str_foreach(gpointer key, gpointer value, gpointer user_data) +{ + char buffer_tmp[80]; + GString * str = (GString *)user_data; + + g_snprintf(buffer_tmp, sizeof(buffer_tmp), "%s=[%s] " + , (char *)key, (char *)value); + str = g_string_append(str, buffer_tmp); +} + +static void +check_queue_duration(lrmd_op_t* op) +{ + unsigned long t_stay_in_list = 0; + static struct msg_ctrl *ml; + + CHECK_ALLOCATED(op, "op", ); + t_stay_in_list = longclockto_ms(op->t_perform - op->t_addtolist); + if ( t_stay_in_list > WARNINGTIME_IN_LIST) + { + if (!ml) + ml = cl_limit_log_new(logmsg_ctrl_defs + OP_STAYED_TOO_LONG); + cl_limit_log(ml, LOG_WARNING + , "perform_ra_op: the %s stayed in operation " + "list for %lu ms (longer than %d ms)" + , small_op_info(op), t_stay_in_list + , WARNINGTIME_IN_LIST + ); + if (debug_level >= 2) { + dump_data_for_debug(); + } + } +} + diff --git a/lrm/lrmd/lrmd.h b/lrm/lrmd/lrmd.h new file mode 100644 index 0000000..eadea88 --- /dev/null +++ b/lrm/lrmd/lrmd.h @@ -0,0 +1,282 @@ +#define MAX_PID_LEN 256 +#define MAX_PROC_NAME 256 +#define MAX_MSGTYPELEN 32 +#define MAX_CLASSNAMELEN 32 +#define WARNINGTIME_IN_LIST 10000 +#define OPTARGS "skrhvmi:" +#define PID_FILE HA_VARRUNDIR"/lrmd.pid" +#define LRMD_COREDUMP_ROOT_DIR HA_COREDIR +#define APPHB_WARNTIME_FACTOR 3 +#define APPHB_INTVL_DETLA 30 /* Millisecond */ + +#define lrmd_log(priority, fmt...); \ + cl_log(priority, fmt); + +#define lrmd_debug(priority, fmt...); \ + if ( debug_level >= 1 ) { \ + cl_log(priority, fmt); \ + } + +#define lrmd_debug2(priority, fmt...); \ + if ( debug_level >= 2 ) { \ + cl_log(priority, fmt); \ + } + +#define lrmd_debug3(priority, fmt...); \ + if ( debug_level >= 3 ) { \ + cl_log(priority, fmt); \ + } + +#define lrmd_nullcheck(p) ((p) ? (p) : "<null>") +#define lrm_str(p) (lrmd_nullcheck(p)) + +#define CHECK_ALLOCATED(thing, name, result) \ + if (!thing) { \ + lrmd_log(LOG_ERR \ + , "%s: %s pointer 0x%lx is not allocated." \ + , __FUNCTION__, name, (unsigned long)thing); \ + if (!in_alloc_dump) { \ + in_alloc_dump = TRUE; \ + dump_data_for_debug(); \ + in_alloc_dump = FALSE; \ + return result; \ + } \ + } + +#define CHECK_RETURN_OF_CREATE_LRM_RET do { \ + if (NULL == msg) { \ + lrmd_log(LOG_ERR \ + , "%s: cannot create a ret message with create_lrm_ret." \ + , __FUNCTION__); \ + return HA_FAIL; \ + } \ +} while(0) + +#define LOG_FAILED_TO_GET_FIELD(field) \ + lrmd_log(LOG_ERR \ + , "%s:%d: cannot get field %s from message." \ + ,__FUNCTION__,__LINE__,field) + +#define LOG_FAILED_TO_ADD_FIELD(field) \ + lrmd_log(LOG_ERR \ + , "%s:%d: cannot add the field %s to a message." \ + , __FUNCTION__ \ + , __LINE__ \ + , field) + +/* NB: There's a return in these macros, hence the names */ +#define return_on_no_int_value(msg,fld,i) do { \ + if (HA_OK != ha_msg_value_int(msg,fld,i)) { \ + LOG_FAILED_TO_GET_FIELD(fld); \ + return HA_FAIL; \ + } \ +} while(0) +#define return_on_no_value(msg,fld,v) do { \ + v = ha_msg_value(msg,fld); \ + if (!v) { \ + LOG_FAILED_TO_GET_FIELD(fld); \ + return HA_FAIL; \ + } \ +} while(0) + +#define LRMD_APPHB_HB \ + if (reg_to_apphb == TRUE) { \ + if (apphb_hb() != 0) { \ + reg_to_apphb = FALSE; \ + } \ + } + +#define tm2age(tm) \ + (cmp_longclock(tm, zero_longclock) <= 0) ? \ + 0 : longclockto_ms(sub_longclock(now, tm)) +#define tm2unix(tm) \ + (time(NULL)-(tm2age(tm)+999)/1000) + +/* + * The basic objects in our world: + * + * lrmd_client_t: + * Client - a process which has connected to us for service. + * + * lrmd_rsc_t: + * Resource - an abstract HA cluster resource implemented by a + * resource agent through our RA plugins + * It has two list of operations (lrmd_op_t) associated with it + * op_list - operations to be run as soon as they're ready + * repeat_op_list - operations to be run later + * It maintains the following tracking structures: + * last_op_done Last operation performed on this resource + * last_op_table Last operations of each type done per client + * + * lrmd_op_t: + * Resource operation - an operation on a resource -- requested + * by a client. + * + * ProcTrack - tracks a currently running resource operation. + * It points back to the lrmd_op_t that started it. + * + * Global structures containing these things: + * + * clients - a hash table of all (currently connected) clients + * + * resources - a hash table of all (currently configured) resources + * + * Proctrack keeps its own private data structures to keep track of + * child processes that it created. They in turn point to the + * lrmd_op_t objects that caused us to fork the child process. + * + * + */ + +/* + * Recognized privilege levels + */ + +#define PRIV_ADMIN 8 /* ADMIN_UIDS are administrators */ +#define ADMIN_UIDS "0,"HA_CCMUSER +#define ADMIN_GIDS "0,"HA_APIGROUP /* unused */ + +typedef struct +{ + char* app_name; + pid_t pid; + gid_t gid; + uid_t uid; + + IPC_Channel* ch_cmd; + IPC_Channel* ch_cbk; + + GCHSource* g_src; + GCHSource* g_src_cbk; + char lastrequest[MAX_MSGTYPELEN]; + time_t lastreqstart; + time_t lastreqend; + time_t lastrcsent; + int priv_lvl; /* client privilege level (depends on uid/gid) */ +}lrmd_client_t; + +typedef struct lrmd_rsc lrmd_rsc_t; +typedef struct lrmd_op lrmd_op_t; +typedef struct ra_pipe_op ra_pipe_op_t; + +#define RSC_REMOVAL_PENDING 1 +#define RSC_FLUSHING_OPS 2 +#define rsc_frozen(r) \ + ((r)->state==RSC_REMOVAL_PENDING || (r)->state==RSC_FLUSHING_OPS) +#define rsc_removal_pending(r) \ + ((r)->state==RSC_REMOVAL_PENDING) +#define set_rsc_removal_pending(r) \ + (r)->state = RSC_REMOVAL_PENDING +#define set_rsc_flushing_ops(r) \ + (r)->state = RSC_FLUSHING_OPS +#define rsc_reset_state(r) (r)->state = 0 +/* log messages for repeating ops (monitor) once an hour */ +#define LOGMSG_INTERVAL (60*60) +#define is_logmsg_due(op) \ + (longclockto_ms(sub_longclock(time_longclock(), op->t_lastlogmsg))/1000 >= \ + (unsigned long)LOGMSG_INTERVAL) +#define probe_str(op,op_type) \ + ((op && !op->interval && !strcmp(op_type,"monitor")) ? "probe" : op_type) +/* exclude stonith class from child count */ +#define no_child_count(rsc) \ + (strcmp((rsc)->class,"stonith") == 0) + +struct lrmd_rsc +{ + char* id; /* Unique resource identifier */ + char* type; /* */ + char* class; /* */ + char* provider; /* Resource provider (optional) */ + GHashTable* params; /* Parameters to this resource */ + /* as name/value pairs */ + GList* op_list; /* Queue of operations to run */ + GList* repeat_op_list; /* Unordered list of repeating */ + /* ops They will run later */ + GHashTable* last_op_table; /* Last operation of each type */ + lrmd_op_t* last_op_done; /* The last finished op of the resource */ + guint delay_timeout; /* The delay value of op_list execution */ + int state; /* status of the resource */ +}; + +struct lrmd_op +{ + char* rsc_id; + gboolean is_copy; + pid_t client_id; + int call_id; + int exec_pid; + guint repeat_timeout_tag; + int interval; + int delay; + gboolean is_cancelled; + int weight; + int copyparams; + struct ha_msg* msg; + ra_pipe_op_t * rapop; + char first_line_ra_stdout[80]; /* only for heartbeat RAs*/ + /*time stamps*/ + longclock_t t_recv; /* set in lrmd_op_new(), i.e. on op create */ + longclock_t t_addtolist; /* set in add_op_to_runlist() */ + longclock_t t_perform; /* set in perform_ra_op() */ + longclock_t t_done; /* set in on_op_done() */ + longclock_t t_rcchange; /* set in on_op_done(), could equal t_perform */ + longclock_t t_lastlogmsg; /* the last time the monitor op was logged */ + ProcTrackKillInfo killseq[3]; +}; + + +/* For reading the output from executing the RA */ +struct ra_pipe_op +{ + /* The same value of the one in corresponding lrmd_op */ + lrmd_op_t * lrmd_op; + int ra_stdout_fd; + int ra_stderr_fd; + GFDSource * ra_stdout_gsource; + GFDSource * ra_stderr_gsource; + gboolean first_line_read; + + /* For providing more detailed information in log */ + char * rsc_id; + char * op_type; + char * rsc_class; +}; + + +const char *gen_op_info(const lrmd_op_t* op, gboolean add_params); +#define op_info(op) gen_op_info(op,TRUE) +#define small_op_info(op) gen_op_info(op,FALSE) + +#define DOLRMAUDITS +#undef DOLRMAUDITS + +#define DOMEGALRMAUDITS +#define LRMAUDIT_CLIENTS +#define LRMAUDIT_RESOURCES + +#ifdef DOLRMAUDITS + + void lrmd_audit(const char *function, int line); + void audit_clients(void); + void audit_resources(void); + void audit_ops(GList* rsc_ops, lrmd_rsc_t *rsc, const char *desc); + void on_client(gpointer key, gpointer value, gpointer user_data); + void on_resource(gpointer key, gpointer value, gpointer user_data); + void on_op(lrmd_op_t *op, lrmd_rsc_t *rsc, const char *desc); + void on_ra_pipe_op(ra_pipe_op_t *rapop, lrmd_op_t *op, const char *desc); + +# define LRMAUDIT() lrmd_audit(__FUNCTION__,__LINE__) +# ifdef DOMEGALRMAUDITS +# define MEGALRMAUDIT lrmd_audit(__FUNCTION__,__LINE__) +# else +# define MEGALRMAUDIT /*nothing*/ +# endif +#else +# define LRMAUDIT() /*nothing*/ +# define MEGALRMAUDIT() /*nothing*/ +#endif + +/* + * load parameters from an ini file (cib_secrets.c) + */ +int replace_secret_params(char* rsc_id, GHashTable* params); diff --git a/lrm/lrmd/lrmd_fdecl.h b/lrm/lrmd/lrmd_fdecl.h new file mode 100644 index 0000000..9c97385 --- /dev/null +++ b/lrm/lrmd/lrmd_fdecl.h @@ -0,0 +1,111 @@ +/* TODO: This ought to be broken up into several source files for easier + * reading and debugging. */ + +/* Debug oriented funtions */ +static gboolean debug_level_adjust(int nsig, gpointer user_data); +static void dump_data_for_debug(void); + +/* glib loop call back functions */ +static gboolean on_connect_cmd(IPC_Channel* ch_cmd, gpointer user_data); +static gboolean on_connect_cbk(IPC_Channel* ch_cbk, gpointer user_data); +static int msg_type_cmp(const void *p1, const void *p2); +static gboolean on_receive_cmd(IPC_Channel* ch_cmd, gpointer user_data); +static gboolean on_repeat_op_readytorun(gpointer data); +static void on_remove_client(gpointer user_data); +static void destroy_pipe_ra_stderr(gpointer user_data); +static void destroy_pipe_ra_stdout(gpointer user_data); + +/* message handlers */ +static int on_msg_register(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_rsc_classes(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_rsc_types(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_rsc_providers(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_metadata(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_add_rsc(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_rsc(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_last_op(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_all(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_del_rsc(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_fail_rsc(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_cancel_op(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_flush_all(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_perform_op(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_state(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_set_lrmd_param(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_lrmd_param(lrmd_client_t* client, struct ha_msg* msg); +static int set_lrmd_param(const char *name, const char *value); +static int get_lrmd_param(const char *name, char *value, int maxstring); +static gboolean sigterm_action(int nsig, gpointer unused); + +/* functions wrap the call to ra plugins */ +static int perform_ra_op(lrmd_op_t* op); + +/* Apphb related functions */ +static int init_using_apphb(void); +static gboolean emit_apphb(gpointer data); + +/* Utility functions */ +static int flush_op(lrmd_op_t* op); +static gboolean rsc_execution_freeze_timeout(gpointer data); +static void add_op_to_runlist(lrmd_rsc_t* rsc, lrmd_op_t* op); +static int perform_op(lrmd_rsc_t* rsc); +static int unregister_client(lrmd_client_t* client); +static int on_op_done(lrmd_rsc_t* rsc, lrmd_op_t* op); +static int send_ret_msg ( IPC_Channel* ch, int rc); +static void send_cbk_msg(struct ha_msg* msg, lrmd_client_t* client); +static void send_msg(struct ha_msg* msg, lrmd_client_t* client); +static void notify_client(lrmd_op_t* op); +static lrmd_client_t* lookup_client (pid_t pid); +static lrmd_rsc_t* lookup_rsc (const char* rid); +static lrmd_rsc_t* lookup_rsc_by_msg (struct ha_msg* msg); +static int read_pipe(int fd, char ** data, gpointer user_data); +static gboolean handle_pipe_ra_stdout(int fd, gpointer user_data); +static gboolean handle_pipe_ra_stderr(int fd, gpointer user_data); +static struct ha_msg* op_to_msg(lrmd_op_t* op); +static int store_timestamps(lrmd_op_t* op); +static void reset_timestamps(lrmd_op_t* op); +static gboolean lrm_shutdown(void); +static gboolean can_shutdown(void); +static gboolean free_str_hash_pair(gpointer key +, gpointer value, gpointer user_data); +static gboolean free_str_op_pair(gpointer key +, gpointer value, gpointer user_data); +static lrmd_op_t* lrmd_op_copy(const lrmd_op_t* op); +static void send_last_op(gpointer key, gpointer value, gpointer user_data); +static void replace_last_op(lrmd_client_t* client, lrmd_rsc_t* rsc, lrmd_op_t* op); +static int record_op_completion(lrmd_rsc_t* rsc, lrmd_op_t* op); +static void to_repeatlist(lrmd_rsc_t* rsc, lrmd_op_t* op); +static void remove_op_history(lrmd_op_t* op); +static void hash_to_str(GHashTable * , GString *); +static void hash_to_str_foreach(gpointer key, gpointer value, gpointer userdata); +static void warning_on_active_rsc(gpointer key, gpointer value, gpointer user_data); +static void check_queue_duration(lrmd_op_t* op); +static gboolean flush_all(GList** listp, int client_pid); +static gboolean cancel_op(GList** listp,int cancel_op_id); +static int prepare_failmsg(struct ha_msg* msg, + int fail_rc, const char *fail_reason); +static void async_notify(gpointer key, gpointer val, gpointer data); +static gboolean client_cmp_name(gpointer key, gpointer val, gpointer app_name); +static lrmd_client_t* lookup_client_by_name(char *app_name); +static void calc_max_children(void); + +/* + * following functions are used to monitor the exit of ra proc + */ +static void on_ra_proc_registered(ProcTrack* p); +static void on_ra_proc_finished(ProcTrack* p, int status +, int signo, int exitcode, int waslogged); +static const char* on_ra_proc_query_name(ProcTrack* p); + + + +/* + * Daemon functions + * + * copy from the code of Andrew Beekhof <andrew@beekhof.net> + */ +static void usage(const char* cmd, int exit_status); +static int init_start(void); +static int init_stop(const char *pid_file); +static int init_status(const char *pid_file, const char *client_name); +static void lrmd_rsc_dump(char* rsc_id, const char * text); diff --git a/lrm/test/LRMBasicSanityCheck.in b/lrm/test/LRMBasicSanityCheck.in new file mode 100755 index 0000000..dbe8548 --- /dev/null +++ b/lrm/test/LRMBasicSanityCheck.in @@ -0,0 +1,55 @@ +#!/bin/sh + + # Copyright (c) 2004 International Business Machines + # Author: Huang Zhen <zhenhltc@cn.ibm.com> + # + # 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.1 of the License, or (at your option) any later version. + # + # This software is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + # General Public License for more details. + # + # You should have received a copy of the GNU General Public + # License along with this library; if not, write to the Free Software + # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + # +HBLIB=@libdir@/heartbeat +LRMD=$HBLIB/lrmd +LRMADMIN=@sbindir@/lrmadmin +export LRMD LRMADMIN + +if [ $# -gt 0 ]; then + LRMD=$1/lrmd +fi + +if [ ! -f $LRMD ]; then + echo $LRMD does not exist + exit 1 +fi + +if [ ! -f $LRMADMIN ]; then + echo $LRMADMIN does not exist + exit 1 +fi + +OUTDIR=/tmp/LRM_BSC_$$ +export OUTDIR +[ -d $OUTDIR ] && { + echo $OUTDIR exists, please cleanup + exit 1 +} + +`dirname $0`/regression.sh -q set:BSC +rc=$? +if [ $rc -eq 0 ]; then + echo "LRM tests PASSED" + rm -rf $OUTDIR +else + echo "LRM tests FAILED" + echo "Please check $OUTDIR for results" +fi +exit $rc diff --git a/lrm/test/Makefile.am b/lrm/test/Makefile.am new file mode 100644 index 0000000..84f6657 --- /dev/null +++ b/lrm/test/Makefile.am @@ -0,0 +1,48 @@ +# +# Author: Sun Jiang Dong <sunjd@cn.ibm.com> +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +SUBDIRS = testcases + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + +COMMONLIBS = $(top_builddir)/lib/clplumbing/libplumb.la $(GLIBLIB) + +noinst_PROGRAMS = apitest plugintest callbacktest + +apitest_SOURCES = apitest.c +apitest_LDFLAGS = $(COMMONLIBS) +apitest_LDADD = $(top_builddir)/lib/$(LRM_DIR)/liblrm.la +apitest_DEPENDENCIES = $(top_builddir)/lib/$(LRM_DIR)/liblrm.la + +plugintest_SOURCES = plugintest.c +plugintest_LDADD = $(COMMONLIBS) +plugintest_LDFLAGS = -L$(top_builddir)/lib/pils -lpils @LIBLTDL@ + +callbacktest_SOURCES = callbacktest.c +callbacktest_LDFLAGS = $(COMMONLIBS) +callbacktest_LDADD = $(top_builddir)/lib/$(LRM_DIR)/liblrm.la +callbacktest_DEPENDENCIES = $(top_builddir)/lib/$(LRM_DIR)/liblrm.la + +testdir = $(datadir)/$(PACKAGE_NAME)/lrmtest +test_SCRIPTS = LRMBasicSanityCheck regression.sh evaltest.sh lrmregtest lrmregtest-lsb +test_DATA = README.regression defaults descriptions lrmadmin-interface language +# shouldn't need this, but we do for some versions of autofoo tools +EXTRA_DIST = $(test_SCRIPTS) $(test_DATA) diff --git a/lrm/test/README.regression b/lrm/test/README.regression new file mode 100644 index 0000000..3588172 --- /dev/null +++ b/lrm/test/README.regression @@ -0,0 +1,164 @@ +LRM regression tests + +* WARNING * WARNING * WARNING * WARNING * WARNING * WARNING * +* +* evaltest.sh uses eval to an extent you don't really want to +* know about. Beware. Beware twice. Any input from the testcases +* directory is considered to be trusted. So, think twice before +* devising your tests lest you kill your precious data. Got it? +* Good. +* +* Furthermore, we are deliberately small on testing the user +* input and no one should try to predict what is to happen on +* random input from the testcases. +* +* WARNING * WARNING * WARNING * WARNING * WARNING * WARNING * + +Manifest + + regression.sh: the top level program + evaltest.sh: the engine test engine + + lrmadmin-interface: interface to lrmd (lrmadmin) + descriptions: describe what we are about to do + defaults: the default settings for test commands + + testcases/: here are the testcases and filters + output/: here goes the output + +All volatile data lives in the testcases/ directory. + +NB: You should never ever need to edit regression.sh and +evaltest.sh. If you really have to, please talk to me and I will +try to fix it so that you do not have to. + +Please write new test cases. The more the merrier :) + +Usage + +The usage is: + + ./regression.sh ["prepare"] ["set:"<setname>|<testcase>] + +Test cases are collected in test sets. The default test set is +basicset and running regression.sh without arguments will do all +tests from that set. + +To show progress, for each test a '.' is printed. For sleeps, +a '+' for each second. Once all tests have been evaluated, the +output is checked against the expect file. If successful, "PASS" +is printed, otherwise "FAIL". + +Specifying "prepare" will make regression.sh create expect +output files for the given set of tests or testcase. + +The script will start and stop lrmd itself. stonithd is also +started to test the XML descriptions printed by stonith agents. +No other parts of stonithd functionality is tested. + +The following files may be generated: + + output/<testcase>.out: the output of the testcase + output/regression.out: the output of regression.sh + output/lrmd.out: the output of lrmd + +On success output from testcases is removed and regression.out is +empty. + +Driving the test cases yourself + +evaltest.sh accepts input from stdin, evaluates it immediately, +and prints results to stdout/stderr. One can perhaps get a better +feeling of what's actually going on by running it interactively. +Please note that you have to start the lrmd yourself beforehand. + +Test cases + +Tests are written in a simple metalanguage. The list of commands +with rough translation to lrmadmin's options is in the language +file. The best description of the language is in the +lrmadmin-interface and descriptions scripts: + +$ egrep '^lrm|echo' lrmadmin-interface descriptions + +A test case is a list of tests, one per line. A few examples: + + add # add a resource with default name + list # list all resources + del rsc=wiwi # remove a wiwi resource + +A set of defaults for LRM options is in the defaults file. That's +why we can write short forms instead of + + add rsc=r1 class=ocf type=lrmregtest provider=heartbeat ... + +Special operations + +There are special operations with which it is possible to change +environment and do other useful things. All special ops start +with the '%' sign and may be followed by additional parameters. + +%setenv + change the environment variable; see defaults for the + set of global variables and resetvars() in evaltest.sh + +%sleep + sleep + +%stop + skip the rest of the tests + +%extcheck + feed the output of the next test case to the specified + external program/filter; the program should either reside in + testcases/ or be in the PATH, i.e. + + %extcheck cat + + simulates a null op :) + + see testcases/metadata for some examples + +%repeat num + repeat the next test num times + there are several variables which are substituted in the test + lines, so that we can simulate a for loop: + + s/%t/$test_cnt/g + s/%l/$line/g + s/%j/$job_cnt/g + s/%i/$repeat_cnt/g + + for example, to add 10 resources: + + %repeat 10 + add rsc=r-%i + +%bg [num] + run next num (or just the next one) tests in background + +%bgrepeat [num] + a combination of the previous two (used often) + +%wait + wait for the last background test to finish + +%shell + feed whatever is in the rest of the line to 'sh -s' + +Filters and except files + +Some output is necessarily very volatile, such as time stamps. +It is possible to specify a filter for each testcase to get rid +of superfluous information. A filter is a filter in UNIX +sense, it takes input from stdin and prints results to stdout. + +There is a common filter called very inventively +testcases/common.filter which is applied to all test cases. + +Except files are a list of extended regular expressions fed to +egrep(1). That way one can filter out lines which are not +interesting. Again, the one applied to all is +testcases/common.excl. + + diff --git a/lrm/test/apitest.c b/lrm/test/apitest.c new file mode 100644 index 0000000..0d4c572 --- /dev/null +++ b/lrm/test/apitest.c @@ -0,0 +1,317 @@ + +/* + * Test program for Local Resource Manager API. + * + * Copyright (C) 2004 Huang Zhen <zhenh@cn.ibm.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> +#include <unistd.h> +#include <stdio.h> +#include <sys/poll.h> +#include <string.h> +#include <glib.h> +#include <lrm/lrm_api.h> +#include <clplumbing/cl_log.h> +#include <syslog.h> + +void lrm_op_done_callback (lrm_op_t* op); +void printf_rsc(lrm_rsc_t* rsc); +void printf_op(lrm_op_t* op); +void printf_hash_table(GHashTable* hash_table); +void get_all_rsc(ll_lrm_t* lrm); +void get_cur_state(lrm_rsc_t* rsc); + +int main (int argc, char* argv[]) +{ + ll_lrm_t* lrm; + lrm_rsc_t* rsc = NULL; + lrm_op_t* op = NULL; + const char* rid = "ip248"; + GHashTable* param = NULL; + GList* classes; + int i; + + cl_log_set_entity("apitest"); + cl_log_set_facility(LOG_USER); + + lrm = ll_lrm_new("lrm"); + + if(NULL == lrm) + { + printf("lrm==NULL\n"); + return 1; + } + puts("sigon..."); + lrm->lrm_ops->signon(lrm,"apitest"); + + classes = lrm->lrm_ops->get_rsc_class_supported(lrm); + lrm_free_str_list(classes); + + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("192.168.192.100")); + puts("add_rsc..."); + lrm->lrm_ops->add_rsc(lrm, rid, "heartbeat", "IPaddr", "heartbeat", param); + puts("get_rsc..."); + rsc = lrm->lrm_ops->get_rsc(lrm, rid); + printf_rsc(rsc); + + puts("perform_op(start)..."); + op = lrm_op_new(); + op->op_type = g_strdup("start"); + op->params = param; + op->timeout = 0; + op->user_data = strdup("It is a start op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 0; + op->target_rc = EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + lrm_free_op(op); + + puts("perform_op(status)..."); + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("192.168.192.100")); + op = lrm_op_new(); + op->op_type = g_strdup("status"); + op->params = param; + op->timeout = 0; + op->user_data = strdup("It is a status op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 1000; + op->target_rc=EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + lrm_free_op(op); + + puts("perform_op(stop)..."); + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("192.168.192.100")); + op = lrm_op_new(); + op->op_type = g_strdup("stop"); + op->params = param; + op->timeout = 0; + op->user_data = strdup("It is a stop op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 0; + op->target_rc=EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + lrm_free_op(op); + + puts("perform_op(status)..."); + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("192.168.192.100")); + op = lrm_op_new(); + op->op_type = g_strdup("status"); + op->params = param; + op->timeout = 0; + op->user_data = strdup("It is a status op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 2000; + op->target_rc=EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + lrm_free_op(op); + + puts("perform_op(start)..."); + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("192.168.192.100")); + op = lrm_op_new(); + op->op_type = g_strdup("start"); + op->params = param; + op->timeout = 0; + op->user_data = strdup("It is a start op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 0; + op->target_rc = EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + lrm_free_op(op); + + puts("perform_op(status)..."); + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("192.168.192.100")); + op = lrm_op_new(); + op->op_type = g_strdup("status"); + op->params = param; + op->timeout = 0; + op->user_data = strdup("It is a status op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 3000; + op->target_rc=EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + lrm_free_op(op); + + puts("perform_op(stop)..."); + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("192.168.192.100")); + op = lrm_op_new(); + op->op_type = g_strdup("stop"); + op->params = param; + op->timeout = 0; + op->user_data = strdup("It is a stop op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 0; + op->target_rc=EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + lrm_free_op(op); + + for(i = 0; i < 5; i++) { + puts("get_cur_state..."); + get_cur_state(rsc); + puts("sleep a while..."); + sleep(1); + } + + puts("delete_rsc..."); + lrm->lrm_ops->delete_rsc(lrm, rid); + lrm_free_rsc(rsc); + + puts("signoff..."); + lrm->lrm_ops->signoff(lrm); + + return 0; +} +void lrm_op_done_callback(lrm_op_t* op) +{ + puts("lrm_op_done_callback..."); + printf_op(op); +} +void printf_rsc(lrm_rsc_t* rsc) +{ + printf("print resource>>>>>>>>>\n"); + if (NULL == rsc) { + printf("resource is null\n"); + printf("print end\n"); + return; + } + printf("\tresource of id:%s\n", rsc->id); + printf("\ttype:%s\n", rsc->type); + printf("\tclass:%s\n", rsc->class); + printf("\tparams:\n"); + printf_hash_table(rsc->params); + printf("print end<<<<<<<<<<<<<<<\n"); +} + +void printf_op(lrm_op_t* op) +{ + printf("print op>>>>>>>>>>>>>>>>\n"); + + if (NULL == op) { + printf("op is null\n"); + printf("print end\n"); + return; + } + + printf("\top_type:%s\n",op->op_type?op->op_type:"null"); + printf("\tparams:\n"); + printf_hash_table(op->params); + printf("\ttimeout:%d\n",op->timeout); + printf("\tuser_data:%s\n",op->user_data?(char*)op->user_data:"null"); + printf("\top_status:%d\n",op->op_status); + printf("\tapp_name:%s\n",op->app_name?op->app_name:"null"); + printf("\toutput:%s\n",op->output?op->output:"null"); + printf("\trc:%d\n",op->rc); + printf("\tcall_id:%d\n",op->call_id); + printf("print end<<<<<<<<<<<<<<<<<<\n"); +} + +static void +printf_pair(gpointer key, gpointer value, gpointer user_data) +{ + printf("\t\t%s=%s\n",(char*)key,(char*)value); +} +void +printf_hash_table(GHashTable* hash_table) +{ + if (NULL == hash_table) { + printf("\t\tnull\n"); + return; + } + g_hash_table_foreach(hash_table, printf_pair, NULL); +} +void +get_all_rsc(ll_lrm_t* lrm) +{ + GList* element = NULL, * rid_list = NULL; + + puts("get_all_rscs..."); + rid_list = lrm->lrm_ops->get_all_rscs(lrm); + if (NULL != rid_list) { + element = g_list_first(rid_list); + while (NULL != element) { + printf("\tid:%s\n",(char*)element->data); + element = g_list_next(element); + } + } else { + puts("\tnone."); + } + lrm_free_str_list(rid_list); +} +void +get_cur_state(lrm_rsc_t* rsc) +{ + state_flag_t state; + GList* node = NULL, * op_list = NULL; + lrm_op_t* op = NULL; + printf("current state>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); + + op_list = rsc->ops->get_cur_state(rsc, &state); + + printf("\tcurrent state:%s\n",state==LRM_RSC_IDLE?"Idle":"Busy"); + + + for(node = g_list_first(op_list); NULL != node; + node = g_list_next(node)) { + op = (lrm_op_t*)node->data; + printf_op(op); + } + lrm_free_op_list(op_list); + printf("current end<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); +} diff --git a/lrm/test/apitest.exp b/lrm/test/apitest.exp new file mode 100644 index 0000000..b153ee3 --- /dev/null +++ b/lrm/test/apitest.exp @@ -0,0 +1,122 @@ +sigon... +add_rsc... +get_rsc... +print resource + resource of id:ip248 + type:IPv6addr + class:heartbeat + params: + 1=3ffe:ffff:0:f101::3 +print end +perform_op(start)... +print op + op_type:start + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:It is a start op! + op_status:0 + app_name:null + output:null + rc:0 +print end +perform_op(status)... +print op + op_type:status + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:It is a status op! + op_status:0 + app_name:null + output:null + rc:0 +print end +perform_op(stop)... +print op + op_type:stop + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:It is a stop op! + op_status:0 + app_name:null + output:null + rc:0 +print end +get_cur_state... + current state:Busy +print op + op_type:start + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:It is a start op! + op_status:-1 + app_name:apitest + output:null + rc:0 +print end +print op + op_type:status + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:It is a status op! + op_status:-1 + app_name:apitest + output:null + rc:0 +print end +print op + op_type:stop + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:It is a stop op! + op_status:-1 + app_name:apitest + output:null + rc:0 +print end +stop_op... +get_cur_state... + current state:Busy +print op + op_type:start + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:null + op_status:-1 + app_name:apitest + output:null + rc:0 +print end +print op + op_type:stop + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:null + op_status:-1 + app_name:apitest + output:null + rc:0 +print end +sleep a while... +get_cur_state... + current state:Idel +print op + op_type:stop + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:null + op_status:0 + app_name:apitest + output:null + rc:0 +print end +delete_rsc... +signoff... diff --git a/lrm/test/callbacktest.c b/lrm/test/callbacktest.c new file mode 100644 index 0000000..48f4d49 --- /dev/null +++ b/lrm/test/callbacktest.c @@ -0,0 +1,204 @@ + +/* + * Test program for the callback function of Local Resource Manager API. + * + * Copyright (C) 2004 Huang Zhen <zhenh@cn.ibm.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <lha_internal.h> +#include <unistd.h> +#include <stdio.h> +#include <sys/poll.h> +#include <string.h> +#include <glib.h> +#include <lrm/lrm_api.h> +#include <syslog.h> +#include <clplumbing/GSource.h> + +static void lrm_op_done_callback(lrm_op_t *op); +static void printf_rsc(lrm_rsc_t *rsc); +static void printf_op(lrm_op_t *op); +static void printf_hash_table(GHashTable *hash_table); +static gboolean lrm_dispatch(IPC_Channel *notused, gpointer user_data); +static GMainLoop *mainloop; + +int +main(int argc, char *argv[]) +{ + ll_lrm_t* lrm; + lrm_rsc_t* rsc = NULL; + lrm_op_t* op = NULL; + const char* rid = "ip248"; + GHashTable* param = NULL; + + lrm = ll_lrm_new("lrm"); + + if(NULL == lrm) + { + printf("lrm==NULL\n"); + return 1; + } + puts("sigon..."); + lrm->lrm_ops->signon(lrm,"apitest"); + lrm->lrm_ops->set_lrm_callback(lrm, lrm_op_done_callback); + + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("3ffe:ffff:0:f101::3")); + puts("add_rsc..."); + lrm->lrm_ops->add_rsc(lrm, rid, "heartbeat", "IPv6addr", NULL, param); + puts("get_rsc..."); + rsc = lrm->lrm_ops->get_rsc(lrm, rid); + printf_rsc(rsc); + + puts("perform_op(start)..."); + op = lrm_op_new(); + op->op_type = g_strdup("start"); + op->params = NULL; + op->timeout = 0; + op->user_data = strdup("It is a start op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 0; + op->target_rc = EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + + puts("perform_op(status)..."); + op = lrm_op_new(); + op->op_type = g_strdup("status"); + op->params = NULL; + op->timeout = 0; + op->user_data = strdup("It is a status op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 1000; + op->target_rc=EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + + puts("perform_op(stop)..."); + op = lrm_op_new(); + op->op_type = g_strdup("stop"); + op->params = NULL; + op->timeout = 0; + op->user_data = strdup("It is a stop op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 0; + op->target_rc=EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + + G_main_add_IPC_Channel(G_PRIORITY_LOW, + lrm->lrm_ops->ipcchan(lrm), + FALSE, + lrm_dispatch, lrm, + NULL); + + mainloop = g_main_new(FALSE); + g_main_run(mainloop); + + puts("delete_rsc..."); + lrm->lrm_ops->delete_rsc(lrm, rid); + + puts("signoff..."); + lrm->lrm_ops->signoff(lrm); + + return 0; +} + +static void +lrm_op_done_callback(lrm_op_t *op) +{ + puts("lrm_op_done_callback..."); + printf_op(op); +} + +static gboolean +lrm_dispatch(IPC_Channel *notused, gpointer user_data) +{ + ll_lrm_t *lrm = (ll_lrm_t*)user_data; + lrm->lrm_ops->rcvmsg(lrm, FALSE); + return TRUE; +} + +static void +printf_rsc(lrm_rsc_t *rsc) +{ + printf("print resource\n"); + if (NULL == rsc) { + printf("resource is null\n"); + printf("print end\n"); + return; + } + printf("\tresource of id:%s\n", rsc->id); + printf("\ttype:%s\n", rsc->type); + printf("\tclass:%s\n", rsc->class); + printf("\tparams:\n"); + printf_hash_table(rsc->params); + printf("print end\n"); +} + +static void +printf_op(lrm_op_t *op) +{ + printf("print op\n"); + + if (NULL == op) { + printf("op is null\n"); + printf("print end\n"); + return; + } + + printf("\top_type:%s\n",op->op_type?op->op_type:"null"); + printf("\tparams:\n"); + printf_hash_table(op->params); + printf("\ttimeout:%d\n",op->timeout); + printf("\tuser_data:%s\n",op->user_data?(char*)op->user_data:"null"); + printf("\tuser_data pointer:%p\n",op->user_data); + printf("\top_status:%d\n",op->op_status); + printf("\tapp_name:%s\n",op->app_name?op->app_name:"null"); + printf("\toutput:%s\n",op->output?op->output:"null"); + printf("\trc:%d\n",op->rc); +/* printf("\tcall_id:%d\n",op->call_id); */ + printf("print end\n"); +} + +static void +printf_pair(gpointer key, gpointer value, gpointer user_data) +{ + printf("\t\t%s=%s\n",(char*)key,(char*)value); +} + +static void +printf_hash_table(GHashTable *hash_table) +{ + if (NULL == hash_table) { + printf("\t\tnull\n"); + return; + } + g_hash_table_foreach(hash_table, printf_pair, NULL); +} diff --git a/lrm/test/defaults b/lrm/test/defaults new file mode 100644 index 0000000..039915b --- /dev/null +++ b/lrm/test/defaults @@ -0,0 +1,9 @@ +# defaults +: ${dflt_rsc:=r1} +: ${dflt_type:=lrmregtest} +: ${dflt_class:=ocf} +: ${dflt_provider:=heartbeat} +: ${dflt_timeout:=1000} +: ${dflt_interval:=0} +: ${dflt_targetrc:=EVERYTIME} +dflt_args="" diff --git a/lrm/test/descriptions b/lrm/test/descriptions new file mode 100644 index 0000000..f2aab6b --- /dev/null +++ b/lrm/test/descriptions @@ -0,0 +1,55 @@ +lead=".TRY" +describe_list() { + echo $lead List resources +} +describe_add() { + echo $lead Add resource \ + ${rsc:-$dflt_rsc} \ + class=${class:-$dflt_class} type=${type:-$dflt_type} \ + provider=${provider:-$dflt_provider} \ + args=$args +} +describe_del() { + echo $lead Delete resource \ + ${rsc:-$dflt_rsc} +} +describe_flush() { + echo $lead Flush resource \ + ${rsc:-$dflt_rsc} +} +describe_state() { + echo $lead Show state \ + ${rsc:-$dflt_rsc} +} +describe_info() { + echo $lead Show info \ + ${rsc:-$dflt_rsc} +} +describe_exec() { + echo $lead Exec \ + ${rsc:-$dflt_rsc} \ + op=${operation:-$dflt_operation} \ + timeout=${timeout:-$dflt_timeout} interval=${interval:-$dflt_interval} \ + target=${targetrc:-$dflt_targetrc} args=$args +} + +describe_classes() { + echo $lead List classes +} +describe_types() { + echo $lead List types \ + class=${class:-$dflt_class} +} +describe_classmeta() { + echo $lead Meta-data \ + class=${class:-$dflt_class} +} +describe_meta() { + echo $lead Show meta-data \ + class=${class:-$dflt_class} \ + type=${type:-$dflt_type} provider=${provider:-$dflt_provider} +} +describe_provider() { + echo $lead Show provider \ + class=${class:-$dflt_class} type=${type:-$dflt_type} +} diff --git a/lrm/test/evaltest.sh b/lrm/test/evaltest.sh new file mode 100755 index 0000000..f369102 --- /dev/null +++ b/lrm/test/evaltest.sh @@ -0,0 +1,171 @@ +#!/bin/sh + + # Copyright (C) 2007 Dejan Muhamedagic <dejan@suse.de> + # + # 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.1 of the License, or (at your option) any later version. + # + # This software is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + # General Public License for more details. + # + # You should have received a copy of the GNU General Public + # License along with this library; if not, write to the Free Software + # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + # + +: ${TESTDIR:=testcases} +: ${LRMADMIN:=../admin/lrmadmin} +test -x $LRMADMIN || LRMADMIN=lrmadmin +: ${OCF_ROOT:=/usr/lib/ocf} + +. ./defaults +. ./lrmadmin-interface +. ./descriptions + +resetvars() { + unset rsc type class provider timeout interval targetrc args + unset extcheck +} + +# +# special operations squad +# +specopt_setenv() { + eval $rest +} +specopt_sleep() { + #sleep $rest + # the while loop below is the same + # but we give user some feedback on what's happening + while [ "$rest" -gt 0 ]; do + sleep 1 + echo -n "+" >&3 + rest=$(($rest-1)) + done +} +specopt_extcheck() { + extcheck="$rest" + set $extcheck + which "$1" >/dev/null 2>&1 || # a program in the PATH + extcheck="$TESTDIR/$extcheck" # or our script +} +specopt_repeat() { + repeat_limit=$rest +} +specopt_bg() { + if [ "$job_cnt" -gt "$bgprocs_num" ]; then + bgprocs_num=${rest:-1} + job_cnt=1 + else + echo ".BG bad usage: more tests yet to be backgrounded" + fi +} +specopt_bgrepeat() { # common + specopt_bg + specopt_repeat +} +specopt_wait() { # common + waitforbgprocs +} +specopt_shell() { # run command with shell + echo "$rest" | sh -s | # and execute the command + { [ "$extcheck" ] && $extcheck || cat;} +} +specopt() { + cmd=`echo $cmd | sed 's/%//'` # strip leading '%' + echo ".`echo $cmd | tr '[a-z]' '[A-Z]'` $rest" # show what we got + specopt_$cmd # do what they asked for +} + +# +# wait for background processes to finish +# and print their output +# NB: We wait for processes in a FIFO order +# The order in which they finish does not matter +# +waitforbgprocs() { + while [ "$bgprocs" ]; do + set $bgprocs + proc=$1 # get the first one + shift 1 # remove it from the list + bgprocs="$@" + IFS=":" + set $proc # split into lineno,pid + testline=$1 jobnum=$2 pid=$3 + unset IFS + + while kill -0 $pid 2>/dev/null; do + sleep 1 + done + wait $pid # capture the exit code + + echo ".BG test line $testline/job $jobnum finished (exit code: $?):" + echo "==========test:$testline:$jobnum start output==========" + cat $OUTDIR/bg$$-$testline-$jobnum + echo "==========test:$testline:$jobnum end output==========" + rm -f $OUTDIR/bg$$-$testline-$jobnum + done +} + +# +# substitute variables in the test line +# +substvars() { + sed " + s/%t/$test_cnt/g + s/%l/$line/g + s/%j/$job_cnt/g + s/%i/$repeat_cnt/g + " +} + +dotest() { + echo -n "." >&3 + test_cnt=$(($test_cnt+1)) + describe_$cmd # show what we are about to do + lrm_$cmd | # and execute the command + { [ "$extcheck" ] && $extcheck || cat;} +} +runonetest() { + eval `echo $rest | substvars` # set parameters + if [ "$job_cnt" -le "$bgprocs_num" ]; then + echo .BG test line $line/job $job_cnt runs in background + dotest > $OUTDIR/bg$$-$line-$job_cnt 2>&1 & + bgprocs="$bgprocs $line:$job_cnt:$!" + job_cnt=$(($job_cnt+1)) + else + dotest + fi +} +runtest() { + while [ $repeat_cnt -le $repeat_limit ]; do + runonetest + resetvars # unset all variables + repeat_cnt=$(($repeat_cnt+1)) + done + repeat_limit=1 repeat_cnt=1 +} + +# +# run the tests +# +bgprocs_num=0 job_cnt=1 +repeat_limit=1 repeat_cnt=1 +line=1 +test_cnt=1 + +while read cmd rest; do + case "$cmd" in + "") : empty ;; + "#"*) : a comment ;; + "%stop") break ;; + "%"*) specopt ;; + *) runtest ;; + esac + line=$(($line+1)) +done +waitforbgprocs diff --git a/lrm/test/language b/lrm/test/language new file mode 100644 index 0000000..d2785e8 --- /dev/null +++ b/lrm/test/language @@ -0,0 +1,16 @@ +The meta language and how it translates to the lrmadmin options: + +list:-L +add:-A %r %C %T %P +del:-D %r +flush:-F %r +state:-S %r +info:-I %r +exec:-E %r %o %t %i %e + +classes:-C +types:-T %C +classmeta:-O %C +meta:-M %C %T %P +provider:-P %C %T + diff --git a/lrm/test/lrmadmin-interface b/lrm/test/lrmadmin-interface new file mode 100644 index 0000000..4eb1656 --- /dev/null +++ b/lrm/test/lrmadmin-interface @@ -0,0 +1,43 @@ +lrm_list() { + $LRMADMIN -L +} +lrm_add() { + $LRMADMIN -A ${rsc:-$dflt_rsc} \ + ${class:-$dflt_class} ${type:-$dflt_type} \ + ${provider:-$dflt_provider} \ + $args +} +lrm_del() { + $LRMADMIN -D ${rsc:-$dflt_rsc} +} +lrm_flush() { + $LRMADMIN -F ${rsc:-$dflt_rsc} +} +lrm_state() { + $LRMADMIN -S ${rsc:-$dflt_rsc} +} +lrm_info() { + $LRMADMIN -I ${rsc:-$dflt_rsc} +} +lrm_exec() { + $LRMADMIN -E ${rsc:-$dflt_rsc} \ + ${operation:-$dflt_operation} \ + ${timeout:-$dflt_timeout} ${interval:-$dflt_interval} \ + ${targetrc:-$dflt_targetrc} $args +} + +lrm_classes() { + $LRMADMIN -C +} +lrm_types() { + $LRMADMIN -T ${class:-$dflt_class} +} +lrm_classmeta() { + $LRMADMIN -O ${class:-$dflt_class} +} +lrm_meta() { + $LRMADMIN -M ${class:-$dflt_class} ${type:-$dflt_type} ${provider:-$dflt_provider} +} +lrm_provider() { + $LRMADMIN -P ${class:-$dflt_class} ${type:-$dflt_type} +} diff --git a/lrm/test/lrmregtest-lsb b/lrm/test/lrmregtest-lsb new file mode 100644 index 0000000..4692b17 --- /dev/null +++ b/lrm/test/lrmregtest-lsb @@ -0,0 +1,54 @@ +#!/bin/sh +# +# WARNING: This script is for LRM regressions tests only +# +### BEGIN INIT INFO +# Provides: lrmregtest +# Required-Start: +# Should-Start: +# Required-Stop: +# Should-Stop: +# Default-Start: +# Default-Stop: +# Short-Description: LRM regression tests LSB RA +# Description: LRM regression tests LSB RA +### END INIT INFO + +TYPE=lrmregtest +# depends on resource-agents and the OCF +: ${OCF_ROOT:=/usr/lib/ocf} +. ${OCF_ROOT}/lib/heartbeat/ocf-shellfuncs + +case "$1" in + start) + echo -n "Starting $TYPE" + ha_pseudo_resource lrmregtest_lsb start + ;; + stop) + echo -n "Shutting down $TYPE" + ha_pseudo_resource lrmregtest_lsb stop + ;; + status) + echo -n "Checking for $TYPE" + ha_pseudo_resource lrmregtest_lsb monitor + if [ $? -eq 0 ]; then + echo " running" + exit 0 + else + echo " stopped" + exit 3 + fi + ;; + *) + echo "Usage: $0 {start|stop|status}" + exit 1 + ;; +esac + +if [ $? -eq 0 ]; then + echo " OK" + exit 0 +else + echo " failed" + exit 1 +fi diff --git a/lrm/test/lrmregtest.in b/lrm/test/lrmregtest.in new file mode 100644 index 0000000..001a662 --- /dev/null +++ b/lrm/test/lrmregtest.in @@ -0,0 +1,220 @@ +#!/bin/sh +# +# +# lrmregtest OCF RA. Does nothing but wait a few seconds, can be +# configured to fail occassionally. +# +# updated to support the LRM regression testing. +# +# Copyright (c) 2007 SUSE LINUX AG, Dejan Muhamedagic +# All Rights Reserved. +# +# Copyright (c) 2004 SUSE LINUX AG, Lars Marowsky-Brée +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +####################################################################### +# Initialization: + +. ${OCF_ROOT}/lib/heartbeat/ocf-shellfuncs + +####################################################################### + +meta_data() { + cat <<END +<?xml version="1.0"?> +<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd"> +<resource-agent name="lrmregtest" version="0.9"> +<version>1.0</version> + +<longdesc lang="en"> +This is a lrmregtest Resource Agent. Use for LRM regression +testing. +</longdesc> +<shortdesc lang="en">lrmregtest resource agent</shortdesc> + +<parameters> +<parameter name="delay" unique="0"> +<longdesc lang="en"> +How long to delay before each action. +</longdesc> +<shortdesc lang="en">Action delay</shortdesc> +<content type="integer" default="0" /> +</parameter> + +<parameter name="check_parallel" unique="0"> +<longdesc lang="en"> +Complain loudly if they try to run us in parallel on the same resource. +</longdesc> +<shortdesc lang="en">Report error if run twice at the same time</shortdesc> +<content type="boolean" default="true" /> +</parameter> + +<parameter name="ignore_TERM" unique="0"> +<longdesc lang="en"> +Process the TERM signal and don't exit. +</longdesc> +<shortdesc lang="en">No TERM ain't gonna kill us.</shortdesc> +<content type="boolean" default="false" /> +</parameter> + +<parameter name="verbose" unique="0"> +<longdesc lang="en"> +Print more information. +</longdesc> +<shortdesc lang="en">Be verbose.</shortdesc> +<content type="boolean" default="false" /> +</parameter> + +</parameters> + +<actions> +<action name="start" timeout="90" /> +<action name="stop" timeout="100" /> +<action name="monitor" timeout="20" interval="10" depth="0" start-delay="0" /> +<action name="reload" timeout="90" /> +<action name="migrate_to" timeout="100" /> +<action name="migrate_from" timeout="90" /> +<action name="meta-data" timeout="5" /> +<action name="validate-all" timeout="30" /> +</actions> +</resource-agent> +END +} + +####################################################################### + +# don't exit on TERM, to test that lrmd makes sure that we do exit +sigterm_handler() { + ocf_log info "They use TERM to bring us down. No such luck." + return +} + +dummy_usage() { + cat <<END +usage: $0 {start|stop|monitor|migrate_to|migrate_from|validate-all|meta-data} + +Expects to have a fully populated OCF RA-compliant environment set. +END +} + +# signals interrupt slow calls (sleep) +# this is an approximation (after all it's just a dummy) +sleepsleep() { + delay=$1 + now=`perl -e 'print time()'` + by=$(($now+$delay)) + while [ $now -lt $by ]; do + ocf_log debug "Gonna sleep for $(($by-$now)) seconds..." + sleep $(($by-$now)) + now=`perl -e 'print time()'` + done +} +dummy_start() { + sleepsleep $OCF_RESKEY_delay + ha_pseudo_resource lrmregtest_${OCF_RESOURCE_INSTANCE} start +} + +dummy_stop() { + sleepsleep $OCF_RESKEY_delay + ha_pseudo_resource lrmregtest_${OCF_RESOURCE_INSTANCE} stop +} + +dummy_monitor() { + sleepsleep $OCF_RESKEY_delay + ha_pseudo_resource lrmregtest_${OCF_RESOURCE_INSTANCE} monitor +} + +dummy_validate() { + exit $OC_ERR_UNIMPLEMENTED +} + +verbose() { + [ "$OCF_RESKEY_verbose" != 0 ] +} +environment() { + echo "OCF environment variables:" + set | egrep 'OCF_RESKEY|OCF_RESOURCE_INSTANCE' +} +invocation() { + echo "invoked with args: $@" +} + +: ${OCF_RESKEY_delay=0} +: ${OCF_RESKEY_check_parallel=1} +: ${OCF_RESKEY_verbose=0} +: ${OCF_RESKEY_ignore_TERM=0} + +verbose && environment + +lockf=` + ha_pseudo_resource lrmregtest_${OCF_RESOURCE_INSTANCE} print | + sed 's/$/.lock/' +` + +check4parallel() { + if [ -f "$lockf" ] && kill -0 `cat $lockf` 2>/dev/null + then + ocf_log err "There is another instance of ${OCF_RESOURCE_INSTANCE} running: pid `cat $lockf`." + exit $OCF_ERR_GENERIC + fi +} + +[ "$OCF_RESKEY_check_parallel" = 1 ] && + check4parallel + +[ "$OCF_RESKEY_ignore_TERM" = 1 ] && + trap sigterm_handler TERM + +echo $$ > $lockf +trap "rm -f $lockf" EXIT + +verbose && invocation $@ + +case $__OCF_ACTION in +meta-data) meta_data + exit $OCF_SUCCESS + ;; +start) dummy_start;; +stop) dummy_stop;; +monitor) dummy_monitor;; +migrate_to) ocf_log info "Migrating ${OCF_RESOURCE_INSTANCE} to ${OCF_RESKEY_CRM_meta_migrate_to}." + dummy_stop + ;; +migrate_from) ocf_log info "Migrating ${OCF_RESOURCE_INSTANCE} to ${OCF_RESKEY_CRM_meta_migrated_from}." + dummy_start + ;; +reload) ocf_log err "Reloading..." + dummy_start + ;; +validate-all) dummy_validate;; +usage|help) dummy_usage + exit $OCF_SUCCESS + ;; +*) dummy_usage + exit $OCF_ERR_UNIMPLEMENTED + ;; +esac +rc=$? +ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc" +exit $rc + diff --git a/lrm/test/plugintest.c b/lrm/test/plugintest.c new file mode 100644 index 0000000..d25c46d --- /dev/null +++ b/lrm/test/plugintest.c @@ -0,0 +1,84 @@ +/* File: plugintest.c + * Description: A small,simple tool to test RA execution plugin + * + * Author: Sun Jiang Dong <sunjd@cn.ibm.com> + * Copyright (c) 2004 International Business Machines + * + * Todo: security verification + * + * 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.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <glib.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <pils/plugin.h> +#include <pils/generic.h> +#include <lrm/raexec.h> + +static void +g_print_item(gpointer data, gpointer user_data) +{ + printf("%s\n", (char*)data); +} + + +int main(void) +{ + PILPluginUniv * PluginLoadingSystem = NULL; + GHashTable * RAExecFuncs = NULL; + GList * ratype_list; + struct RAExecOps * RAExec; + /* + GHashTable * cmd_params; + */ + int ret; + + PILGenericIfMgmtRqst RegisterRqsts[]= { + {"RAExec", &RAExecFuncs, NULL, NULL, NULL}, + { NULL, NULL, NULL, NULL, NULL} }; + + PluginLoadingSystem = NewPILPluginUniv ("/usr/lib/heartbeat/plugins"); + + PILLoadPlugin(PluginLoadingSystem , "InterfaceMgr", "generic" , &RegisterRqsts); + + PILLoadPlugin(PluginLoadingSystem , "RAExec", "ocf", NULL); + RAExec = g_hash_table_lookup(RAExecFuncs,"ocf"); + ret = RAExec->get_resource_list(&ratype_list); + printf("length=%d\n", g_list_length(ratype_list)); + if (ret >= 0) { + g_list_foreach(ratype_list, g_print_item, NULL); + } + + /* + PILLoadPlugin(PluginLoadingSystem , "RAExec", "lsb", NULL); + RAExec = g_hash_table_lookup(RAExecFuncs,"lsb"); + cmd_params = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(cmd_params, g_strdup("1"), g_strdup("par1")); + g_hash_table_insert(cmd_params, g_strdup("2"), g_strdup("par2")); + ret = RAExec->execra("/tmp/test.sh", "start", cmd_params,NULL); + */ + + /* For test the dealing with directory appended to RA */ + /* + PILLoadPlugin(PluginLoadingSystem , "RAExec", "ocf", NULL); + RAExec = g_hash_table_lookup(RAExecFuncs,"ocf"); + if (0>RAExec->execra("/root/linux-ha-checkout/linux-ha/lrm/test.sh", + "stop",NULL,NULL, TRUE, &key)) + */ + printf("execra result: ret = %d\n", ret); + return -1; +} diff --git a/lrm/test/regression.sh.in b/lrm/test/regression.sh.in new file mode 100755 index 0000000..550321e --- /dev/null +++ b/lrm/test/regression.sh.in @@ -0,0 +1,248 @@ +#!/bin/sh + + # Copyright (C) 2007 Dejan Muhamedagic <dmuhamedagic@suse.de> + # + # 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.1 of the License, or (at your option) any later version. + # + # This software is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + # General Public License for more details. + # + # You should have received a copy of the GNU General Public + # License along with this library; if not, write to the Free Software + # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + # + +OCF_ROOT=@OCF_ROOT_DIR@ +export OCF_ROOT +if [ -z "$OCF_ROOT" ]; then + [ -d /usr/lib/ocf ] && OCF_ROOT=/usr/lib/ocf +fi +if [ ! -d "$OCF_ROOT" ]; then + echo "OCF_ROOT environment variable not set" + exit 2 +fi + +TESTDIR=${TESTDIR:-testcases} +DFLT_TESTSET=basicset +OUTDIR=${OUTDIR:-output} +LRMD_OUTF="$OUTDIR/lrmd.out" +LRMD_LOGF="$OUTDIR/lrmd.log" +LRMD_DEBUGF="$OUTDIR/lrmd.debug" +OUTF="$OUTDIR/regression.out" +LRMADMIN="@sbindir@/lrmadmin" +LRMD_OPTS="-vvv" +STONITHD_OPTS="-at" +DIFF_OPTS="--ignore-all-space -U 1" +common_filter=$TESTDIR/common.filter +common_exclf=$TESTDIR/common.excl +OCF_RA=$OCF_ROOT/resource.d/heartbeat/lrmregtest +LSB_RA=@LSB_RA_DIR@/lrmregtest +export OUTDIR TESTDIR LRMADMIN + +logmsg() { + echo "`date`: $*" | tee -a $LRMD_DEBUGF | tee -a $LRMD_LOGF +} +abspath() { + echo $1 | grep -qs "^/" && + echo $1 || + echo `pwd`/$1 +} + +usage() { + cat<<EOF + +usage: $0 [-q] [testcase...|set:testset] + +Test lrmd using supplied testcases. If none are given, +set:basicset is used. All testcases and sets are in testcases/. +See also README.regression for description. + +-q: quiet operation (no progress shown) + +EOF +exit 2 +} + +if [ `id -u` != 0 ]; then + echo "sorry, but i talk to root only" + exit 2 +fi +cd `dirname $0` +if [ ! -d "$TESTDIR" ]; then + echo "$0: $TESTDIR does not exit" + usage +fi + +which xmllint >/dev/null 2>&1 || { + echo "WARNING: xmllint not available, some of the tests may fail" +} + +rm -f $LRMD_LOGF $LRMD_DEBUGF + +# make lrmd log to our files only +HA_logfile=`abspath $LRMD_LOGF` +HA_debugfile=`abspath $LRMD_DEBUGF` +HA_use_logd=no +HA_logfacility="" +export HA_logfile HA_debugfile HA_use_logd HA_logfacility + +mkdir -p $OUTDIR +. ${OCF_ROOT}/lib/heartbeat/ocf-shellfuncs + +args=`getopt hq $*` +[ $? -ne 0 ] && usage +eval set -- "$args" + +SILENT="" +while [ x"$1" != x ]; do + case "$1" in + -h) usage;; + -q) SILENT=1;; + --) shift 1; break;; + *) usage;; + esac + shift 1 +done + +exec >$OUTF 2>&1 +if [ "$SILENT" = 1 ]; then + exec 3>/dev/null +else + exec 3>/dev/tty +fi + +start_stonithd() { + echo "starting stonithd" >&3 + $HA_BIN/stonithd -s 2>/dev/null + if [ $? -ne 0 ]; then + STOP_STONITHD=1 + $HA_BIN/stonithd $STONITHD_OPTS + sleep 1 + $HA_BIN/stonithd -s 2>/dev/null + else + STOP_STONITHD= + fi +} +stop_stonithd() { + if [ "$STOP_STONITHD" ]; then + echo "stopping stonithd" >&3 + $HA_BIN/stonithd -k >/dev/null 2>&1 + fi +} +start_lrmd() { + echo "starting lrmd" >&3 + $HA_BIN/lrmd -s 2>/dev/null + if [ $? -eq 3 ]; then + #strace -o /tmp/lrmd.trc $HA_BIN/lrmd $LRMD_OPTS >$LRMD_OUTF 2>&1 & + $HA_BIN/lrmd $LRMD_OPTS >$LRMD_OUTF 2>&1 & + sleep 1 + $HA_BIN/lrmd -s 2>/dev/null + else + echo "lrmd already running; can't proceed" >&3 + return 2 + fi +} +stop_lrmd() { + echo "stopping lrmd" >&3 + $HA_BIN/lrmd -k +} +cp_ra() { + cp -p lrmregtest $OCF_RA + chmod +x $OCF_RA + cp -p lrmregtest-lsb $LSB_RA + chmod +x $LSB_RA +} +rm_ra() { + rm -f $OCF_RA $LSB_RA +} + +cp_ra +start_lrmd || exit $? +# start_stonithd || exit $? +trap "stop_lrmd; stop_stonithd; rm_ra" EXIT + +setenvironment() { + filterf=$TESTDIR/$testcase.filter + exclf=$TESTDIR/$testcase.excl + log_filter=$TESTDIR/$testcase.log_filter + expf=$TESTDIR/$testcase.exp + outf=$OUTDIR/$testcase.out + difff=$OUTDIR/$testcase.diff +} + +filter_output() { + { [ -x $common_filter ] && $common_filter || cat;} | + { [ -f $common_exclf ] && egrep -vf $common_exclf || cat;} | + { [ -x $filterf ] && $filterf || cat;} | + { [ -f $exclf ] && egrep -vf $exclf || cat;} +} + +dumpcase() { + cat<<EOF +---------- +testcase $testcase failed +output is in $outf +diff (from $difff): +`cat $difff` +---------- +EOF +} + +runtestcase() { + setenvironment + echo -n "$testcase" >&3 + logmsg "BEGIN testcase $testcase" + ./evaltest.sh < $TESTDIR/$testcase > $outf 2>&1 + + filter_output < $outf | + if [ "$prepare" ]; then + echo " saving to expect file" >&3 + cat > $expf + else + echo -n " checking..." >&3 + diff $DIFF_OPTS $expf - > $difff + if [ $? -ne 0 ]; then + echo " FAIL" >&3 + dumpcase + return 1 + else + echo " PASS" >&3 + rm -f $outf $difff + fi + fi + sed -n "/BEGIN testcase $testcase/,\$p" $LRMD_LOGF | + { [ -x $log_filter ] && $log_filter || cat;} | + egrep '(CRIT|ERROR):' + logmsg "END testcase $testcase" +} + +[ "$1" = prepare ] && { prepare=1; shift 1;} +[ $# -eq 0 ] && set "set:$DFLT_TESTSET" + +for a; do + if [ "$a" -a -f "$TESTDIR/$a" ]; then + testcase=$a + runtestcase + else + echo "$a" | grep -q "^set:" && + TESTSET=$TESTDIR/`echo $a | sed 's/set://'` + while read testcase; do + runtestcase + done < $TESTSET + fi +done + +if egrep -wv '(BEGIN|END) testcase' $OUTF >/dev/null +then + echo "seems like some tests failed or else something not expected" + echo "check $OUTF and diff files in $OUTDIR" + echo "in case you wonder what lrmd was doing, read $LRMD_LOGF and $LRMD_DEBUGF" + exit 1 +else + rm -f $OUTF $LRMD_OUTF +fi >&3 diff --git a/lrm/test/testcases/BSC b/lrm/test/testcases/BSC new file mode 100644 index 0000000..157fb6c --- /dev/null +++ b/lrm/test/testcases/BSC @@ -0,0 +1,4 @@ +rscmgmt +metadata +rscexec +stonith diff --git a/lrm/test/testcases/Makefile.am b/lrm/test/testcases/Makefile.am new file mode 100644 index 0000000..49728d9 --- /dev/null +++ b/lrm/test/testcases/Makefile.am @@ -0,0 +1,27 @@ +# +# Author: Sun Jiang Dong <sunjd@cn.ibm.com> +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +testcasesdir = $(datadir)/$(PACKAGE_NAME)/lrmtest/testcases +testcases_SCRIPTS = common.filter ra-list.sh rscmgmt.log_filter xmllint.sh +testcases_DATA = BSC basicset metadata metadata.exp rscexec \ + rscexec.exp rscmgmt rscmgmt.exp \ + stonith stonith.exp +# shouldn't need this next line... +EXTRA_DIST = $(testcases_SCRIPTS) $(testcases_DATA) diff --git a/lrm/test/testcases/basicset b/lrm/test/testcases/basicset new file mode 100644 index 0000000..62b9c04 --- /dev/null +++ b/lrm/test/testcases/basicset @@ -0,0 +1,6 @@ +rscmgmt +metadata +rscexec +stonith +serialize +flood diff --git a/lrm/test/testcases/common.filter b/lrm/test/testcases/common.filter new file mode 100755 index 0000000..f95e9d8 --- /dev/null +++ b/lrm/test/testcases/common.filter @@ -0,0 +1,27 @@ +#!/bin/sh + +sed ' +/^lrmadmin/s/([0-9][0-9]*)/()/ +/^lrmadmin/s/^lrmadmin[^:]*: [^ ]* // +s/call_id=[0-9][0-9]*/call_id=(removed)/ +/run at:/d +/last rc change at:/d +/queue time:/d +' | +awk ' +/Waiting for lrmd to callback.../ { n=1; next; } +n==1 && /----------------operation--------------/ { n++; next; } +n==2 && /type:/ { op=$0; sub("type:","",op); next } +n==2 && /operation status:/ { desc=$0; sub("operation status:","",desc); next } +n==2 && /op_status:/ { stat=$0; sub("op_status: *","",stat); next } +n==2 && /return code:/ { rc=$0; sub("return code: *","",rc); next } +n==2 && /output data:/ { n++; next; } +n==3 && /---------------------------------------/ { + printf("> %s %s (status=%s,rc=%s): %s\n",op,desc,stat,rc,substr(output,2)); + n=0; + output=""; + next; +} +n==3 && $1!="" { output=output"/"$0; next; } +{ print } +' diff --git a/lrm/test/testcases/flood b/lrm/test/testcases/flood new file mode 100644 index 0000000..de6d742 --- /dev/null +++ b/lrm/test/testcases/flood @@ -0,0 +1,19 @@ +# 30 secs should be enough even on slow machines +list +%setenv dflt_timeout=30000 +# add 64 resources +%repeat 64 +add rsc=r%i args="delay=0" +# start all in background +%bgrepeat 64 +exec rsc=r%i operation=start +%sleep 1 +# and run a monitor on all in background +%bgrepeat 64 +exec rsc=r%i operation=monitor +%sleep 1 +# finally, stop all +%repeat 64 +exec rsc=r%i operation=stop +%repeat 64 +del rsc=r%i diff --git a/lrm/test/testcases/flood.exp b/lrm/test/testcases/flood.exp new file mode 100644 index 0000000..cf8a2bb --- /dev/null +++ b/lrm/test/testcases/flood.exp @@ -0,0 +1,1354 @@ +.TRY List resources +Currently no resources are managed by LRM. +.SETENV dflt_timeout=30000 +.REPEAT 64 +.TRY Add resource r1 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r2 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r3 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r4 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r5 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r6 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r7 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r8 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r9 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r10 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r11 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r12 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r13 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r14 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r15 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r16 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r17 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r18 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r19 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r20 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r21 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r22 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r23 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r24 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r25 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r26 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r27 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r28 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r29 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r30 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r31 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r32 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r33 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r34 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r35 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r36 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r37 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r38 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r39 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r40 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r41 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r42 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r43 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r44 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r45 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r46 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r47 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r48 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r49 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r50 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r51 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r52 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r53 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r54 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r55 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r56 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r57 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r58 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r59 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r60 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r61 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r62 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r63 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r64 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.BGREPEAT 64 +.BG test line 9/job 1 runs in background +.BG test line 9/job 2 runs in background +.BG test line 9/job 3 runs in background +.BG test line 9/job 4 runs in background +.BG test line 9/job 5 runs in background +.BG test line 9/job 6 runs in background +.BG test line 9/job 7 runs in background +.BG test line 9/job 8 runs in background +.BG test line 9/job 9 runs in background +.BG test line 9/job 10 runs in background +.BG test line 9/job 11 runs in background +.BG test line 9/job 12 runs in background +.BG test line 9/job 13 runs in background +.BG test line 9/job 14 runs in background +.BG test line 9/job 15 runs in background +.BG test line 9/job 16 runs in background +.BG test line 9/job 17 runs in background +.BG test line 9/job 18 runs in background +.BG test line 9/job 19 runs in background +.BG test line 9/job 20 runs in background +.BG test line 9/job 21 runs in background +.BG test line 9/job 22 runs in background +.BG test line 9/job 23 runs in background +.BG test line 9/job 24 runs in background +.BG test line 9/job 25 runs in background +.BG test line 9/job 26 runs in background +.BG test line 9/job 27 runs in background +.BG test line 9/job 28 runs in background +.BG test line 9/job 29 runs in background +.BG test line 9/job 30 runs in background +.BG test line 9/job 31 runs in background +.BG test line 9/job 32 runs in background +.BG test line 9/job 33 runs in background +.BG test line 9/job 34 runs in background +.BG test line 9/job 35 runs in background +.BG test line 9/job 36 runs in background +.BG test line 9/job 37 runs in background +.BG test line 9/job 38 runs in background +.BG test line 9/job 39 runs in background +.BG test line 9/job 40 runs in background +.BG test line 9/job 41 runs in background +.BG test line 9/job 42 runs in background +.BG test line 9/job 43 runs in background +.BG test line 9/job 44 runs in background +.BG test line 9/job 45 runs in background +.BG test line 9/job 46 runs in background +.BG test line 9/job 47 runs in background +.BG test line 9/job 48 runs in background +.BG test line 9/job 49 runs in background +.BG test line 9/job 50 runs in background +.BG test line 9/job 51 runs in background +.BG test line 9/job 52 runs in background +.BG test line 9/job 53 runs in background +.BG test line 9/job 54 runs in background +.BG test line 9/job 55 runs in background +.BG test line 9/job 56 runs in background +.BG test line 9/job 57 runs in background +.BG test line 9/job 58 runs in background +.BG test line 9/job 59 runs in background +.BG test line 9/job 60 runs in background +.BG test line 9/job 61 runs in background +.BG test line 9/job 62 runs in background +.BG test line 9/job 63 runs in background +.BG test line 9/job 64 runs in background +.SLEEP 1 +.BGREPEAT 64 +.BG test line 13/job 1 runs in background +.BG test line 13/job 2 runs in background +.BG test line 13/job 3 runs in background +.BG test line 13/job 4 runs in background +.BG test line 13/job 5 runs in background +.BG test line 13/job 6 runs in background +.BG test line 13/job 7 runs in background +.BG test line 13/job 8 runs in background +.BG test line 13/job 9 runs in background +.BG test line 13/job 10 runs in background +.BG test line 13/job 11 runs in background +.BG test line 13/job 12 runs in background +.BG test line 13/job 13 runs in background +.BG test line 13/job 14 runs in background +.BG test line 13/job 15 runs in background +.BG test line 13/job 16 runs in background +.BG test line 13/job 17 runs in background +.BG test line 13/job 18 runs in background +.BG test line 13/job 19 runs in background +.BG test line 13/job 20 runs in background +.BG test line 13/job 21 runs in background +.BG test line 13/job 22 runs in background +.BG test line 13/job 23 runs in background +.BG test line 13/job 24 runs in background +.BG test line 13/job 25 runs in background +.BG test line 13/job 26 runs in background +.BG test line 13/job 27 runs in background +.BG test line 13/job 28 runs in background +.BG test line 13/job 29 runs in background +.BG test line 13/job 30 runs in background +.BG test line 13/job 31 runs in background +.BG test line 13/job 32 runs in background +.BG test line 13/job 33 runs in background +.BG test line 13/job 34 runs in background +.BG test line 13/job 35 runs in background +.BG test line 13/job 36 runs in background +.BG test line 13/job 37 runs in background +.BG test line 13/job 38 runs in background +.BG test line 13/job 39 runs in background +.BG test line 13/job 40 runs in background +.BG test line 13/job 41 runs in background +.BG test line 13/job 42 runs in background +.BG test line 13/job 43 runs in background +.BG test line 13/job 44 runs in background +.BG test line 13/job 45 runs in background +.BG test line 13/job 46 runs in background +.BG test line 13/job 47 runs in background +.BG test line 13/job 48 runs in background +.BG test line 13/job 49 runs in background +.BG test line 13/job 50 runs in background +.BG test line 13/job 51 runs in background +.BG test line 13/job 52 runs in background +.BG test line 13/job 53 runs in background +.BG test line 13/job 54 runs in background +.BG test line 13/job 55 runs in background +.BG test line 13/job 56 runs in background +.BG test line 13/job 57 runs in background +.BG test line 13/job 58 runs in background +.BG test line 13/job 59 runs in background +.BG test line 13/job 60 runs in background +.BG test line 13/job 61 runs in background +.BG test line 13/job 62 runs in background +.BG test line 13/job 63 runs in background +.BG test line 13/job 64 runs in background +.SLEEP 1 +.REPEAT 64 +.TRY Exec r1 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r2 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r3 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r4 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r5 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r6 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r7 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r8 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r9 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r10 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r11 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r12 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r13 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r14 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r15 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r16 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r17 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r18 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r19 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r20 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r21 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r22 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r23 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r24 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r25 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r26 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r27 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r28 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r29 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r30 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r31 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r32 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r33 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r34 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r35 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r36 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r37 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r38 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r39 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r40 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r41 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r42 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r43 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r44 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r45 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r46 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r47 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r48 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r49 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r50 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r51 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r52 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r53 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r54 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r55 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r56 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r57 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r58 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r59 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r60 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r61 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r62 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r63 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r64 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.REPEAT 64 +.TRY Delete resource r1 +Succeeded in deleting this resource. +.TRY Delete resource r2 +Succeeded in deleting this resource. +.TRY Delete resource r3 +Succeeded in deleting this resource. +.TRY Delete resource r4 +Succeeded in deleting this resource. +.TRY Delete resource r5 +Succeeded in deleting this resource. +.TRY Delete resource r6 +Succeeded in deleting this resource. +.TRY Delete resource r7 +Succeeded in deleting this resource. +.TRY Delete resource r8 +Succeeded in deleting this resource. +.TRY Delete resource r9 +Succeeded in deleting this resource. +.TRY Delete resource r10 +Succeeded in deleting this resource. +.TRY Delete resource r11 +Succeeded in deleting this resource. +.TRY Delete resource r12 +Succeeded in deleting this resource. +.TRY Delete resource r13 +Succeeded in deleting this resource. +.TRY Delete resource r14 +Succeeded in deleting this resource. +.TRY Delete resource r15 +Succeeded in deleting this resource. +.TRY Delete resource r16 +Succeeded in deleting this resource. +.TRY Delete resource r17 +Succeeded in deleting this resource. +.TRY Delete resource r18 +Succeeded in deleting this resource. +.TRY Delete resource r19 +Succeeded in deleting this resource. +.TRY Delete resource r20 +Succeeded in deleting this resource. +.TRY Delete resource r21 +Succeeded in deleting this resource. +.TRY Delete resource r22 +Succeeded in deleting this resource. +.TRY Delete resource r23 +Succeeded in deleting this resource. +.TRY Delete resource r24 +Succeeded in deleting this resource. +.TRY Delete resource r25 +Succeeded in deleting this resource. +.TRY Delete resource r26 +Succeeded in deleting this resource. +.TRY Delete resource r27 +Succeeded in deleting this resource. +.TRY Delete resource r28 +Succeeded in deleting this resource. +.TRY Delete resource r29 +Succeeded in deleting this resource. +.TRY Delete resource r30 +Succeeded in deleting this resource. +.TRY Delete resource r31 +Succeeded in deleting this resource. +.TRY Delete resource r32 +Succeeded in deleting this resource. +.TRY Delete resource r33 +Succeeded in deleting this resource. +.TRY Delete resource r34 +Succeeded in deleting this resource. +.TRY Delete resource r35 +Succeeded in deleting this resource. +.TRY Delete resource r36 +Succeeded in deleting this resource. +.TRY Delete resource r37 +Succeeded in deleting this resource. +.TRY Delete resource r38 +Succeeded in deleting this resource. +.TRY Delete resource r39 +Succeeded in deleting this resource. +.TRY Delete resource r40 +Succeeded in deleting this resource. +.TRY Delete resource r41 +Succeeded in deleting this resource. +.TRY Delete resource r42 +Succeeded in deleting this resource. +.TRY Delete resource r43 +Succeeded in deleting this resource. +.TRY Delete resource r44 +Succeeded in deleting this resource. +.TRY Delete resource r45 +Succeeded in deleting this resource. +.TRY Delete resource r46 +Succeeded in deleting this resource. +.TRY Delete resource r47 +Succeeded in deleting this resource. +.TRY Delete resource r48 +Succeeded in deleting this resource. +.TRY Delete resource r49 +Succeeded in deleting this resource. +.TRY Delete resource r50 +Succeeded in deleting this resource. +.TRY Delete resource r51 +Succeeded in deleting this resource. +.TRY Delete resource r52 +Succeeded in deleting this resource. +.TRY Delete resource r53 +Succeeded in deleting this resource. +.TRY Delete resource r54 +Succeeded in deleting this resource. +.TRY Delete resource r55 +Succeeded in deleting this resource. +.TRY Delete resource r56 +Succeeded in deleting this resource. +.TRY Delete resource r57 +Succeeded in deleting this resource. +.TRY Delete resource r58 +Succeeded in deleting this resource. +.TRY Delete resource r59 +Succeeded in deleting this resource. +.TRY Delete resource r60 +Succeeded in deleting this resource. +.TRY Delete resource r61 +Succeeded in deleting this resource. +.TRY Delete resource r62 +Succeeded in deleting this resource. +.TRY Delete resource r63 +Succeeded in deleting this resource. +.TRY Delete resource r64 +Succeeded in deleting this resource. +.BG test line 9/job 1 finished (exit code: 0): +==========test:9:1 start output========== +.TRY Exec r1 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:1 end output========== +.BG test line 9/job 2 finished (exit code: 0): +==========test:9:2 start output========== +.TRY Exec r2 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:2 end output========== +.BG test line 9/job 3 finished (exit code: 0): +==========test:9:3 start output========== +.TRY Exec r3 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:3 end output========== +.BG test line 9/job 4 finished (exit code: 0): +==========test:9:4 start output========== +.TRY Exec r4 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:4 end output========== +.BG test line 9/job 5 finished (exit code: 0): +==========test:9:5 start output========== +.TRY Exec r5 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:5 end output========== +.BG test line 9/job 6 finished (exit code: 0): +==========test:9:6 start output========== +.TRY Exec r6 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:6 end output========== +.BG test line 9/job 7 finished (exit code: 0): +==========test:9:7 start output========== +.TRY Exec r7 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:7 end output========== +.BG test line 9/job 8 finished (exit code: 0): +==========test:9:8 start output========== +.TRY Exec r8 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:8 end output========== +.BG test line 9/job 9 finished (exit code: 0): +==========test:9:9 start output========== +.TRY Exec r9 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:9 end output========== +.BG test line 9/job 10 finished (exit code: 0): +==========test:9:10 start output========== +.TRY Exec r10 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:10 end output========== +.BG test line 9/job 11 finished (exit code: 0): +==========test:9:11 start output========== +.TRY Exec r11 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:11 end output========== +.BG test line 9/job 12 finished (exit code: 0): +==========test:9:12 start output========== +.TRY Exec r12 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:12 end output========== +.BG test line 9/job 13 finished (exit code: 0): +==========test:9:13 start output========== +.TRY Exec r13 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:13 end output========== +.BG test line 9/job 14 finished (exit code: 0): +==========test:9:14 start output========== +.TRY Exec r14 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:14 end output========== +.BG test line 9/job 15 finished (exit code: 0): +==========test:9:15 start output========== +.TRY Exec r15 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:15 end output========== +.BG test line 9/job 16 finished (exit code: 0): +==========test:9:16 start output========== +.TRY Exec r16 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:16 end output========== +.BG test line 9/job 17 finished (exit code: 0): +==========test:9:17 start output========== +.TRY Exec r17 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:17 end output========== +.BG test line 9/job 18 finished (exit code: 0): +==========test:9:18 start output========== +.TRY Exec r18 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:18 end output========== +.BG test line 9/job 19 finished (exit code: 0): +==========test:9:19 start output========== +.TRY Exec r19 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:19 end output========== +.BG test line 9/job 20 finished (exit code: 0): +==========test:9:20 start output========== +.TRY Exec r20 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:20 end output========== +.BG test line 9/job 21 finished (exit code: 0): +==========test:9:21 start output========== +.TRY Exec r21 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:21 end output========== +.BG test line 9/job 22 finished (exit code: 0): +==========test:9:22 start output========== +.TRY Exec r22 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:22 end output========== +.BG test line 9/job 23 finished (exit code: 0): +==========test:9:23 start output========== +.TRY Exec r23 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:23 end output========== +.BG test line 9/job 24 finished (exit code: 0): +==========test:9:24 start output========== +.TRY Exec r24 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:24 end output========== +.BG test line 9/job 25 finished (exit code: 0): +==========test:9:25 start output========== +.TRY Exec r25 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:25 end output========== +.BG test line 9/job 26 finished (exit code: 0): +==========test:9:26 start output========== +.TRY Exec r26 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:26 end output========== +.BG test line 9/job 27 finished (exit code: 0): +==========test:9:27 start output========== +.TRY Exec r27 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:27 end output========== +.BG test line 9/job 28 finished (exit code: 0): +==========test:9:28 start output========== +.TRY Exec r28 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:28 end output========== +.BG test line 9/job 29 finished (exit code: 0): +==========test:9:29 start output========== +.TRY Exec r29 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:29 end output========== +.BG test line 9/job 30 finished (exit code: 0): +==========test:9:30 start output========== +.TRY Exec r30 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:30 end output========== +.BG test line 9/job 31 finished (exit code: 0): +==========test:9:31 start output========== +.TRY Exec r31 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:31 end output========== +.BG test line 9/job 32 finished (exit code: 0): +==========test:9:32 start output========== +.TRY Exec r32 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:32 end output========== +.BG test line 9/job 33 finished (exit code: 0): +==========test:9:33 start output========== +.TRY Exec r33 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:33 end output========== +.BG test line 9/job 34 finished (exit code: 0): +==========test:9:34 start output========== +.TRY Exec r34 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:34 end output========== +.BG test line 9/job 35 finished (exit code: 0): +==========test:9:35 start output========== +.TRY Exec r35 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:35 end output========== +.BG test line 9/job 36 finished (exit code: 0): +==========test:9:36 start output========== +.TRY Exec r36 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:36 end output========== +.BG test line 9/job 37 finished (exit code: 0): +==========test:9:37 start output========== +.TRY Exec r37 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:37 end output========== +.BG test line 9/job 38 finished (exit code: 0): +==========test:9:38 start output========== +.TRY Exec r38 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:38 end output========== +.BG test line 9/job 39 finished (exit code: 0): +==========test:9:39 start output========== +.TRY Exec r39 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:39 end output========== +.BG test line 9/job 40 finished (exit code: 0): +==========test:9:40 start output========== +.TRY Exec r40 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:40 end output========== +.BG test line 9/job 41 finished (exit code: 0): +==========test:9:41 start output========== +.TRY Exec r41 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:41 end output========== +.BG test line 9/job 42 finished (exit code: 0): +==========test:9:42 start output========== +.TRY Exec r42 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:42 end output========== +.BG test line 9/job 43 finished (exit code: 0): +==========test:9:43 start output========== +.TRY Exec r43 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:43 end output========== +.BG test line 9/job 44 finished (exit code: 0): +==========test:9:44 start output========== +.TRY Exec r44 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:44 end output========== +.BG test line 9/job 45 finished (exit code: 0): +==========test:9:45 start output========== +.TRY Exec r45 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:45 end output========== +.BG test line 9/job 46 finished (exit code: 0): +==========test:9:46 start output========== +.TRY Exec r46 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:46 end output========== +.BG test line 9/job 47 finished (exit code: 0): +==========test:9:47 start output========== +.TRY Exec r47 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:47 end output========== +.BG test line 9/job 48 finished (exit code: 0): +==========test:9:48 start output========== +.TRY Exec r48 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:48 end output========== +.BG test line 9/job 49 finished (exit code: 0): +==========test:9:49 start output========== +.TRY Exec r49 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:49 end output========== +.BG test line 9/job 50 finished (exit code: 0): +==========test:9:50 start output========== +.TRY Exec r50 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:50 end output========== +.BG test line 9/job 51 finished (exit code: 0): +==========test:9:51 start output========== +.TRY Exec r51 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:51 end output========== +.BG test line 9/job 52 finished (exit code: 0): +==========test:9:52 start output========== +.TRY Exec r52 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:52 end output========== +.BG test line 9/job 53 finished (exit code: 0): +==========test:9:53 start output========== +.TRY Exec r53 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:53 end output========== +.BG test line 9/job 54 finished (exit code: 0): +==========test:9:54 start output========== +.TRY Exec r54 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:54 end output========== +.BG test line 9/job 55 finished (exit code: 0): +==========test:9:55 start output========== +.TRY Exec r55 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:55 end output========== +.BG test line 9/job 56 finished (exit code: 0): +==========test:9:56 start output========== +.TRY Exec r56 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:56 end output========== +.BG test line 9/job 57 finished (exit code: 0): +==========test:9:57 start output========== +.TRY Exec r57 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:57 end output========== +.BG test line 9/job 58 finished (exit code: 0): +==========test:9:58 start output========== +.TRY Exec r58 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:58 end output========== +.BG test line 9/job 59 finished (exit code: 0): +==========test:9:59 start output========== +.TRY Exec r59 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:59 end output========== +.BG test line 9/job 60 finished (exit code: 0): +==========test:9:60 start output========== +.TRY Exec r60 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:60 end output========== +.BG test line 9/job 61 finished (exit code: 0): +==========test:9:61 start output========== +.TRY Exec r61 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:61 end output========== +.BG test line 9/job 62 finished (exit code: 0): +==========test:9:62 start output========== +.TRY Exec r62 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:62 end output========== +.BG test line 9/job 63 finished (exit code: 0): +==========test:9:63 start output========== +.TRY Exec r63 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:63 end output========== +.BG test line 9/job 64 finished (exit code: 0): +==========test:9:64 start output========== +.TRY Exec r64 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:64 end output========== +.BG test line 13/job 1 finished (exit code: 0): +==========test:13:1 start output========== +.TRY Exec r1 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:1 end output========== +.BG test line 13/job 2 finished (exit code: 0): +==========test:13:2 start output========== +.TRY Exec r2 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:2 end output========== +.BG test line 13/job 3 finished (exit code: 0): +==========test:13:3 start output========== +.TRY Exec r3 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:3 end output========== +.BG test line 13/job 4 finished (exit code: 0): +==========test:13:4 start output========== +.TRY Exec r4 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:4 end output========== +.BG test line 13/job 5 finished (exit code: 0): +==========test:13:5 start output========== +.TRY Exec r5 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:5 end output========== +.BG test line 13/job 6 finished (exit code: 0): +==========test:13:6 start output========== +.TRY Exec r6 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:6 end output========== +.BG test line 13/job 7 finished (exit code: 0): +==========test:13:7 start output========== +.TRY Exec r7 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:7 end output========== +.BG test line 13/job 8 finished (exit code: 0): +==========test:13:8 start output========== +.TRY Exec r8 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:8 end output========== +.BG test line 13/job 9 finished (exit code: 0): +==========test:13:9 start output========== +.TRY Exec r9 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:9 end output========== +.BG test line 13/job 10 finished (exit code: 0): +==========test:13:10 start output========== +.TRY Exec r10 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:10 end output========== +.BG test line 13/job 11 finished (exit code: 0): +==========test:13:11 start output========== +.TRY Exec r11 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:11 end output========== +.BG test line 13/job 12 finished (exit code: 0): +==========test:13:12 start output========== +.TRY Exec r12 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:12 end output========== +.BG test line 13/job 13 finished (exit code: 0): +==========test:13:13 start output========== +.TRY Exec r13 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:13 end output========== +.BG test line 13/job 14 finished (exit code: 0): +==========test:13:14 start output========== +.TRY Exec r14 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:14 end output========== +.BG test line 13/job 15 finished (exit code: 0): +==========test:13:15 start output========== +.TRY Exec r15 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:15 end output========== +.BG test line 13/job 16 finished (exit code: 0): +==========test:13:16 start output========== +.TRY Exec r16 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:16 end output========== +.BG test line 13/job 17 finished (exit code: 0): +==========test:13:17 start output========== +.TRY Exec r17 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:17 end output========== +.BG test line 13/job 18 finished (exit code: 0): +==========test:13:18 start output========== +.TRY Exec r18 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:18 end output========== +.BG test line 13/job 19 finished (exit code: 0): +==========test:13:19 start output========== +.TRY Exec r19 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:19 end output========== +.BG test line 13/job 20 finished (exit code: 0): +==========test:13:20 start output========== +.TRY Exec r20 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:20 end output========== +.BG test line 13/job 21 finished (exit code: 0): +==========test:13:21 start output========== +.TRY Exec r21 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:21 end output========== +.BG test line 13/job 22 finished (exit code: 0): +==========test:13:22 start output========== +.TRY Exec r22 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:22 end output========== +.BG test line 13/job 23 finished (exit code: 0): +==========test:13:23 start output========== +.TRY Exec r23 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:23 end output========== +.BG test line 13/job 24 finished (exit code: 0): +==========test:13:24 start output========== +.TRY Exec r24 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:24 end output========== +.BG test line 13/job 25 finished (exit code: 0): +==========test:13:25 start output========== +.TRY Exec r25 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:25 end output========== +.BG test line 13/job 26 finished (exit code: 0): +==========test:13:26 start output========== +.TRY Exec r26 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:26 end output========== +.BG test line 13/job 27 finished (exit code: 0): +==========test:13:27 start output========== +.TRY Exec r27 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:27 end output========== +.BG test line 13/job 28 finished (exit code: 0): +==========test:13:28 start output========== +.TRY Exec r28 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:28 end output========== +.BG test line 13/job 29 finished (exit code: 0): +==========test:13:29 start output========== +.TRY Exec r29 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:29 end output========== +.BG test line 13/job 30 finished (exit code: 0): +==========test:13:30 start output========== +.TRY Exec r30 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:30 end output========== +.BG test line 13/job 31 finished (exit code: 0): +==========test:13:31 start output========== +.TRY Exec r31 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:31 end output========== +.BG test line 13/job 32 finished (exit code: 0): +==========test:13:32 start output========== +.TRY Exec r32 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:32 end output========== +.BG test line 13/job 33 finished (exit code: 0): +==========test:13:33 start output========== +.TRY Exec r33 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:33 end output========== +.BG test line 13/job 34 finished (exit code: 0): +==========test:13:34 start output========== +.TRY Exec r34 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:34 end output========== +.BG test line 13/job 35 finished (exit code: 0): +==========test:13:35 start output========== +.TRY Exec r35 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:35 end output========== +.BG test line 13/job 36 finished (exit code: 0): +==========test:13:36 start output========== +.TRY Exec r36 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:36 end output========== +.BG test line 13/job 37 finished (exit code: 0): +==========test:13:37 start output========== +.TRY Exec r37 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:37 end output========== +.BG test line 13/job 38 finished (exit code: 0): +==========test:13:38 start output========== +.TRY Exec r38 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:38 end output========== +.BG test line 13/job 39 finished (exit code: 0): +==========test:13:39 start output========== +.TRY Exec r39 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:39 end output========== +.BG test line 13/job 40 finished (exit code: 0): +==========test:13:40 start output========== +.TRY Exec r40 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:40 end output========== +.BG test line 13/job 41 finished (exit code: 0): +==========test:13:41 start output========== +.TRY Exec r41 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:41 end output========== +.BG test line 13/job 42 finished (exit code: 0): +==========test:13:42 start output========== +.TRY Exec r42 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:42 end output========== +.BG test line 13/job 43 finished (exit code: 0): +==========test:13:43 start output========== +.TRY Exec r43 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:43 end output========== +.BG test line 13/job 44 finished (exit code: 0): +==========test:13:44 start output========== +.TRY Exec r44 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:44 end output========== +.BG test line 13/job 45 finished (exit code: 0): +==========test:13:45 start output========== +.TRY Exec r45 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:45 end output========== +.BG test line 13/job 46 finished (exit code: 0): +==========test:13:46 start output========== +.TRY Exec r46 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:46 end output========== +.BG test line 13/job 47 finished (exit code: 0): +==========test:13:47 start output========== +.TRY Exec r47 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:47 end output========== +.BG test line 13/job 48 finished (exit code: 0): +==========test:13:48 start output========== +.TRY Exec r48 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:48 end output========== +.BG test line 13/job 49 finished (exit code: 0): +==========test:13:49 start output========== +.TRY Exec r49 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:49 end output========== +.BG test line 13/job 50 finished (exit code: 0): +==========test:13:50 start output========== +.TRY Exec r50 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:50 end output========== +.BG test line 13/job 51 finished (exit code: 0): +==========test:13:51 start output========== +.TRY Exec r51 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:51 end output========== +.BG test line 13/job 52 finished (exit code: 0): +==========test:13:52 start output========== +.TRY Exec r52 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:52 end output========== +.BG test line 13/job 53 finished (exit code: 0): +==========test:13:53 start output========== +.TRY Exec r53 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:53 end output========== +.BG test line 13/job 54 finished (exit code: 0): +==========test:13:54 start output========== +.TRY Exec r54 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:54 end output========== +.BG test line 13/job 55 finished (exit code: 0): +==========test:13:55 start output========== +.TRY Exec r55 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:55 end output========== +.BG test line 13/job 56 finished (exit code: 0): +==========test:13:56 start output========== +.TRY Exec r56 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:56 end output========== +.BG test line 13/job 57 finished (exit code: 0): +==========test:13:57 start output========== +.TRY Exec r57 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:57 end output========== +.BG test line 13/job 58 finished (exit code: 0): +==========test:13:58 start output========== +.TRY Exec r58 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:58 end output========== +.BG test line 13/job 59 finished (exit code: 0): +==========test:13:59 start output========== +.TRY Exec r59 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:59 end output========== +.BG test line 13/job 60 finished (exit code: 0): +==========test:13:60 start output========== +.TRY Exec r60 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:60 end output========== +.BG test line 13/job 61 finished (exit code: 0): +==========test:13:61 start output========== +.TRY Exec r61 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:61 end output========== +.BG test line 13/job 62 finished (exit code: 0): +==========test:13:62 start output========== +.TRY Exec r62 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:62 end output========== +.BG test line 13/job 63 finished (exit code: 0): +==========test:13:63 start output========== +.TRY Exec r63 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:63 end output========== +.BG test line 13/job 64 finished (exit code: 0): +==========test:13:64 start output========== +.TRY Exec r64 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:64 end output========== diff --git a/lrm/test/testcases/metadata b/lrm/test/testcases/metadata new file mode 100644 index 0000000..d155757 --- /dev/null +++ b/lrm/test/testcases/metadata @@ -0,0 +1,29 @@ +# list various meta-data +%setenv LANG=POSIX +%extcheck sort +classes +%extcheck ra-list.sh +types class=ocf +%extcheck ra-list.sh +types class=lsb +%extcheck ra-list.sh +types class=heartbeat +#%extcheck ra-list.sh +#types class=stonith +%extcheck xmllint.sh many +classmeta class=ocf +%extcheck xmllint.sh many +classmeta class=lsb +%extcheck xmllint.sh many +classmeta class=heartbeat +#%extcheck xmllint.sh many +#classmeta class=stonith +%extcheck xmllint.sh +meta class=ocf type=Dummy +%extcheck xmllint.sh +meta class=lsb type=lrmregtest +%extcheck xmllint.sh +meta class=heartbeat type=Dummy +#%extcheck xmllint.sh +#meta class=stonith type=ssh +provider class=ocf type=IPaddr diff --git a/lrm/test/testcases/metadata.exp b/lrm/test/testcases/metadata.exp new file mode 100644 index 0000000..158bad2 --- /dev/null +++ b/lrm/test/testcases/metadata.exp @@ -0,0 +1,31 @@ +.SETENV LANG=POSIX +.EXTCHECK sort +.TRY List classes +There are 4 RA classes supported: +heartbeat +lsb +ocf +stonith +.EXTCHECK ra-list.sh +.TRY List types class=ocf +Cool. RA list passed. +.EXTCHECK ra-list.sh +.TRY List types class=lsb +Cool. RA list passed. +.EXTCHECK ra-list.sh +.TRY List types class=heartbeat +Cool. RA list passed. +.EXTCHECK xmllint.sh many +.TRY Meta-data class=ocf +.EXTCHECK xmllint.sh many +.TRY Meta-data class=lsb +.EXTCHECK xmllint.sh many +.TRY Meta-data class=heartbeat +.EXTCHECK xmllint.sh +.TRY Show meta-data class=ocf type=Dummy provider=heartbeat +.EXTCHECK xmllint.sh +.TRY Show meta-data class=lsb type=lrmregtest provider=heartbeat +.EXTCHECK xmllint.sh +.TRY Show meta-data class=heartbeat type=Dummy provider=heartbeat +.TRY Show provider class=ocf type=IPaddr +heartbeat diff --git a/lrm/test/testcases/ra-list.sh b/lrm/test/testcases/ra-list.sh new file mode 100755 index 0000000..38fb67b --- /dev/null +++ b/lrm/test/testcases/ra-list.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +awk ' +NR==1 {num=$3;next} +{in_num++} +END{ + if( num!=in_num ) + print "ERROR: A mismatch in number of reported RAs!"; + else + print "Cool. RA list passed."; +} +' diff --git a/lrm/test/testcases/rscexec b/lrm/test/testcases/rscexec new file mode 100644 index 0000000..e118ae1 --- /dev/null +++ b/lrm/test/testcases/rscexec @@ -0,0 +1,48 @@ +list +# ocf +%setenv dflt_rsc=rscexec_rsc_r1 +add rsc=rscexec_rsc_r1 args="delay=0" +list +exec operation=start +state +exec operation=monitor +exec operation=start +exec operation=monitor +exec operation=stop +state +exec operation=monitor +exec operation=stop +exec operation=monitor +exec operation=meta-data +del +# lsb +%setenv dflt_class=lsb dftl_rsc=rscexec_rsc_r1-lsb +add +exec operation=start +state +exec operation=monitor +exec operation=start +exec operation=monitor +exec operation=stop +state +exec operation=monitor +exec operation=stop +exec operation=monitor +exec operation=meta-data +del +%stop +# stonith +%setenv dflt_class=stonith dftl_rsc=rscexec_rsc_r1-stonith +add type=null args="hostlist=node1" +exec operation=start +state +exec operation=monitor +exec operation=start +exec operation=monitor +exec operation=stop +state +exec operation=monitor +exec operation=stop +exec operation=monitor +exec operation=meta-data +del diff --git a/lrm/test/testcases/rscexec.exp b/lrm/test/testcases/rscexec.exp new file mode 100644 index 0000000..71bdc2e --- /dev/null +++ b/lrm/test/testcases/rscexec.exp @@ -0,0 +1,117 @@ +.TRY List resources +Currently no resources are managed by LRM. +.SETENV dflt_rsc=rscexec_rsc_r1 +.TRY Add resource rscexec_rsc_r1 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY List resources + +Resource ID:rscexec_rsc_r1 +Resource agent class:ocf +Resource agent type:lrmregtest +Resource agent provider:heartbeat +Resource agent parameters:delay=0 +.TRY Exec rscexec_rsc_r1 op=start timeout=1000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +.TRY Show state rscexec_rsc_r1 +resource state:LRM_RSC_IDLE +The resource 1 operations' information: + operation 'start' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=0 +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=start timeout=1000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=stop timeout=1000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Show state rscexec_rsc_r1 +resource state:LRM_RSC_IDLE +The resource 3 operations' information: + operation 'start' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=0 + operation 'monitor' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=0 + operation 'stop' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=0 +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=7): [null] + +.TRY Exec rscexec_rsc_r1 op=stop timeout=1000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=7): [null] + +.TRY Exec rscexec_rsc_r1 op=meta-data timeout=1000 interval=0 target=EVERYTIME args= +> meta-data succeed (status=0,rc=0): [null] + +.TRY Delete resource rscexec_rsc_r1 +Succeeded in deleting this resource. +.SETENV dflt_class=lsb dftl_rsc=rscexec_rsc_r1-lsb +.TRY Add resource rscexec_rsc_r1 class=lsb type=lrmregtest provider=heartbeat args= +Succeeded in adding this resource. +.TRY Exec rscexec_rsc_r1 op=start timeout=1000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +.TRY Show state rscexec_rsc_r1 +resource state:LRM_RSC_IDLE +The resource 1 operations' information: + operation 'start' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=start timeout=1000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=stop timeout=1000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Show state rscexec_rsc_r1 +resource state:LRM_RSC_IDLE +The resource 3 operations' information: + operation 'start' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: + operation 'monitor' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: + operation 'stop' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=7): [null] + +.TRY Exec rscexec_rsc_r1 op=stop timeout=1000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=7): [null] + +.TRY Exec rscexec_rsc_r1 op=meta-data timeout=1000 interval=0 target=EVERYTIME args= +> meta-data succeed (status=0,rc=0): [null] + +.TRY Delete resource rscexec_rsc_r1 +Succeeded in deleting this resource. diff --git a/lrm/test/testcases/rscmgmt b/lrm/test/testcases/rscmgmt new file mode 100644 index 0000000..8d745d3 --- /dev/null +++ b/lrm/test/testcases/rscmgmt @@ -0,0 +1,29 @@ +list +# add/remove resources +# +add rsc=r1 +info rsc=r1 +list +del rsc=r1 +%setenv dflt_class=lsb dflt_type=lrmregtest +list +add rsc=r1 +list +del rsc=r1 +list +# +# a bit of mix +# +%setenv dflt_class=ocf +add rsc=r1 +add rsc=r1 class=lsb type=lrmregtest +add rsc=r1 +del rsc=r1 +add rsc=r1 class=lsb type=lrmregtest +list +add rsc=r2 +list +del rsc=r1 +del rsc=r2 +list +del rsc=r1 diff --git a/lrm/test/testcases/rscmgmt.exp b/lrm/test/testcases/rscmgmt.exp new file mode 100644 index 0000000..3a5c4bf --- /dev/null +++ b/lrm/test/testcases/rscmgmt.exp @@ -0,0 +1,74 @@ +.TRY List resources +Currently no resources are managed by LRM. +.TRY Add resource r1 class=ocf type=lrmregtest provider=heartbeat args= +Succeeded in adding this resource. +.TRY Show info r1 + +Resource ID:r1 +Resource agent class:ocf +Resource agent type:lrmregtest +Resource agent provider:heartbeat +.TRY List resources + +Resource ID:r1 +Resource agent class:ocf +Resource agent type:lrmregtest +Resource agent provider:heartbeat +.TRY Delete resource r1 +Succeeded in deleting this resource. +.SETENV dflt_class=lsb dflt_type=lrmregtest +.TRY List resources +Currently no resources are managed by LRM. +.TRY Add resource r1 class=lsb type=lrmregtest provider=heartbeat args= +Succeeded in adding this resource. +.TRY List resources + +Resource ID:r1 +Resource agent class:lsb +Resource agent type:lrmregtest +Resource agent provider:heartbeat +.TRY Delete resource r1 +Succeeded in deleting this resource. +.TRY List resources +Currently no resources are managed by LRM. +.SETENV dflt_class=ocf +.TRY Add resource r1 class=ocf type=lrmregtest provider=heartbeat args= +Succeeded in adding this resource. +.TRY Add resource r1 class=lsb type=lrmregtest provider=heartbeat args= +ERROR: lrm_add_rsc(): got a return code HA_FAIL from a reply message of addrsc with function get_ret_from_msg. +Failed to add this resource. +.TRY Add resource r1 class=ocf type=lrmregtest provider=heartbeat args= +ERROR: lrm_add_rsc(): got a return code HA_FAIL from a reply message of addrsc with function get_ret_from_msg. +Failed to add this resource. +.TRY Delete resource r1 +Succeeded in deleting this resource. +.TRY Add resource r1 class=lsb type=lrmregtest provider=heartbeat args= +Succeeded in adding this resource. +.TRY List resources + +Resource ID:r1 +Resource agent class:lsb +Resource agent type:lrmregtest +Resource agent provider:heartbeat +.TRY Add resource r2 class=ocf type=lrmregtest provider=heartbeat args= +Succeeded in adding this resource. +.TRY List resources + +Resource ID:r2 +Resource agent class:ocf +Resource agent type:lrmregtest +Resource agent provider:heartbeat + +Resource ID:r1 +Resource agent class:lsb +Resource agent type:lrmregtest +Resource agent provider:heartbeat +.TRY Delete resource r1 +Succeeded in deleting this resource. +.TRY Delete resource r2 +Succeeded in deleting this resource. +.TRY List resources +Currently no resources are managed by LRM. +.TRY Delete resource r1 +ERROR: lrm_delete_rsc(): got a return code HA_FAIL from a reply message of delrsc with function get_ret_from_msg. +Failed to delete this resource. diff --git a/lrm/test/testcases/rscmgmt.log_filter b/lrm/test/testcases/rscmgmt.log_filter new file mode 100755 index 0000000..34debc5 --- /dev/null +++ b/lrm/test/testcases/rscmgmt.log_filter @@ -0,0 +1,13 @@ +#!/bin/sh + +awk ' +n<2 && /ERROR: on_msg_add_rsc: same id resource exists./ {n++; next} +m<1 && /ERROR: on_msg_del_rsc: no rsc with id/ {m++; next} +{print} +END{ + if( n!=2 ) + print "ERROR: missed on_msg_add_rsc errors"; + if( m!=1 ) + print "ERROR: missed on_msg_del_rsc errors"; +} +' diff --git a/lrm/test/testcases/serialize b/lrm/test/testcases/serialize new file mode 100644 index 0000000..cad96b3 --- /dev/null +++ b/lrm/test/testcases/serialize @@ -0,0 +1,33 @@ +list +# allow for a delay of 2 seconds +%setenv dflt_timeout=2500 +add rsc=r1 args="delay=2" +# +# we run the next three ops in the background +# in case ops are not serialized, the lrmregtest RA should complain +# +%bg 2 +exec operation=start +# insert sleeps to make sure that the operations are started in +# the order given here +%sleep 1 +# set timeouts high enough so that no op fails +exec operation=start timeout=3000 +%sleep 1 +%bgrepeat 4 +exec operation=monitor timeout=11000 +%sleep 11 +state +exec operation=stop +state +del rsc=r1 +# +# +# +%setenv dflt_rsc=r2 dflt_timeout=10500 +add rsc=r2 args="ignore_TERM=1 delay=9" +exec operation=start +%bg +exec operation=monitor timeout=500 +exec operation=monitor +del rsc=r2 diff --git a/lrm/test/testcases/serialize.exp b/lrm/test/testcases/serialize.exp new file mode 100644 index 0000000..b290c95 --- /dev/null +++ b/lrm/test/testcases/serialize.exp @@ -0,0 +1,100 @@ +.TRY List resources +Currently no resources are managed by LRM. +.SETENV dflt_timeout=2500 +.TRY Add resource r1 class=ocf type=lrmregtest provider=heartbeat args=delay=2 +Succeeded in adding this resource. +.BG 2 +.BG test line 10/job 1 runs in background +.SLEEP 1 +.BG test line 15/job 2 runs in background +.SLEEP 1 +.BGREPEAT 4 +.BG test line 18/job 1 runs in background +.BG test line 18/job 2 runs in background +.BG test line 18/job 3 runs in background +.BG test line 18/job 4 runs in background +.SLEEP 11 +.TRY Show state r1 +resource state:LRM_RSC_IDLE +The resource 2 operations' information: + operation 'start' [call_id=(removed)]: + start_delay=0, interval=0, timeout=3000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=2 + operation 'monitor' [call_id=(removed)]: + start_delay=0, interval=0, timeout=11000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=2 +.TRY Exec r1 op=stop timeout=2500 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Show state r1 +resource state:LRM_RSC_IDLE +The resource 3 operations' information: + operation 'start' [call_id=(removed)]: + start_delay=0, interval=0, timeout=3000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=2 + operation 'monitor' [call_id=(removed)]: + start_delay=0, interval=0, timeout=11000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=2 + operation 'stop' [call_id=(removed)]: + start_delay=0, interval=0, timeout=2500, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=2 +.TRY Delete resource r1 +Succeeded in deleting this resource. +.SETENV dflt_rsc=r2 dflt_timeout=10500 +.TRY Add resource r2 class=ocf type=lrmregtest provider=heartbeat args=ignore_TERM=1 delay=9 +Succeeded in adding this resource. +.TRY Exec r2 op=start timeout=10500 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +.BG +.BG test line 31/job 1 runs in background +.TRY Exec r2 op=monitor timeout=10500 interval=0 target=EVERYTIME args= +ERROR: This operation has timed out - no result from lrmd. +.TRY Delete resource r2 +Succeeded in deleting this resource. +.BG test line 10/job 1 finished (exit code: 0): +==========test:10:1 start output========== +.TRY Exec r1 op=start timeout=2500 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:10:1 end output========== +.BG test line 15/job 2 finished (exit code: 0): +==========test:15:2 start output========== +.TRY Exec r1 op=start timeout=3000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:15:2 end output========== +.BG test line 18/job 1 finished (exit code: 0): +==========test:18:1 start output========== +.TRY Exec r1 op=monitor timeout=11000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:18:1 end output========== +.BG test line 18/job 2 finished (exit code: 0): +==========test:18:2 start output========== +.TRY Exec r1 op=monitor timeout=11000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:18:2 end output========== +.BG test line 18/job 3 finished (exit code: 0): +==========test:18:3 start output========== +.TRY Exec r1 op=monitor timeout=11000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:18:3 end output========== +.BG test line 18/job 4 finished (exit code: 0): +==========test:18:4 start output========== +.TRY Exec r1 op=monitor timeout=11000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:18:4 end output========== +.BG test line 31/job 1 finished (exit code: 0): +==========test:31:1 start output========== +.TRY Exec r2 op=monitor timeout=500 interval=0 target=EVERYTIME args= +ERROR: This operation has timed out - no result from lrmd. +==========test:31:1 end output========== diff --git a/lrm/test/testcases/stonith b/lrm/test/testcases/stonith new file mode 100644 index 0000000..f21cf18 --- /dev/null +++ b/lrm/test/testcases/stonith @@ -0,0 +1,2 @@ +%extcheck xmllint.sh many +%shell stonith -L | while read p; do echo $p:heartbeat; stonith -m -t $p; done diff --git a/lrm/test/testcases/stonith.exp b/lrm/test/testcases/stonith.exp new file mode 100644 index 0000000..f9f1042 --- /dev/null +++ b/lrm/test/testcases/stonith.exp @@ -0,0 +1,2 @@ +.EXTCHECK xmllint.sh many +.SHELL stonith -L | while read p; do echo $p:heartbeat; stonith -m -t $p; done diff --git a/lrm/test/testcases/xmllint.sh b/lrm/test/testcases/xmllint.sh new file mode 100755 index 0000000..f61288c --- /dev/null +++ b/lrm/test/testcases/xmllint.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +gawk -v many="$1" ' +BEGIN{XMLLINT="xmllint --noout -";} +function chkoutput(ra) { + if( ra=="" ) return; + if( close(XMLLINT) ) # we need gawk for this + print "xmllint reported error in RA:",ra; +} +many=="many" && /^[a-zA-Z][^:]*:[a-zA-Z0-9]+$/ { + chkoutput(ra); + ra=$0; + next; +} +{ print | XMLLINT } +END{ + if( many!="many" ) + chkoutput("noname"); +} +' diff --git a/replace/Makefile.am b/replace/Makefile.am new file mode 100644 index 0000000..52892ba --- /dev/null +++ b/replace/Makefile.am @@ -0,0 +1,29 @@ +# +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include \ + -I$(top_srcdir)/linux-ha -I$(top_builddir)/linux-ha + +QUIET_LIBTOOL_OPTS = @QUIET_LIBTOOL_OPTS@ +LIBTOOL = @LIBTOOL@ @QUIET_LIBTOOL_OPTS@ + + +noinst_LTLIBRARIES = libreplace.la +libreplace_la_SOURCES = +libreplace_la_LIBADD = @LTLIBOBJS@ diff --git a/replace/NoSuchFunctionName.c b/replace/NoSuchFunctionName.c new file mode 100644 index 0000000..373eabd --- /dev/null +++ b/replace/NoSuchFunctionName.c @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2002 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +void nosuchfunctionname(void); + +/* + * This is a completely useless function put here only to make OpenBSD make + * procedures happy. I hope no one ever makes such a function ;-) + */ +void +nosuchfunctionname(void) +{ + return; +} diff --git a/replace/alphasort.c b/replace/alphasort.c new file mode 100644 index 0000000..94cd811 --- /dev/null +++ b/replace/alphasort.c @@ -0,0 +1,53 @@ +/* + * + * alphasort - replacement for alphasort functions. + * + * Matt Soffen + + * Copyright (C) 2001 Matt Soffen <matt@soffen.com> + * + * Taken from the FreeBSD file (with copyright notice) + * /usr/src/gnu/lib/libdialog/dir.c + *************************************************************************** + * Program: dir.c + * Author: Marc van Kempen + * desc: Directory routines, sorting and reading + * + * Copyright (c) 1995, Marc van Kempen + * + * All rights reserved. + * + * This software may be used, modified, copied, distributed, and + * sold, in both source and binary form provided that the above + * copyright and these terms are retained, verbatim, as the first + * lines of this file. Under no circumstances is the author + * responsible for the proper functioning of this software, nor does + * the author assume any responsibility for damages incurred with + * its use. + * + *************************************************************************** + */ + +#include <lha_internal.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <unistd.h> /* XXX for _POSIX_VERSION ifdefs */ + +#if HAVE_STRINGS_H +#include <strings.h> +#endif + +#if !defined sgi && !defined _POSIX_VERSION +#include <sys/dir.h> +#endif + +#include <sys/types.h> +#include <dirent.h> +#include <stdlib.h> +#include <stddef.h> + +int alphasort(const void *dirent1, const void *dirent2) { + return(strcmp((*(const struct dirent **)dirent1)->d_name, + (*(const struct dirent **)dirent2)->d_name)); +} diff --git a/replace/daemon.c b/replace/daemon.c new file mode 100644 index 0000000..7697113 --- /dev/null +++ b/replace/daemon.c @@ -0,0 +1,83 @@ +/*- + * + * daemon - replacement for daemon function. + * + * Matt Soffen + * Copyright (C) 2004 Matt Soffen <matt@soffen.com> + * + * Taken from the FreeBSD file (with copyright notice) + * ------------------------------------------------------------ + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/lib/libc/gen/daemon.c,v 1.3 2000/01/27 23:06:14 jasone Exp $ + * + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)daemon.c 8.1 (Berkeley) 6/4/93"; +#endif /* LIBC_SCCS and not lint */ + +#include <lha_internal.h> + +#include <fcntl.h> +#include <unistd.h> + +int +daemon(nochdir, noclose) + int nochdir, noclose; +{ + int fd; + + switch (fork()) { + case -1: + return (-1); + case 0: + break; + default: + exit(0); + } + + if (setsid() == -1) + return (-1); + + if (!nochdir) + (void)chdir("/"); + + if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) { + (void)dup2(fd, STDIN_FILENO); + (void)dup2(fd, STDOUT_FILENO); + (void)dup2(fd, STDERR_FILENO); + if (fd > 2) + (void)close(fd); + } + return (0); +} diff --git a/replace/inet_pton.c b/replace/inet_pton.c new file mode 100644 index 0000000..f5aa93b --- /dev/null +++ b/replace/inet_pton.c @@ -0,0 +1,245 @@ +/* + * Copyright (c) 1996,1999 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/* Chris Wright <chris@wirex.com> June 22, 2001 + * Merged contents of inet_pton.c from Apache2.0.16 and BIND8 + * The Apache base is more portable within heartbeat's envrionment, + * however, the BIND8 version has two small logic changes that are + * newer. + */ + +#include <lha_internal.h> + +#if HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#if HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#if HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#if HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#include <string.h> +#include <errno.h> + +#ifndef IN6ADDRSZ +#define IN6ADDRSZ 16 +#endif + +#ifndef INT16SZ +#define INT16SZ sizeof(short) +#endif + +#ifndef INADDRSZ +#define INADDRSZ 4 +#endif + +#ifndef __P +#define __P(x) x +#endif + +/* + * WARNING: Don't even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + */ + +static int inet_pton4 __P((const char *src, unsigned char *dst)); +#if HAVE_IPV6 +static int inet_pton6 __P((const char *src, unsigned char *dst)); +#endif + +/* int + * inet_pton(af, src, dst) + * convert from presentation format (which usually means ASCII printable) + * to network format (which is usually some kind of binary format). + * return: + * 1 if the address was valid for the specified address family + * 0 if the address wasn't valid (`dst' is untouched in this case) + * -1 if some other error occurred (`dst' is untouched in this case, too) + * author: + * Paul Vixie, 1996. + */ +int +inet_pton(int af, const char *src, void *dst) +{ + switch (af) { + case AF_INET: + return (inet_pton4(src, dst)); +#if HAVE_IPV6 + case AF_INET6: + return (inet_pton6(src, dst)); +#endif + default: + errno = EAFNOSUPPORT; + return (-1); + } + /* NOTREACHED */ +} + +/* int + * inet_pton4(src, dst) + * like inet_aton() but without all the hexadecimal and shorthand. + * return: + * 1 if `src' is a valid dotted quad, else 0. + * notice: + * does not touch `dst' unless it's returning 1. + * author: + * Paul Vixie, 1996. + */ +static int +inet_pton4(const char *src, unsigned char *dst) +{ + static const char digits[] = "0123456789"; + int saw_digit, octets, ch; + unsigned char tmp[INADDRSZ], *tp; + + saw_digit = 0; + octets = 0; + *(tp = tmp) = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr(digits, ch)) != NULL) { + unsigned int new = *tp * 10 + (pch - digits); + + if (new > 255) + return (0); + *tp = new; + if (! saw_digit) { + if (++octets > 4) + return (0); + saw_digit = 1; + } + } else if (ch == '.' && saw_digit) { + if (octets == 4) + return (0); + *++tp = 0; + saw_digit = 0; + } else + return (0); + } + if (octets < 4) + return (0); + + memcpy(dst, tmp, INADDRSZ); + return (1); +} + +#if HAVE_IPV6 +/* int + * inet_pton6(src, dst) + * convert presentation level address to network order binary form. + * return: + * 1 if `src' is a valid [RFC1884 2.2] address, else 0. + * notice: + * (1) does not touch `dst' unless it's returning 1. + * (2) :: in a full address is silently ignored. + * credit: + * inspired by Mark Andrews. + * author: + * Paul Vixie, 1996. + */ +static int +inet_pton6(const char *src, unsigned char *dst) +{ + static const char xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + unsigned char tmp[IN6ADDRSZ], *tp, *endp, *colonp; + const char *xdigits, *curtok; + int ch, saw_xdigit; + unsigned int val; + + memset((tp = tmp), '\0', IN6ADDRSZ); + endp = tp + IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if (*src == ':') + if (*++src != ':') + return (0); + curtok = src; + saw_xdigit = 0; + val = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) + pch = strchr((xdigits = xdigits_u), ch); + if (pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + if (val > 0xffff) + return (0); + saw_xdigit = 1; + continue; + } + if (ch == ':') { + curtok = src; + if (!saw_xdigit) { + if (colonp) + return (0); + colonp = tp; + continue; + } else if (*src == '\0') { + return (0); + } + if (tp + INT16SZ > endp) + return (0); + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + if (ch == '.' && ((tp + INADDRSZ) <= endp) && + inet_pton4(curtok, tp) > 0) { + tp += INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return (0); + } + if (saw_xdigit) { + if (tp + INT16SZ > endp) + return (0); + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + } + if (colonp != NULL) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const int n = tp - colonp; + int i; + + if (tp == endp) + return (0); + for (i = 1; i <= n; i++) { + endp[- i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp) + return (0); + memcpy(dst, tmp, IN6ADDRSZ); + return (1); +} +#endif /* HAVE_IPV6 */ diff --git a/replace/scandir.c b/replace/scandir.c new file mode 100644 index 0000000..528f544 --- /dev/null +++ b/replace/scandir.c @@ -0,0 +1,236 @@ +/* scandir: Scan a directory, collecting all (selected) items into a an array. + * + * This code borrowed from 'libit', which can be found here: + * + * http://www.iro.umontreal.ca/~pinard/libit/dist/scandir/ + * + * The original author put this code in the public domain. + * It has been modified slightly to get rid of warnings, etc. + * + * Below is the email I received from pinard@iro.umontreal.ca (François Pinard) + * when I sent him an email asking him about the license, etc. of this + * code which I obtained from his site. + * + * I think the correct spelling of his name is Rich Salz. I think he's now + * rsalz@datapower.com... + * -- + * Rich Salz, Chief Security Architect + * DataPower Technology http://www.datapower.com + * XS40 XML Security Gateway http://www.datapower.com/products/xs40.html + * + * Copyright(C): none (public domain) + * License: none (public domain) + * Author: Rich Salz <rsalz@datapower.com> + * + * + * + * -- Alan Robertson + * alanr@unix.sh + * + ************************************************************************** + * + * Subject: Re: Scandir replacement function + * Date: 18 May 2001 12:00:48 -0400 + * From: pinard@iro.umontreal.ca (François Pinard) + * To: Alan Robertson <alanr@unix.sh> + * References: 1 + * + * + * [Alan Robertson] + * + * > Hi, I'd like to use your scandir replacement function found here: + * > http://www.iro.umontreal.ca/~pinard/libit/dist/scandir/ But, it does + * > not indicate authorship or licensing terms in it. Could you tell me + * > who wrote this code, under what license you distribute it, and whether + * > and under what terms I may further distribute it? + * + * Hello, Alan. These are (somewhat) explained in UNSHAR.HDR found in the + * same directory. The routines have been written by Rick Saltz (I'm not + * completely sure of the spelling) a long while ago. I think that nowadays, + * Rick is better known as the main author of the nice INN package. + * + ************************************************************************** + * + * I spent a little time verifying this with Rick Salz. + * The results are below: + * + ************************************************************************** + * + * Date: Tue, 20 Sep 2005 21:52:09 -0400 (EDT) + * From: Rich Salz <rsalz@datapower.com> + * To: Alan Robertson <alanr@unix.sh> + * Subject: Re: Verifying permissions/licenses/etc on some old code of yours - + * scandir.c + * In-Reply-To: <433071CA.8000107@unix.sh> + * Message-ID: <Pine.LNX.4.44L0.0509202151270.9198-100000@smtp.datapower.com> + * Content-Type: TEXT/PLAIN; charset=US-ASCII + * + * yes, it's most definitely in the public domain. + * + * I'm glad you find it useful. I'm surprised it hasn't been replaced by, + * e.g,. something in GLibC. Ii'm impressed you tracked me down. + * + * /r$ + * + * -- + * Rich Salz Chief Security Architect + * DataPower Technology http://www.datapower.com + * XS40 XML Security Gateway http://www.datapower.com/products/xs40.html + * ----------------------------------------------------------------------> + * Subject: scandir, ftw REDUX + * Date: 1 Jan 88 00:47:01 GMT + * From: rsalz@pebbles.bbn.com + * Newsgroups: comp.sources.misc + * + * + * Forget my previous message -- I just decided for completeness's sake to + * implement the SysV ftw(3) routine, too. + * + * To repeat, these are public-domain implementations of the SystemV ftw() + * routine, the BSD scandir() and alphasort() routines, and documentation for + * same. The FTW manpage could be more readable, but so it goes. + * + * Anyhow, feel free to post these, and incorporate them into your existing + * packages. I have readdir() routiens for MSDOS and the Amiga if anyone + * wants them, and should have them for VMS by the end of January; let me + * know if you want copies. + * + * Yours in filesystems, + * /r$ + * + * Anyhow, feel free to post + * ----------------------------------------------------------------------< + * + */ + +#include <lha_internal.h> +#include <sys/types.h> +#include <dirent.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> + +#ifndef NULL +# define NULL ((void *) 0) +#endif + +/* Initial guess at directory allocated. */ +#define INITIAL_ALLOCATION 20 + +int +scandir (const char *directory_name, + struct dirent ***array_pointer, + int (*select_function) (const struct dirent *), + +#ifdef USE_SCANDIR_COMPARE_STRUCT_DIRENT + /* This is what the Linux man page says */ + int (*compare_function) (const struct dirent**, const struct dirent**) +#else + /* This is what the Linux header file says ... */ + int (*compare_function) (const void *, const void *) +#endif + ); + +int +scandir (const char *directory_name, + struct dirent ***array_pointer, + int (*select_function) (const struct dirent *), +#ifdef USE_SCANDIR_COMPARE_STRUCT_DIRENT + /* This is what the linux man page says */ + int (*compare_function) (const struct dirent**, const struct dirent**) +#else + /* This is what the linux header file says ... */ + int (*compare_function) (const void *, const void *) +#endif + ) +{ + DIR *directory; + struct dirent **array; + struct dirent *entry; + struct dirent *copy; + int allocated = INITIAL_ALLOCATION; + int counter = 0; + + /* Get initial list space and open directory. */ + + if (directory = opendir (directory_name), directory == NULL) + return -1; + + if (array = (struct dirent **) malloc (allocated * sizeof (struct dirent *)), + array == NULL) + return -1; + + /* Read entries in the directory. */ + + while (entry = readdir (directory), entry) + if (select_function == NULL || (*select_function) (entry)) + { + /* User wants them all, or he wants this one. Copy the entry. */ + + /* + * On some OSes the declaration of "entry->d_name" is a minimal-length + * placeholder. Example: Solaris: + * /usr/include/sys/dirent.h: + * "char d_name[1];" + * man page "dirent(3)": + * The field d_name is the beginning of the character array + * giving the name of the directory entry. This name is + * null terminated and may have at most MAXNAMLEN chars. + * So our malloc length may need to be increased accordingly. + * sizeof(entry->d_name): space (possibly minimal) in struct. + * strlen(entry->d_name): actual length of the entry. + * + * John Kavadias <john_kavadias@hotmail.com> + * David Lee <t.d.lee@durham.ac.uk> + */ + int namelength = strlen(entry->d_name) + 1; /* length with NULL */ + int extra = 0; + + if (sizeof(entry->d_name) <= namelength) { + /* allocated space <= required space */ + extra += namelength - sizeof(entry->d_name); + } + + if (copy = (struct dirent *) malloc (sizeof (struct dirent) + extra), + copy == NULL) + { + closedir (directory); + free (array); + return -1; + } + copy->d_ino = entry->d_ino; + copy->d_reclen = entry->d_reclen; + strcpy (copy->d_name, entry->d_name); + + /* Save the copy. */ + + if (counter + 1 == allocated) + { + allocated <<= 1; + array = (struct dirent **) + realloc ((char *) array, allocated * sizeof (struct dirent *)); + if (array == NULL) + { + closedir (directory); + free (array); + free (copy); + return -1; + } + } + array[counter++] = copy; + } + + /* Close things off. */ + + array[counter] = NULL; + *array_pointer = array; + closedir (directory); + + /* Sort? */ + + if (counter > 1 && compare_function) + qsort ((char *) array, counter, sizeof (struct dirent *) + , (int (*)(const void *, const void *))(compare_function)); + + return counter; +} diff --git a/replace/setenv.c b/replace/setenv.c new file mode 100644 index 0000000..e8cafb1 --- /dev/null +++ b/replace/setenv.c @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2001 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <lha_internal.h> +#include <stdlib.h> +#include <stdio.h> + +/* + * Small replacement function for setenv() + */ +int +setenv(const char *name, const char * value, int why) +{ + int rc = -1; + + if ( name && value ) { + char * envp = NULL; + envp = malloc(strlen(name)+strlen(value)+2); + if (envp) { + /* + * Unfortunately, the putenv API guarantees memory leaks when + * changing environment variables repeatedly... :-( + */ + + sprintf(envp, "%s=%s", name, value); + + /* Cannot free envp (!) */ + rc = putenv(envp); + } + + } + return(rc); +} diff --git a/replace/strerror.c b/replace/strerror.c new file mode 100644 index 0000000..477239f --- /dev/null +++ b/replace/strerror.c @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2002 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <lha_internal.h> +#include <errno.h> +#include <stdio.h> +extern const char * sys_err[]; +extern int sys_nerr; +char * +strerror(int errnum) +{ + static char whaterr[32]; + + if (errnum < 0) { + return "negative errno"; + } + if (errnum >= sys_nerr) { + snprintf(whaterr, sizeof(whaterr),"error %d", errnum); + return whaterr; + } + return sys_err[sys_nerr]; +} diff --git a/replace/strlcat.c b/replace/strlcat.c new file mode 100644 index 0000000..8b909f9 --- /dev/null +++ b/replace/strlcat.c @@ -0,0 +1,33 @@ +#include <lha_internal.h> +#include <string.h> +/* + * Copyright (C) 2007 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +size_t +strlcat(char *dest, const char * src, size_t maxlen) +{ + size_t curlen = strlen(dest); + size_t addlen = strlen(src); + size_t appendlen = (maxlen-1) - curlen; + if (appendlen > 0) { + strlcpy(dest+curlen, src, maxlen-curlen); + } + return curlen + addlen; +} diff --git a/replace/strlcpy.c b/replace/strlcpy.c new file mode 100644 index 0000000..661d02e --- /dev/null +++ b/replace/strlcpy.c @@ -0,0 +1,32 @@ +#include <lha_internal.h> +#include <string.h> +/* + * Copyright (C) 2007 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +size_t +strlcpy(char *dest, const char * src, size_t maxlen) +{ + size_t srclen = strlen(src); + if (maxlen > 0) { + strncpy(dest, src, maxlen); + dest[maxlen-1]=EOS; + } + return srclen; +} diff --git a/replace/strndup.c b/replace/strndup.c new file mode 100644 index 0000000..4312743 --- /dev/null +++ b/replace/strndup.c @@ -0,0 +1,38 @@ +#include <lha_internal.h> +#include <stdlib.h> +#include <string.h> +/* + * Copyright (C) 2004 Matt Soffen <sirgeek-ha@mrsucko.org> + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* Taken from the GlibC implementation of strndup */ + +char *strndup(const char *str, size_t len) +{ + size_t n = strnlen(str,len); + char *new = (char *) malloc (len+1); + + if (NULL == new) { + return NULL; + } + + new[n] = '\0'; + return (char *)memcpy (new, str, len); +} + diff --git a/replace/strnlen.c b/replace/strnlen.c new file mode 100644 index 0000000..8b3bcd2 --- /dev/null +++ b/replace/strnlen.c @@ -0,0 +1,31 @@ +#include <lha_internal.h> +#include <string.h> +/* + * Copyright (C) 2003 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +size_t +strnlen(const char *s, size_t maxlen) +{ + const char * eospos; + + eospos = memchr(s, (int)'\0', maxlen); + + return (eospos == NULL ? maxlen : (size_t)(eospos-s)); +} diff --git a/replace/unsetenv.c b/replace/unsetenv.c new file mode 100644 index 0000000..aeb84a3 --- /dev/null +++ b/replace/unsetenv.c @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2001 Alan Robertson <alanr@unix.sh> + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <lha_internal.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define __environ environ +#ifndef HAVE_ENVIRON_DECL +extern char **environ; +#endif + +int +unsetenv (const char *name) +{ + const size_t len = strlen (name); + char **ep; + + for (ep = __environ; *ep; ++ep) { + if (!strncmp (*ep, name, len) && (*ep)[len] == '=') { + /* Found it. */ + /* Remove this pointer by moving later ones back. */ + char **dp = ep; + do + dp[0] = dp[1]; + while (*dp++); + /* Continue the loop in case NAME appears again. */ + } + } + return 0; +} diff --git a/replace/uuid_parse.c b/replace/uuid_parse.c new file mode 100644 index 0000000..beecac6 --- /dev/null +++ b/replace/uuid_parse.c @@ -0,0 +1,519 @@ +/* + * uuid: emulation of e2fsprogs interface if implementation lacking. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Original uuid implementation: copyright (C) Theodore Ts'o + * + * This importation into heartbeat: + * Copyright (C) 2004 David Lee <t.d.lee@durham.ac.uk> + * + */ + +#include <lha_internal.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#include <string.h> +#include <ctype.h> + +#include <replace_uuid.h> + +/* + * Local "replace" implementation of uuid functions. + */ + + +#include <sys/types.h> +#include <sys/time.h> +#include <time.h> + +/* UUID Variant definitions */ +#define UUID_VARIANT_NCS 0 +#define UUID_VARIANT_DCE 1 +#define UUID_VARIANT_MICROSOFT 2 +#define UUID_VARIANT_OTHER 3 + +/* UUID Type definitions */ +#define UUID_TYPE_DCE_TIME 1 +#define UUID_TYPE_DCE_RANDOM 4 + + + +/* For uuid_compare() */ +#define UUCMP(u1,u2) if (u1 != u2) return((u1 < u2) ? -1 : 1); + +/************************************ + * Private types + ************************************/ + +#define longlong long long + +/* + * Offset between 15-Oct-1582 and 1-Jan-70 + */ +#define TIME_OFFSET_HIGH 0x01B21DD2 +#define TIME_OFFSET_LOW 0x13814000 + +#if (SIZEOF_INT == 4) +typedef unsigned int __u32; +#elif (SIZEOF_LONG == 4) +typedef unsigned long __u32; +#endif + +#if (SIZEOF_INT == 2) +typedef int __s16; +typedef unsigned int __u16; +#elif (SIZEOF_SHORT == 2) +typedef short __s16; +typedef unsigned short __u16; +#endif + +typedef unsigned char __u8; + +struct uuid { + __u32 time_low; + __u16 time_mid; + __u16 time_hi_and_version; + __u16 clock_seq; + __u8 node[6]; +}; + +/************************************ + * internal routines + ************************************/ +static void uuid_pack(const struct uuid *uu, uuid_t ptr) +{ + __u32 tmp; + unsigned char *out = ptr; + + tmp = uu->time_low; + out[3] = (unsigned char) tmp; + tmp >>= 8; + out[2] = (unsigned char) tmp; + tmp >>= 8; + out[1] = (unsigned char) tmp; + tmp >>= 8; + out[0] = (unsigned char) tmp; + + tmp = uu->time_mid; + out[5] = (unsigned char) tmp; + tmp >>= 8; + out[4] = (unsigned char) tmp; + + tmp = uu->time_hi_and_version; + out[7] = (unsigned char) tmp; + tmp >>= 8; + out[6] = (unsigned char) tmp; + + tmp = uu->clock_seq; + out[9] = (unsigned char) tmp; + tmp >>= 8; + out[8] = (unsigned char) tmp; + + memcpy(out+10, uu->node, 6); +} + +static void uuid_unpack(const uuid_t in, struct uuid *uu) +{ + const __u8 *ptr = in; + __u32 tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + tmp = (tmp << 8) | *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_low = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_mid = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_hi_and_version = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->clock_seq = tmp; + + memcpy(uu->node, ptr, 6); +} + +/************************************ + * Main routines, except uuid_generate*() + ************************************/ +void +uuid_clear(uuid_t uu) +{ + memset(uu, 0, 16); +} + +int +uuid_compare(const uuid_t uu1, const uuid_t uu2) +{ + struct uuid uuid1, uuid2; + + uuid_unpack(uu1, &uuid1); + uuid_unpack(uu2, &uuid2); + + UUCMP(uuid1.time_low, uuid2.time_low); + UUCMP(uuid1.time_mid, uuid2.time_mid); + UUCMP(uuid1.time_hi_and_version, uuid2.time_hi_and_version); + UUCMP(uuid1.clock_seq, uuid2.clock_seq); + return memcmp(uuid1.node, uuid2.node, 6); +} + +void +uuid_copy(uuid_t dst, const uuid_t src) +{ + unsigned char *cp1; + const unsigned char *cp2; + int i; + + for (i=0, cp1 = dst, cp2 = src; i < 16; i++) + *cp1++ = *cp2++; +} + +/* if uu is the null uuid, return 1 else 0 */ +int +uuid_is_null(const uuid_t uu) +{ + const unsigned char *cp; + int i; + + for (i=0, cp = uu; i < 16; i++) + if (*cp++) + return 0; + return 1; +} + +/* 36byte-string=>uuid */ +int +uuid_parse(const char *in, uuid_t uu) +{ + struct uuid uuid; + int i; + const char *cp; + char buf[3]; + + if (strlen(in) != 36) + return -1; + for (i=0, cp = in; i <= 36; i++,cp++) { + if ((i == 8) || (i == 13) || (i == 18) || + (i == 23)) { + if (*cp == '-') + continue; + else + return -1; + } + if (i== 36) + if (*cp == 0) + continue; + if (!isxdigit((int) *cp)) + return -1; + } + uuid.time_low = strtoul(in, NULL, 16); + uuid.time_mid = strtoul(in+9, NULL, 16); + uuid.time_hi_and_version = strtoul(in+14, NULL, 16); + uuid.clock_seq = strtoul(in+19, NULL, 16); + cp = in+24; + buf[2] = 0; + for (i=0; i < 6; i++) { + buf[0] = *cp++; + buf[1] = *cp++; + uuid.node[i] = strtoul(buf, NULL, 16); + } + + uuid_pack(&uuid, uu); + return 0; +} + +/* uuid=>36byte-string-with-null */ +void +uuid_unparse(const uuid_t uu, char *out) +{ + struct uuid uuid; + + uuid_unpack(uu, &uuid); + sprintf(out, + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid.time_low, uuid.time_mid, uuid.time_hi_and_version, + uuid.clock_seq >> 8, uuid.clock_seq & 0xFF, + uuid.node[0], uuid.node[1], uuid.node[2], + uuid.node[3], uuid.node[4], uuid.node[5]); +} + + +/************************************ + * Main routines: uuid_generate*() + ************************************/ + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> + +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_SYS_SOCKIO_H +#include <sys/sockio.h> +#endif +#ifdef HAVE_NET_IF_H +#include <net/if.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#ifdef HAVE_SRANDOM +#define srand(x) srandom(x) +#define rand() random() +#endif + +static int get_random_fd(void) +{ + struct timeval tv; + static int fd = -2; + int i; + + if (fd == -2) { + gettimeofday(&tv, 0); + fd = open("/dev/urandom", O_RDONLY); + if (fd == -1) + fd = open("/dev/random", O_RDONLY | O_NONBLOCK); + srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec); + } + /* Crank the random number generator a few times */ + gettimeofday(&tv, 0); + for (i = (tv.tv_sec ^ tv.tv_usec) & 0x1F; i > 0; i--) + rand(); + return fd; +} + +/* + * Generate a series of random bytes. Use /dev/urandom if possible, + * and if not, use srandom/random. + */ +static void get_random_bytes(void *buf, int nbytes) +{ + int i, n = nbytes, fd = get_random_fd(); + int lose_counter = 0; + unsigned char *cp = (unsigned char *) buf; + + if (fd >= 0) { + while (n > 0) { + i = read(fd, cp, n); + if (i <= 0) { + if (lose_counter++ > 16) + break; + continue; + } + n -= i; + cp += i; + lose_counter = 0; + } + } + + /* + * We do this all the time, but this is the only source of + * randomness if /dev/random/urandom is out to lunch. + */ + for (cp = buf, i = 0; i < nbytes; i++) + *cp++ ^= (rand() >> 7) & 0xFF; + return; +} + +/* + * Get the ethernet hardware address, if we can find it... + */ +static int get_node_id(unsigned char *node_id) +{ +#ifdef HAVE_NET_IF_H + int sd; + struct ifreq ifr, *ifrp; + struct ifconf ifc; + char buf[1024]; + int n, i; + unsigned char *a; + +/* + * BSD 4.4 defines the size of an ifreq to be + * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len + * However, under earlier systems, sa_len isn't present, so the size is + * just sizeof(struct ifreq) + */ +#ifdef HAVE_SA_LEN +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif +#define ifreq_size(i) max(sizeof(struct ifreq),\ + sizeof((i).ifr_name)+(i).ifr_addr.sa_len) +#else +#define ifreq_size(i) sizeof(struct ifreq) +#endif /* HAVE_SA_LEN*/ + + sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sd < 0) { + return -1; + } + memset(buf, 0, sizeof(buf)); + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + if (ioctl (sd, SIOCGIFCONF, (char *)&ifc) < 0) { + close(sd); + return -1; + } + n = ifc.ifc_len; + for (i = 0; i < n; i+= ifreq_size(*ifr) ) { + ifrp = (struct ifreq *)((char *) ifc.ifc_buf+i); + strncpy(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ); +#ifdef SIOCGIFHWADDR + if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0) + continue; + a = (unsigned char *) &ifr.ifr_hwaddr.sa_data; +#else +#ifdef SIOCGENADDR + if (ioctl(sd, SIOCGENADDR, &ifr) < 0) + continue; + a = (unsigned char *) ifr.ifr_enaddr; +#else + /* + * XXX we don't have a way of getting the hardware + * address + */ + close(sd); + return 0; +#endif /* SIOCGENADDR */ +#endif /* SIOCGIFHWADDR */ + if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) + continue; + if (node_id) { + memcpy(node_id, a, 6); + close(sd); + return 1; + } + } + close(sd); +#endif + return 0; +} + +/* Assume that the gettimeofday() has microsecond granularity */ +#define MAX_ADJUSTMENT 10 + +static int +get_clock(__u32 *clock_high, __u32 *clock_low, __u16 *ret_clock_seq) +{ + static int adjustment = 0; + static struct timeval last = {0, 0}; + static __u16 clock_seq; + struct timeval tv; + unsigned longlong clock_reg; + +try_again: + gettimeofday(&tv, 0); + if ((last.tv_sec == 0) && (last.tv_usec == 0)) { + get_random_bytes(&clock_seq, sizeof(clock_seq)); + clock_seq &= 0x1FFF; + last = tv; + last.tv_sec--; + } + if ((tv.tv_sec < last.tv_sec) || + ((tv.tv_sec == last.tv_sec) && + (tv.tv_usec < last.tv_usec))) { + clock_seq = (clock_seq+1) & 0x1FFF; + adjustment = 0; + last = tv; + } else if ((tv.tv_sec == last.tv_sec) && + (tv.tv_usec == last.tv_usec)) { + if (adjustment >= MAX_ADJUSTMENT) + goto try_again; + adjustment++; + } else { + adjustment = 0; + last = tv; + } + + clock_reg = tv.tv_usec*10 + adjustment; + clock_reg += ((unsigned longlong) tv.tv_sec)*10000000; + clock_reg += (((unsigned longlong) 0x01B21DD2) << 32) + 0x13814000; + + *clock_high = clock_reg >> 32; + *clock_low = clock_reg; + *ret_clock_seq = clock_seq; + return 0; +} + +/* create a new uuid, based on randomness */ +void +uuid_generate_random(uuid_t out) +{ + uuid_t buf; + struct uuid uu; + + get_random_bytes(buf, sizeof(buf)); + uuid_unpack(buf, &uu); + + uu.clock_seq = (uu.clock_seq & 0x3FFF) | 0x8000; + uu.time_hi_and_version = (uu.time_hi_and_version & 0x0FFF) | 0x4000; + uuid_pack(&uu, out); +} + +/* create a new uuid, based on time */ +static void +uuid_generate_time(uuid_t out) +{ + static unsigned char node_id[6]; + static int has_init = 0; + struct uuid uu; + __u32 clock_mid; + + if (!has_init) { + if (get_node_id(node_id) <= 0) { + get_random_bytes(node_id, 6); + /* + * Set multicast bit, to prevent conflicts + * with IEEE 802 addresses obtained from + * network cards + */ + node_id[0] |= 0x80; + } + has_init = 1; + } + get_clock(&clock_mid, &uu.time_low, &uu.clock_seq); + uu.clock_seq |= 0x8000; + uu.time_mid = (__u16) clock_mid; + uu.time_hi_and_version = (clock_mid >> 16) | 0x1000; + memcpy(uu.node, node_id, 6); + uuid_pack(&uu, out); +} + +void +uuid_generate(uuid_t out) +{ + if (get_random_fd() >= 0) { + uuid_generate_random(out); + }else{ + uuid_generate_time(out); + } +} |