diff options
Diffstat (limited to 'trunk/src')
60 files changed, 5657 insertions, 0 deletions
diff --git a/trunk/src/ChangeLog b/trunk/src/ChangeLog new file mode 100644 index 00000000..92947fd9 --- /dev/null +++ b/trunk/src/ChangeLog @@ -0,0 +1,248 @@ +2007-06-15 Navid Sheikhol-Eslami <navid@redhat.com> + + * [BZ#241282] initial port to RHEL4 :) sos core now runs happily, plugins will probably need to be fixed as well. + * Initial commit of XML reporting to gather details about commands executed and files gathered. + * Exceptions in plugin.analyse() were not catched allowing a bad plugin to break sosreport. + +2007-06-15 Eugene Teo <eteo@redhat.com> + + * lib/sos/plugins/apache.py, lib/sos/plugins/nfsserver.py, lib/sos/plugins/selinux.py, lib/sos/plugins/xinetd.py, lib/sos/plugins/ssh.py, lib/sos/plugins/sendmail.py, lib/sos/plugins/samba.py, lib/sos/plugins/named.py, lib/sos/plugins/cluster.py: + - Edited apache.py to gather /var/log/httpd/* log files + - Added nfsserver.py to gather NFS server-related debugging information + - Edited selinux.py to gather /etc/selinux/* configuration files + - Added xinetd.py to gather xinetd-related information + - Added ssh.py to gather ssh-related information + - Added sendmail.py to gather sendmail information + - Edited samba.py to gather /var/log/samba/* log files + - Edited named.py to gather /etc/sysconfig/named + - Edited cluster.py to gather the output of fdisk -l to show the + shared storage devices that should be available to each system + +2007-05-28 Eugene Teo <eteo@redhat.com> + + * lib/sos/plugins/systemtap.py: + - Added systemtap.py to gather SystemTap pre-requisites information + +2007-05-28 Eugene Teo <eteo@redhat.com> + + * lib/sos/plugins/amd.py: + - Added amd.py to gather Amd automounter information + +2007-05-25 Eugene Teo <eteo@redhat.com> + + * lib/sos/plugins/xen.py, lib/sos/plugins/pam.py, lib/sos/plugins/memory.py: + - Edited xen.py to determine if CPU has PAE/Intel VT/AMD-V support + - Edited pam.py to gather configurations in /etc/security, and files + listing of /lib/security/pam_*so + - Edited memory.py to gather /proc/{vmstat,slabinfo}, and free -m + output + +2007-04-23 Navid Sheikhol-Eslami <navid@redhat.com> + + * Running "multipath" without arguments might change the device-mapper maps, which we want to avoid if things are broken. + +2007-04-02 Navid Sheikhol-Eslami <navid@redhat.com> + + * Replaced xen plugin with (better) version from Chris Lalancette <clalance@redhat.com> + +2007-03-29 Navid Sheikhol-Eslami <navid@redhat.com> + + * Added a checkenabled() function which can be used to disable a plugin at run-time. + * Disable the progress-bar if verbosity is enabled. + +2007-03-27 Navid Sheikhol-Eslami <navid@redhat.com> + + * Fixed hardware plugin to use modules.pcimap instead of deprecated pcitable. + * Added a random suffix to sosreport tree to avoid overwriting an existing tree with same name. + * Better logging using python's logging module. + * Verbose logs included in sosreport (sos_logs/sos.log) + +2007-03-15 Navid Sheikhol-Eslami <navid@redhat.com> + + * Implemented a progress bar (RFE BZ#219672) which can be disabled from the command line. + * Added check to see if the loaded module matches the copy on the file-system + +2007-03-14 Navid Sheikhol-Eslami <navid@redhat.com> + + * fixed BZ#219877 (ncurses "Cancel" button makes sosreport exit) + +2007-03-07 Navid Sheikhol-Eslami <navid@redhat.com> + + * Allow passing multiple comma-separated plugin names to -n (--noplugin) and -o (--onlyplugin) options. + * Added further commands' output to gather from lvm_dump + + +2007-02-20 Navid Sheikhol-Eslami <navid@redhat.com> + + * Added a specialized plugin for device-mapper related configuration files and command outputs (device-mapper, LVM and multipath) + * Added --onlyplugin option (-o) to selectively choose which plugins to load (complementary to existing --noplugin) + * Exit if no valid plugin was selected (rather than building an empty sosreport). + +2007-02-16 Navid Sheikhol-Eslami <navid@redhat.com> + + * Strip out the shared secret (bindpw) from /etc/ldap.conf + * Collect parsed configuration tree directly from ccsd (useful for troubleshooting parsing issues) + * Scamble password information for fencing devices. + +2007-01-26 Navid Sheikhol-Eslami <navid@redhat.com> + + * Added doRegexSub() to be called in postproc() to apply a regexp substitution to files + * Added radius plugin for freeradius data collection + * Ask full name to prevent errors when moving the sos tree before packaging + * Reformat tar file name is no ticket number is given + +2006-06-19 Steve Conklin <sconklin@tintin> + + * ChangeLog, LICENSE, setup.py, sos.spec: + Added License file and bumped release + +2006-06-08 dlehman <dlehman@tintin> + + * example_plugins/example.py, example_plugins/fsusage.py, example_plugins/release.py, example_plugins/template.py, lib/sos/helpers.py, lib/sos/plugins/apache.py, lib/sos/plugins/bootloader.py, lib/sos/plugins/cluster.py, lib/sos/plugins/filesys.py, lib/sos/plugins/ftp.py, lib/sos/plugins/general.py, lib/sos/plugins/hardware.py, lib/sos/plugins/kernel.py, lib/sos/plugins/ldap.py, lib/sos/plugins/libraries.py, lib/sos/plugins/mail.py, lib/sos/plugins/memory.py, lib/sos/plugins/named.py, lib/sos/plugins/networking.py, lib/sos/plugins/pam.py, lib/sos/plugins/process.py, lib/sos/plugins/rhn.py, lib/sos/plugins/rpm.py, lib/sos/plugins/samba.py, lib/sos/plugins/selinux.py, lib/sos/plugins/squid.py, lib/sos/plugins/startup.py, lib/sos/plugins/system.py, lib/sos/plugins/tarball.py, lib/sos/plugins/x11.py, lib/sos/plugintools.py, lib/sos/policyredhat.py, setup.py, sosreport: + - Flesh out rhn plugin to handle Proxy or Satellite + - Add package queries to policyredhat.py (allPkgsByName, pkgByName, pkgNVRA) + - Add policy instance to the commons dict for access from plugins + - Use string objects' methods instead of the string module where possible + - Remove imports of unused string module + - Cleanup some typos, redundant initializations, &c + +2006-06-08 dlehman <dlehman@tintin> + + * example_plugins/example.py, example_plugins/fsusage.py, example_plugins/release.py, example_plugins/template.py, lib/sos/helpers.py, lib/sos/plugins/apache.py, lib/sos/plugins/bootloader.py, lib/sos/plugins/cluster.py, lib/sos/plugins/filesys.py, lib/sos/plugins/ftp.py, lib/sos/plugins/general.py, lib/sos/plugins/hardware.py, lib/sos/plugins/kernel.py, lib/sos/plugins/ldap.py, lib/sos/plugins/libraries.py, lib/sos/plugins/mail.py, lib/sos/plugins/memory.py, lib/sos/plugins/named.py, lib/sos/plugins/networking.py, lib/sos/plugins/pam.py, lib/sos/plugins/process.py, lib/sos/plugins/rhn.py, lib/sos/plugins/rpm.py, lib/sos/plugins/samba.py, lib/sos/plugins/selinux.py, lib/sos/plugins/squid.py, lib/sos/plugins/startup.py, lib/sos/plugins/system.py, lib/sos/plugins/tarball.py, lib/sos/plugins/x11.py, lib/sos/plugintools.py, lib/sos/policyredhat.py, setup.py, sosreport: + - Flesh out rhn plugin to handle Proxy or Satellite + - Add package queries to policyredhat.py (allPkgsByName, pkgByName, pkgNVRA) + - Add policy instance to the commons dict for access from plugins + - Use string objects' methods instead of the string module where possible + - Remove imports of unused string module + - Cleanup some typos, redundant initializations, &c + +2006-06-05 jwhiter <jwhiter@tintin> + + * lib/sos/plugins/system.py: + adding the abilit to capture the autofs maps to system.py + +2006-05-31 Steve Conklin <sconklin@tintin> + + * ChangeLog, Makefile: New Makefile and ChangeLog (autogenerated) + + * Changelog, setup.py, sos.spec: + Removed old Changelog file and sync'd version and Changelog in spec file + + * lib/sos/plugins/networking.py, TODO, setup.py, sos.spec: + Final patches and version change before submission to Fedora + +2006-05-31 Steve Conklin <sconklin@tintin> + + * Changelog, setup.py, sos.spec: + Removed old Changelog file and sync'd version and Changelog in spec file + + * lib/sos/plugins/networking.py, TODO, setup.py, sos.spec: + Final patches and version change before submission to Fedora + +2006-05-26 Steve Conklin <sconklin@tintin> + + * Changelog, lib/sos/helpers.py, lib/sos/plugintools.py, setup.py, sosreport: + Added pamadio's curses UI for selecting plugin options + Added flushing stdout after informational messages + +2006-05-26 jwhiter <jwhiter@tintin> + + * lib/sos/plugins/filesys.py: + - making the filesys.py plugin call 'blkid' when running at the request of L1, this will allow us to map labels with actual devices. + + * lib/sos/plugins/tarball.py, lib/sos/plugintools.py, sosreport: + - Adding tarball.py to create a tarball of the report after it is run + - Updated sosreport to call postproc() in all the plugins, which handles the post run + - Added runExeInd() which will just run the exe and return the status to plugintools.py + - Added postproc() to plugintools.py so that plugins can implement it + +2006-05-25 Steve Conklin <sconklin@tintin> + + * lib/sos/plugintools.py, sosreport: + Fixed file naming for commands to eliminate special chars and prevent + name collisions. Added sorting of plugins by name before reporting. + + * TODO, lib/sos/plugins/kernel.py, lib/sos/plugintools.py, setup.py: + Fixed option handling + +2006-05-23 jwhiter <jwhiter@tintin> + + * Changelog, lib/sos/plugins/kernel.py, setup.py: + Adding jwb's patch to have sosreport grab sysrq data. + +2006-05-22 Steve Conklin <sconklin@tintin> + + * lib/sos/plugins/apache.py: oops, forgot this file + + * lib/sos/plugins/bootloader.py, lib/sos/plugins/filesys.py, lib/sos/plugins/ftp.py, lib/sos/plugins/general.py, lib/sos/plugins/hardware.py, lib/sos/plugins/kernel.py, lib/sos/plugins/ldap.py, lib/sos/plugins/mail.py, lib/sos/plugins/memory.py, lib/sos/plugins/named.py, lib/sos/plugins/samba.py, lib/sos/plugins/squid.py, lib/sos/plugins/x11.py, sos.spec: + Patch from jwb + + * lib/sos/plugins/filesys.py, lib/sos/plugins/kernel.py: + jwb's patches for kernel.py and filesys.py + + * sosreport: minor fix to the dir perms patch + + * lib/sos/plugins/bootloader.py, lib/sos/plugins/cluster.py, lib/sos/plugins/filesys.py, lib/sos/plugins/general.py, lib/sos/plugins/hardware.py, lib/sos/plugins/kernel.py, lib/sos/plugins/libraries.py, lib/sos/plugins/memory.py, lib/sos/plugins/networking.py, lib/sos/plugins/pam.py, lib/sos/plugins/process.py, lib/sos/plugins/rhn.py, lib/sos/plugins/rpm.py, lib/sos/plugins/selinux.py, lib/sos/plugins/startup.py, lib/sos/plugins/system.py, lib/sos/plugins/template.py, lib/sos/plugins/x11.py, Changelog, example_plugins/template.py, lib/sos/plugintools.py, setup.py, sosreport: + Merged all of jwb's weekend patches. Make output dirs world readable, check before + executing executables, and added a lot of plugins. + +2006-05-19 Steve Conklin <sconklin@tintin> + + * lib/sos/plugins/template.py: Removed unneeded variabled from plugin. + + * Changelog, TODO, example_plugins/example.py, example_plugins/fsusage.py, example_plugins/release.py, example_plugins/runcommand.py, lib/sos/plugins/template.py, lib/sos/plugintools.py, setup.py, sosreport: + Applied jwb's fix, added his examples, improved html output + +2006-05-18 Steve Conklin <sconklin@tintin> + + * example_plugins/example.py, example_plugins/runcommand.py, lib/sos/plugins/template.py: + Put instance variables in plugins in addition to base class + + * example_plugins/example.py, example_plugins/runcommand.py, lib/sos/plugins/template.py, lib/sos/plugintools.py, setup.py, sosreport: + Removed separate pit class, and put everything having to do with the plugin in + pluginBase. Still incorrectly aggregates data across all plugins. + + * README: Added Jwb as a contributor + + * Changelog, TODO, lib/sos/plugins/template.py, lib/sos/plugintools.py, sosreport: + Implemented a base class for plugins + +2006-05-17 Steve Conklin <sconklin@tintin> + + * Changelog, TODO, lib/sos/helpers.py, lib/sos/plugins/template.py, lib/sos/plugintools.py, sosreport, tests/maketesttree.sh: + Cleaned up code, added comments, fixed dir copying bug, changed option + handling. See Changelog + +2006-05-16 Steve Conklin <sconklin@tintin> + + * lib/sos/helpers.py, lib/sos/plugintools.py: + Missed an edit to change log file descriptor to the dictionary. Fixed. + + * TODO: Added documentation to list + + * TODO: Added need for example plugin + +2006-05-15 Steve Conklin <sconklin@tintin> + + * README, TODO, lib/sos/plugins/template.py, lib/sos/plugintools.py, setup.py, sosreport: + Added a dictionary of things that need to be known to all, like paths for + reports. + + Added Pierre Amadio's command line arg handling + + Fixed incorrect handling of command completion status + + Added html generation + +2006-05-09 Steve Conklin <sconklin@tintin> + + * TODO: changed it + + * setup.cfg: removed RFC file + + * MANIFEST.in, Notes.txt, README, TODO, lib/sos/__init__.py, lib/sos/helpers.py, lib/sos/plugins/__init__.py, lib/sos/plugins/template.py, lib/sos/plugintools.py, lib/sos/policyredhat.py, setup.cfg, setup.py, sosreport: + Initial checkin of the sos project + + * MANIFEST.in, Notes.txt, README, TODO, lib/sos/__init__.py, lib/sos/helpers.py, lib/sos/plugins/__init__.py, lib/sos/plugins/template.py, lib/sos/plugintools.py, lib/sos/policyredhat.py, setup.cfg, setup.py, sosreport: + New file. + diff --git a/trunk/src/LICENSE b/trunk/src/LICENSE new file mode 100644 index 00000000..7a8e8abf --- /dev/null +++ b/trunk/src/LICENSE @@ -0,0 +1,340 @@ + 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 Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <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/trunk/src/Makefile b/trunk/src/Makefile new file mode 100644 index 00000000..99d2ed5d --- /dev/null +++ b/trunk/src/Makefile @@ -0,0 +1,95 @@ +# +# Makefile for sos system support tools +# + +NAME = sos +VERSION = $(shell awk '/^%define version / { print $$3 }' sos.spec) +REPO = https://sos.108.redhat.com/svn/sos +SVNTAG = r$(subst .,-,$(VERSION)) +SRCDIR = $(PWD) +TOPDIR = $(PWD)/build/rpm-$(NAME)-$(VERSION) + + +all: + +.PHONY: tag-release tarball release install version clean + +diff-tag: + svn diff $(REPO)/trunk/src $(REPO)/tags/$(SVNTAG) + +tag: + @if ( svn list $(REPO)/tags/$(SVNTAG)/Makefile &> /dev/null ); then \ + echo "The repository already contains a tag for version $(VERSION)"; \ + exit 1; \ + fi + @svn copy $(REPO)/trunk/src $(REPO)/tags/$(SVNTAG) \ + -m "Tagging the $(SVNTAG) release of the sos project" + @echo "Tagged as $(SVNTAG)" + +tag-force: + @echo svn del $(REPO)/tags/$(SVNTAG) + @echo make diff-tag + +tarball: clean + @echo "Creating an archive from HEAD of development" + @rm -rf /tmp/$(NAME) + @svn export -q $(REPO)/trunk/src /tmp/$(NAME) \ + || echo GRRRrrrrr -- ignore [export aborted] + @mv /tmp/$(NAME) /tmp/$(NAME)-$(VERSION) + @cd /tmp; tar --bzip2 -cSpf $(NAME)-$(VERSION).tar.bz2 $(NAME)-$(VERSION) + @rm -rf /tmp/$(NAME)-$(VERSION) + @mv /tmp/$(NAME)-$(VERSION).tar.bz2 . + @echo " " + @echo "The final archive is ./$(NAME)-$(VERSION).tar.bz2." + +release: clean + @if ( ! svn list $(REPO)/tags/$(SVNTAG)/Makefile &> /dev/null ); then \ + echo "There is no tag in the repository for this version, must be tagged before release"; \ + exit 1; \ + fi + @echo "Creating an archive from tag $(SVNTAG)" + @rm -rf /tmp/$(NAME) + @svn export -q $(REPO)/tags/$(SVNTAG) /tmp/$(NAME) \ + || echo GRRRrrrrr -- ignore [export aborted] + @mv /tmp/$(NAME) /tmp/$(NAME)-$(VERSION) + @cd /tmp; tar --bzip2 -cSpf $(NAME)-$(VERSION).tar.bz2 $(NAME)-$(VERSION) + @rm -rf /tmp/$(NAME)-$(VERSION) + @cp /tmp/$(NAME)-$(VERSION).tar.bz2 . + @rm -f /tmp/$(NAME)-$(VERSION).tar.bz2 + @echo " " + @echo "The final archive is ./$(NAME)-$(VERSION).tar.bz2." + +install:mo + python setup.py install + +version: + @echo "The version is $(NAME)-$(VERSION)" + +clean: + @rm -fv *~ .*~ changenew ChangeLog.old $(NAME)-$(VERSION).tar.bz2 sosreport.1.gz + +rpm: mo + @test -f sos.spec + @mkdir -p $(TOPDIR)/SOURCES $(TOPDIR)/SRPMS $(TOPDIR)/RPMS $(TOPDIR)/BUILD + +# this builds an RPM from the current working copy + @cd $(TOPDIR)/BUILD ; \ + rm -rf $(NAME)-$(VERSION) ; \ + ln -s $(SRCDIR) $(NAME)-$(VERSION) ; \ + tar --gzip --exclude=.svn --exclude=svn-commit.tmp --exclude=$(NAME)-$(VERSION)/build --exclude=$(NAME)-$(VERSION)/dist \ + -chSpf $(TOPDIR)/SOURCES/$(NAME)-$(VERSION).tar.gz $(NAME)-$(VERSION) ; \ + rm -f $(NAME)-$(VERSION) + +# this builds an RPM from HEAD +# @rm -rf $(TOPDIR)/BUILD/$(NAME)-$(VERSION) +# @svn export -q $(REPO)/trunk/src $(TOPDIR)/BUILD/$(NAME)-$(VERSION) +# @cd $(TOPDIR)/BUILD ; tar --gzip -cSpf $(TOPDIR)/SOURCES/$(NAME)-$(VERSION).tar.gz $(NAME)-$(VERSION); rm -rf $(NAME)-$(VERSION) + + rpmbuild -ba --define="_topdir $(TOPDIR)" sos.spec + @mv $(TOPDIR)/RPMS/noarch/$(NAME)-$(VERSION)*.rpm $(TOPDIR)/SRPMS/$(NAME)-$(VERSION)*.rpm $(TOPDIR)/SOURCES/$(NAME)-$(VERSION).tar.gz dist/ + +pot: + python tools/pygettext.py -o locale/sos.pot sosreport + +mo: + python tools/msgfmt.py locale/*/LC_MESSAGES/sos.po diff --git a/trunk/src/README b/trunk/src/README new file mode 100644 index 00000000..b65b07ed --- /dev/null +++ b/trunk/src/README @@ -0,0 +1,24 @@ +This set of tools is designed to provide information to support +organizations in an extensible manner, allowing third parties, +package maintainers, and anyone else to provide plugins that will +collect, analyze, and report information that is useful for supporting +software packages. + +This project is hosted at http://sos.108.redhat.com. For the latest version, +to contribute, and for more information, please visit there. + +To access to the public source code repository for this project run: + + svn checkout https://sos.108.redhat.com/svn/sos/trunk sos + +(all the following as root) +to install locally ==> python setup.py install +to build an rpm ==> make rpm + +See the Makefile. + +Contributors: +Steve Conklin <sconklin@redhat.com> +Pierre Amadio <pamadio@redhat.com> +John Berninger <jwb@redhat.com> +Navid Sheikhol-Eslami <navid@redhat.com> diff --git a/trunk/src/TODO b/trunk/src/TODO new file mode 100644 index 00000000..2f2d323f --- /dev/null +++ b/trunk/src/TODO @@ -0,0 +1,21 @@ +To Do List: + + * Gather statistics (time, files collected, commands run) per plugin + + * Choose sane defaults to satisfy Red Hat support requirements (ie. what + plugins should be loaded by default, with what options, etc) + + * Display on screen what files/commands are being gathered. + + * Make sosreport a drop-in replacement for sysreport + + * Allow to use a different rootdir than / + + * Add support for distributions other than Fedora. + + * Make it easier to select a policy module and perhaps optionally include + plugins from the library, to allow per-distribution customization. + + * Documentation - plugin howto doc, inline doc in plugin template + + * Review and test error handling for things like a full tmp file system diff --git a/trunk/src/example_plugins/example.py b/trunk/src/example_plugins/example.py new file mode 100755 index 00000000..1e650417 --- /dev/null +++ b/trunk/src/example_plugins/example.py @@ -0,0 +1,94 @@ +## example.py +## An example sos plugin + +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +# Class name must be the same as file name and method names must not change +class example(sos.plugintools.PluginBase): + """This is an example plugin for sos. Plugins gather, analyze, and report on various aspects + of system operation that are of interest. plugins are based on the PluginBase class, which + you should inspect if you wish to override any methods in your plugin. The methods of use + to plugin developers are: + collect() - use the functions sosCp and sosRunExe to gether information + analyze() - perform any special analysis you require beyond just saving the + information gathered by collect(). Use sosAlert() and sosAddCustomText() + to add information to the report. + report() - override this method if you wish to replace the default reporting + + All plugins will use collect(), some will use analyze(), few will override report() + """ + + # Add your options here, indicate whether they are slow to run, and set whether they are enabled by default + # Options are python dictionaries that conatin a short name, long description, speed, and whether they are enabled by default + optionList = [("init.d", 'Gathers the init.d directory', 'slow', 0), + ('follicles', 'Gathers information about each follicle on every toe', 'slow', 0), + ('color', 'Gathers toenail polish color', 'fast', 0)] + + def setup(self): + ''' First phase - Collect all the information we need. + Directories are copied recursively. arbitrary commands may be + executed using the susRunExe method. Information is automatically saved, and + links are presented in the report to each file or directory which has been + copied to the saved tree. Also, links are provided to the output from each command. + ''' + # Here's how to copy files and directory trees + self.addCopySpec("/etc/hosts") + # this one saves a file path to the copy for later analysis + # FIXME: Need to figure out how to do this + # self.fooFilePath = self.copyFileOrDir("/proc/cpuinfo") + + # Here's how to test your options and execute if enabled + if self.isOptionEnabled("init.d"): + self.addCopySpec("/etc/init.d") # copies a whole directory tree + + # Here's how to execute a command + # you can save the path to the copied file for later analysis if desired + # FIXME: Need to figure out how to do this + self.psCmdDstFileName = self.runExe("/bin/ps -ef") + return + + def analyze(self): + ''' This is optional and need not be defined. + If you wish to perform some analysis on either files + that were gathered or on the output of commands, then save the filenames on the + destination file system when gathering that information in the collect() method + and use them here + ''' + # This is an example of opening and reading the output of a command that + # was run in the collect() method. Note that the output of the command is + # included in the report anyway + fd = open(self.fooFilePath) + lines = fd.readlines() + fd.close() + for line in lines: + if line.count("vendor_id"): + self.addCustomText("Vendor ID string is: %s <br>\n" % line) + # + # Alerts can optionally be generated, and will be included in the + # report automatically + # + self.addAlert("This is an alert") + return + +# def report(self): +# """ Usually, this doesn't even need to be defined, and you can inherit the +# base class, unless you want to replace what sosGetResults() +# does with your own custom report generator. If you are going to do that, have a good +# look at that method to see what it does. Custom text and alerts can still be added +# here using sosAddCustomText() and sosAddAlert() +# This method returns html that will be included inline in the report +# """ diff --git a/trunk/src/example_plugins/fsusage.py b/trunk/src/example_plugins/fsusage.py new file mode 100644 index 00000000..2532c5f7 --- /dev/null +++ b/trunk/src/example_plugins/fsusage.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +# Class name must be the same as file name and method names must not change +class fsusage(sos.plugintools.PluginBase): + def setup(self): + self.psCmdDstFileName = self.collectExtOutput("/bin/df -al") + return diff --git a/trunk/src/example_plugins/release.py b/trunk/src/example_plugins/release.py new file mode 100644 index 00000000..db4d7581 --- /dev/null +++ b/trunk/src/example_plugins/release.py @@ -0,0 +1,21 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +# Class name must be the same as file name and method names must not change +class release(sos.plugintools.PluginBase): + def setup(self): + self.addCopySpec("/etc/redhat-release") + return diff --git a/trunk/src/example_plugins/runcommand.py b/trunk/src/example_plugins/runcommand.py new file mode 100755 index 00000000..df8951d2 --- /dev/null +++ b/trunk/src/example_plugins/runcommand.py @@ -0,0 +1,45 @@ +## runcommand.py +## An example plugin for sos + +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +# Class name must be the same as file name and method names must not change +class runcommand(sos.plugintools.PluginBase): + """This is a very simple example plugin that just runs one command in a shell. That + command could be a script supplied with your package which outputs information of interest + to support. When the script or command is run, stdout is collected and included in the + report generated by sosreport, and stderr is collected and copied into the sos log. + + This is useful for people who have minimal knowledge of python, who wish to write + collection tools in other languages, or who have existing tools. + + If your script or command generates output files that you want included in the sosreport + collection of files, include sosCp calls to include them. + + The only method required for this simple plugin is collect(). + + Your finished plugin should be included with your package and installed in python's + site-packages/sos/plugins/ directory + """ + def setup(self): + ''' Run a command. Output is automatically included in the report. + ''' + self.collectExtOutput("/path/to/my/script --myoption --anotheroption") + + # if (for example) that command created files /foo/bar/baz.txt and + # we want to include that in our report, we include the next line: + #self.pit.sosCp("/foo/bar/baz.txt") diff --git a/trunk/src/lib/sos/__init__.py b/trunk/src/lib/sos/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/trunk/src/lib/sos/__init__.py diff --git a/trunk/src/lib/sos/helpers.py b/trunk/src/lib/sos/helpers.py new file mode 100755 index 00000000..54a86dba --- /dev/null +++ b/trunk/src/lib/sos/helpers.py @@ -0,0 +1,120 @@ +## helpers.py +## Implement policies required for the sos system support tool + +## Copyright (C) 2006 Steve Conklin <sconklin@redhat.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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +## Some code adapted from "Python Cookbook, 2nd ed", by Alex +## Martelli, Anna Martelli Ravenscroft, and David Ascher +## (O'Reilly Media, 2005) 0-596-00797-3 +## + +""" +helper functions used by sosreport and plugins +""" +import os, popen2, fcntl, select, itertools, sys, commands +from time import time +from tempfile import mkdtemp + +def importPlugin(pluginname, name): + """ Import a plugin to extend capabilities of sosreport + """ + try: + plugin = __import__(pluginname, globals(), locals(), [name]) + except ImportError: + return None + return getattr(plugin, name) + + +def sosFindTmpDir(): + """Find a temp directory to form the root for our gathered information + and reports. + """ + workingBase = mkdtemp("","sos_") + return workingBase + + +def makeNonBlocking(afd): + """ Make the file descriptor non-blocking. This prevents deadlocks. + """ + fl = fcntl.fcntl(afd, fcntl.F_GETFL) + try: + fcntl.fcntl(afd, fcntl.F_SETFL, fl | os.O_NDELAY) + except AttributeError: + fcntl.fcntl(afd, fcntl.F_SETFL, fl | os.FNDELAY) + + +def sosGetCommandOutput(command): + """ Execute a command and gather stdin, stdout, and return status. + Adapted from Python Cookbook - O'Reilly + """ + stime = time() + errdata = '' + status,outdata=commands.getstatusoutput(command) + return (status, outdata, time()-stime) + + +# FIXME: this needs to be made clean and moved to the plugin tools, so +# that it prints nice color output like sysreport if the progress bar +# is not enabled. +def sosStatus(stat): + """ Complete a status line that has been output to the console, + providing pass/fail indication. + """ + if not stat: + print " [ OK ]" + else: + print " [ FAILED ]" + sys.stdout.flush() + return + + +def allEqual(elements): + ''' return True if all the elements are equal, otherwise False. ''' + first_element = elements[0] + for other_element in elements[1:]: + if other_element != first_element: + return False + return True + + +def commonPrefix(*sequences): + ''' return a list of common elements at the start of all sequences, + then a list of lists that are the unique tails of each sequence. ''' + # if there are no sequences at all, we're done + if not sequences: + return [], [] + # loop in parallel on the sequences + common = [] + for elements in itertools.izip(*sequences): + # unless all elements are equal, bail out of the loop + if not allEqual(elements): + break + # got one more common element, append it and keep looping + common.append(elements[0]) + # return the common prefix and unique tails + return common, [ sequence[len(common):] for sequence in sequences ] + +def sosRelPath(path1, path2, sep=os.path.sep, pardir=os.path.pardir): + ''' return a relative path from path1 equivalent to path path2. + In particular: the empty string, if path1 == path2; + path2, if path1 and path2 have no common prefix. + ''' + common, (u1, u2) = commonPrefix(path1.split(sep), path2.split(sep)) + if not common: + return path2 # leave path absolute if nothing at all in common + return sep.join( [pardir]*len(u1) + u2 ) + diff --git a/trunk/src/lib/sos/plugins/__init__.py b/trunk/src/lib/sos/plugins/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/trunk/src/lib/sos/plugins/__init__.py diff --git a/trunk/src/lib/sos/plugins/amd.py b/trunk/src/lib/sos/plugins/amd.py new file mode 100644 index 00000000..1b1375cc --- /dev/null +++ b/trunk/src/lib/sos/plugins/amd.py @@ -0,0 +1,31 @@ +## Copyright (C) 2007 Red Hat, Inc., Eugene Teo <eteo@redhat.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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class amd(sos.plugintools.PluginBase): + """Amd automounter information + """ + def setup(self): + self.addCopySpec("/etc/amd.*") + self.addCopySpec("/etc/rc.d/init.d/amd") + self.addCopySpec("/etc/sysconfig/amd") + self.collectExtOutput("/bin/rpm -qV am-utils") + self.collectExtOutput("/bin/egrep -e 'automount|pid.*nfs' /proc/mounts") + self.collectExtOutput("/bin/mount | egrep -e 'automount|pid.*nfs'") + self.collectExtOutput("/sbin/chkconfig --list amd") + return + diff --git a/trunk/src/lib/sos/plugins/apache.py b/trunk/src/lib/sos/plugins/apache.py new file mode 100644 index 00000000..c5250fd9 --- /dev/null +++ b/trunk/src/lib/sos/plugins/apache.py @@ -0,0 +1,26 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools +from threading import Thread + +class apache(sos.plugintools.PluginBase): + """Apache related information + """ + def setup(self): + self.addCopySpec("/etc/httpd/conf/httpd.conf") + self.addCopySpec("/etc/httpd/conf.d/*.conf") + self.addCopySpec("/var/log/httpd/*") + return + diff --git a/trunk/src/lib/sos/plugins/bootloader.py b/trunk/src/lib/sos/plugins/bootloader.py new file mode 100644 index 00000000..a820069e --- /dev/null +++ b/trunk/src/lib/sos/plugins/bootloader.py @@ -0,0 +1,31 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class bootloader(sos.plugintools.PluginBase): + """Bootloader information + """ + def setup(self): + self.addCopySpec("/etc/lilo.conf") + self.addCopySpec("/etc/milo.conf") + self.addCopySpec("/etc/silo.conf") + self.addCopySpec("/boot/grub/grub.conf") + self.addCopySpec("/boot/grub/device.map") + self.addCopySpec("/boot/efi/elilo.conf") + self.addCopySpec("/boot/yaboot.conf") + + self.collectExtOutput("/sbin/lilo -q") + return + diff --git a/trunk/src/lib/sos/plugins/cluster.py b/trunk/src/lib/sos/plugins/cluster.py new file mode 100644 index 00000000..595d940a --- /dev/null +++ b/trunk/src/lib/sos/plugins/cluster.py @@ -0,0 +1,78 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools +import commands, os + +class cluster(sos.plugintools.PluginBase): + """cluster suite and GFS related information + """ + def checkenabled(self): + # enable if any related package is installed + for pkg in [ "ccs", "cman", "cman-kernel", "magma", "magma-plugins", + "rgmanager", "fence", "dlm", "dlm-kernel", "gulm", + "GFS", "GFS-kernel", "lvm2-cluster" ]: + if self.cInfo["policy"].pkgByName(pkg) != None: + return True + + # enable if any related file is present + for fname in [ "/etc/cluster/cluster.conf" ]: + try: os.stat(fname) + except:pass + else: return True + + # no data related to RHCS/GFS exists + return False + + def diagnose(self): + rhelver = self.cInfo["policy"].pkgDictByName("fedora-release")[0] + if rhelver == "6": + # check if the minimum set of packages is installed + # for RHEL4 RHCS(ccs, cman, cman-kernel, magma, magma-plugins, (dlm, dlm-kernel) || gulm, perl-Net-Telnet, rgmanager, fence) + # RHEL4 GFS (GFS, GFS-kernel, ccs, lvm2-cluster, fence) + for pkg in [ "ccs", "cman", "cman-kernel", "magma", "magma-plugins", "perl-Net-Telnet", "rgmanager", "fence" ]: + if self.cInfo["policy"].pkgByName(pkg) == None: + self.addDiagnose("required package is missing: %s" % pkg) + + # check if all the needed daemons are active at sosreport time + # check if they are started at boot time in RHEL4 RHCS (cman, ccsd, rgmanager, fenced) + # and GFS (gfs, ccsd, clvmd, fenced) + for service in [ "cman", "ccsd", "rgmanager", "fence" ]: + if commands.getstatus("/sbin/service %s status" % service): + self.addDiagnose("service %s is not running" % service) + + if not self.cInfo["policy"].runlevelDefault() in self.cInfo["policy"].runlevelByService(service): + self.addDiagnose("service %s is not started in default runlevel" % service) + + # FIXME: what locking are we using ? check if packages exist +# if self.cInfo["policy"].pkgByName(pkg) and self.cInfo["policy"].pkgByName(pkg) and not self.cInfo["policy"].pkgByName(pkg) + + def setup(self): + self.collectExtOutput("/sbin/fdisk -l") + self.addCopySpec("/etc/cluster.conf") + self.addCopySpec("/etc/cluster.xml") + self.addCopySpec("/etc/cluster") + self.collectExtOutput("/usr/sbin/rg_test test /etc/cluster/cluster.conf") + self.addCopySpec("/proc/cluster") + self.collectExtOutput("/usr/bin/cman_tool status") + self.collectExtOutput("/usr/bin/cman_tool services") + self.collectExtOutput("/usr/bin/cman_tool -af nodes") + self.collectExtOutput("/usr/bin/ccs_tool lsnode") + self.collectExtOutput("/usr/bin/openais-cfgtool -s") + self.collectExtOutput("/usr/bin/clustat") + return + + def postproc(self): + self.doRegexSub("/etc/cluster/cluster.conf", r"(\s*\<fencedevice\s*.*\s*passwd\s*=\s*)\S+(\")", r"\1***") + return diff --git a/trunk/src/lib/sos/plugins/devicemapper.py b/trunk/src/lib/sos/plugins/devicemapper.py new file mode 100644 index 00000000..e517aa3d --- /dev/null +++ b/trunk/src/lib/sos/plugins/devicemapper.py @@ -0,0 +1,38 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class devicemapper(sos.plugintools.PluginBase): + """device-mapper related information (dm, lvm, multipath) + """ + def setup(self): + self.collectExtOutput("/sbin/dmsetup info -c") + self.collectExtOutput("/sbin/dmsetup table") + self.collectExtOutput("/sbin/dmsetup status") + + self.collectExtOutput("/usr/sbin/vgscan -vvv") + self.collectExtOutput("/usr/sbin/vgdisplay -vv", root_symlink = "vgdisplay") + self.collectExtOutput("/usr/sbin/pvscan -v") + self.collectExtOutput("/usr/sbin/lvs -a -o +devices") + self.collectExtOutput("/usr/sbin/pvs -a -v") + self.collectExtOutput("/usr/sbin/vgs -v") + + self.addCopySpec("/etc/lvm/lvm.conf") + + self.addCopySpec("/etc/multipath.conf") + self.addCopySpec("/var/lib/multipath/bindings") + self.collectExtOutput("/sbin/multipath -v4 -ll") + + return diff --git a/trunk/src/lib/sos/plugins/filesys.py b/trunk/src/lib/sos/plugins/filesys.py new file mode 100644 index 00000000..02bde2eb --- /dev/null +++ b/trunk/src/lib/sos/plugins/filesys.py @@ -0,0 +1,47 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools +import commands + +class filesys(sos.plugintools.PluginBase): + """information on filesystems + """ + def setup(self): + self.addCopySpec("/proc/filesystems") + self.addCopySpec("/etc/fstab") + self.addCopySpec("/proc/self/mounts") + self.addCopySpec("/proc/mounts") + self.addCopySpec("/proc/mdstat") + self.addCopySpec("/etc/raidtab") + self.addCopySpec("/etc/mdadm.conf") + self.addCopySpec("/etc/auto.master") + self.addCopySpec("/etc/auto.misc") + self.addCopySpec("/etc/auto.net") + + self.collectExtOutput("/bin/df -al", root_symlink = "df") + self.collectExtOutput("/usr/sbin/lsof -b +M -n -l", root_symlink = "lsof") + self.collectExtOutput("/bin/mount -l", root_symlink = "mount") + self.collectExtOutput("/sbin/blkid") + + raiddevs = commands.getoutput("/bin/cat /proc/partitions | /bin/egrep -v \"^major|^$\" | /bin/awk '{print $4}' | /bin/grep \/ | /bin/egrep -v \"p[0123456789]$\"") + disks = commands.getoutput("/bin/cat /proc/partitions | /bin/egrep -v \"^major|^$\" | /bin/awk '{print $4}' | /bin/grep -v / | /bin/egrep -v \"[0123456789]$\"") + for disk in raiddevs.split('\n'): + if '' != disk.strip(): + self.collectExtOutput("/sbin/fdisk -l /dev/%s" % (disk,)) + for disk in disks.split('\n'): + if '' != disk.strip(): + self.collectExtOutput("/sbin/fdisk -l /dev/%s" % (disk,)) + return + diff --git a/trunk/src/lib/sos/plugins/ftp.py b/trunk/src/lib/sos/plugins/ftp.py new file mode 100644 index 00000000..026954c0 --- /dev/null +++ b/trunk/src/lib/sos/plugins/ftp.py @@ -0,0 +1,24 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class ftp(sos.plugintools.PluginBase): + """FTP server related information + """ + def setup(self): + self.addCopySpec("/etc/ftp*") + self.addCopySpec("/etc/vsftpd") + return + diff --git a/trunk/src/lib/sos/plugins/general.py b/trunk/src/lib/sos/plugins/general.py new file mode 100644 index 00000000..06f55228 --- /dev/null +++ b/trunk/src/lib/sos/plugins/general.py @@ -0,0 +1,43 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools +import glob + +class general(sos.plugintools.PluginBase): + """very basic system information + """ + + optionList = [("syslogsize", "maximum size (in MiB) of logs to collect per syslog file", "", 15)] + + def setup(self): + self.addCopySpec("/etc/redhat-release") + self.addCopySpec("/etc/sysconfig") + self.addCopySpec("/proc/stat") + self.addCopySpec("/var/log/dmesg") + self.addCopySpec("/var/log/messages") + self.addCopySpecLimit("/var/log/messages.*", sizelimit = self.isOptionEnabled("syslogsize")) + self.addCopySpec("/var/log/secure") + self.addCopySpecLimit("/var/log/secure.*", sizelimit = self.isOptionEnabled("syslogsize")) + self.addCopySpec("/var/log/sa") + self.addCopySpec("/var/log/up2date") + self.addCopySpec("/etc/exports") + self.collectExtOutput("/bin/hostname", root_symlink = "hostname") + self.collectExtOutput("/bin/date", root_symlink = "date") + self.collectExtOutput("/usr/bin/uptime", root_symlink = "uptime") + return + + def postproc(self): + self.doRegexSub("/etc/sysconfig/rhn/up2date", r"(\s*proxyPassword\s*=\s*)\S+", r"\1***") + return diff --git a/trunk/src/lib/sos/plugins/hardware.py b/trunk/src/lib/sos/plugins/hardware.py new file mode 100644 index 00000000..f8eeda88 --- /dev/null +++ b/trunk/src/lib/sos/plugins/hardware.py @@ -0,0 +1,55 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools +import commands + +class hardware(sos.plugintools.PluginBase): + """hardware related information + """ + def setup(self): + self.addCopySpec("/proc/partitions") + self.addCopySpec("/proc/cpuinfo") + self.addCopySpec("/proc/meminfo") + self.addCopySpec("/proc/ioports") + self.addCopySpec("/proc/interrupts") + self.addCopySpec("/proc/scsi") + self.addCopySpec("/proc/dma") + self.addCopySpec("/proc/devices") + self.addCopySpec("/proc/rtc") + self.addCopySpec("/proc/ide") + self.addCopySpec("/proc/bus") + self.addCopySpec("/etc/stinit.def") + self.addCopySpec("/etc/sysconfig/hwconf") + self.addCopySpec("/proc/chandev") + self.addCopySpec("/proc/dasd") + self.addCopySpec("/proc/s390dbf/tape") + self.collectExtOutput("/usr/share/rhn/up2dateclient/hardware.py") + self.collectExtOutput("""/bin/echo "lspci" ; /bin/echo ; /sbin/lspci ; /bin/echo ; /bin/echo ; /bin/echo "lspci -nvv" ; /bin/echo ; /sbin/lspci -nvv""", suggest_filename = "lspci", root_symlink = "lspci") + + # FIXME: what is this for? + self.collectExtOutput("/bin/dmesg | /bin/grep -e 'e820.' -e 'agp.'") + + # FIXME: what is this for? + tmpreg = "" + for hwmodule in commands.getoutput('cat /lib/modules/$(uname -r)/modules.pcimap | cut -d " " -f 1 | grep "[:alpha:]" | sort -u').split("\n"): + hwmodule = hwmodule.strip() + if len(hwmodule): + tmpreg = tmpreg + "|" + hwmodule + self.collectExtOutput("/bin/dmesg | /bin/egrep '(%s)'" % tmpreg[1:]) + + self.collectExtOutput("/sbin/lsusb") + self.collectExtOutput("/usr/bin/lshal") + return + diff --git a/trunk/src/lib/sos/plugins/initrd.py b/trunk/src/lib/sos/plugins/initrd.py new file mode 100644 index 00000000..83356548 --- /dev/null +++ b/trunk/src/lib/sos/plugins/initrd.py @@ -0,0 +1,28 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools +import glob + +class initrd(sos.plugintools.PluginBase): + """initrd related information + """ + def setup(self): + for initrd in glob.glob('/boot/initrd-*.img'): + self.collectExtOutput("/bin/zcat "+initrd+" | /bin/cpio "+ + "--extract --to-stdout init" ) + return + + def defaultenabled(self): + return False diff --git a/trunk/src/lib/sos/plugins/kernel.py b/trunk/src/lib/sos/plugins/kernel.py new file mode 100644 index 00000000..09381b38 --- /dev/null +++ b/trunk/src/lib/sos/plugins/kernel.py @@ -0,0 +1,131 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools +import commands, os, re + +class kernel(sos.plugintools.PluginBase): + """kernel related information + """ + optionList = [("modinfo", 'Gathers module information on all modules', 'fast', 1), + ('sysrq', 'Trigger SysRq dumps', 'fast', 0)] + moduleFile = "" + taintList = [ + {'regex':'mvfs*', 'description':'Clearcase module'}, + {'regex':'vnode*', 'description':'Clearcase module'}, + {'regex':'vxfs*', 'description':'Veritas file system module'}, + {'regex':'vxportal*', 'description':'Veritas module'}, + {'regex':'vxdmp*', 'description':'Veritas dynamic multipathing module'}, + {'regex':'vxio*', 'description':'Veritas module'}, + {'regex':'vxspec*"', 'description':'Veritas module'}, + {'regex':'dcd*', 'description':'Dell OpenManage Server Administrator module'}, + {'regex':'ocfs', 'description':'Oracle cluster filesystem module'}, + {'regex':'oracle*', 'description':'Oracle module'}, + {'regex':'vmnet*', 'description':'VMware module'}, + {'regex':'vmmon*', 'description':'VMware module'}, + {'regex':'egenera*', 'description':'Egenera module'}, + {'regex':'emcp*', 'description':'EMC module'}, + {'regex':'ocfs*', 'description':'OCFS module'}, + {'regex':'nvidia', 'description':'NVidia module'}, + {'regex':'ati-', 'description':'ATI module'} + ] + + # HP + # + # + + + def setup(self): + self.collectExtOutput("/bin/uname -a", root_symlink = "uname") + self.moduleFile = self.collectOutputNow("/sbin/lsmod", root_symlink = "lsmod") + if self.isOptionEnabled('modinfo'): + runcmd = "" + for kmod in commands.getoutput('/sbin/lsmod | /bin/cut -f1 -d" " 2>/dev/null | /bin/grep -v Module 2>/dev/null').split('\n'): + if '' != kmod.strip(): + runcmd = runcmd + " " + kmod + if len(runcmd): + self.collectExtOutput("/sbin/modinfo " + runcmd) + self.collectExtOutput("/sbin/ksyms") + self.addCopySpec("/proc/filesystems") + self.addCopySpec("/proc/ksyms") + self.addCopySpec("/proc/slabinfo") + kver = commands.getoutput('/bin/uname -r') + depfile = "/lib/modules/%s/modules.dep" % (kver,) + self.addCopySpec(depfile) + self.addCopySpec("/etc/conf.modules") + self.addCopySpec("/etc/modules.conf") + self.addCopySpec("/etc/modprobe.conf") + self.collectExtOutput("/usr/sbin/dmidecode", root_symlink = "dmidecode") + self.collectExtOutput("/usr/sbin/dkms status") + self.addCopySpec("/proc/cmdline") + self.addCopySpec("/proc/driver") + self.addCopySpec("/proc/sys/kernel/tainted") + # trigger some sysrq's. I'm not sure I like doing it this way, but + # since we end up with the sysrq dumps in syslog whether we run the + # syslog report before or after this, I suppose I can live with it. + if self.isOptionEnabled('sysrq') and os.access("/proc/sysrq-trigger", os.W_OK) and os.access("/proc/sys/kernel/sysrq", os.R_OK): + sysrq_state = commands.getoutput("/bin/cat /proc/sys/kernel/sysrq") + commands.getoutput("/bin/echo 1 > /proc/sys/kernel/sysrq") + for key in ['m', 'p', 't']: + commands.getoutput("/bin/echo %s > /proc/sysrq-trigger" % (key,)) + commands.getoutput("/bin/echo %s > /proc/sys/kernel/sysrq" % (sysrq_state,)) + # No need to grab syslog here if we can't trigger sysrq, so keep this + # inside the if + self.addCopySpec("/var/log/messages") + + return + + def analyze(self): + infd = open("/proc/modules", "r") + modules = infd.readlines() + infd.close() + + for modname in modules: + modname=modname.split(" ")[0] + modinfo_srcver = commands.getoutput("/sbin/modinfo -F srcversion %s" % modname) + if not os.access("/sys/module/%s/srcversion" % modname, os.R_OK): + continue + infd = open("/sys/module/%s/srcversion" % modname, "r") + sys_srcver = infd.read().strip("\n") + infd.close() + if modinfo_srcver != sys_srcver: + self.addAlert("Loaded module %s differs from the one present on the file-system") + + # this would be a good moment to check the module's signature + # but at the moment there's no easy way to do that outside of + # the kernel. i will probably need to write a C lib (derived from + # the kernel sources to do this verification. + + savedtaint = os.path.join(self.cInfo['dstroot'], "/proc/sys/kernel/tainted") + infd = open(savedtaint, "r") + line = infd.read() + infd.close() + line = line.strip() + if (line != "0"): + self.addAlert("Kernel taint flag is <%s>\n" % line) + + + infd = open(self.moduleFile, "r") + modules = infd.readlines() + infd.close() + + #print(modules) + for tainter in self.taintList: + p = re.compile(tainter['regex']) + for line in modules: + if p.match(line) != None: + # found a taint match, create an alert + moduleName = line.split()[0] + self.addAlert("Check for tainted kernel by module %s, which is %s" % (moduleName, tainter['description'])) + return diff --git a/trunk/src/lib/sos/plugins/ldap.py b/trunk/src/lib/sos/plugins/ldap.py new file mode 100644 index 00000000..318a3ba9 --- /dev/null +++ b/trunk/src/lib/sos/plugins/ldap.py @@ -0,0 +1,27 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class ldap(sos.plugintools.PluginBase): + """LDAP related information + """ + def setup(self): + self.addCopySpec("/etc/ldap.conf") + self.addCopySpec("/etc/openldap") + return + + def postproc(self): + self.doRegexSub("/etc/ldap.conf", r"(\s*bindpw\s*)\S+", r"\1***") + return diff --git a/trunk/src/lib/sos/plugins/libraries.py b/trunk/src/lib/sos/plugins/libraries.py new file mode 100644 index 00000000..70b63dba --- /dev/null +++ b/trunk/src/lib/sos/plugins/libraries.py @@ -0,0 +1,24 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class libraries(sos.plugintools.PluginBase): + """information on shared libraries + """ + def setup(self): + self.addCopySpec("/etc/ld.so.conf") + self.addCopySpec("/etc/ld.so.conf.d") + return + diff --git a/trunk/src/lib/sos/plugins/mail.py b/trunk/src/lib/sos/plugins/mail.py new file mode 100644 index 00000000..0d4dda29 --- /dev/null +++ b/trunk/src/lib/sos/plugins/mail.py @@ -0,0 +1,25 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class mail(sos.plugintools.PluginBase): + """mail server related information + """ + def setup(self): + self.addCopySpec("/etc/mail") + self.addCopySpec("/etc/postfix/main.cf") + self.addCopySpec("/etc/postfix/master.cf") + return + diff --git a/trunk/src/lib/sos/plugins/memory.py b/trunk/src/lib/sos/plugins/memory.py new file mode 100644 index 00000000..7fbe39c3 --- /dev/null +++ b/trunk/src/lib/sos/plugins/memory.py @@ -0,0 +1,30 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class memory(sos.plugintools.PluginBase): + """memory usage information + """ + def setup(self): + self.addCopySpec("/proc/pci") + self.addCopySpec("/proc/meminfo") + self.addCopySpec("/proc/vmstat") + self.addCopySpec("/proc/slabinfo") + + self.collectExtOutput("/bin/dmesg | grep -e 'e820.' -e 'aperature.'") + self.collectExtOutput("/usr/bin/free", root_symlink = "free") + self.collectExtOutput("/usr/bin/free -m") + return + diff --git a/trunk/src/lib/sos/plugins/named.py b/trunk/src/lib/sos/plugins/named.py new file mode 100644 index 00000000..68d56ca0 --- /dev/null +++ b/trunk/src/lib/sos/plugins/named.py @@ -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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools +import commands +import os + +class named(sos.plugintools.PluginBase): + """named related information + """ + def setup(self): + dnsdir = "" + self.addCopySpec("/etc/named.boot") + self.addCopySpec("/etc/named.conf") + self.addCopySpec("/etc/sysconfig/named") + if os.access("/etc/named.conf", os.R_OK): + dnsdir = commands.getoutput("/bin/grep -i directory /etc/named.conf | /bin/gawk '{print $2}' | /bin/sed 's/\\\"//g' | /bin/sed 's/\;//g'") + if os.access("/etc/named.boot", os.R_OK): + dnsdir = commands.getoutput("/bin/grep -i directory /etc/named.boot | /bin/gawk '{print $2}' | /bin/sed 's/\\\"//g' | /bin/sed 's/\;//g'") + if '' != dnsdir.strip(): + self.addCopySpec(dnsdir) + self.addForbiddenPath('/var/named/chroot/proc') + self.addForbiddenPath('/var/named/chroot/dev') + return + diff --git a/trunk/src/lib/sos/plugins/networking.py b/trunk/src/lib/sos/plugins/networking.py new file mode 100644 index 00000000..54723663 --- /dev/null +++ b/trunk/src/lib/sos/plugins/networking.py @@ -0,0 +1,75 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools +import os,re,commands + +class networking(sos.plugintools.PluginBase): + """network related information + """ + optionList = [("traceroute", "collects a traceroute to rhn.redhat.com", "slow", 0)] + + def get_interface_name(self,ifconfigFile): + """Return a dictionary for which key are interface name according to the + output of ifconifg-a stored in ifconfigFile. + """ + out={} + if(os.path.isfile(ifconfigFile)): + f=open(ifconfigFile,'r') + content=f.read() + f.close() + reg=re.compile(r"^(eth\d+)\D",re.MULTILINE) + for name in reg.findall(content): + out[name]=1 + return out + + def collectIPTable(self,tablename): + """ When running the iptables command, it unfortunately auto-loads + the modules before trying to get output. Some people explicitly + don't want this, so check if the modules are loaded before running + the command. If they aren't loaded, there can't possibly be any + relevant rules in that table """ + + cmd = "/sbin/iptables -t "+tablename+" -nvL" + + (status, output) = commands.getstatusoutput("/sbin/lsmod | grep -q "+tablename) + if status == 0: + self.collectExtOutput(cmd) + else: + self.writeTextToCommand(cmd,"IPTables module "+tablename+" not loaded\n") + + def setup(self): + self.addCopySpec("/etc/nsswitch.conf") + self.addCopySpec("/etc/yp.conf") + self.addCopySpec("/etc/inetd.conf") + self.addCopySpec("/etc/xinetd.conf") + self.addCopySpec("/etc/xinetd.d") + self.addCopySpec("/etc/host*") + self.addCopySpec("/etc/resolv.conf") + ifconfigFile=self.collectExtOutput("/sbin/ifconfig -a", root_symlink = "ifconfig") + self.collectExtOutput("/sbin/route -n", root_symlink = "route") + self.collectExtOutput("/sbin/ipchains -nvL") + self.collectIPTable("filter") + self.collectIPTable("nat") + self.collectIPTable("mangle") + self.collectExtOutput("/bin/netstat -nap") + if ifconfigFile: + for eth in self.get_interface_name(ifconfigFile): + self.collectExtOutput("/sbin/ethtool "+eth) + if self.isOptionEnabled("traceroute"): + # The semicolon prevents the browser from thinking this is a link when viewing the report + self.collectExtOutput("/bin/traceroute rhn.redhat.com;") + + return + diff --git a/trunk/src/lib/sos/plugins/nfsserver.py b/trunk/src/lib/sos/plugins/nfsserver.py new file mode 100644 index 00000000..6c894177 --- /dev/null +++ b/trunk/src/lib/sos/plugins/nfsserver.py @@ -0,0 +1,27 @@ +## Copyright (C) 2007 Red Hat, Inc., Eugene Teo <eteo@redhat.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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class nfsserver(sos.plugintools.PluginBase): + """NFS server-related information + """ + def setup(self): + self.addCopySpec("/etc/exports") + self.collectExtOutput("/usr/sbin/rpcinfo -p localhost") + self.collectExtOutput("/usr/sbin/nfsstat") + return + diff --git a/trunk/src/lib/sos/plugins/pam.py b/trunk/src/lib/sos/plugins/pam.py new file mode 100644 index 00000000..8164bba3 --- /dev/null +++ b/trunk/src/lib/sos/plugins/pam.py @@ -0,0 +1,25 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class pam(sos.plugintools.PluginBase): + """PAM related information + """ + def setup(self): + self.addCopySpec("/etc/pam.d") + self.addCopySpec("/etc/security") + self.collectExtOutput("/bin/ls -laF /lib/security/pam_*so") + return + diff --git a/trunk/src/lib/sos/plugins/printing.py b/trunk/src/lib/sos/plugins/printing.py new file mode 100644 index 00000000..76a476eb --- /dev/null +++ b/trunk/src/lib/sos/plugins/printing.py @@ -0,0 +1,24 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class printing(sos.plugintools.PluginBase): + """printing related information (cups) + """ + def setup(self): + self.addCopySpec("/etc/cups/*.conf") + self.addCopySpec("/var/log/cups") + return + diff --git a/trunk/src/lib/sos/plugins/process.py b/trunk/src/lib/sos/plugins/process.py new file mode 100644 index 00000000..7ed13a5e --- /dev/null +++ b/trunk/src/lib/sos/plugins/process.py @@ -0,0 +1,24 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class process(sos.plugintools.PluginBase): + """process information + """ + def setup(self): + self.collectExtOutput("/bin/ps auxww", root_symlink = "ps") + self.collectExtOutput("/usr/bin/pstree", root_symlink = "pstree") + return + diff --git a/trunk/src/lib/sos/plugins/radius.py b/trunk/src/lib/sos/plugins/radius.py new file mode 100644 index 00000000..7ab53810 --- /dev/null +++ b/trunk/src/lib/sos/plugins/radius.py @@ -0,0 +1,29 @@ +## Copyright (C) 2007 Navid Sheikhol-Eslami <navid@redhat.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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class radius(sos.plugintools.PluginBase): + """radius related information + """ + def setup(self): + self.addCopySpec("/etc/raddb") + self.addCopySpec("/etc/pam.d/radiusd") + return + + def postproc(self): + self.doRegexSub("/etc/raddb/sql.conf", r"(\s*password\s*=\s*)\S+", r"\1***") + return diff --git a/trunk/src/lib/sos/plugins/rhn.py b/trunk/src/lib/sos/plugins/rhn.py new file mode 100644 index 00000000..049e51bc --- /dev/null +++ b/trunk/src/lib/sos/plugins/rhn.py @@ -0,0 +1,91 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +from sos.plugintools import PluginBase + +class rhn(PluginBase): + """RHN server related information + """ + def checkenabled(self): + # XXX check for the presence of requisite packages + satellite = self.cInfo["policy"].pkgByName("rhns-satellite-tools") + proxy = self.cInfo["policy"].pkgByName("rhns-proxy-tools") + if not satellite and not proxy: + return False + return True + + def setup(self): + # + # First, grab things needed from both Satellite and Proxy systems + # + # TODO: add chain load so we can use existing modules for httpd, &c. + # + + # basic RHN logs and configs + self.addCopySpec("/var/log/rhn*") + self.addCopySpec("/etc/rhn") + self.collectExtOutput("/usr/share/rhn/up2date_client/hardware.py") + + # httpd + self.addCopySpec("/etc/httpd/conf") + self.addCopySpec("/var/log/httpd") + + # RPM manifests + self.collectExtOutput("/bin/rpm -qa --last | sort") + + # monitoring scout logs + self.addCopySpec("/home/nocpulse/var/*.log*") + self.addCopySpec("/home/nocpulse/var/commands/*.log*") + + satellite = self.cInfo["policy"].pkgByName("rhns-satellite-tools") + proxy = self.cInfo["policy"].pkgByName("rhns-proxy-tools") + + # + # Now, go for product-specific data + # + if satellite: + self.setupSatellite(satellite) + + if proxy: + self.setupProxy(proxy) + + def setupSatellite(self, satellite): + self.collectExtOutput("/usr/bin/rhn-schema-version") + self.collectExtOutput("/usr/bin/rhn-charsets") + + # oracle + self.addCopySpec("/etc/tnsnames.ora") + + # tomcat (4.x and newer satellites only) + if not self.cInfo["policy"].pkgNVRA(satellite)[1].startswith("3."): + self.addCopySpec("/etc/tomcat5") + self.addCopySpec("/var/log/tomcat5") + + # jabberd + # - logs to /var/log/messages + self.addCopySpec("/etc/jabberd") + + # SSL build + self.addCopySpec("/root/ssl-build") + + # monitoring logs + self.addCopySpec("/opt/notification/var/*.log*") + self.addCopySpec("/var/tmp/ack_handler.log*") + self.addCopySpec("/var/tmp/enqueue.log*") + + def setupProxy(self, proxy): + # squid + self.addCopySpec("/etc/squid") + self.addCopySpec("/var/log/squid") + diff --git a/trunk/src/lib/sos/plugins/rpm.py b/trunk/src/lib/sos/plugins/rpm.py new file mode 100644 index 00000000..b6fdb699 --- /dev/null +++ b/trunk/src/lib/sos/plugins/rpm.py @@ -0,0 +1,33 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class rpm(sos.plugintools.PluginBase): + """RPM information + """ + optionList = [("rpmq", "Queries for package information via rpm -q", "fast", 1), + ("rpmva", "Runs a verify on all packages", "slow", 1)] + + def setup(self): + self.addCopySpec("/var/log/rpmpkgs") + + if self.isOptionEnabled("rpmq"): + self.collectExtOutput("/bin/rpm -qa --qf \"%{NAME}-%{VERSION}-%{RELEASE}-%{ARCH}\n\"", root_symlink = "installed-rpms") + + if self.isOptionEnabled("rpmva"): + self.eta_weight += 800 # this plugins takes 200x longer (for ETA) + self.collectExtOutput("/bin/rpm -Va", root_symlink = "rpm-Va") + return + diff --git a/trunk/src/lib/sos/plugins/samba.py b/trunk/src/lib/sos/plugins/samba.py new file mode 100644 index 00000000..ec65370e --- /dev/null +++ b/trunk/src/lib/sos/plugins/samba.py @@ -0,0 +1,26 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class samba(sos.plugintools.PluginBase): + """Samba related information + """ + def setup(self): + self.addCopySpec("/etc/samba") + self.addCopySpec("/var/log/samba/*") + self.collectExtOutput("/usr/bin/wbinfo -g") + self.collectExtOutput("/usr/bin/wbinfo -u") + return + diff --git a/trunk/src/lib/sos/plugins/satellite.py b/trunk/src/lib/sos/plugins/satellite.py new file mode 100644 index 00000000..930d11b0 --- /dev/null +++ b/trunk/src/lib/sos/plugins/satellite.py @@ -0,0 +1,53 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class satellite(sos.plugintools.PluginBase): + """RHN Satellite related information + """ + + def defaultenabled(self): + return False + + def setup(self): + self.addCopySpec("/etc/httpd/conf") + self.addCopySpec("/etc/rhn") + self.addCopySpec("/etc/sysconfig/rhn") + self.addCopySpec("/etc/tnsnames.ora") + self.addCopySpec("/var/log/httpd") # httpd-logs + self.addCopySpec("/var/log/rhn*") # rhn-logs + self.addCopySpec("/var/log/rhn/rhn-database-installation.log") + self.addCopySpec("/etc/jabberd") + + # tomcat for satellite 400+ + self.addCopySpec("/etc/tomcat5") + self.addCopySpec("/var/log/tomcat5") + + # all these used to go in $DIR/mon-logs + self.addCopySpec("/opt/notification/var/*.log*") + self.addCopySpec("/var/tmp/ack_handler.log*") + self.addCopySpec("/var/tmp/enqueue.log*") + + self.addCopySpec("/home/nocpulse/var/*.log*") + self.addCopySpec("/home/nocpulse/var/commands/*.log*") + self.addCopySpec("/var/tmp/ack_handler.log*") + self.addCopySpec("/var/tmp/enqueue.log*") + + self.addCopySpec("/root/ssl-build") + self.addCopySpec("rpm -qa --last") # $DIR/rpm-manifest + self.addCopySpec("/usr/bin/rhn-schema-version") # $DIR/database-schema-version + self.addCopySpec("/usr/bin/rhn-charsets") # $DIR/database-character-sets + + return diff --git a/trunk/src/lib/sos/plugins/selinux.py b/trunk/src/lib/sos/plugins/selinux.py new file mode 100644 index 00000000..897c3991 --- /dev/null +++ b/trunk/src/lib/sos/plugins/selinux.py @@ -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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools +import commands + +class selinux(sos.plugintools.PluginBase): + """selinux related information + """ + def setup(self): + self.addCopySpec("/etc/selinux/*") + self.collectExtOutput("/usr/bin/selinuxconfig") + self.collectExtOutput("/usr/sbin/sestatus", root_symlink = "sestatus") + self.collectExtOutput("/bin/rpm -q -V selinux-policy-targeted") + self.collectExtOutput("/bin/rpm -q -V selinux-policy-strict") + return + + def checkenabled(self): + # is selinux enabled ? + try: + if commands.getoutput("/usr/sbin/sestatus").split(":")[1].strip() == "disabled": + return False + except: + pass + return True diff --git a/trunk/src/lib/sos/plugins/sendmail.py b/trunk/src/lib/sos/plugins/sendmail.py new file mode 100644 index 00000000..d95b5d7b --- /dev/null +++ b/trunk/src/lib/sos/plugins/sendmail.py @@ -0,0 +1,27 @@ +## Copyright (C) 2007 Red Hat, Inc., Eugene Teo <eteo@redhat.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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class sendmail(sos.plugintools.PluginBase): + """sendmail information + """ + def setup(self): + self.addCopySpec("/etc/mail/*") + self.addCopySpec("/var/log/maillog") + self.collectExtOutput("/sbin/chkconfig --list sendmail") + return + diff --git a/trunk/src/lib/sos/plugins/squid.py b/trunk/src/lib/sos/plugins/squid.py new file mode 100644 index 00000000..4544ef9a --- /dev/null +++ b/trunk/src/lib/sos/plugins/squid.py @@ -0,0 +1,23 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class squid(sos.plugintools.PluginBase): + """squid related information + """ + def setup(self): + self.addCopySpec("/etc/squid/squid.conf") + return + diff --git a/trunk/src/lib/sos/plugins/ssh.py b/trunk/src/lib/sos/plugins/ssh.py new file mode 100644 index 00000000..92a4c9e5 --- /dev/null +++ b/trunk/src/lib/sos/plugins/ssh.py @@ -0,0 +1,27 @@ +## Copyright (C) 2007 Red Hat, Inc., Eugene Teo <eteo@redhat.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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class ssh(sos.plugintools.PluginBase): + """ssh-related information + """ + def setup(self): + self.addCopySpec("/etc/ssh/ssh_config") + self.addCopySpec("/etc/ssh/sshd_config") + self.collectExtOutput("/sbin/chkconfig --list sshd") + return + diff --git a/trunk/src/lib/sos/plugins/startup.py b/trunk/src/lib/sos/plugins/startup.py new file mode 100644 index 00000000..02361393 --- /dev/null +++ b/trunk/src/lib/sos/plugins/startup.py @@ -0,0 +1,25 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class startup(sos.plugintools.PluginBase): + """startup information + """ + def setup(self): + self.addCopySpec("/etc/rc.d") + + self.collectExtOutput("/sbin/chkconfig --list", root_symlink = "chkconfig") + return + diff --git a/trunk/src/lib/sos/plugins/system.py b/trunk/src/lib/sos/plugins/system.py new file mode 100644 index 00000000..19d7859e --- /dev/null +++ b/trunk/src/lib/sos/plugins/system.py @@ -0,0 +1,31 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class system(sos.plugintools.PluginBase): + """core system related information + """ + def setup(self): + self.addCopySpec("/proc/sys") + self.addCopySpec("/etc/sysctl.conf") + self.addCopySpec("/etc/cron*") + self.addCopySpec("/etc/syslog.conf") + self.addCopySpec("/etc/ntp.conf") + self.addCopySpec("/etc/ntp/step-tickers") + self.addCopySpec("/etc/ntp/ntpservers") + self.addCopySpec("/etc/auto.*") + + return + diff --git a/trunk/src/lib/sos/plugins/systemtap.py b/trunk/src/lib/sos/plugins/systemtap.py new file mode 100644 index 00000000..b99ce0cf --- /dev/null +++ b/trunk/src/lib/sos/plugins/systemtap.py @@ -0,0 +1,29 @@ +## Copyright (C) 2007 Red Hat, Inc., Eugene Teo <eteo@redhat.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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class systemtap(sos.plugintools.PluginBase): + """SystemTap pre-requisites information + """ + def setup(self): + # requires systemtap, systemtap-runtime, kernel-devel, + # kernel-debuginfo, kernel-debuginfo-common + self.collectExtOutput("/bin/rpm -qa | /bin/egrep -e kernel.*`uname -r` -e systemtap -e elfutils | sort") + self.collectExtOutput("/usr/bin/stap -V 2") + self.collectExtOutput("/bin/uname -r") + return + diff --git a/trunk/src/lib/sos/plugins/x11.py b/trunk/src/lib/sos/plugins/x11.py new file mode 100644 index 00000000..9b7b7ac9 --- /dev/null +++ b/trunk/src/lib/sos/plugins/x11.py @@ -0,0 +1,26 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class x11(sos.plugintools.PluginBase): + """X related information + """ + def setup(self): + self.addCopySpec("/etc/X11") + self.addCopySpec("/var/log/Xorg.*.log") + self.addCopySpec("/var/log/XFree86.*.log") + self.collectExtOutput("/bin/dmesg | grep -e 'agpgart.'") + return + diff --git a/trunk/src/lib/sos/plugins/xen.py b/trunk/src/lib/sos/plugins/xen.py new file mode 100644 index 00000000..d6daec63 --- /dev/null +++ b/trunk/src/lib/sos/plugins/xen.py @@ -0,0 +1,82 @@ +### 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools +import os, commands +from stat import * + +class xen(sos.plugintools.PluginBase): + """Xen related information + """ + def determineXenHost(self): + if os.access("/proc/acpi/dsdt", os.R_OK): + (status, output) = commands.getstatusoutput("/usr/bin/strings /proc/acpi/dsdt | grep -q int-xen") + if status == 0: + return "hvm" + + if os.access("/proc/xen/capabilities", os.R_OK): + (status, output) = commands.getstatusoutput("grep -q control_d /proc/xen/capabilities") + if status == 0: + return "dom0" + else: + return "domU" + return "baremetal" + + def checkenabled(self): + if self.determineXenHost() == "baremetal": + return False + return True + + def domCollectProc(self): + self.addCopySpec("/proc/xen/balloon") + self.addCopySpec("/proc/xen/capabilities") + self.addCopySpec("/proc/xen/xsd_kva") + self.addCopySpec("/proc/xen/xsd_port") + # determine if CPU has PAE support + self.collectExtOutput("/bin/grep pae /proc/cpuinfo") + # determine if CPU has Intel-VT or AMD-V support + self.collectExtOutput("/bin/egrep -e 'vmx|svm' /proc/cpuinfo") + + def setup(self): + host_type = self.determineXenHost() + if host_type == "domU": + # we should collect /proc/xen and /sys/hypervisor + self.domCollectProc() + # determine if hardware virtualization support is enabled + # in BIOS: /sys/hypervisor/properties/capabilities + self.addCopySpec("/sys/hypervisor") + elif host_type == "hvm": + # what do we collect here??? + pass + elif host_type == "dom0": + # default of dom0, collect lots of system information + self.addCopySpec("/var/log/xen") + self.addCopySpec("/etc/xen") + self.collectExtOutput("/usr/bin/xenstore-ls") + self.collectExtOutput("/usr/sbin/xm dmesg") + self.collectExtOutput("/usr/sbin/xm info") + self.collectExtOutput("/usr/sbin/brctl show") + self.domCollectProc() + self.addCopySpec("/sys/hypervisor") + # FIXME: we *might* want to collect things in /sys/bus/xen*, + # /sys/class/xen*, /sys/devices/xen*, /sys/modules/blk*, + # /sys/modules/net*, but I've never heard of them actually being + # useful, so I'll leave it out for now + else: + # for bare-metal, we don't have to do anything special + return + + self.addCustomText("Xen hostType: "+host_type) + return + diff --git a/trunk/src/lib/sos/plugins/xinetd.py b/trunk/src/lib/sos/plugins/xinetd.py new file mode 100644 index 00000000..715c831f --- /dev/null +++ b/trunk/src/lib/sos/plugins/xinetd.py @@ -0,0 +1,27 @@ +## Copyright (C) 2007 Red Hat, Inc., Eugene Teo <eteo@redhat.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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class xinetd(sos.plugintools.PluginBase): + """xinetd information + """ + def setup(self): + self.addCopySpec("/etc/xinetd.conf") + self.addCopySpec("/etc/xinetd.d/*") + self.collectExtOutput("/sbin/chkconfig --list xinetd") + return + diff --git a/trunk/src/lib/sos/plugins/yum.py b/trunk/src/lib/sos/plugins/yum.py new file mode 100644 index 00000000..89102c38 --- /dev/null +++ b/trunk/src/lib/sos/plugins/yum.py @@ -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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import sos.plugintools + +class yum(sos.plugintools.PluginBase): + """yum information + """ + + def defaultenabled(self): + # enable with -e or -o + return False + + def setup(self): + # Pull all yum related information + self.addCopySpec("/etc/yum") + self.addCopySpec("/etc/yum.repos.d") + self.addCopySpec("/etc/yum.conf") + self.addCopySpec("/var/log/yum.log") + + # Get a list of channels the machine is subscribed to. + self.collectExtOutput("/bin/echo \"repo list\" | /usr/bin/yum shell") + # List various information about available packages + self.collectExtOutput("/usr/bin/yum list") + + return diff --git a/trunk/src/lib/sos/plugintools.py b/trunk/src/lib/sos/plugintools.py new file mode 100644 index 00000000..238036ce --- /dev/null +++ b/trunk/src/lib/sos/plugintools.py @@ -0,0 +1,568 @@ +## plugintools.py +## This exports methods available for use by plugins for sos + +## Copyright (C) 2006 Steve Conklin <sconklin@redhat.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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +# pylint: disable-msg = R0902 +# pylint: disable-msg = R0904 +# pylint: disable-msg = W0702 +# pylint: disable-msg = W0703 +# pylint: disable-msg = R0201 +# pylint: disable-msg = W0611 +# pylint: disable-msg = W0613 + +""" +This is the base class for sosreport plugins +""" +from sos.helpers import * +from threading import Thread, activeCount +import os, os.path, sys, string, itertools, glob, re, traceback +import logging +from stat import * +from time import time + +class PluginBase: + """ + Base class for plugins + """ + def __init__(self, pluginname, commons): + # pylint: disable-msg = E0203 + try: + len(self.optionList) + except: + self.optionList = [] + # pylint: enable-msg = E0203 + self.copiedFiles = [] + self.copiedDirs = [] + self.executedCommands = [] + self.diagnose_msgs = [] + self.alerts = [] + self.customText = "" + self.optNames = [] + self.optParms = [] + self.piName = pluginname + self.cInfo = commons + self.forbiddenPaths = [] + self.copyPaths = [] + self.collectProgs = [] + self.thread = None + self.pid = None + self.eta_weight = 1 + self.time_start = None + self.time_stop = None + + self.soslog = logging.getLogger('sos') + + # get the option list into a dictionary + for opt in self.optionList: + self.optNames.append(opt[0]) + self.optParms.append({'desc':opt[1], 'speed':opt[2], 'enabled':opt[3]}) + + # Method for applying regexp substitutions + def doRegexSub(self, srcpath, regexp, subst): + '''Apply a regexp substitution to a file archived by sosreport. + ''' + if len(self.copiedFiles): + for afile in self.copiedFiles: + if afile['srcpath'] == srcpath: + abspath = os.path.join(self.cInfo['dstroot'], srcpath.lstrip(os.path.sep)) + try: + fp = open(abspath, 'r') + tmpout, occurs = re.subn( regexp, subst, fp.read() ) + fp.close() + if occurs > 0: + fp = open(abspath,'w') + fp.write(tmpout) + fp.close() + return occurs + except SystemExit: + raise SystemExit + except KeyboardInterrupt: + raise KeyboardInterrupt + except Exception, e: + self.soslog.log(logging.VERBOSE, "Problem at path %s (%s)" % (abspath,e)) + break + return False + + # Methods for copying files and shelling out + def doCopyFileOrDir(self, srcpath): + # pylint: disable-msg = R0912 + # pylint: disable-msg = R0915 + ''' Copy file or directory to the destination tree. If a directory, then everything + below it is recursively copied. A list of copied files are saved for use later + in preparing a report + ''' + copyProhibited = 0 + for path in self.forbiddenPaths: + if ( srcpath.count(path) > 0 ): + copyProhibited = 1 + + if copyProhibited: + return '' + + if os.path.islink(srcpath): + # This is a symlink - We need to also copy the file that it points to + # file and dir symlinks ar ehandled the same + link = os.readlink(srcpath) + if os.path.isabs(link): + # the link was an absolute path, and will not point to the new + # tree. We must adjust it. + + # What's the name of the symlink on the dest tree? + dstslname = os.path.join(self.cInfo['dstroot'], srcpath.lstrip(os.path.sep)) + + # make sure the dst dir exists + if not (os.path.exists(os.path.dirname(dstslname)) and os.path.isdir(os.path.dirname(dstslname))): + # create the directory + os.makedirs(os.path.dirname(dstslname)) + + dstsldir = os.path.join(self.cInfo['dstroot'], link.lstrip(os.path.sep)) + # Create the symlink on the dst tree + rpth = sosRelPath(os.path.dirname(dstslname), dstsldir) + os.symlink(rpth, dstslname) + else: + # no adjustment, symlink is the relative path + dstslname = link + + if os.path.isdir(srcpath): + for afile in os.listdir(srcpath): + if afile == '.' or afile == '..': + pass + else: + try: + abspath = self.doCopyFileOrDir(srcpath+'/'+afile) + except SystemExit: + raise SystemExit + except KeyboardInterrupt: + raise KeyboardInterrupt + except Exception, e: + self.soslog.warning(traceback.format_exc()) + + # if on forbidden list, abspath is null + if not abspath == '': + dstslname = sosRelPath(self.cInfo['rptdir'], abspath) + self.copiedDirs.append({'srcpath':srcpath, 'dstpath':dstslname, 'symlink':"yes", 'pointsto':link}) + else: + try: + dstslname, abspath = self.__copyFile(srcpath) + self.copiedFiles.append({'srcpath':srcpath, 'dstpath':dstslname, 'symlink':"yes", 'pointsto':link}) + self.cInfo['xmlreport'].add_file(srcpath,os.stat(srcpath)) + except SystemExit: + raise SystemExit + except KeyboardInterrupt: + raise KeyboardInterrupt + except Exception, e: + self.soslog.log(logging.VERBOSE, "Problem at path %s (%s)" % (srcpath, e)) + + return abspath + + else: + if not os.path.exists(srcpath): + self.soslog.debug("File or directory %s does not exist\n" % srcpath) + elif os.path.isdir(srcpath): + for afile in os.listdir(srcpath): + if afile == '.' or afile == '..': + pass + else: + self.doCopyFileOrDir(srcpath+'/'+afile) + else: + # This is not a directory or a symlink + tdstpath, abspath = self.__copyFile(srcpath) + self.copiedFiles.append({'srcpath':srcpath, 'dstpath':tdstpath, 'symlink':"no"}) # save in our list + return abspath + + def __copyFile(self, src): + """ call cp to copy a file, collect return status and output. Returns the + destination file name. + """ + try: + # pylint: disable-msg = W0612 + status, shout, runtime = sosGetCommandOutput("/bin/cp --parents -P --preserve=mode,ownership,timestamps,links " + src +" " + self.cInfo['dstroot']) + if status: + self.soslog.debug(shout) + abspath = os.path.join(self.cInfo['dstroot'], src.lstrip(os.path.sep)) + relpath = sosRelPath(self.cInfo['rptdir'], abspath) + return relpath, abspath + except SystemExit: + raise SystemExit + except KeyboardInterrupt: + raise KeyboardInterrupt + except Exception,e: + self.soslog.warning("Problem copying file %s (%s)" % (src, e)) + + def addForbiddenPath(self, forbiddenPath): + """Specify a path to not copy, even if it's part of a copyPaths[] entry. + Note: do NOT use globs here. + """ + self.forbiddenPaths.append(forbiddenPath) + + def getAllOptions(self): + """ + return a list of all options selected + """ + return (self.optNames, self.optParms) + + def setOption(self, optionname, enable): + ''' enable or disable the named option. + ''' + for name, parms in zip(self.optNames, self.optParms): + if name == optionname: + parms['enabled'] = enable + + def isOptionEnabled(self, optionname): + ''' see whether the named option is enabled. + ''' + for name, parms in zip(self.optNames, self.optParms): + if name == optionname: + return parms['enabled'] + # nonexistent options aren't enabled. + return 0 + + def addCopySpecLimit(self,fname,sizelimit = None): + """Add a file specification (with limits) + """ + files = glob.glob(fname) + files.sort() + cursize = 0 + for flog in files: + cursize += os.stat(flog)[ST_SIZE] + if sizelimit and (cursize / 1024 / 1024) > sizelimit: + break + self.addCopySpec(flog) + + def addCopySpec(self, copyspec): + """ Add a file specification (can be file, dir,or shell glob) to be + copied into the sosreport by this module + """ + # Glob case handling is such that a valid non-glob is a reduced glob + for filespec in glob.glob(copyspec): + self.copyPaths.append(filespec) + + def copyFileGlob(self, srcglob): + """ Deprecated - please modify modules to use addCopySpec() + """ + sys.stderr.write("Warning: thecopyFileGlob() function has been deprecated. Please") + sys.stderr.write("use addCopySpec() instead. Calling addCopySpec() now.") + self.addCopySpec(srcglob) + + def copyFileOrDir(self, srcpath): + """ Deprecated - please modify modules to use addCopySpec() + """ + sys.stderr.write("Warning: the copyFileOrDir() function has been deprecated. Please\n") + sys.stderr.write("use addCopySpec() instead. Calling addCopySpec() now.\n") + raise ValueError + #self.addCopySpec(srcpath) + + def runExeInd(self, exe): + """ Deprecated - use callExtProg() + """ + sys.stderr.write("Warning: the runExeInd() function has been deprecated. Please use\n") + sys.stderr.write("the callExtProg() function. This should only be called\n") + sys.stderr.write("if collect() is overridden.") + pass + + def callExtProg(self, prog): + """ Execute a command independantly of the output gathering part of + sosreport + """ + # Log if binary is not runnable or does not exist + if not os.access(prog.split()[0], os.X_OK): + self.soslog.log(logging.VERBOSE, "binary '%s' does not exist or is not runnable" % prog.split()[0]) + + # pylint: disable-msg = W0612 + status, shout, runtime = sosGetCommandOutput(prog) + return status + + def runExe(self, exe): + """ Deprecated - use collectExtOutput() + """ + sys.stderr.write("Warning: the runExe() function has been deprecated. Please use\n") + sys.stderr.write("the collectExtOutput() function.\n") + pass + + def collectExtOutput(self, exe, suggest_filename = None, root_symlink = None): + """ + Run a program and collect the output + """ + self.collectProgs.append( (exe,suggest_filename,root_symlink) ) + + def makeCommandFilename(self, exe): + """ The internal function to build up a filename based on a command """ + + mangledname = re.sub(r"[^\w\-\.\/]+", "_", exe) + mangledname = re.sub(r"/", ".", mangledname).strip(" ._-")[0:64] + + outfn = self.cInfo['cmddir'] + "/" + self.piName + "/" + mangledname + + # check for collisions + inc = 0 + if os.path.exists(outfn): + inc = 2 + while True: + newfn = outfn + "_" + inc + if not os.path.exists(newfn): + break + inc +=1 + + return outfn + + def collectOutputNow(self, exe, suggest_filename = None, root_symlink = False): + """ Execute a command and save the output to a file for inclusion in + the report + """ + # First check to make sure the binary exists and is runnable. + if not os.access(exe.split()[0], os.X_OK): + self.soslog.log(logging.VERBOSE, "binary '%s' does not exist or is not runnable, trying anyways" % exe.split()[0]) + + # pylint: disable-msg = W0612 + status, shout, runtime = sosGetCommandOutput(exe) + + if suggest_filename: + outfn = self.makeCommandFilename(suggest_filename) + else: + outfn = self.makeCommandFilename(exe) + + if not os.path.isdir(os.path.dirname(outfn)): + os.mkdir(os.path.dirname(outfn)) + + outfd = open(outfn, "w") + if status == 127: outfd.write("# the command returned exit status 127, this normally means that the command was not found.\n\n") + if len(shout): outfd.write(shout+"\n") + outfd.close() + + if root_symlink: + curdir = os.getcwd() + os.chdir(self.cInfo['dstroot']) + os.symlink(outfn[len(self.cInfo['dstroot'])+1:], root_symlink.strip("/.")) + os.chdir(curdir) + + outfn = outfn[len(self.cInfo['cmddir'])+1:] + + # sosStatus(status) + # save info for later + self.executedCommands.append({'exe': exe, 'file':outfn}) # save in our list + self.cInfo['xmlreport'].add_command(cmdline=exe,exitcode=status,f_stdout=outfn,runtime=runtime) + return outfn + + def writeTextToCommand(self, exe, text): + """ A function that allows you to write a random text string to the + command output location referenced by exe; this is useful if you want + to conditionally collect information, but still want the output file + to exist so as not to confuse readers """ + + outfn = self.makeCommandFilename(exe) + + if not os.path.isdir(os.path.dirname(outfn)): + os.mkdir(os.path.dirname(outfn)) + + outfd = open(outfn, "w") + outfd.write(text) + outfd.close() + + self.executedCommands.append({'exe': exe, 'file': outfn}) # save in our list + return outfn + + # For adding warning messages regarding configuration sanity + def addDiagnose(self, alertstring): + """ Add a configuration sanity warning for this plugin. These + will be displayed on-screen before collection and in the report as well. + """ + self.diagnose_msgs.append(alertstring) + return + + # For adding output + def addAlert(self, alertstring): + """ Add an alert to the collection of alerts for this plugin. These + will be displayed in the report + """ + self.alerts.append(alertstring) + return + + + def addCustomText(self, text): + """ Append text to the custom text that is included in the report. This + is freeform and can include html. + """ + self.customText = self.customText + text + return + + def doCollect(self): + """ This function has been replaced with copyStuff(threaded = True). Please change your + module calls. Calling setup() now. + """ + return self.copyStuff(threaded = True) + + def isRunning(self): + """ + if threaded, is thread running ? + """ + if self.thread: return self.thread.isAlive() + return None + + def wait(self,timeout=None): + """ + wait for a thread to complete - only called for threaded execution + """ + self.thread.join(timeout) + return self.thread.isAlive() + + def copyStuff(self, threaded = False, semaphore = None): + """ + Collect the data for a plugin + """ + if threaded and self.thread == None: + self.thread = Thread(target=self.copyStuff, name=self.piName+'-thread', args = [True, semaphore] ) + self.thread.start() + return self.thread + + if semaphore: semaphore.acquire() + + self.soslog.log(logging.VERBOSE, "starting threaded plugin %s" % self.piName) + + self.time_start = time() + self.time_stop = None + + for path in self.copyPaths: + self.soslog.debug("copying pathspec %s" % path) + try: + self.doCopyFileOrDir(path) + except SystemExit: + raise SystemExit + except KeyboardInterrupt: + raise KeyboardInterrupt + except Exception, e: + self.soslog.log(logging.VERBOSE, "error copying from pathspec %s (%s), traceback follows:" % (path,e)) + self.soslog.log(logging.VERBOSE, traceback.format_exc()) + for (prog,suggest_filename,root_symlink) in self.collectProgs: + self.soslog.debug("collecting output of '%s'" % prog) + try: + self.collectOutputNow(prog, suggest_filename, root_symlink) + except SystemExit: + raise SystemExit + except KeyboardInterrupt: + raise KeyboardInterrupt + except: + self.soslog.log(logging.VERBOSE, "error collection output of '%s', traceback follows:" % prog) + self.soslog.log(logging.VERBOSE, traceback.format_exc()) + + self.time_stop = time() + + if semaphore: semaphore.release() + self.soslog.log(logging.VERBOSE, "plugin %s returning" % self.piName) + + def get_description(self): + """ This function will return the description for the plugin""" + try: + return self.__doc__.strip() + except: + return "<no description available>" + + def checkenabled(self): + """ This function can be overidden to let the plugin decide whether + it should run or not. + """ + return True + + def defaultenabled(self): + """This devices whether a plugin should be automatically loaded or + only if manually specified in the command line.""" + return True + + def collect(self): + """ This function has been replaced with setup(). Please change your + module calls. Calling setup() now. + """ + self.setup() + + def diagnose(self): + """This class must be overridden to check the sanity of the system's + configuration before the collection begins. + """ + pass + + def setup(self): + """This class must be overridden to add the copyPaths, forbiddenPaths, + and external programs to be collected at a minimum. + """ + pass + + def analyze(self): + """ + perform any analysis. To be replaced by a plugin if desired + """ + pass + + def postproc(self): + """ + perform any postprocessing. To be replaced by a plugin if desired + """ + pass + + def report(self): + """ Present all information that was gathered in an html file that allows browsing + the results. + """ + # make this prettier + html = '<hr/><a name="%s"></a>\n' % self.piName + + # Intro + html = html + "<h2> Plugin <em>" + self.piName + "</em></h2>\n" + + # Files + if len(self.copiedFiles): + html = html + "<p>Files copied:<br><ul>\n" + for afile in self.copiedFiles: + html = html + '<li><a href="%s">%s</a>' % (afile['dstpath'], afile['srcpath']) + if (afile['symlink'] == "yes"): + html = html + " (symlink to %s)" % afile['pointsto'] + html = html + '</li>\n' + html = html + "</ul></p>\n" + + # Dirs + if len(self.copiedDirs): + html = html + "<p>Directories Copied:<br><ul>\n" + for adir in self.copiedDirs: + html = html + '<li><a href="%s">%s</a>\n' % (adir['dstpath'], adir['srcpath']) + if (adir['symlink'] == "yes"): + html = html + " (symlink to %s)" % adir['pointsto'] + html = html + '</li>\n' + html = html + "</ul></p>\n" + + # Command Output + if len(self.executedCommands): + html = html + "<p>Commands Executed:<br><ul>\n" + # convert file name to relative path from our root + for cmd in self.executedCommands: + cmdOutRelPath = sosRelPath(self.cInfo['rptdir'], cmd['file']) + html = html + '<li><a href="%s">%s</a></li>\n' % (cmdOutRelPath, cmd['exe']) + html = html + "</ul></p>\n" + + # Alerts + if len(self.alerts): + html = html + "<p>Alerts:<br><ul>\n" + for alert in self.alerts: + html = html + '<li>%s</li>\n' % alert + html = html + "</ul></p>\n" + + # Custom Text + if (self.customText != ""): + html = html + "<p>Additional Information:<br>\n" + html = html + self.customText + "</p>\n" + + return html + + diff --git a/trunk/src/lib/sos/policyredhat.py b/trunk/src/lib/sos/policyredhat.py new file mode 100755 index 00000000..d4a4074d --- /dev/null +++ b/trunk/src/lib/sos/policyredhat.py @@ -0,0 +1,151 @@ +## policy-redhat.py +## Implement policies required for the sos system support tool + +## Copyright (C) Steve Conklin <sconklin@redhat.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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import os +import commands +import sys +import string +from tempfile import gettempdir +from sos.helpers import * +import random + +SOME_PATH = "/tmp/SomePath" + +#class SosError(Exception): +# def __init__(self, code, message): +# self.code = code +# self.message = message +# +# def __str__(self): +# return 'Sos Error %s: %s' % (self.code, self.message) + + +class SosPolicy: + "This class implements various policies for sos" + def __init__(self): + #print "Policy init" + return + + def setCommons(self, commons): + self.cInfo = commons + return + + def validatePlugin(self, pluginpath): + "Validates the plugin as being acceptable to run" + # return value + # TODO implement this + #print "validating %s" % pluginpath + return True + + def allPkgsByName(self, name): + # FIXME: we're relying on rpm to sort the output list + cmd = "/bin/rpm --qf '%%{N}-%%{V}-%%{R}-%%{ARCH}\n' -q %s" % (name,) + pkgs = os.popen(cmd).readlines() + return [pkg[:-1] for pkg in pkgs if pkg.startswith(name)] + + def pkgByName(self, name): + # TODO: do a full NEVRA compare and return newest version, best arch + try: + # lame attempt at locating newest + pkg = self.allPkgsByName(name)[-1] + except IndexError: + pkg = None + + return pkg + + def pkgDictByName(self, name): + pkgName = self.pkgByName(name) + if pkgName and len(pkgName) > len(name): + return pkgName[len(name)+1:].split("-") + else: + return None + + def runlevelByService(self, name): + ret = [] + try: + for tabs in commands.getoutput("/sbin/chkconfig --list %s" % name).split(): + (runlevel, onoff) = tabs.split(":") + if onoff == "on": + ret.append(int(runlevel)) + except: + pass + return ret + + def runlevelDefault(self): + # FIXME: get this from /etc/inittab + return 3 + + def pkgNVRA(self, pkg): + fields = pkg.split("-") + version, release, arch = fields[-3:] + name = "-".join(fields[:-3]) + return (name, version, release, arch) + + def packageResults(self): + localname = commands.getoutput("/bin/uname -n").split(".")[0] + + try: + name = raw_input("Please enter your first initial and last name [%s]: " % localname) + if len(name) == 0: name = localname + + ticketNumber = raw_input("Please enter the case number that you are generating this report for: ") + except KeyboardInterrupt: + print _("<interrupted>") + print _("Temporary files have been stored in ") % self.cInfo['dstroot'] + return + + if len(ticketNumber): + namestr = name + "." + ticketNumber + else: + namestr = name + + ourtempdir = gettempdir() + tarballName = os.path.join(ourtempdir, "sosreport-" + namestr + ".tar.bz2") + + namestr = namestr + "-" + str(random.randint(1, 999999)) + + aliasdir = os.path.join(ourtempdir, namestr) + + tarcmd = "/bin/tar -jcf %s %s" % (tarballName, namestr) + + print + print "Creating compressed tar archive..." + if not os.access(string.split(tarcmd)[0], os.X_OK): + print "Unable to create tarball" + return + + # FIXME: gotta be a better way... + os.system("/bin/mv %s %s" % (self.cInfo['dstroot'], aliasdir)) + curwd = os.getcwd() + os.chdir(ourtempdir) + oldmask = os.umask(077) + # pylint: disable-msg = W0612 + status, shout, runtime = sosGetCommandOutput(tarcmd) + os.umask(oldmask) + os.chdir(curwd) + # FIXME: use python internal command + os.system("/bin/mv %s %s" % (aliasdir, self.cInfo['dstroot'])) + + sys.stdout.write("\n") + print "Your sosreport has been generated and saved in %s" % tarballName + print "Please send this file to your support representative." + sys.stdout.write("\n") + + return + diff --git a/trunk/src/locale/en/LC_MESSAGES/sos.po b/trunk/src/locale/en/LC_MESSAGES/sos.po new file mode 100644 index 00000000..c103a495 --- /dev/null +++ b/trunk/src/locale/en/LC_MESSAGES/sos.po @@ -0,0 +1,99 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2007-07-14 11:57\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Generated-By: pygettext.py 1.5\n" + + +#: sosreport:399 +msgid "sosreport (version %s)" +msgstr "" + +#: sosreport:417 +msgid "plugin %s does not validate, skipping" +msgstr "" + +#: sosreport:421 +msgid "plug %s skipped (noplugins)" +msgstr "" + +#: sosreport:425 +msgid "plugin %s is inactive (use -e or -o to enable)." +msgstr "" + +#: sosreport:433 +msgid "plugin %s not specified in --onlyplugin list" +msgstr "" + +#: sosreport:438 +msgid "Plugin %s does not install, skipping" +msgstr "" + +#: sosreport:441 +msgid "plugin load failed for %s" +msgstr "" + +#: sosreport:447 +msgid "processing options from plugin: %s" +msgstr "" + +#: sosreport:454 +msgid "no valid plugins found" +msgstr "" + +#: sosreport:457 +msgid "The following plugins are currently enabled:" +msgstr "" + +#: sosreport:463 +msgid "The following plugin options are available:" +msgstr "" + +#: sosreport:469 +msgid "The following plugins are currently disabled:" +msgstr "" + +#: sosreport:480 +msgid "sosreport requires root permissions to run." +msgstr "" + +#: sosreport:487 +msgid "no valid plugins were enabled" +msgstr "" + +#: sosreport:491 +msgid "" +"This utility will collect some detailed information about the\n" +"hardware and setup of your Red Hat Enterprise Linux system.\n" +"This information will be used to diagnose problems with your \n" +"system and will be considered confidential information.\n" +"Red Hat will use this information for diagnostic purposes ONLY.\n" +"\n" +"This process may take a while to complete.\n" +"No changes will be made to your system.\n" +"\n" +"Press ENTER to continue, or CTRL-C to quit.\n" +msgstr "" + +#: sosreport:529 +msgid "Exiting." +msgstr "" + +#: sosreport:676 +msgid "Collected information is in " +msgstr "" + +#: sosreport:677 +msgid "Your html report is in " +msgstr "" + diff --git a/trunk/src/locale/it/LC_MESSAGES/sos.po b/trunk/src/locale/it/LC_MESSAGES/sos.po new file mode 100644 index 00000000..0b5e2b32 --- /dev/null +++ b/trunk/src/locale/it/LC_MESSAGES/sos.po @@ -0,0 +1,99 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2007-07-14 12:17\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Generated-By: pygettext.py 1.5\n" + + +#: sosreport:399 +msgid "sosreport (version %s)" +msgstr "sosreport (versione %s)" + +#: sosreport:417 +msgid "plugin %s does not validate, skipping" +msgstr "" + +#: sosreport:421 +msgid "plug %s skipped (noplugins)" +msgstr "" + +#: sosreport:425 +msgid "plugin %s is inactive (use -e or -o to enable)." +msgstr "" + +#: sosreport:433 +msgid "plugin %s not specified in --onlyplugin list" +msgstr "" + +#: sosreport:438 +msgid "Plugin %s does not install, skipping" +msgstr "" + +#: sosreport:441 +msgid "plugin load failed for %s" +msgstr "" + +#: sosreport:447 +msgid "processing options from plugin: %s" +msgstr "" + +#: sosreport:454 +msgid "no valid plugins found" +msgstr "" + +#: sosreport:457 +msgid "The following plugins are currently enabled:" +msgstr "" + +#: sosreport:463 +msgid "The following plugin options are available:" +msgstr "" + +#: sosreport:469 +msgid "The following plugins are currently disabled:" +msgstr "" + +#: sosreport:480 +msgid "sosreport requires root permissions to run." +msgstr "" + +#: sosreport:487 +msgid "no valid plugins were enabled" +msgstr "" + +#: sosreport:491 +msgid "" +"This utility will collect some detailed information about the\n" +"hardware and setup of your Red Hat Enterprise Linux system.\n" +"This information will be used to diagnose problems with your \n" +"system and will be considered confidential information.\n" +"Red Hat will use this information for diagnostic purposes ONLY.\n" +"\n" +"This process may take a while to complete.\n" +"No changes will be made to your system.\n" +"\n" +"Press ENTER to continue, or CTRL-C to quit.\n" +msgstr "" + +#: sosreport:529 +msgid "Exiting." +msgstr "" + +#: sosreport:676 +msgid "Collected information is in " +msgstr "" + +#: sosreport:677 +msgid "Your html report is in " +msgstr "" + diff --git a/trunk/src/pylintrc b/trunk/src/pylintrc new file mode 100644 index 00000000..934f5255 --- /dev/null +++ b/trunk/src/pylintrc @@ -0,0 +1,354 @@ +# lint Python modules using external checkers. +# +# This is the main checker controling the other ones and the reports +# generation. It is itself both a raw checker and an astng checker in order +# to: +# * handle message activation / deactivation at the module level +# * handle some basic but necessary stats'data (number of classes, methods...) +# +# This checker also defines the following reports: +# * R0001: Total errors / warnings +# * R0002: % errors / warnings by module +# * R0003: Messages +# * R0004: Global evaluation +[MASTER] + +# Profiled execution. +profile=no + +# Add <file or directory> to the black list. It should be a base name, not a +# path. You may set this option multiple times. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# Set the cache size for astng objects. +cache-size=500 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[REPORTS] + +# Tells wether to display a full report or only the messages +reports=yes + +# Use HTML as output format instead of text +html=no + +# Use a parseable text output format, so your favorite text editor will be able +# to jump to the line corresponding to a message. +parseable=yes + +# Colorizes text output using ansi escape codes +color=no + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Python expression which should return a note less than 10 (10 is the highest +# note).You have access to the variables errors warning, statement which +# respectivly contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (R0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (R0004). +comment=no + +# Include message's id in output +include-ids=yes + + +# checks for +# * unused variables / imports +# * undefined variables +# * redefinition of variable from builtins or from an outer scope +# * use of variable before assigment +# +[VARIABLES] + +# Enable / disable this checker +enable-variables=yes + +# Tells wether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching names used for dummy variables (i.e. not used). +dummy-variables-rgx=_|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +# try to find bugs in the code using type inference +# +[TYPECHECK] + +# Enable / disable this checker +enable-typecheck=yes + +# Tells wether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# When zope mode is activated, consider the acquired-members option to ignore +# access to some undefined attributes. +zope=no + +# List of members which are usually get through zope's acquisition mecanism and +# so shouldn't trigger E0201 when accessed (need zope=yes to be considered. +acquired-members=REQUEST,acl_users,aq_parent + + +# checks for : +# * doc strings +# * modules / classes / functions / methods / arguments / variables name +# * number of arguments, local variables, branchs, returns and statements in +# functions, methods +# * required module attributes +# * dangerous default values as arguments +# * redefinition of function / method / class +# * uses of the global statement +# +# This checker also defines the following reports: +# * R0101: Statistics by type +[BASIC] + +# Enable / disable this checker +enable-basic=yes + +#disable-msg=C0121 + +# Required attributes for module, separated by a comma +required-attributes= + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][A-Za-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][A-Za-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][A-Za-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][A-Za-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][A-Za-z0-9_]{0,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + + +# checks for sign of poor/misdesign: +# * number of methods, attributes, local variables... +# * size, complexity of functions, methods +# +[DESIGN] + +# Enable / disable this checker +enable-design=yes + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +# checks for : +# * methods without self as first argument +# * overriden methods signature +# * access only to existant members via self +# * attributes not defined in the __init__ method +# * supported interfaces implementation +# * unreachable code +# +[CLASSES] + +# Enable / disable this checker +enable-classes=yes + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + + +# checks for +# * external modules dependencies +# * relative / wildcard imports +# * cyclic imports +# * uses of deprecated modules +# +# This checker also defines the following reports: +# * R0401: External dependencies +# * R0402: Modules dependencies graph +[IMPORTS] + +# Enable / disable this checker +enable-imports=no + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report R0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report R0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report R0402 must +# not be disabled) +int-import-graph= + + +# checks for usage of new style capabilities on old style classes and +# other new/old styles conflicts problems +# * use of property, __slots__, super +# * "super" usage +# * raising a new style class as exception +# +[NEWSTYLE] + +# Enable / disable this checker +enable-newstyle=yes + + +# checks for +# * excepts without exception filter +# * string exceptions +# +[EXCEPTIONS] + +# Enable / disable this checker +enable-exceptions=yes + + +# checks for : +# * unauthorized constructions +# * strict indentation +# * line length +# * use of <> instead of != +# +[FORMAT] + +# Enable / disable this checker +enable-format=yes + +# Maximum number of characters on a single line. +max-line-length=132 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +# checks for similarities and duplicated code. This computation may be +# memory / CPU intensive, so you should disable it if you experiments some +# problems. +# +# This checker also defines the following reports: +# * R0801: Duplication +[SIMILARITIES] + +# Enable / disable this checker +enable-similarities=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + + +# checks for: +# * warning notes in the code like FIXME, XXX +# * PEP 263: source code with non ascii character but no encoding declaration +# +[MISCELLANEOUS] + +# Enable / disable this checker +enable-miscellaneous=yes + +# List of note tags to take in consideration, separated by a comma. Default to +# FIXME, XXX, TODO +notes=FIXME,XXX,TODO + + +# does not check anything but gives some raw metrics : +# * total number of lines +# * total number of code lines +# * total number of docstring lines +# * total number of comments lines +# * total number of empty lines +# +# This checker also defines the following reports: +# * R0701: Raw metrics +[METRICS] + +# Enable / disable this checker +enable-metrics=no diff --git a/trunk/src/setup.py b/trunk/src/setup.py new file mode 100644 index 00000000..26504147 --- /dev/null +++ b/trunk/src/setup.py @@ -0,0 +1,14 @@ +""" +setup.py - Setup package with the help from Python's DistUtils +""" + +from distutils.core import setup + +setup( + name = 'sos', + packages = ['sos', 'sos.plugins'], + scripts = [], + package_dir = {'': 'lib',}, + data_files = [ ('/usr/sbin', ['sosreport']), ('/usr/share/man/man1', ['sosreport.1']), ('/usr/share/locale/en', []), ('/usr/share/locale/it', []), ('/usr/share/locale/en/LC_MESSAGES', ['locale/en/LC_MESSAGES/sos.mo']), ('/usr/share/locale/it/LC_MESSAGES', ['locale/it/LC_MESSAGES/sos.mo']) + ] +) diff --git a/trunk/src/sos.spec b/trunk/src/sos.spec new file mode 100644 index 00000000..4b4237d7 --- /dev/null +++ b/trunk/src/sos.spec @@ -0,0 +1,145 @@ +%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} + +%define name sos +%define version 1.7 +%define release 1 + +%define _localedir %_datadir/locale + +Summary: A set of tools to gather troubleshooting information from a system +Name: %{name} +Version: %{version} +Release: %{release}%{?dist} +# The source for this package was pulled from upstream's svn. Use the +# following commands to generate the tarball: +# svn --username guest export https://sos.108.redhat.com/svn/sos/tags/r1-6 sos-1.6 +# tar -czvf sos-1.6.tar.gz sos-1.6 +Source0: %{name}-%{version}.tar.gz +License: GPL +Group: Development/Libraries +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot +BuildArch: noarch +Url: http://sos.108.redhat.com/ +BuildRequires: python-devel + +%description +Sos is a set of tools that gathers information about system +hardware and configuration. The information can then be used for +diagnostic purposes and debugging. Sos is commonly used to help +support technicians and developers. + +%prep +%setup -q + +%build +python setup.py build + +%install +rm -rf ${RPM_BUILD_ROOT} +python setup.py install --optimize 1 --root=$RPM_BUILD_ROOT + +%clean +rm -rf ${RPM_BUILD_ROOT} + +%files +%defattr(-,root,root,-) +%{_sbindir}/sosreport +%{python_sitelib}/sos/ +%{_mandir}/man1/sosreport.1* +%{_localedir}/*/LC_MESSAGES/sos.mo +%doc README TODO LICENSE ChangeLog + +%changelog +* Mon Jul 5 2007 Navid Sheikhol-Eslami <navid at redhat dot com> - 1.6-5 +- Yet more fixes to make package Fedora compliant. + +* Mon Jul 5 2007 Navid Sheikhol-Eslami <navid at redhat dot com> - 1.6-4 +- More fixes to make package Fedora compliant. + +* Mon Jul 2 2007 Navid Sheikhol-Eslami <navid at redhat dot com> - 1.6-3 +- Other fixes to make package Fedora compliant. + +* Mon Jul 2 2007 Navid Sheikhol-Eslami <navid at redhat dot com> - 1.6-2 +- Minor fixes. + +* Mon Jul 2 2007 Navid Sheikhol-Eslami <navid at redhat dot com> - 1.6-1 +- Beautified output of --list-plugins. +- GPL licence is now included in the package. +- added python-devel requirement for building package +- fixed incompatibility with python from RHEL4 + +* Fri May 25 2007 Steve Conklin <sconklin at redhat dot com> - 1.5-1 +- Bumped version + +* Fri May 25 2007 Steve Conklin <sconklin at redhat dot com> - 1.4-2 +- Fixed a backtrace on nonexistent file in kernel plugin (thanks, David Robinson) + +* Mon Apr 30 2007 Steve Conklin <sconklin at redhat dot com> - 1.4-1 +- Fixed an error in option handling +- Forced the file generated by traceroute to not end in .com +- Fixed a problem with manpage +- Added optional traceroute collection to networking plugin +- Added clalance's patch to gather iptables info. +- Fixes to the device-mapper plugin +- Fixed a problem with installation of man page + +* Mon Apr 16 2007 Steve Conklin <sconklin at redhat dot com> - 1.3-3 +- including patches to fix the following: +- Resolves: bz219745 sosreport needs a man page +- Resolves: bz219667 sosreport does not terminate cleanly on ^C +- Resolves: bz233375 Make SOS flag the situation when running on a fully virtu... +- Resolves: bz234873 rhel5 sos needs to include rpm-va by default +- Resolves: bz219669 sosreport multi-threaded option sometimes fails +- Resolves: bz219671 RFE for sosreport - allow specification of plugins to be run +- Resolves: bz219672 RFE - show progress while sosreport is running +- Resolves: bz219673 Add xen information gathering to sosreport +- Resolves: bz219675 Collect information related to the new driver update model +- Resolves: bz219877 'Cancel' button during option selection only cancels sele... + +* Tue Feb 20 2007 John Berninger <jwb at redhat dot com> - 1.3-2 +- Add man page + +* Fri Dec 15 2006 Steve Conklin <sconklin at redhat dot com> - 1.3-1 +- really fixed bz_219654 + +* Fri Dec 15 2006 Steve Conklin <sconklin at redhat dot com> - 1.2-1 +- fixed a build problem + +* Fri Dec 15 2006 Steve Conklin <sconklin at redhat dot com> - 1.1-1 +- Tighten permissions of tmp directory so only readable by creator bz_219657 +- Don't print message 'Problem at path ...' bz_219654 +- Removed useless message bz_219670 +- Preserve file modification times bz_219674 +- Removed unneeded message about files on copyProhibitedList bz_219712 + +* Wed Aug 30 2006 Steve Conklin <sconklin at redhat dot com> - 1.0-1 +- Seperated upstream and RPM versioning + +* Mon Aug 21 2006 Steve Conklin <sconklin at redhat dot com> - 0.1-11 +- Code cleanup, fixed a regression in threading + +* Mon Aug 14 2006 Steve Conklin <sconklin at redhat dot com> - 0.1-10 +- minor bugfixes, added miltithreading option, setup now quiet + +* Mon Jul 17 2006 Steve Conklin <sconklin at redhat dot com> - 0.1-9 +- migrated to svn on 108.redhat.com, fixed a problem with command output linking in report + +* Mon Jun 19 2006 Steve Conklin <sconklin at redhat dot com> - 0.1-6 +- Added LICENSE file containing GPL + +* Wed May 31 2006 Steve Conklin <sconklin at redhat dot com> - 0.1-5 +- Added fixes to network plugin and prepped for Fedora submission + +* Wed May 31 2006 John Berninger <jwb at redhat dot com> - 0.1-4 +- Reconsolidated subpackages into one package per discussion with sconklin + +* Mon May 22 2006 John Berninger <jwb at redhat dot com> - 0.1-3 +- Added ftp, ldap, mail, named, samba, squid SOS plugins +- Fixed various errors in kernel and hardware plugins + +* Mon May 22 2006 John Benringer <jwb at redhat dot com> - 0.1-2 +- split off cluster plugin into subpackage +- correct file payload lists + +* Mon May 22 2006 John Berninger <jwb at redhat dot com> - 0.1-1 +- initial package build diff --git a/trunk/src/sosreport b/trunk/src/sosreport new file mode 100755 index 00000000..86d66b4d --- /dev/null +++ b/trunk/src/sosreport @@ -0,0 +1,725 @@ +#!/usr/bin/env python +""" +Gather information about a system and report it using plugins +supplied for application-specific information +""" +## sosreport.py +## gather information about a system and report it + +## Copyright (C) 2006 Steve Conklin <sconklin@redhat.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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +# pylint: disable-msg = W0611 +# pylint: disable-msg = W0702 + +import sys +import os +#import curses +from optparse import OptionParser, Option +import sos.policyredhat +from sos.helpers import * +from snack import * +from threading import Thread, activeCount, enumerate +import signal +import logging +from stat import * +from time import strftime, localtime, time +from pwd import getpwuid +import gettext +from threading import Semaphore + +__version__ = 1.7 + +__breakHits__ = 0 # Use this to track how many times we enter the exit routine + +## Set up routines to be linked to signals for termination handling +def exittermhandler(signum, frame): + doExitCode() + +def doExitCode(): + global __breakHits__ + __breakHits__ += 1 + if ( ( activeCount() > 1 ) and ( __breakHits__ == 1 ) ): + print "SIGTERM received, multiple threads detected, waiting for all threads to exit" + for thread in enumerate(): + if thread.getName() != "MainThread": + thread.join() + print "All threads ended, cleaning up." + if ( ( activeCount() > 1 ) and ( __breakHits__ > 1 ) ): + print "Multiple SIGTERMs, multiple threads, attempting to signal threads to die immediately" + ## FIXME: Add thread-kill code (see FIXME below) + print "Threads dead, cleaning up." + if ( ( activeCount() == 1 ) and ( __breakHits__ > 2 ) ): + print "Multiple SIGTERMs, single thread, exiting without cleaning up." + sys.exit(3) + + # FIXME: Add code here to clean up /tmp + sys.exit("Abnormal exit") + +# Handle any sort of exit signal cleanly +# Currently, we intercept only sig 15 (TERM) +signal.signal(signal.SIGTERM, exittermhandler) + +## FIXME: Need to figure out how to IPC with child threads in case of +## multiple SIGTERMs. +## FIXME: Need to figure out how to handle SIGKILL - we can't intercept it. + +# for debugging +__raisePlugins__ = 1 + +class SosOption (Option): + """Allow to specify comma delimited list of plugins""" + ACTIONS = Option.ACTIONS + ("extend",) + STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",) + TYPED_ACTIONS = Option.TYPED_ACTIONS + ("extend",) + + def take_action(self, action, dest, opt, value, values, parser): + if action == "extend": + try: lvalue = value.split(",") + except: pass + else: values.ensure_value(dest, []).extend(lvalue) + else: + Option.take_action(self, action, dest, opt, value, values, parser) + +__cmdParser__ = OptionParser(option_class=SosOption) +__cmdParser__.add_option("-a", "--alloptions", action="store_true", \ + dest="usealloptions", default=False, \ + help="Use all options for loaded plugins") +__cmdParser__.add_option("-f", "--fastoptions", action="store_true", \ + dest="fastoptions", default=False, \ + help="Use only fast options for loaded plugins") +__cmdParser__.add_option("-g", "--gatheronly", action="store_true", \ + dest="gatheronly", default=False, \ + help="Gather information locally but don't package or submit") +__cmdParser__.add_option("-l", "--list-plugins", action="store_true", \ + dest="listPlugins", default=False, \ + help="list existing plugins") +__cmdParser__.add_option("-n", "--noplugin", action="extend", \ + dest="noplugins", type="string", \ + help="skip these plugins", default = []) +__cmdParser__.add_option("-o", "--onlyplugin", action="extend", \ + dest="onlyplugins", type="string", \ + help="enable these plugins only", default = []) +__cmdParser__.add_option("-e", "--enableplugin", action="extend", \ + dest="enableplugins", type="string", \ + help="list of inactive plugins to be enabled", default = []) +__cmdParser__.add_option("-k", "--pluginopts", action="extend", \ + dest="plugopts", type="string", \ + help="plugin options in plugin_name.option=value format") +__cmdParser__.add_option("-v", "--verbose", action="count", \ + dest="verbosity", \ + help="How obnoxious we're being about telling the user what we're doing.") +__cmdParser__.add_option("-c", "--curses", action="store_true", \ + dest="use_curses", default=False, \ + help="Display a text GUI menu to modify plugin options.") +__cmdParser__.add_option("--no-progressbar", action="store_false", \ + dest="progressbar", default=True, \ + help="Do not display a progress bar.") +__cmdParser__.add_option("--no-multithread", action="store_true", \ + dest="nomultithread", \ + help="Disable multi-threaded gathering mode (slower)", default=False) +(__cmdLineOpts__, __cmdLineArgs__)=__cmdParser__.parse_args() + +def textcolor(text, fg, bg=None, raw=0): + colors = { "black":"30", "red":"31", "green":"32", "brown":"33", "blue":"34", + "purple":"35", "cyan":"36", "lgray":"37", "gray":"1;30", "lred":"1;31", + "lgreen":"1;32", "yellow":"1;33", "lblue":"1;34", "pink":"1;35", + "lcyan":"1;36", "white":"1;37" } + opencol = "\033[" + closecol = "m" + clear = opencol + "0" + closecol + f = opencol + colors[fg] + closecol + return "%s%s%s" % (f, text, clear) + +def get_curse_options(alloptions): + """ + use curses to enable the user to select some options + """ + # alloptions is an array of (plug, plugname, optname, parms(dictionary)) tuples + plugName = [] + out = [] + + # get a sorted list of all plugin names + for rrr in alloptions: + if rrr[1] not in plugName: + plugName.append(rrr[1]) + + plugName.sort() + plugCbox = CheckboxTree(height=5, scroll=1) + + countOpt = -1 + + optDic = {} + optDicCounter = 0 + + # iterate over all plugins with options + for curPlugName in plugName: + plugCbox.addItem(curPlugName, (snackArgs['append'],)) + countOpt = countOpt+1 + + for opt in alloptions: + if opt[1] != curPlugName: + continue + + snt = opt[2] + " ("+opt[3]['desc']+") is " + opt[3]['speed'] + plugCbox.addItem(snt, (countOpt, snackArgs['append']), item = optDicCounter, selected = opt[3]['enabled']) + optDic[optDicCounter] = opt + optDicCounter += 1 + + + screen = SnackScreen() + bb = ButtonBar(screen, (("Ok", "ok"), ("Cancel", "cancel"))) + g = GridForm(screen, "Select Sosreport Options", 1, 10) + g.add(plugCbox, 0, 0) + g.add(bb, 0, 1, growx = 1) + result = g.runOnce() + + screen.finish() + + if bb.buttonPressed(result) == "cancel": + raise "Cancelled" + + for rrr in range(0, optDicCounter): + optDic[rrr][3]['enabled'] = plugCbox.getEntryValue(rrr)[1] + out.append((optDic[rrr])) + + return out + +class progressBar: + def __init__(self, minValue = 0, maxValue = 10, totalWidth=40): + self.progBar = "[]" # This holds the progress bar string + self.min = minValue + self.max = maxValue + self.width = totalWidth + self.amount = 0 # When amount == max, we are 100% done + self.time_start = time() + self.eta = 0 + self.last_amount_update = time() + self.update() + + def updateAmount(self, newAmount = 0): + if newAmount < self.min: newAmount = self.min + if newAmount > self.max: newAmount = self.max + if self.amount != newAmount: + self.last_amount_update = time() + self.amount = newAmount + last_update_relative = round(self.last_amount_update - self.time_start) + self.eta = round(last_update_relative * self.max / self.amount) + + # generate ETA + timeElapsed = round(time() - self.time_start) + last_update_relative = round(self.last_amount_update - self.time_start) + if timeElapsed >= 10 and self.amount > 0: + percentDone = round(timeElapsed * 100 / self.eta) + if percentDone > 100: + percentDone = 100 + ETA = timeElapsed + elif self.eta < timeElapsed: + ETA = timeElapsed + else: + ETA = self.eta + ETA = "[%02d:%02d/%02d:%02d]" % (round(timeElapsed/60), timeElapsed % 60, round(ETA/60), ETA % 60) + else: + ETA = "[%02d:%02d/--:--]" % (round(timeElapsed/60), timeElapsed % 60) + if self.amount < self.max: + percentDone = 0 + else: + percentDone = 100 + + # Figure out how many hash bars the percentage should be + allFull = self.width - 2 + numHashes = (percentDone / 100.0) * allFull + numHashes = int(round(numHashes)) + + # build a progress bar with hashes and spaces + self.progBar = " [" + '#'*numHashes + ' '*(allFull-numHashes) + "]" + + # figure out where to put the percentage, roughly centered + percentPlace = (len(self.progBar) / 2) - len(str(percentDone)) + percentString = str(percentDone) + "%" + + # slice the percentage into the bar + self.progBar = " Progress" + self.progBar[0:percentPlace] + percentString + self.progBar[percentPlace+len(percentString):] + ETA + + def incAmount(self, toInc = 1): + self.updateAmount(self.amount+toInc) + + def finished(self): + self.updateAmount(self.max) + sys.stdout.write(self.progBar + '\n') + sys.stdout.flush() + + def update(self): + self.updateAmount(self.amount) + sys.stdout.write(self.progBar + '\r') + sys.stdout.flush() + +class XmlReport: + def __init__(self): + try: + import libxml2 + except: + self.enabled = False + return + else: + self.enabled = True + self.doc = libxml2.newDoc("1.0") + self.root = self.doc.newChild(None, "sos", None) + self.commands = self.root.newChild(None, "commands", None) + self.files = self.root.newChild(None, "files", None) + + def add_command(self,cmdline,exitcode,stdout = None,stderr = None,f_stdout=None,f_stderr=None, runtime=None): + if not self.enabled: return + + cmd = self.commands.newChild(None, "cmd", None) + + cmd.setNsProp(None, "cmdline", cmdline) + + cmdchild = cmd.newChild(None, "exitcode", str(exitcode)) + + if runtime: + cmd.newChild(None, "runtime", str(runtime)) + + if stdout or f_stdout: + cmdchild = cmd.newChild(None, "stdout", stdout) + if f_stdout: + cmdchild.setNsProp(None, "file", f_stdout) + + if stderr or f_stderr: + cmdchild = cmd.newChild(None, "stderr", stderr) + if f_stderr: + cmdchild.setNsProp(None, "file", f_stderr) + + def add_file(self,fname,stats): + if not self.enabled: return + + cfile = self.files.newChild(None,"file",None) + + cfile.setNsProp(None, "fname", fname) + + cchild = cfile.newChild(None, "uid", str(stats[ST_UID])) + cchild.setNsProp(None,"name", getpwuid(stats[ST_UID])[0]) + cchild = cfile.newChild(None, "gid", str(stats[ST_GID])) + cchild.setNsProp(None,"name", getpwuid(stats[ST_GID])[0]) + cfile.newChild(None, "mode", str(oct(S_IMODE(stats[ST_MODE])))) + cchild = cfile.newChild(None, "ctime", strftime('%a %b %d %H:%M:%S %Y', localtime(stats[ST_CTIME]))) + cchild.setNsProp(None,"tstamp", str(stats[ST_CTIME])) + cchild = cfile.newChild(None, "atime", strftime('%a %b %d %H:%M:%S %Y', localtime(stats[ST_ATIME]))) + cchild.setNsProp(None,"tstamp", str(stats[ST_ATIME])) + cchild = cfile.newChild(None, "mtime", strftime('%a %b %d %H:%M:%S %Y', localtime(stats[ST_MTIME]))) + cchild.setNsProp(None,"tstamp", str(stats[ST_MTIME])) + + def serialize(self): + if not self.enabled: return + + print self.doc.serialize(None, 1) + + def serialize_to_file(self,fname): + if not self.enabled: return + + outfn = open(fname,"w") + outfn.write(self.doc.serialize(None,1)) + outfn.close() + +def sosreport(): + # pylint: disable-msg = R0912 + # pylint: disable-msg = R0914 + # pylint: disable-msg = R0915 + """ + This is the top-level function that gathers and processes all sosreport information + """ + loadedplugins = [] + skippedplugins = [] + alloptions = [] + + # perhaps we should automatically locate the policy module?? + policy = sos.policyredhat.SosPolicy() + + # find the plugins path + paths = sys.path + for path in paths: + if path.strip()[-len("site-packages"):] == "site-packages": + pluginpath = path + "/sos/plugins" + reporterpath = path + "/sos/reporters" + + # Set up common info and create destinations + + dstroot = sosFindTmpDir() + cmddir = os.path.join(dstroot, "sos_commands") + logdir = os.path.join(dstroot, "sos_logs") + rptdir = os.path.join(dstroot, "sos_reports") + os.mkdir(cmddir, 0755) + os.mkdir(logdir, 0755) + os.mkdir(rptdir, 0755) + + # initialize i18n language localization + gettext.install('sos', '/usr/share/locale', unicode=False) + + # initialize logging + soslog = logging.getLogger('sos') + soslog.setLevel(logging.DEBUG) + + # log to a file + flog = logging.FileHandler(logdir + "/sos.log") + flog.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s')) + flog.setLevel(logging.DEBUG) + soslog.addHandler(flog) + + # define a Handler which writes INFO messages or higher to the sys.stderr + console = logging.StreamHandler(sys.stderr) + if __cmdLineOpts__.verbosity > 0: + console.setLevel(20 - __cmdLineOpts__.verbosity) + __cmdLineOpts__.progressbar = False + else: + console.setLevel(logging.INFO) + console.setFormatter(logging.Formatter('%(message)s')) + soslog.addHandler(console) + + logging.VERBOSE = logging.INFO - 1 + logging.VERBOSE2 = logging.INFO - 2 + logging.VERBOSE3 = logging.INFO - 3 + logging.addLevelName(logging.VERBOSE, "verbose") + logging.addLevelName(logging.VERBOSE2,"verbose2") + logging.addLevelName(logging.VERBOSE3,"verbose3") + + xmlrep = XmlReport() + + # set up dict so everyone can share the following + commons = {'dstroot': dstroot, 'cmddir': cmddir, 'logdir': logdir, 'rptdir': rptdir, + 'soslog': soslog, 'policy': policy, 'verbosity' : __cmdLineOpts__.verbosity, + 'xmlreport' : xmlrep } + + # Make policy aware of the commons + policy.setCommons(commons) + + print + soslog.info ( _("sosreport (version %s)") % __version__) + print + + # generate list of available plugins + plugins = os.listdir(pluginpath) + plugins.sort() + + # validate and load plugins + for plug in plugins: + plugbase = plug[:-3] + if not plug[-3:] == '.py' or plugbase == "__init__": + continue + try: + #print "importing plugin: %s" % plugbase + try: + if policy.validatePlugin(pluginpath + plug): + pluginClass = importPlugin("sos.plugins." + plugbase, plugbase) + else: + soslog.warning(_("plugin %s does not validate, skipping") % plug) + skippedplugins.append((plugbase, pluginClass(plugbase, commons))) + continue + if plugbase in __cmdLineOpts__.noplugins: + soslog.log(logging.VERBOSE, _("plug %s skipped (noplugins)") % plugbase) + skippedplugins.append((plugbase, pluginClass(plugbase, commons))) + continue + if not pluginClass(plugbase, commons).checkenabled() and not plugbase in __cmdLineOpts__.enableplugins and not plugbase in __cmdLineOpts__.onlyplugins: + soslog.log(logging.VERBOSE, _("plugin %s is inactive (use -e or -o to enable).") % plug) + skippedplugins.append((plugbase, pluginClass(plugbase, commons))) + continue + if not pluginClass(plugbase, commons).defaultenabled() and not plugbase in __cmdLineOpts__.enableplugins and not plugbase in __cmdLineOpts__.onlyplugins: + soslog.log(logging.VERBOSE, "plugin %s not loaded by default (use -e or -o to enable)." % plug) + skippedplugins.append((plugbase, pluginClass(plugbase, commons))) + continue + if __cmdLineOpts__.onlyplugins and not plugbase in __cmdLineOpts__.onlyplugins: + soslog.log(logging.VERBOSE, _("plugin %s not specified in --onlyplugin list") % plug) + skippedplugins.append((plugbase, pluginClass(plugbase, commons))) + continue + loadedplugins.append((plugbase, pluginClass(plugbase, commons))) + except: + soslog.warning(_("plugin %s does not install, skipping") % plug) + raise + except: + soslog.warning(_("could not load plugin %s") % plug) + if __raisePlugins__: + raise + + # First, gather and process options + for plugname, plug in loadedplugins: + soslog.log(logging.VERBOSE3, _("processing options from plugin: %s") % plugname) + names, parms = plug.getAllOptions() + for optname, optparm in zip(names, parms): + alloptions.append((plug, plugname, optname, optparm)) + + if __cmdLineOpts__.listPlugins: + if not len(loadedplugins) and not len(skippedplugins): + soslog.error(_("no valid plugins found")) + sys.exit(1) + + if len(loadedplugins): + print _("The following plugins are currently enabled:") + print + for (plugname,plug) in loadedplugins: + print " %-25s %s" % (textcolor(plugname,"lblue"),plug.get_description()) + else: + print _("No plugin enabled.") + print + + if len(alloptions): + print _("The following plugin options are available:") + print + for (plug, plugname, optname, optparm) in alloptions: + print " %-25s %s [%d]" % (plugname + "." + optname, optparm["desc"], optparm["enabled"]) + else: + print _("No plugin options available.") + + if len(skippedplugins): + print + print _("The following plugins are currently disabled:") + print + for (plugname,plugclass) in skippedplugins: + print " %-25s %s" % (textcolor(plugname,"blue"),plugclass.get_description()) + + print + sys.exit() + + # to go anywhere further than listing the plugins we will need root permissions. + # + if os.getuid() != 0: + print _('sosreport requires root permissions to run.') + sys.exit(1) + + # we don't need to keep in memory plugins we are not going to use + del skippedplugins + + if not len(loadedplugins): + soslog.error(_("no valid plugins were enabled")) + sys.exit(1) + + try: + raw_input(_("""This utility will collect some detailed information about the +hardware and setup of your Red Hat Enterprise Linux system. +This information will be used to diagnose problems with your +system and will be considered confidential information. +Red Hat will use this information for diagnostic purposes ONLY. + +This process may take a while to complete. +No changes will be made to your system. + +Press ENTER to continue, or CTRL-C to quit. +""")) + except KeyboardInterrupt: + print + sys.exit(0) + + # setup plugin options + if __cmdLineOpts__.plugopts: + opts = {} + for opt in __cmdLineOpts__.plugopts: + try: opt, val = opt.split("=") + except: val=1 + plug, opt = opt.split(".") + try: val = int(val) # try to convert string "val" to int() + except: pass + try: opts[plug] + except KeyError: opts[plug] = [] + opts[plug].append( (opt,val) ) + for plugname, plug in loadedplugins: + if opts.has_key(plugname): + for opt,val in opts[plugname]: + soslog.log(logging.VERBOSE, "setting option %s for plugin %s to %s" % (plugname,opt,val)) + plug.setOption(opt,val) + del opt,opts,val + elif not __cmdLineOpts__.fastoptions and not __cmdLineOpts__.usealloptions: + if len(alloptions) and __cmdLineOpts__.use_curses: + try: + get_curse_options(alloptions) + except "Cancelled": + sys.exit(_("Exiting.")) + elif __cmdLineOpts__.fastoptions: + for i in range(len(alloptions)): + for plug, plugname, optname, optparm in alloptions: + if optparm['speed'] == 'fast': + plug.setOption(optname, 1) + else: + plug.setOption(optname, 0) + elif __cmdLineOpts__.usealloptions: + for i in range(len(alloptions)): + for plug, plugname, optname, optparm in alloptions: + plug.setOption(optname, 1) + + # Call the diagnose() method for each plugin + tmpcount = 0 + for plugname, plug in loadedplugins: + soslog.log(logging.VERBOSE2, "Performing sanity check for plugin %s" % plugname) + plug.diagnose() + tmpcount += len(plug.diagnose_msgs) + if tmpcount > 0: + print _("One or more plugin has detected a problem in your configuration.") + print _("Please review the following messages:") + print + for plugname, plug in loadedplugins: + for msg in plug.diagnose_msgs: + soslog.warning(" * %s: %s", plugname, msg) + print + try: + raw_input( _("Press ENTER to continue, or CTRL-C to quit.\n") ) + except KeyboardInterrupt: + print + sys.exit(0) + + # Call the setup() method for each plugin + for plugname, plug in loadedplugins: + soslog.log(logging.VERBOSE2, "Preloading files and commands to be gathered by plugin %s" % plugname) + plug.setup() + + # Setup the progress bar + if __cmdLineOpts__.progressbar: + # gather information useful for generating ETA + eta_weight = len(loadedplugins) + for plugname, plug in loadedplugins: + eta_weight += plug.eta_weight + pbar = progressBar(minValue = 0, maxValue = eta_weight) + # pbar.max = number_of_plugins + weight (default 1 per plugin) + + if __cmdLineOpts__.nomultithread: + soslog.log(logging.VERBOSE, "using single-threading") + else: + soslog.log(logging.VERBOSE, "using multi-threading") + + # Call the collect method for each plugin + plugrunning = Semaphore(2) + for plugname, plug in loadedplugins: + soslog.log(logging.VERBOSE, "executing plugin %s" % plugname) + if not __cmdLineOpts__.nomultithread: + plug.copyStuff(threaded = True, semaphore = plugrunning) + else: + plug.copyStuff() + if __cmdLineOpts__.progressbar: + pbar.incAmount(plug.eta_weight) + pbar.update() + del plugrunning + + # Wait for all the collection threads to exit + if not __cmdLineOpts__.nomultithread: + finishedplugins = [] + while len(loadedplugins) > 0: + plugname, plug = loadedplugins.pop(0) + if not plug.wait(0.5): + finishedplugins.append((plugname,plug)) + soslog.log(logging.VERBOSE2, "plugin %s has returned" % plugname) + if __cmdLineOpts__.progressbar: + pbar.incAmount(plug.eta_weight) + else: + soslog.log(logging.VERBOSE3, "plugin %s still hasn't returned" % plugname) + loadedplugins.append((plugname,plug)) + if __cmdLineOpts__.progressbar: + pbar.update() + loadedplugins = finishedplugins + del finishedplugins + + xmlrep.serialize_to_file(rptdir + "/" + "sosreport.xml") + + # Call the analyze method for each plugin + for plugname, plug in loadedplugins: + soslog.log(logging.VERBOSE2, "Analyzing results of plugin %s" % plugname,) + try: + plug.analyze() + except: + # catch exceptions in analyse() and keep working + pass + if __cmdLineOpts__.progressbar: + pbar.incAmount() + pbar.update() + + if __cmdLineOpts__.progressbar: + pbar.finished() + sys.stdout.write("\n") + + # Generate the header for the html output file + rfd = open(rptdir + "/" + "sosreport.html", "w") + rfd.write(""" + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> + <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> + <head> + <link rel="stylesheet" type="text/css" media="screen" href="donot.css" /> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <title>Sos System Report</title> + </head> + + <body> + """) + + + # Make a pass to gather Alerts and a list of module names + allAlerts = [] + plugNames = [] + for plugname, plug in loadedplugins: + for alert in plug.alerts: + allAlerts.append('<a href="#%s">%s</a>: %s' % (plugname, plugname, alert)) + plugNames.append(plugname) + + + + # Create a table of links to the module info + rfd.write("<hr/><h3>Loaded Plugins:</h3>") + rfd.write("<table><tr>\n") + rr = 0 + for i in range(len(plugNames)): + rfd.write('<td><a href="#%s">%s</a></td>\n' % (plugNames[i], plugNames[i])) + rr = divmod(i, 4)[1] + if (rr == 3): + rfd.write('</tr>') + if not (rr == 3): + rfd.write('</tr>') + rfd.write('</table>\n') + + rfd.write('<hr/><h3>Alerts:</h3>') + rfd.write('<ul>') + for alert in allAlerts: + rfd.write('<li>%s</li>' % alert) + rfd.write('</ul>') + + + # Call the report method for each plugin + for plugname, plug in loadedplugins: + html = plug.report() + rfd.write(html) + + rfd.write("</body></html>") + + rfd.close() + + # Collect any needed user information (name, etc) + + # Call the postproc method for each plugin + for plugname, plug in loadedplugins: + plug.postproc() + + if __cmdLineOpts__.gatheronly: + soslog.info(_("Collected information is in ") + dstroot) + soslog.info(_("Your html report is in ") + rptdir + "/" + "sosreport.html") + else: + # package up the results for the support organization + policy.packageResults() + # delete gathered files + os.system("/bin/rm -rf %s" % dstroot) + # automated submission will go here + + # Close all log files and perform any cleanup + logging.shutdown() + + +if __name__ == '__main__': + try: + sosreport() + except KeyboardInterrupt: + doExitCode() diff --git a/trunk/src/sosreport.1 b/trunk/src/sosreport.1 new file mode 100644 index 00000000..cf76afdb --- /dev/null +++ b/trunk/src/sosreport.1 @@ -0,0 +1,55 @@ +.TH SOSREPORT 1 "Tue Feb 20 2007" +.SH NAME +sosreport \- Generate debugging information for this system +.SH SYNOPSIS +.B sosreport +[-a|--alloptions] [-f|--fastoptions] [-g|--gatheronly] + [-l|--list-plugins] [-n|--noplugin \fIplugin-name\fR] + [-o|--onlyplugin \fIplugin-name\fR] + [-v|--verbose [...]] [-m|--multithreaded] +.SH DESCRIPTION +\fBsosreport\fR generates a compressed tarball of debugging information +for the system it is run on that can be sent to technical support +reps that will give them a more complete view of the overall system +status. +.SH OPTIONS +.TP +.B \-a, \--alloptions +Enable all options for all loaded plugins +.TP +.B \-f, \--fastoptions +Enable all options marked as "fast" for loaded plugins. This will +reduce running time while still gathering helpful information, but +you may be asked to re-run later with some or all "slow" options +enabled depending on the specific issue. +.TP +.B \-g, \--gatheronly +Gather the diagnostic data and bundle it up, but do not attempt to +send it to any support site. This option currently has no effect as +sosreport currently does not support report transmission. +.TP +.B \-l, \--list-plugins +List available plugins +.TP +.B \-n, \--noplugin +Do not load specified plugin(s) +.TP +.B \-o, \--onlyplugin +Load only the specified plugin(s), all otherplugins should be disabled +.TP +.B \-v, \--verbose +Increase the verbosity of the output as sosreport is running. This option +can be specified more than once. +.TP +.B \-m, \--multithreaded +Enable a multithreaded collection and analysis of the sosreport data. Please +note that this option is experimental and is known to have intermittent issues. +.SH BUGS +The multithreaded option can fail intermittently, please use it with care. +.SH AUTHORS +.nf +Steve Conklin <sconklin@redhat.com> +John Berninger <jwb@redhat.com> +Navid Sheikhol-Eslami <navid@redhat.com> +Pierre Amadio <pamadio@redhat.com> +.fi diff --git a/trunk/src/tools/msgfmt.py b/trunk/src/tools/msgfmt.py new file mode 100644 index 00000000..8a2d4e66 --- /dev/null +++ b/trunk/src/tools/msgfmt.py @@ -0,0 +1,203 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-1 -*- +# Written by Martin v. Löwis <loewis@informatik.hu-berlin.de> + +"""Generate binary message catalog from textual translation description. + +This program converts a textual Uniforum-style message catalog (.po file) into +a binary GNU catalog (.mo file). This is essentially the same function as the +GNU msgfmt program, however, it is a simpler implementation. + +Usage: msgfmt.py [OPTIONS] filename.po + +Options: + -o file + --output-file=file + Specify the output file to write to. If omitted, output will go to a + file named filename.mo (based off the input file name). + + -h + --help + Print this message and exit. + + -V + --version + Display version information and exit. +""" + +import sys +import os +import getopt +import struct +import array + +__version__ = "1.1" + +MESSAGES = {} + + + +def usage(code, msg=''): + print >> sys.stderr, __doc__ + if msg: + print >> sys.stderr, msg + sys.exit(code) + + + +def add(id, str, fuzzy): + "Add a non-fuzzy translation to the dictionary." + global MESSAGES + if not fuzzy and str: + MESSAGES[id] = str + + + +def generate(): + "Return the generated output." + global MESSAGES + keys = MESSAGES.keys() + # the keys are sorted in the .mo file + keys.sort() + offsets = [] + ids = strs = '' + for id in keys: + # For each string, we need size and file offset. Each string is NUL + # terminated; the NUL does not count into the size. + offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id]))) + ids += id + '\0' + strs += MESSAGES[id] + '\0' + output = '' + # The header is 7 32-bit unsigned integers. We don't use hash tables, so + # the keys start right after the index tables. + # translated string. + keystart = 7*4+16*len(keys) + # and the values start after the keys + valuestart = keystart + len(ids) + koffsets = [] + voffsets = [] + # The string table first has the list of keys, then the list of values. + # Each entry has first the size of the string, then the file offset. + for o1, l1, o2, l2 in offsets: + koffsets += [l1, o1+keystart] + voffsets += [l2, o2+valuestart] + offsets = koffsets + voffsets + output = struct.pack("Iiiiiii", + 0x950412deL, # Magic + 0, # Version + len(keys), # # of entries + 7*4, # start of key index + 7*4+len(keys)*8, # start of value index + 0, 0) # size and offset of hash table + output += array.array("i", offsets).tostring() + output += ids + output += strs + return output + + + +def make(filename, outfile): + ID = 1 + STR = 2 + + # Compute .mo name from .po name and arguments + if filename.endswith('.po'): + infile = filename + else: + infile = filename + '.po' + if outfile is None: + outfile = os.path.splitext(infile)[0] + '.mo' + + try: + lines = open(infile).readlines() + except IOError, msg: + print >> sys.stderr, msg + sys.exit(1) + + section = None + fuzzy = 0 + + # Parse the catalog + lno = 0 + for l in lines: + lno += 1 + # If we get a comment line after a msgstr, this is a new entry + if l[0] == '#' and section == STR: + add(msgid, msgstr, fuzzy) + section = None + fuzzy = 0 + # Record a fuzzy mark + if l[:2] == '#,' and l.find('fuzzy'): + fuzzy = 1 + # Skip comments + if l[0] == '#': + continue + # Now we are in a msgid section, output previous section + if l.startswith('msgid'): + if section == STR: + add(msgid, msgstr, fuzzy) + section = ID + l = l[5:] + msgid = msgstr = '' + # Now we are in a msgstr section + elif l.startswith('msgstr'): + section = STR + l = l[6:] + # Skip empty lines + l = l.strip() + if not l: + continue + # XXX: Does this always follow Python escape semantics? + l = eval(l) + if section == ID: + msgid += l + elif section == STR: + msgstr += l + else: + print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \ + 'before:' + print >> sys.stderr, l + sys.exit(1) + # Add last entry + if section == STR: + add(msgid, msgstr, fuzzy) + + # Compute output + output = generate() + + try: + open(outfile,"wb").write(output) + except IOError,msg: + print >> sys.stderr, msg + + + +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], 'hVo:', + ['help', 'version', 'output-file=']) + except getopt.error, msg: + usage(1, msg) + + outfile = None + # parse options + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-V', '--version'): + print >> sys.stderr, "msgfmt.py", __version__ + sys.exit(0) + elif opt in ('-o', '--output-file'): + outfile = arg + # do it + if not args: + print >> sys.stderr, 'No input file given' + print >> sys.stderr, "Try `msgfmt --help' for more information." + return + + for filename in args: + make(filename, outfile) + + +if __name__ == '__main__': + main() diff --git a/trunk/src/tools/pygettext.py b/trunk/src/tools/pygettext.py new file mode 100644 index 00000000..040b5c7f --- /dev/null +++ b/trunk/src/tools/pygettext.py @@ -0,0 +1,762 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-1 -*- +# Originally written by Barry Warsaw <barry@zope.com> +# +# Minimally patched to make it even more xgettext compatible +# by Peter Funk <pf@artcom-gmbh.de> +# +# 2002-11-22 Jürgen Hermann <jh@web.de> +# Added checks that _() only contains string literals, and +# command line args are resolved to module lists, i.e. you +# can now pass a filename, a module or package name, or a +# directory (including globbing chars, important for Win32). +# Made docstring fit in 80 chars wide displays using pydoc. +# + +import codecs + +# for selftesting +import re +try: + import fintl + _ = fintl.gettext +except ImportError: + _ = lambda s: s + +__doc__ = _("""pygettext -- Python equivalent of xgettext(1) + +Many systems (Solaris, Linux, Gnu) provide extensive tools that ease the +internationalization of C programs. Most of these tools are independent of +the programming language and can be used from within Python programs. +Martin von Loewis' work[1] helps considerably in this regard. + +There's one problem though; xgettext is the program that scans source code +looking for message strings, but it groks only C (or C++). Python +introduces a few wrinkles, such as dual quoting characters, triple quoted +strings, and raw strings. xgettext understands none of this. + +Enter pygettext, which uses Python's standard tokenize module to scan +Python source code, generating .pot files identical to what GNU xgettext[2] +generates for C and C++ code. From there, the standard GNU tools can be +used. + +A word about marking Python strings as candidates for translation. GNU +xgettext recognizes the following keywords: gettext, dgettext, dcgettext, +and gettext_noop. But those can be a lot of text to include all over your +code. C and C++ have a trick: they use the C preprocessor. Most +internationalized C source includes a #define for gettext() to _() so that +what has to be written in the source is much less. Thus these are both +translatable strings: + + gettext("Translatable String") + _("Translatable String") + +Python of course has no preprocessor so this doesn't work so well. Thus, +pygettext searches only for _() by default, but see the -k/--keyword flag +below for how to augment this. + + [1] http://www.python.org/workshops/1997-10/proceedings/loewis.html + [2] http://www.gnu.org/software/gettext/gettext.html + +NOTE: pygettext attempts to be option and feature compatible with GNU +xgettext where ever possible. However some options are still missing or are +not fully implemented. Also, xgettext's use of command line switches with +option arguments is broken, and in these cases, pygettext just defines +additional switches. + +Usage: pygettext [options] inputfile ... + +Options: + + -a + --extract-all + Extract all strings. + + -d name + --default-domain=name + Rename the default output file from messages.pot to name.pot. + + -E + --escape + Replace non-ASCII characters with octal escape sequences. + + -D + --docstrings + Extract module, class, method, and function docstrings. These do + not need to be wrapped in _() markers, and in fact cannot be for + Python to consider them docstrings. (See also the -X option). + + -h + --help + Print this help message and exit. + + -k word + --keyword=word + Keywords to look for in addition to the default set, which are: + %(DEFAULTKEYWORDS)s + + You can have multiple -k flags on the command line. + + -K + --no-default-keywords + Disable the default set of keywords (see above). Any keywords + explicitly added with the -k/--keyword option are still recognized. + + --no-location + Do not write filename/lineno location comments. + + -n + --add-location + Write filename/lineno location comments indicating where each + extracted string is found in the source. These lines appear before + each msgid. The style of comments is controlled by the -S/--style + option. This is the default. + + -o filename + --output=filename + Rename the default output file from messages.pot to filename. If + filename is `-' then the output is sent to standard out. + + -p dir + --output-dir=dir + Output files will be placed in directory dir. + + -S stylename + --style stylename + Specify which style to use for location comments. Two styles are + supported: + + Solaris # File: filename, line: line-number + GNU #: filename:line + + The style name is case insensitive. GNU style is the default. + + -v + --verbose + Print the names of the files being processed. + + -V + --version + Print the version of pygettext and exit. + + -w columns + --width=columns + Set width of output to columns. + + -x filename + --exclude-file=filename + Specify a file that contains a list of strings that are not be + extracted from the input files. Each string to be excluded must + appear on a line by itself in the file. + + -X filename + --no-docstrings=filename + Specify a file that contains a list of files (one per line) that + should not have their docstrings extracted. This is only useful in + conjunction with the -D option above. + +If `inputfile' is -, standard input is read. +""") + +import os +import imp +import sys +import glob +import time +import getopt +import token +import tokenize +import operator +import codecs + +from elementtree.ElementTree import ElementTree, XML + +__version__ = '1.5' + +default_keywords = ['_'] +DEFAULTKEYWORDS = ', '.join(default_keywords) + +EMPTYSTRING = '' + + + +# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's +# there. +pot_header = _('''\ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\\n" +"POT-Creation-Date: %(time)s\\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n" +"Language-Team: LANGUAGE <LL@li.org>\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=%(charset)s\\n" +"Content-Transfer-Encoding: %(charset)s\\n" +"Generated-By: pygettext.py %(version)s\\n" + +''') + + +def usage(code, msg=''): + print >> sys.stderr, __doc__ % globals() + if msg: + print >> sys.stderr, msg + sys.exit(code) + + + +escapes = [] + +def make_escapes(pass_iso8859): + global escapes + if pass_iso8859: + # Allow iso-8859 characters to pass through so that e.g. 'msgid + # "Höhe"' would result not result in 'msgid "H\366he"'. Otherwise we + # escape any character outside the 32..126 range. + mod = 128 + else: + mod = 256 + for i in range(256): + if 32 <= (i % mod) <= 126: + escapes.append(chr(i)) + else: + escapes.append("\\%03o" % i) + escapes[ord('\\')] = '\\\\' + escapes[ord('\t')] = '\\t' + escapes[ord('\r')] = '\\r' + escapes[ord('\n')] = '\\n' + escapes[ord('\"')] = '\\"' + + +def escape_ascii(s): + "Escapes all text outside of 7bit ASCII plus control characters and Python literals." + global escapes + s = list(s) + for i in range(len(s)): + s[i] = escapes[ord(s[i])] + return EMPTYSTRING.join(s) + +def escape_unicode(s): + "Escapes control characters and Python literals only leaving non-ascii text intact." + #for sp in ('\t', '\r', '\n', '\"', '\\'): + s = s.replace('\\', '\\\\') + s = s.replace('\t', '\\t') + s = s.replace('\r', '\\r') + s = s.replace('\n', '\\n') + s = s.replace('\"', '\\"') + # escape control chars + def repl(m): return "\\%03o" % ord(m.group(0)) + s = re.sub('[\001-\037]', repl, s) + return s + +def safe_eval(s): + # unwrap quotes, safely + return eval(s, {'__builtins__':{}}, {}) + + +def normalize(s, escape=False): + # This converts the various Python string types into a format that is + # appropriate for .po files, namely much closer to C style. + lines = s.split('\n') + if len(lines) == 1: + s = '"' + escape_unicode(s) + '"' + else: + if not lines[-1]: + del lines[-1] + lines[-1] = lines[-1] + '\n' + for i in range(len(lines)): + lines[i] = escape_unicode(lines[i]) + lineterm = '\\n"\n"' + s = '""\n"' + lineterm.join(lines) + '"' + if isinstance(s, unicode): + s = s.encode('utf-8') + if escape: + def repl(m): return "\\%03o" % ord(m.group(0)) + s = re.sub('[\200-\377]', repl, s) + return s + + +def containsAny(str, set): + """Check whether 'str' contains ANY of the chars in 'set'""" + return 1 in [c in str for c in set] + + +def _visit_pyfiles(list, dirname, names): + """Helper for getFilesForName().""" + # get extension for python source files + if not globals().has_key('_py_ext'): + global _py_ext + _py_ext = [triple[0] for triple in imp.get_suffixes() + if triple[2] == imp.PY_SOURCE][0] + + # don't recurse into CVS directories + if 'CVS' in names: + names.remove('CVS') + if '.svn' in names: + names.remove('.svn') + + # add all *.py files to list + list.extend( + [os.path.join(dirname, file) for file in names + if os.path.splitext(file)[1] == _py_ext] + ) + + +def _get_modpkg_path(dotted_name, pathlist=None): + """Get the filesystem path for a module or a package. + + Return the file system path to a file for a module, and to a directory for + a package. Return None if the name is not found, or is a builtin or + extension module. + """ + # split off top-most name + parts = dotted_name.split('.', 1) + + if len(parts) > 1: + # we have a dotted path, import top-level package + try: + file, pathname, description = imp.find_module(parts[0], pathlist) + if file: file.close() + except ImportError: + return None + + # check if it's indeed a package + if description[2] == imp.PKG_DIRECTORY: + # recursively handle the remaining name parts + pathname = _get_modpkg_path(parts[1], [pathname]) + else: + pathname = None + else: + # plain name + try: + file, pathname, description = imp.find_module( + dotted_name, pathlist) + if file: + file.close() + if description[2] not in [imp.PY_SOURCE, imp.PKG_DIRECTORY]: + pathname = None + except ImportError: + pathname = None + + return pathname + + +def getFilesForName(name): + """Get a list of module files for a filename, a module or package name, + or a directory. + """ + if not os.path.exists(name): + # check for glob chars + if containsAny(name, "*?[]"): + files = glob.glob(name) + list = [] + for file in files: + list.extend(getFilesForName(file)) + return list + + # try to find module or package + name = _get_modpkg_path(name) + if not name: + return [] + + if os.path.isdir(name): + # find all python files in directory + list = [] + os.path.walk(name, _visit_pyfiles, list) + return list + elif os.path.exists(name): + # a single file + return [name] + + return [] + + +class TokenEater: + def __init__(self, options): + self.__options = options + self.__messages = {} + self.__state = self.__waiting + self.__data = [] + self.__lineno = -1 + self.__freshmodule = 1 + self.__curfile = None + self.__encoding = None + + def __call__(self, ttype, tstring, stup, etup, line): + # dispatch +## import token +## print >> sys.stderr, 'ttype:', token.tok_name[ttype], \ +## 'tstring:', tstring + self.__state(ttype, tstring, stup[0]) + + def __waiting(self, ttype, tstring, lineno): + opts = self.__options + # Do docstring extractions, if enabled + if opts.docstrings and not opts.nodocstrings.get(self.__curfile): + # module docstring? + if self.__freshmodule: + if ttype == tokenize.STRING: + self.__addentry(safe_eval(tstring), lineno, isdocstring=1) + self.__freshmodule = 0 + elif ttype not in (tokenize.COMMENT, tokenize.NL): + self.__freshmodule = 0 + return + # class docstring? + if ttype == tokenize.NAME and tstring in ('class', 'def'): + self.__state = self.__suiteseen + return + if ttype == tokenize.NAME and tstring in opts.keywords: + self.__state = self.__keywordseen + + def __suiteseen(self, ttype, tstring, lineno): + # ignore anything until we see the colon + if ttype == tokenize.OP and tstring == ':': + self.__state = self.__suitedocstring + + def __suitedocstring(self, ttype, tstring, lineno): + # ignore any intervening noise + if ttype == tokenize.STRING: + self.__addentry(safe_eval(tstring), lineno, isdocstring=1) + self.__state = self.__waiting + elif ttype not in (tokenize.NEWLINE, tokenize.INDENT, + tokenize.COMMENT): + # there was no class docstring + self.__state = self.__waiting + + def __keywordseen(self, ttype, tstring, lineno): + if ttype == tokenize.OP and tstring == '(': + self.__data = [] + self.__lineno = lineno + self.__state = self.__openseen + else: + self.__state = self.__waiting + + def __openseen(self, ttype, tstring, lineno): + if ttype == tokenize.OP and tstring == ')': + # We've seen the last of the translatable strings. Record the + # line number of the first line of the strings and update the list + # of messages seen. Reset state for the next batch. If there + # were no strings inside _(), then just ignore this entry. + if self.__data: + self.__addentry(EMPTYSTRING.join(self.__data)) + self.__state = self.__waiting + elif ttype == tokenize.STRING: + self.__data.append(safe_eval(tstring)) + elif ttype not in [tokenize.COMMENT, token.INDENT, token.DEDENT, + token.NEWLINE, tokenize.NL]: + # warn if we see anything else than STRING or whitespace + print >> sys.stderr, _( + '*** %(file)s:%(lineno)s: Seen unexpected token "%(token)s"' + ) % { + 'token': tstring, + 'file': self.__curfile, + 'lineno': self.__lineno + } + self.__state = self.__waiting + + def __addentry(self, msg, lineno=None, isdocstring=0, iskidstring=0): + # tokenize module always return unicode strings + # even when they are in fact coded string instances + # to deal with this we use a hack: + # evaluate string's representation without leading "u" + # to force interpration as coded string + # then we decode it using already known file's encoding + if not iskidstring: + if type(msg) is str: + msg = eval(repr(msg)) + else: + msg = eval(repr(msg)[1:]) + msg = msg.decode(self.__encoding) + if lineno is None: + lineno = self.__lineno + if not msg in self.__options.toexclude: + entry = (self.__curfile, lineno) + self.__messages.setdefault(msg, {})[entry] = isdocstring + + def set_filename(self, filename): + self.__curfile = filename + self.__freshmodule = 1 + + def set_file_encoding(self, fp): + """Searches for -*- coding: -*- magic comment to find out file encoding.""" + self.__encoding = 'utf-8' # reset to default for each new file + for line in fp.readlines()[:5]: + m = re.match('#\s*-\*-\s+coding:\s+(\w+)\s+-\*-', line) + if m: + self.__encoding = m.group(1) + break + fp.seek(0) + + def contains_inline_python(self,msg): + if '${' in msg and not '$${' in msg: return True + return False + + def strip_namespace_uri(self,tag): + return tag.split('}')[-1] + + def get_text_node(self,node): + tag = re.sub('({[^}]+})?(\w+)', '\\2', node.tag) + + if node.text: + msg = node.text.strip() + if msg and not self.contains_inline_python(msg): + if tag not in ['script','style']: + self.__addentry(msg,self.strip_namespace_uri(node.tag), iskidstring=1) + + if node.getchildren(): + for child in node: self.get_text_node(child) + + if node.tail: + msg = node.tail.strip() + if msg and not self.contains_inline_python(msg): + self.__addentry(msg,self.strip_namespace_uri(node.tag), iskidstring=1) + + def extract_kid_strings(self): + if not self.__curfile: return + f = None + try: + file = open(self.__curfile) + f = ElementTree(XML( fixentities(file.read() ))) + except Exception, e: + print 'Skip %s: %s' % (self.__curfile, e) + return + + node = f.getroot() + self.get_text_node(node) + + def write(self, fp): + options = self.__options + # format without tz information + # because %Z is timezone's name, not offset + # and, say, on localized Windows XP this is non-ascii string + timestamp = time.strftime('%Y-%m-%d %H:%M') + # The time stamp in the header doesn't have the same format as that + # generated by xgettext... + t = {'time': timestamp, 'version': __version__, 'charset':'utf-8'} + print >> fp, pot_header % t + # Sort the entries. First sort each particular entry's keys, then + # sort all the entries by their first item. + reverse = {} + for k, v in self.__messages.items(): + keys = v.keys() + keys.sort() + reverse.setdefault(tuple(keys), []).append((k, v)) + rkeys = reverse.keys() + rkeys.sort() + for rkey in rkeys: + rentries = reverse[rkey] + rentries.sort() + for k, v in rentries: + isdocstring = 0 + # If the entry was gleaned out of a docstring, then add a + # comment stating so. This is to aid translators who may wish + # to skip translating some unimportant docstrings. + if reduce(operator.__add__, v.values()): + isdocstring = 1 + # k is the message string, v is a dictionary-set of (filename, + # lineno) tuples. We want to sort the entries in v first by + # file name and then by line number. + v = v.keys() + v.sort() + if not options.writelocations: + pass + # location comments are different b/w Solaris and GNU: + elif options.locationstyle == options.SOLARIS: + for filename, lineno in v: + d = {'filename': filename, 'lineno': lineno} + print >>fp, _( + '# File: %(filename)s, line: %(lineno)s') % d + elif options.locationstyle == options.GNU: + # fit as many locations on one line, as long as the + # resulting line length doesn't exceeds 'options.width' + locline = '#:' + for filename, lineno in v: + d = {'filename': filename, 'lineno': lineno} + s = _(' %(filename)s:%(lineno)s') % d + if len(locline) + len(s) <= options.width: + locline = locline + s + else: + print >> fp, locline + locline = "#:" + s + if len(locline) > 2: + print >> fp, locline + if isdocstring: + print >> fp, '#, docstring' + if k: # do not output empty msgid + print >> fp, 'msgid', normalize(k, options.escape) + print >> fp, 'msgstr ""\n' + + +def main(): + global default_keywords + try: + opts, args = getopt.getopt( + sys.argv[1:], + 'ad:UDEhk:Kno:p:S:Vvw:x:X:', + ['extract-all', 'default-domain=', 'escape', 'help', + 'keyword=', 'no-default-keywords', + 'add-location', 'no-location', 'output=', 'output-dir=', + 'style=', 'verbose', 'version', 'width=', 'exclude-file=', + 'docstrings', 'no-docstrings', 'support-unicode', + ]) + except getopt.error, msg: + usage(1, msg) + + # for holding option values + class Options: + # constants + GNU = 1 + SOLARIS = 2 + # defaults + extractall = 0 # FIXME: currently this option has no effect at all. + escape = 0 + keywords = [] + outpath = '' + outfile = 'messages.pot' + writelocations = 1 + locationstyle = GNU + verbose = 0 + width = 78 + excludefilename = '' + docstrings = 0 + nodocstrings = {} + + options = Options() + locations = {'gnu' : options.GNU, + 'solaris' : options.SOLARIS, + } + + # parse options + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-a', '--extract-all'): + options.extractall = 1 + elif opt in ('-d', '--default-domain'): + options.outfile = arg + '.pot' + elif opt in ('-E', '--escape'): + options.escape = 1 + elif opt in ('-D', '--docstrings'): + options.docstrings = 1 + elif opt in ('-k', '--keyword'): + options.keywords.append(arg) + elif opt in ('-K', '--no-default-keywords'): + default_keywords = [] + elif opt in ('-n', '--add-location'): + options.writelocations = 1 + elif opt in ('--no-location',): + options.writelocations = 0 + elif opt in ('-S', '--style'): + options.locationstyle = locations.get(arg.lower()) + if options.locationstyle is None: + usage(1, _('Invalid value for --style: %s') % arg) + elif opt in ('-o', '--output'): + options.outfile = arg + elif opt in ('-p', '--output-dir'): + options.outpath = arg + elif opt in ('-v', '--verbose'): + options.verbose = 1 + elif opt in ('-V', '--version'): + print _('pygettext.py (xgettext for Python) %s') % __version__ + sys.exit(0) + elif opt in ('-w', '--width'): + try: + options.width = int(arg) + except ValueError: + usage(1, _('--width argument must be an integer: %s') % arg) + elif opt in ('-x', '--exclude-file'): + options.excludefilename = arg + elif opt in ('-X', '--no-docstrings'): + fp = open(arg) + try: + while 1: + line = fp.readline() + if not line: + break + options.nodocstrings[line[:-1]] = 1 + finally: + fp.close() + + # calculate escapes + make_escapes(0) + + # calculate all keywords + options.keywords.extend(default_keywords) + + # initialize list of strings to exclude + if options.excludefilename: + try: + fp = open(options.excludefilename) + options.toexclude = fp.readlines() + fp.close() + except IOError: + print >> sys.stderr, _( + "Can't read --exclude-file: %s") % options.excludefilename + sys.exit(1) + else: + options.toexclude = [] + + # resolve args to module lists + expanded = [] + for arg in args: + if arg == '-': + expanded.append(arg) + else: + expanded.extend(getFilesForName(arg)) + args = expanded + + # slurp through all the files + eater = TokenEater(options) + for filename in args: + if filename == '-': + if options.verbose: + print _('Reading standard input') + fp = sys.stdin + closep = 0 + else: + if options.verbose: + print _('Working on %s') % filename + fp = open(filename) + eater.set_file_encoding(fp) + closep = 1 + try: + eater.set_filename(filename) + try: + tokenize.tokenize(fp.readline, eater) + except tokenize.TokenError, e: + print >> sys.stderr, '%s: %s, line %d, column %d' % ( + e[0], filename, e[1][0], e[1][1]) + finally: + if closep: + fp.close() + + if os.path.splitext(filename)[-1].lower() == '.kid': eater.extract_kid_strings() + + # write the output + if options.outfile == '-': + fp = sys.stdout + closep = 0 + else: + if options.outpath: + options.outfile = os.path.join(options.outpath, options.outfile) + fp = open(options.outfile, 'wt') + closep = 1 + try: + eater.write(fp) + finally: + if closep: + fp.close() + + +if __name__ == '__main__': + main() + # some more test strings + _(u'a unicode string') + # this one creates a warning + _('*** Seen unexpected token "%(token)s"') % {'token': 'test'} + _('more' 'than' 'one' 'string') |