diff options
author | astokes <astokes@ef72aa8b-4018-0410-8976-d6e080ef94d8> | 2008-10-23 13:31:29 +0000 |
---|---|---|
committer | astokes <astokes@ef72aa8b-4018-0410-8976-d6e080ef94d8> | 2008-10-23 13:31:29 +0000 |
commit | 8d443ca846f670e971f09235ca792e45239f6f81 (patch) | |
tree | dc9caa0c9414fe876707162f53b4722b54f3cdf7 | |
parent | c5bf8bbac0c3c672d6bb22d0c54c9ab479abfde3 (diff) | |
download | sos-8d443ca846f670e971f09235ca792e45239f6f81.tar.gz |
sync
git-svn-id: svn+ssh://svn.fedorahosted.org/svn/sos/trunk@532 ef72aa8b-4018-0410-8976-d6e080ef94d8
37 files changed, 1126 insertions, 819 deletions
@@ -22,14 +22,10 @@ Maintainer: Developers and Contributors: - Adam Stokes <astokes@redhat.com> - Cris Lalancette <clalance@redhat.com> - Eugene Teo <eteo@redhat.com> - John Berninger <jwb@redhat.com> - Justin Payne <jpayne@redhat.com> - Pierre Amadio <pamadio@redhat.com> - Qian Shen <qshen@redhat.com> Steve Conklin <sconklin@redhat.com> + Pierre Amadio <pamadio@redhat.com> + John Berninger <jwb@redhat.com> + Adam Stokes <astokes@redhat.com> Thanks to: diff --git a/src/extras/sysreport/functions b/src/extras/sysreport/functions index 74061d18..fff52ccd 100755 --- a/src/extras/sysreport/functions +++ b/src/extras/sysreport/functions @@ -48,14 +48,14 @@ getpciinfo() { catiffile() { if [ -d $1 ]; then - /bin/cp -x --parents -R $1 $ROOT 2>>$ROOT/$log + /bin/cp -p -x --parents -R $1 $ROOT 2>>$ROOT/$log find $ROOT/$1 -type b -o -type c | xargs rm -f 2>/dev/null || : echo -n $STATUS echo_success return 1 fi if [ -f $1 ]; then - /bin/cp --parents $1 $ROOT 2>>$ROOT/$log + /bin/cp -p --parents $1 $ROOT 2>>$ROOT/$log echo -n $STATUS echo_success return 1 diff --git a/src/extras/sysreport/sysreport.legacy b/src/extras/sysreport/sysreport.legacy index 3ffc1069..043ba026 100755 --- a/src/extras/sysreport/sysreport.legacy +++ b/src/extras/sysreport/sysreport.legacy @@ -13,8 +13,8 @@ umask 0077 UTILDIR=/usr/share/sysreport VER=`/bin/uname -r` PATH="" +PROGNAME="sysreport" DATE=`/bin/date -u +%G%m%d%k%M%S | /usr/bin/tr -d ' '` -TEMP='/tmp' function usage { echo "Sysreport is a utility that gathers information about a system's" @@ -27,17 +27,24 @@ function usage { echo " -help : show help" echo " -norpm : omit collecting information about currently installed packages" echo " -dmidecode: enable dmidecode, getting information about the hardware" + echo " -firewall : collecting the system firewall rules" + echo " -home : place sysreport outputs within home directory" + echo " -usesysrq : collecting information about sysrq-trigger" echo exit 0 } -[ $# -lt 3 ] || usage +[ $# -lt 6 ] || usage for i do case "$i" in -help) usage;; -norpm) NORPM=yes;; -dmidecode) DMIDECODE=yes;; + -firewall) FIREWALL=yes;; + -home) HOMEDIR=yes;; + -sysrq) SYSRQ_TRIGGER=yes;; + -usesysrq) SYSRQ_TRIGGER=yes;; *) usage;; esac done @@ -53,8 +60,15 @@ if [ $UID != 0 ]; then exit 1 fi -ROOT=$TEMP/sysreport-$DATE -if ( ! mkdir $ROOT >& /dev/null ) ; then +if [ "$HOMEDIR" = yes ] ; then + TEMPDIR=$HOME + ROOT=$HOME/sysreport-$DATE + /bin/mkdir $ROOT >& /dev/null +else + TEMPDIR=/tmp + ROOT=`/bin/mktemp -dq /tmp/sysreport.XXXXXXXX` +fi +if [ $? != 0 ] ; then echo "Cannot make temp dir" exit 1 fi @@ -198,7 +212,7 @@ STATUS="Collecting information about X:" catiffile "/etc/X11" STATUS="Gathering sysctl information (/proc/sys):" -catiffile "/proc/sys" +catifexec "/sbin/sysctl" "-a" STATUS="Gathering sysctl information (/etc/sysctl.conf):" catiffile "/etc/sysctl.conf" @@ -381,6 +395,9 @@ catiffile "/etc/auto.net" STATUS="Collecting LVM information:" catifexec "/usr/sbin/vgdisplay" "-vv" +STATUS="Gathering LVM setup" +catiffile "/etc/lvm" + STATUS="Collecting SCSI Tape information (/etc/stinit.def)" catiffile "/etc/stinit.def" @@ -425,23 +442,25 @@ done STATUS="Collecting information about ypbind configuration:" catiffile "/etc/yp.conf" -KERNELMIN=`/bin/uname -r | /bin/sed -e 's,[^\.]*\.,,' -e 's,\..*,,'` -ipchainsmod=`/sbin/lsmod 2>/dev/null| /bin/grep ipchains` - -if [ "$KERNELMIN" -lt 3 ] || [ -n "${ipchainsmod}" ] ; then - STATUS="Getting ipchains information:" - catifexec "/sbin/ipchains" "-nvL" -elif [ "$KERNELMIN" -gt 3 ]; then - STATUS="Getting iptables information:" - if [ -f /etc/sysconfig/iptables-config ] ; then - catiffile "/etc/sysconfig/iptables-config" +if [ "$FIREWALL" == "yes" ]; then + KERNELMIN=`/bin/uname -r | /bin/sed -e 's,[^\.]*\.,,' -e 's,\..*,,'` + ipchainsmod=`/sbin/lsmod 2>/dev/null| /bin/grep ipchains` + + if [ "$KERNELMIN" -lt 3 ] || [ -n "${ipchainsmod}" ] ; then + STATUS="Getting ipchains information:" + catifexec "/sbin/ipchains" "-nvL" + elif [ "$KERNELMIN" -gt 3 ]; then + STATUS="Getting iptables information:" + if [ -f /etc/sysconfig/iptables-config ] ; then + catiffile "/etc/sysconfig/iptables-config" + fi + STATUS="Getting iptables information (filter):" + catifexec "/sbin/iptables" "-t filter -nvL" + STATUS="Getting iptables information (mangle):" + catifexec "/sbin/iptables" "-t mangle -nvL" + STATUS="Getting iptables information (nat):" + catifexec "/sbin/iptables" "-t nat -nvL" fi - STATUS="Getting iptables information (filter):" - catifexec "/sbin/iptables" "-t filter -nvL" - STATUS="Getting iptables information (mangle):" - catifexec "/sbin/iptables" "-t mangle -nvL" - STATUS="Getting iptables information (nat):" - catifexec "/sbin/iptables" "-t nat -nvL" fi # ldap client and server config @@ -584,23 +603,22 @@ for x in `/bin/ls -d /etc/cups/*.conf 2>/dev/null` ; do catiffile "$x" done -STATUS="Getting information about printcap" -catiffile "/etc/printcap" - echo echo "Gathering information from system logs" echo STATUS="Collecting information from dmesg:" catiffile "/var/log/dmesg" -STATUS="Collecting information from /proc/sysrq-trigger" -if [ -f /proc/sysrq-trigger -a -f /proc/sys/kernel/sysrq ] ; then - sysr_state="$(/bin/cat /proc/sys/kernel/sysrq)" - echo 1 > /proc/sys/kernel/sysrq - for key in m p t ; do - echo $key > /proc/sysrq-trigger - done - echo $sysr_state > /proc/sys/kernel/sysrq +if [ "$SYSRQ_TRIGGER" == "yes" ]; then + STATUS="Collecting information from /proc/sysrq-trigger" + if [ -f /proc/sysrq-trigger -a -f /proc/sys/kernel/sysrq ] ; then + sysr_state="$(/bin/cat /proc/sys/kernel/sysrq)" + echo 1 > /proc/sys/kernel/sysrq + for key in m p t ; do + echo $key > /proc/sysrq-trigger + done + echo $sysr_state > /proc/sys/kernel/sysrq + fi fi for x in `/bin/ls /var/log/messages*` ; do @@ -684,6 +702,7 @@ if [ -f $RHNDIR/systemid ] ; then catiffile "$RHNDIR/systemid" fi fi + # Get hardware profile information (for verification with system state and RHN) if [ -x /usr/share/rhn/up2date_client/hardware.py ] ; then STATUS="Gathering RHN hardware profile information" @@ -698,6 +717,7 @@ if [ -x /usr/bin/rhn-charsets ] ; then catifexec "/usr/bin/rhn-charsets" fi +# Get SELinux information echo echo "Gathering information on SELinux setup" echo @@ -706,9 +726,8 @@ catifexec "/usr/sbin/sestatus" catifexec "rpm" "-q -V selinux-policy-targeted" catifexec "rpm" "-q -V selinux-policy-strict" -cd $TEMP +cd $TEMPDIR /bin/echo -HOSTNM=`hostname -s` /bin/echo -n "Please enter your case number (if you have one): " while read CASENUM @@ -723,9 +742,9 @@ do esac done if [ ! $CASENUM ]; then - NAME=$HOSTNM.$DATE + NAME=$PROGNAME-$HOSTNAME.$DATE else - NAME="$HOSTNM-$CASENUM.$DATE" + NAME="$PROGNAME-$HOSTNAME-$CASENUM.$DATE" fi /bin/rm -Rf $NAME /bin/mv $ROOT $NAME @@ -740,7 +759,7 @@ fi /bin/rm -Rf $NAME /bin/echo -/bin/echo "Please send $TEMP/${NAME}.${SUFFIX} to your support" +/bin/echo "Please send $TEMPDIR/${NAME}.${SUFFIX} to your support" /bin/echo "representative." /bin/echo diff --git a/src/lib/sos/helpers.py b/src/lib/sos/helpers.py index bcdee6fb..77f85b01 100755 --- a/src/lib/sos/helpers.py +++ b/src/lib/sos/helpers.py @@ -25,9 +25,29 @@ """ helper functions used by sosreport and plugins """ -import os, popen2, fcntl, select, itertools, sys, commands -from time import time -from tempfile import mkdtemp +import os, popen2, fcntl, select, sys, commands, signal +from time import time, sleep + +if sys.version_info[0] <= 2 and sys.version_info[1] <= 2: + # it's a RHEL3, activate work-arounds + # + import sos.rhel3_logging + logging = sos.rhel3_logging + + def mkdtemp(suffix = "", prefix = "temp_"): + import random + while True: + tempdir = "/tmp/%s_%d%s" % (prefix, random.randint(1,9999999), suffix) + if not os.path.exists(tempdir): break + os.mkdir(tempdir) + return tempdir + + os.path.sep = "/" + os.path.pardir = ".." +else: + # RHEL4+, business as usual + import logging + from tempfile import mkdtemp def importPlugin(pluginname, name): """ Import a plugin to extend capabilities of sosreport @@ -57,17 +77,72 @@ def makeNonBlocking(afd): fcntl.fcntl(afd, fcntl.F_SETFL, fl | os.FNDELAY) -def sosGetCommandOutput(command): +def sosGetCommandOutput(command, timeout = 300): """ Execute a command and gather stdin, stdout, and return status. """ - stime = time() - inpipe, pipe = os.popen4(command, 'r') - inpipe.close() - text = pipe.read() - sts = pipe.close() - if sts is None: sts = 0 - if text[-1:] == '\n': text = text[:-1] - return (sts, text, time()-stime) + soslog = logging.getLogger('sos') + + # Log if binary is not runnable or does not exist + for path in os.environ["PATH"].split(":"): + cmdfile = command.strip("(").split()[0] + # handle both absolute or relative paths + if ( ( not os.path.isabs(cmdfile) and os.access(os.path.join(path,cmdfile), os.X_OK) ) or \ + ( os.path.isabs(cmdfile) and os.access(cmdfile, os.X_OK) ) ): + break + else: + soslog.log(logging.VERBOSE, "binary '%s' does not exist or is not runnable" % cmdfile) + return (127, "", 0) + + # these are file descriptors, not file objects + r, w = os.pipe() + + pid = os.fork() + + if pid: + # we are the parent + os.close(w) # use os.close() to close a file descriptor + r_fd = os.fdopen(r) # turn r into a file object + stime=time() + txt = "" + sts = -1 + soslog.log(logging.VERBOSE2, 'forked command "%s" with pid %d, timeout is %d' % (command, pid, timeout) ) + while True: + # read output from pipe + ready = select.select([r], [], [], 1) + if r in ready[0]: + txt = txt + r_fd.read() + # is child still running ? + try: os.waitpid(pid, os.WNOHANG) + except: + # not running, make sure the child process gets cleaned up + try: sts = os.waitpid(pid, 0)[1] + except: pass + break + # has timeout passed ? + if time() - stime > timeout: + soslog.log(logging.VERBOSE, 'killing hung child with pid %s after %d seconds (command was "%s")' % (pid,timeout,command) ) + try: os.kill(pid, signal.SIGKILL) + except: pass + break + if txt[-1:] == '\n': txt = txt[:-1] + return (sts, txt, time()-stime) + else: + # we are the child + os.dup2(r, 0) + os.dup2(w, 1) + os.dup2(w, 2) + + import resource + maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + if not hasattr(resource, "RLIM_INFINITY"): + resource.RLIM_INFINITY = -1L + if (maxfd == resource.RLIM_INFINITY): + maxfd = MAXFD + for fd in range(3, maxfd): + try: os.close(fd) + except OSError: pass + os.execl("/bin/sh", "/bin/sh", "-c", command) + os._exit(127) # 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 @@ -92,23 +167,11 @@ def allEqual(elements): return False return True - -def commonPrefix(*sequences): +def commonPrefix(l1, l2, common = []): ''' 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 ] + if len(l1) < 1 or len(l2) < 1 or l1[0] != l2[0]: return common, [l1, l2] + return commonPrefix(l1[1:], l2[1:], common+[l1[0]]) def sosRelPath(path1, path2, sep=os.path.sep, pardir=os.path.pardir): ''' return a relative path from path1 equivalent to path path2. @@ -124,3 +187,9 @@ def sosRelPath(path1, path2, sep=os.path.sep, pardir=os.path.pardir): return path2 # leave path absolute if nothing at all in common return sep.join( [pardir]*len(u1) + u2 ) +def sosReadFile(fname): + ''' reads a file and returns its contents''' + fp = open(fname,"r") + content = fp.read() + fp.close() + return content diff --git a/src/lib/sos/plugins/autofs.py b/src/lib/sos/plugins/autofs.py index 48dd57b4..16a8d3fb 100644 --- a/src/lib/sos/plugins/autofs.py +++ b/src/lib/sos/plugins/autofs.py @@ -15,7 +15,7 @@ ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import sos.plugintools -import os, re +import os class autofs(sos.plugintools.PluginBase): """autofs server-related information @@ -24,38 +24,8 @@ class autofs(sos.plugintools.PluginBase): if self.cInfo["policy"].runlevelDefault() in self.cInfo["policy"].runlevelByService("autofs"): return True return False - - def checkdebug(self): - """ testing if autofs debug has been enabled anywhere - """ - # Global debugging - optlist=[] - opt = self.fileGrep(r"^(DEFAULT_LOGGING|DAEMONOPTIONS)=(.*)", "/etc/sysconfig/autofs") - for opt1 in opt: - for opt2 in opt1.split(" "): - optlist.append(opt2) - for dtest in optlist: - if dtest == "--debug" or dtest == "debug": - return True - - def getdaemondebug(self): - """ capture daemon debug output - """ - debugout = self.fileGrep(r"^(daemon.*)\s+(\/var\/log\/.*)", "/etc/sysconfig/autofs") - for i in debugout: - return i[1] - + def setup(self): self.addCopySpec("/etc/auto*") self.addCopySpec("/etc/sysconfig/autofs") - self.addCopySpec("/etc/init.d/autofs") - self.collectExtOutput("/bin/rpm -qV autofs") - self.collectExtOutput("/etc/init.d/autofs status") - self.collectExtOutput("ps auxwww | grep automount") - self.collectExtOutput("/bin/egrep -e 'automount|pid.*nfs' /proc/mounts") - self.collectExtOutput("/bin/mount | egrep -e 'automount|pid.*nfs'") - self.collectExtOutput("/sbin/chkconfig --list autofs") - if self.checkdebug(): - self.addCopySpec(self.getdaemondebug()) - return - + self.addCopySpec("/etc/rc.d/init.d/autofs") diff --git a/src/lib/sos/plugins/cluster.py b/src/lib/sos/plugins/cluster.py index f6169ddb..6d4de2a0 100644 --- a/src/lib/sos/plugins/cluster.py +++ b/src/lib/sos/plugins/cluster.py @@ -25,139 +25,178 @@ class cluster(sos.plugintools.PluginBase): ('taskdump', 'trigger 3 sysrq+t dumps every 5 seconds (dangerous)', 'slow', False)] def checkenabled(self): - # enable if any related package is installed - for pkg in [ "rgmanager", "luci", "ricci", "system-config-cluster", - "gfs-utils", "gnbd", "kmod-gfs", "kmod-gnbd", "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", "/proc/cluster" ]: - try: os.stat(fname) - except:pass - else: return True - - # no data related to RHCS/GFS exists - return False + rhelver = self.cInfo["policy"].rhelVersion() + if rhelver == 4: + self.packages = [ "ccs", "cman", "cman-kernel", "magma", "magma-plugins", + "rgmanager", "fence", "dlm", "dlm-kernel", "gulm", + "GFS", "GFS-kernel", "lvm2-cluster" ] + elif rhelver == 5: + self.packages = [ "rgmanager", "luci", "ricci", "system-config-cluster", + "gfs-utils", "gnbd", "kmod-gfs", "kmod-gnbd", "lvm2-cluster" ] + + self.files = [ "/etc/cluster/cluster.conf", "/proc/cluster" ] + return sos.plugintools.PluginBase.checkenabled(self) def has_gfs(self): - fp = open("/proc/mounts","r") - for line in fp.readlines(): - mntline = line.split(" ") - if mntline[2] == "gfs": - return True - fp.close() - return False + try: + if len(self.doRegexFindAll(r'^\S+\s+\S+\s+gfs\s+.*$', "/etc/mtab")): + return True + except: + return False def diagnose(self): - try: rhelver = self.cInfo["policy"].pkgByName("redhat-release")[1] - except: rhelver = None - - # FIXME: we should only run tests specific for the version, now just do them all regardless - if rhelver.startswith("5"): - # check that kernel module packages are installed for - # running kernel version - pkgs_check = [ ] - if self.has_gfs(): pkgs_check.append("kmod-gfs") - - for pkgname in pkgs_check: - if not self.cInfo["policy"].pkgByName(pkgname): - self.addDiagnose("required package is missing: %s" % pkgname) - - # 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 [ "cman", "perl-Net-Telnet", "rgmanager" ]: - if self.cInfo["policy"].pkgByName(pkg) == None: - self.addDiagnose("required package is missing: %s" % pkg) - - # let's make modules sure are loaded - mods_check = [ "dlm" ] - if self.has_gfs(): mods_check.append("gfs") - for module in mods_check: - if len(self.fileGrep("^%s " % module, "/proc/modules")) == 0: - self.addDiagnose("required module is not loaded: %s" % module) - - # check if all the needed daemons are active at sosreport time - # check if they are started at boot time in RHEL5 RHCS (rgmanager, cman) - # and GFS (gfs, ccsd, clvmd, fenced) - checkserv = [ "cman", "rgmanager" ] - if self.has_gfs(): checkserv.extend( ["gfs", "clvmd"] ) - for service in checkserv: - status, output = commands.getstatusoutput("/sbin/service %s status" % service) - if status: - 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: any cman service whose state != run ? - # Fence Domain: "default" 2 2 run - - - # is cluster quorate - if not self.is_cluster_quorate(): - self.addDiagnose("cluster node is not quorate") - - # if there is no cluster.conf, diagnose() finishes here. - try: - os.stat("/etc/cluster/cluster.conf") - except: - self.addDiagnose("/etc/cluster/cluster.conf is missing") - return - - # setup XML xpath context - xml = libxml2.parseFile("/etc/cluster/cluster.conf") - xpathContext = xml.xpathNewContext() - - # check fencing (warn on no fencing) - if len(xpathContext.xpathEval("/cluster/clusternodes/clusternode[not(fence/method/device)]")): - if self.has_gfs(): - self.addDiagnose("one or more nodes have no fencing agent configured: fencing is required for GFS to work") - else: - self.addDiagnose("one or more nodes have no fencing agent configured: the cluster infrastructure might not work as intended") - - # check fencing (warn on manual) - if len(xpathContext.xpathEval("/cluster/clusternodes/clusternode[/cluster/fencedevices/fencedevice[@agent='fence_manual']/@name=fence/method/device/@name]")): - self.addDiagnose("one or more nodes have manual fencing agent configured (data integrity is not guaranteed)") - - # if fence_ilo or fence_drac, make sure acpid is not running - hostname = commands.getoutput("/bin/uname -n").split(".")[0] - if len(xpathContext.xpathEval('/cluster/clusternodes/clusternode[@name = "%s" and /cluster/fencedevices/fencedevice[@agent="fence_rsa" or @agent="fence_drac"]/@name=fence/method/device/@name]' % hostname )): - status, output = commands.getstatusoutput("/sbin/service acpid status") - if status == 0 or self.cInfo["policy"].runlevelDefault() in self.cInfo["policy"].runlevelByService("acpid"): - self.addDiagnose("acpid is enabled, this may cause problems with your fencing method.") - - # check for fs exported via nfs without nfsid attribute - if len(xpathContext.xpathEval("/cluster/rm/service//fs[not(@fsid)]/nfsexport")): - self.addDiagnose("one or more nfs export do not have a fsid attribute set.") - - # cluster.conf file version and the in-memory cluster configuration version matches - status, cluster_version = commands.getstatusoutput("cman_tool status | grep 'Config version'") - if not status: cluster_version = cluster_version[16:] - else: cluster_version = None - conf_version = xpathContext.xpathEval("/cluster/@config_version")[0].content - - if status == 0 and conf_version != cluster_version: - self.addDiagnose("cluster.conf and in-memory configuration version differ (%s != %s)" % (conf_version, cluster_version) ) - - # make sure the first part of the lock table matches the cluster name - # and that the locking protocol is sane - cluster_name = xpathContext.xpathEval("/cluster/@name")[0].content - - for fs in self.fileGrep(r'^[^#][/\w]*\W*[/\w]*\W*gfs', "/etc/fstab"): - # for each gfs entry - fs = fs.split() - - lockproto = self.get_gfs_sb_field(fs[0], "sb_lockproto") - if lockproto and lockproto != self.get_locking_proto(): - self.addDiagnose("gfs mountpoint (%s) is using the wrong locking protocol (%s)" % (fs[0], lockproto) ) - - locktable = self.get_gfs_sb_field(fs[0], "sb_locktable") - try: locktable = locktable.split(":")[0] - except: continue - if locktable != cluster_name: - self.addDiagnose("gfs mountpoint (%s) is using the wrong locking table" % fs[0]) + rhelver = self.cInfo["policy"].rhelVersion() + + # 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) + + pkgs_check = [] + mods_check = [] + serv_check = [] + + if rhelver == 4: + pkgs_check.extend( [ "ccs", "cman", "magma", "magma-plugins", "perl-Net-Telnet", "rgmanager", "fence" ] ) + mods_check.extend( [ "cman", "dlm" ] ) + if self.has_gfs(): + mods_check.append("gfs") + serv_check.extend( [ "cman", "ccsd", "rgmanager", "fenced" ] ) + if self.has_gfs(): + serv_check.extend( ["gfs", "clvmd"] ) + elif rhelver == 5: + pkgs_check.extend ( [ "cman", "perl-Net-Telnet", "rgmanager" ] ) + mods_check.extend( [ "dlm" ] ) + if self.has_gfs(): + mods_check.extend( ["gfs", "gfs2"] ) + serv_check.extend( [ "cman", "rgmanager" ] ) + if self.has_gfs(): + serv_check.extend( ["gfs", "clvmd"] ) + + # check that kernel module packages are installed for + # running kernel version + + for modname in mods_check: + found = 0 + + if self.cInfo["policy"].allPkgsByNameRegex( "^" + modname ): + found = 1 + + status, output = commands.getstatusoutput('/sbin/modinfo -F vermagic ' + modname) + + if status == 0: + found = 2 + + if len(self.fileGrep("^%s\s+" % modname, "/proc/modules")) > 0: + found = 3 + + if found == 0: + self.addDiagnose("required kernel module is missing: %s" % modname) + elif found == 1: + self.addDiagnose("required module is not available for current kernel: %s" % modname) + elif found == 2: + self.addDiagnose("required module is available but not loaded: %s" % modname) + + for pkg in pkgs_check: + if self.cInfo["policy"].pkgByName(pkg) == None: + self.addDiagnose("required package is missing: %s" % pkg) + + if rhelver == "4": + # (dlm, dlm-kernel) || gulm + if not ((self.cInfo["policy"].pkgByName("dlm") and self.cInfo["policy"].pkgByName("dlm-kernel")) or self.cInfo["policy"].pkgByName("gulm")): + self.addDiagnose("required packages are missing: (dlm, dlm-kernel) || gulm") + + # 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 serv_check: + status, output = commands.getstatusoutput("/sbin/service %s status &> /dev/null" % service) + if status != 0: + 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: missing important cman services + # FIXME: any cman service whose state != run ? + # Fence Domain: "default" 2 2 run - + + # is cluster quorate + if not self.is_cluster_quorate(): + self.addDiagnose("cluster node is not quorate") + + # if there is no cluster.conf, diagnose() finishes here. + try: + os.stat("/etc/cluster/cluster.conf") + except: + self.addDiagnose("/etc/cluster/cluster.conf is missing") + return + + # setup XML xpath context + xml = libxml2.parseFile("/etc/cluster/cluster.conf") + xpathContext = xml.xpathNewContext() + + # make sure that the node names are valid according to RFC 2181 + for hostname in xpathContext.xpathEval('/cluster/clusternodes/clusternode/@name'): + if not re.match('^[a-zA-Z]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$', hostname.content): + self.addDiagnose("node name (%s) contains invalid characters" % hostname.content) + + # do not rely on DNS to resolve node names, must have them in /etc/hosts + for hostname in xpathContext.xpathEval('/cluster/clusternodes/clusternode/@name'): + if len(self.fileGrep(r'^.*\W+%s' % hostname.content , "/etc/hosts")) == 0: + self.addDiagnose("node %s is not defined in /etc/hosts" % hostname.content) + + # check fencing (warn on no fencing) + if len(xpathContext.xpathEval("/cluster/clusternodes/clusternode[not(fence/method/device)]")): + if self.has_gfs(): + self.addDiagnose("one or more nodes have no fencing agent configured: fencing is required for GFS to work") + else: + self.addDiagnose("one or more nodes have no fencing agent configured: the cluster infrastructure might not work as intended") + + # check fencing (warn on manual) + if len(xpathContext.xpathEval("/cluster/clusternodes/clusternode[/cluster/fencedevices/fencedevice[@agent='fence_manual']/@name=fence/method/device/@name]")): + self.addDiagnose("one or more nodes have manual fencing agent configured (data integrity is not guaranteed)") + + # if fence_ilo or fence_drac, make sure acpid is not running + hostname = commands.getoutput("/bin/uname -n").split(".")[0] + if len(xpathContext.xpathEval('/cluster/clusternodes/clusternode[@name = "%s" and /cluster/fencedevices/fencedevice[@agent="fence_rsa" or @agent="fence_drac"]/@name=fence/method/device/@name]' % hostname )): + status, output = commands.getstatusoutput("/sbin/service acpid status") + if status == 0 or self.cInfo["policy"].runlevelDefault() in self.cInfo["policy"].runlevelByService("acpid"): + self.addDiagnose("acpid is enabled, this may cause problems with your fencing method.") + + # check for fs exported via nfs without nfsid attribute + if len(xpathContext.xpathEval("/cluster/rm/service//fs[not(@fsid)]/nfsexport")): + self.addDiagnose("one or more nfs export do not have a fsid attribute set.") + + # cluster.conf file version and the in-memory cluster configuration version matches + status, cluster_version = commands.getstatusoutput("cman_tool status | grep 'Config version'") + if not status: cluster_version = cluster_version[16:] + else: cluster_version = None + conf_version = xpathContext.xpathEval("/cluster/@config_version")[0].content + + if status == 0 and conf_version != cluster_version: + self.addDiagnose("cluster.conf and in-memory configuration version differ (%s != %s)" % (conf_version, cluster_version) ) + + status, output = commands.getstatusoutput("/usr/sbin/rg_test test /etc/cluster/cluster.conf") + if output.find("Error: ") > 0: + self.addDiagnose("configuration errors are present according to rg_test") + + # make sure the first part of the lock table matches the cluster name + # and that the locking protocol is sane + cluster_name = xpathContext.xpathEval("/cluster/@name")[0].content + + for fs in self.fileGrep(r'^[^#][/\w]*\W*[/\w]*\W*gfs', "/etc/fstab"): + # for each gfs entry + fs = fs.split() + lockproto = self.get_gfs_sb_field(fs[0], "sb_lockproto") + if lockproto and lockproto != self.get_locking_proto(): + self.addDiagnose("gfs mountpoint (%s) is using the wrong locking protocol (%s)" % (fs[0], lockproto) ) + + locktable = self.get_gfs_sb_field(fs[0], "sb_locktable") + try: locktable = locktable.split(":")[0] + except: continue + if locktable != cluster_name: + self.addDiagnose("gfs mountpoint (%s) is using the wrong locking table" % fs[0]) def setup(self): self.collectExtOutput("/sbin/fdisk -l") @@ -175,9 +214,9 @@ class cluster(sos.plugintools.PluginBase): self.collectExtOutput("/sbin/ipvsadm -L") - if self.isOptionEnabled('gfslockdump'): self.do_gfslockdump() - if self.isOptionEnabled('lockdump'): self.do_lockdump() - if self.isOptionEnabled('taskdump'): self.do_taskdump() + if self.getOption('gfslockdump'): self.do_gfslockdump() + if self.getOption('lockdump'): self.do_lockdump() + if self.getOption('taskdump'): self.do_taskdump() return @@ -194,20 +233,24 @@ class cluster(sos.plugintools.PluginBase): self.addCopySpec("/var/log/messages") def do_lockdump(self): - try: - fp = open("/proc/cluster/services","r") - except: - return - for line in fp.readlines(): - if line[0:14] == "DLM Lock Space": - try: - lockspace = line.split('"')[1] - except: - pass - else: - commands.getstatusoutput("echo %s > /proc/cluster/dlm_locks" % lockspace) - self.collectOutputNow("cat /proc/cluster/dlm_locks", root_symlink = "dlm_locks_%s" % lockspace) - fp.close() + status, output = commands.getstatusoutput("cman_tool services") + if status: + # command somehow failed + return False + + import re + + rhelver = self.get_redhat_release() + + if rhelver == "4": + regex = r'^DLM Lock Space:\s*"([^"]*)".*$' + elif rhelver == "5Server" or rhelver == "5Client": + regex = r'^dlm\s+[^\s]+\s+([^\s]+)\s.*$' + + reg=re.compile(regex,re.MULTILINE) + for lockspace in reg.findall(output): + commands.getstatusoutput("echo %s > /proc/cluster/dlm_locks" % lockspace) + self.collectOutputNow("cat /proc/cluster/dlm_locks", root_symlink = "dlm_locks_%s" % lockspace) def get_locking_proto(self): # FIXME: what's the best way to find out ? @@ -215,15 +258,11 @@ class cluster(sos.plugintools.PluginBase): return "lock_gulm" def do_gfslockdump(self): - fp = open("/proc/mounts","r") - for line in fp.readlines(): - mntline = line.split(" ") - if mntline[2] == "gfs": - self.collectExtOutput("/sbin/gfs_tool lockdump %s" % mntline[1], root_symlink = "gfs_lockdump_" + self.mangleCommand(mntline[1]) ) - fp.close() - - def do_rgmgr_bt(self): - # FIXME: threads backtrace + for mntpoint in self.doRegexFindAll(r'^\S+\s+([^\s]+)\s+gfs\s+.*$', "/proc/mounts"): + self.collectExtOutput("/sbin/gfs_tool lockdump %s" % mntpoint, root_symlink = "gfs_lockdump_" + self.mangleCommand(mntpoint) ) + + def do_rgmanager_bt(self): + # FIXME: threads backtrace via SIGALRM return def postproc(self): @@ -233,7 +272,7 @@ class cluster(sos.plugintools.PluginBase): def is_cluster_quorate(self): output = commands.getoutput("cman_tool status | grep '^Membership state: '") try: - if output.split(":")[1].strip() == "Cluster-Member": + if output[18:] == "Cluster-Member": return True else: return False diff --git a/src/lib/sos/plugins/devicemapper.py b/src/lib/sos/plugins/devicemapper.py index 38009689..5a1b63c2 100644 --- a/src/lib/sos/plugins/devicemapper.py +++ b/src/lib/sos/plugins/devicemapper.py @@ -22,11 +22,6 @@ class devicemapper(sos.plugintools.PluginBase): optionList = [("lvmdump", 'collect raw metadata from PVs', 'slow', False)] - def do_lvmdump(self): - """Collects raw metadata directly from the PVs using dd - """ - sosGetCommandOutput("lvmdump -d %s" % os.path.join(self.cInfo['dstroot'],"lvmdump")) - def setup(self): self.collectExtOutput("/sbin/dmsetup info -c") self.collectExtOutput("/sbin/dmsetup table") @@ -45,17 +40,18 @@ class devicemapper(sos.plugintools.PluginBase): self.addCopySpec("/var/lib/multipath/bindings") self.collectExtOutput("/sbin/multipath -v4 -ll") - self.collectExtOutput("/usr/bin/systool -v -c -b scsi") + self.collectExtOutput("/usr/bin/systool -v -C -b scsi") self.collectExtOutput("/bin/ls -laR /dev") self.collectExtOutput("/bin/ls -laR /sys/block") if self.getOption('lvmdump'): - self.do_lvmdump() + sosGetCommandOutput("lvmdump -d %s" % os.path.join(self.cInfo['dstroot'],"lvmdump")) - for disk in os.listdir("/sys/block"): - if disk in [ ".", ".." ] or disk.startswith("ram"): - continue - self.collectExtOutput("/usr/bin/udevinfo -ap /sys/block/%s" % (disk)) + if os.path.isdir("/sys/block"): + for disk in os.listdir("/sys/block"): + if disk in [ ".", ".." ] or disk.startswith("ram"): + continue + self.collectExtOutput("/usr/bin/udevinfo -ap /sys/block/%s" % (disk)) return diff --git a/src/lib/sos/plugins/filesys.py b/src/lib/sos/plugins/filesys.py index bec046f2..93645314 100644 --- a/src/lib/sos/plugins/filesys.py +++ b/src/lib/sos/plugins/filesys.py @@ -13,7 +13,7 @@ ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import sos.plugintools -import commands +import os class filesys(sos.plugintools.PluginBase): """information on filesystems @@ -29,12 +29,11 @@ class filesys(sos.plugintools.PluginBase): self.addCopySpec("/etc/mdadm.conf") self.collectExtOutput("/bin/df -al", root_symlink = "df") - self.collectExtOutput("/usr/sbin/lsof -b +M -n -l", root_symlink = "lsof") self.collectExtOutput("/sbin/blkid") - + self.collectExtOutput("/sbin/fdisk -l", root_symlink = "fdisk-l") for extfs in self.doRegexFindAll(r"^(/dev/.+) on .+ type ext.\s+", mounts): - self.collectExtOutput("/sbin/dumpe2fs %s" % (extfs)) - return + self.collectExtOutput("/sbin/tune2fs -l %s" % (extfs)) + return diff --git a/src/lib/sos/plugins/general.py b/src/lib/sos/plugins/general.py index 648dee75..434bfdaa 100644 --- a/src/lib/sos/plugins/general.py +++ b/src/lib/sos/plugins/general.py @@ -20,25 +20,26 @@ class general(sos.plugintools.PluginBase): """basic system information """ - optionList = [("syslogsize", "max size (MiB) to collect per syslog file", "", 15)] + optionList = [("syslogsize", "max size (MiB) to collect per syslog file", "", 15), + ("all_logs", "collect all log files defined in syslog.conf", "", False)] def setup(self): self.addCopySpec("/etc/redhat-release") self.addCopySpec("/etc/fedora-release") + self.addCopySpec("/etc/inittab") + self.addCopySpec("/etc/sos.conf") 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.addCopySpecLimit("/var/log/messages.*", sizelimit = self.getOption("syslogsize")) self.addCopySpec("/var/log/secure") - self.addCopySpecLimit("/var/log/secure.*", sizelimit = self.isOptionEnabled("syslogsize")) + self.addCopySpecLimit("/var/log/secure.*", sizelimit = self.getOption("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") - self.addCopySpec("/root/anaconda-ks.cfg") self.collectExtOutput("/bin/env") if self.getOption('all_logs'): diff --git a/src/lib/sos/plugins/hardware.py b/src/lib/sos/plugins/hardware.py index ba10a356..ad4a5537 100644 --- a/src/lib/sos/plugins/hardware.py +++ b/src/lib/sos/plugins/hardware.py @@ -36,23 +36,12 @@ class hardware(sos.plugintools.PluginBase): 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 "lspci -nvv:" ; /bin/echo ; /sbin/lspci -nvv""", suggest_filename = "lspci", root_symlink = "lspci") + self.collectExtOutput("""/bin/echo -e "lspci:\n" ; /sbin/lspci ; /bin/echo -e "\nlspci -nvv:\n" ; /sbin/lspci -nvv ; /bin/echo -e "\nlspci -tv:\n" ; /sbin/lspci -tv""", suggest_filename = "lspci", root_symlink = "lspci") self.collectExtOutput("/usr/sbin/dmidecode", root_symlink = "dmidecode") - # FIXME: if arch == i386: -# self.collectExtOutput("/usr/sbin/x86info -a") - - # 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:]) + if self.cInfo["policy"].getArch().endswith("386"): + self.collectExtOutput("/usr/sbin/x86info -a") self.collectExtOutput("/sbin/lsusb") self.collectExtOutput("/usr/bin/lshal") diff --git a/src/lib/sos/plugins/initrd.py b/src/lib/sos/plugins/initrd.py index 83356548..7bd7e1f3 100644 --- a/src/lib/sos/plugins/initrd.py +++ b/src/lib/sos/plugins/initrd.py @@ -20,9 +20,9 @@ class initrd(sos.plugintools.PluginBase): """ def setup(self): for initrd in glob.glob('/boot/initrd-*.img'): - self.collectExtOutput("/bin/zcat "+initrd+" | /bin/cpio "+ + self.collectExtOutput("/bin/zcat "+initrd+" | /bin/cpio "+ "--extract --to-stdout init" ) return def defaultenabled(self): - return False + return False diff --git a/src/lib/sos/plugins/kernel.py b/src/lib/sos/plugins/kernel.py index 8ca53b66..4fb0a864 100644 --- a/src/lib/sos/plugins/kernel.py +++ b/src/lib/sos/plugins/kernel.py @@ -18,7 +18,7 @@ import commands, os, re class kernel(sos.plugintools.PluginBase): """kernel related information """ - optionList = [("modinfo", 'gathers module information on all modules', 'fast', True), + optionList = [("modinfo", 'gathers information on all kernel modules', 'fast', True), ('sysrq', 'trigger sysrq+[m,p,t] dumps', 'fast', False)] moduleFile = "" taintList = [ @@ -28,7 +28,7 @@ class kernel(sos.plugintools.PluginBase): {'regex':'vxportal*', 'description':'Veritas module'}, {'regex':'vxdmp*', 'description':'Veritas dynamic multipathing module'}, {'regex':'vxio*', 'description':'Veritas module'}, - {'regex':'vxspec*"', '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'}, @@ -49,22 +49,22 @@ class kernel(sos.plugintools.PluginBase): 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) + + if self.getOption('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/sysctl -a") self.collectExtOutput("/sbin/ksyms") self.addCopySpec("/sys/module/*/parameters") 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("/lib/modules/%s/modules.dep" % self.cInfo["policy"].kernelVersion()) self.addCopySpec("/etc/conf.modules") self.addCopySpec("/etc/modules.conf") self.addCopySpec("/etc/modprobe.conf") @@ -72,25 +72,18 @@ class kernel(sos.plugintools.PluginBase): self.addCopySpec("/proc/cmdline") self.addCopySpec("/proc/driver") self.addCopySpec("/proc/sys/kernel/tainted") - # FIXME: both RHEL4 and RHEL5 don't need sysrq to be enabled to trigger via sysrq-trigger - 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") - + + if self.getOption('sysrq') and os.access("/proc/sysrq-trigger", os.W_OK): + for key in ['m', 'p', 't']: + commands.getoutput("/bin/echo %s > /proc/sysrq-trigger" % (key,)) + self.addCopySpec("/var/log/messages") + return - def analyze(self): - infd = open("/proc/modules", "r") - modules = infd.readlines() - infd.close() + def diagnose(self): - for modname in modules: + infd = open("/proc/modules", "r") + for modname in infd.readlines(): 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): @@ -99,13 +92,17 @@ class kernel(sos.plugintools.PluginBase): 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") + self.addDiagnose("loaded module %s differs from the one present on the file-system" % modname) # 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. + infd.close() + + def analyze(self): + savedtaint = os.path.join(self.cInfo['dstroot'], "/proc/sys/kernel/tainted") infd = open(savedtaint, "r") line = infd.read() @@ -114,12 +111,10 @@ class kernel(sos.plugintools.PluginBase): 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: diff --git a/src/lib/sos/plugins/ldap.py b/src/lib/sos/plugins/ldap.py index 318a3ba9..b1a48420 100644 --- a/src/lib/sos/plugins/ldap.py +++ b/src/lib/sos/plugins/ldap.py @@ -13,14 +13,37 @@ ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import sos.plugintools +import os class ldap(sos.plugintools.PluginBase): """LDAP related information """ + def checkenabled(self): + self.packages = [ "openldap" ] + self.files = [ "/etc/openldap/ldap.conf" ] + return sos.plugintools.PluginBase.checkenabled(self) + + def get_ldap_opts(self): + # capture /etc/openldap/ldap.conf options in dict + # FIXME: possibly not hardcode these options in? + ldapopts=["URI","BASE","TLS_CACERTDIR"] + results={} + tmplist=[] + for i in ldapopts: + t=self.doRegexFindAll(r"^(%s)\s+(.*)" % i,"/etc/openldap/ldap.conf") + for x in t: + results[x[0]]=x[1].rstrip("\n") + return results + + def diagnose(self): + # Validate ldap client options + ldapopts=self.get_ldap_opts() + if ldapopts.has_key("TLS_CACERTDIR") and not os.path.exists(ldapopts["TLS_CACERTDIR"]): + self.addDiagnose("%s does not exist and can cause connection issues involving TLS" % ldapopts["TLS_CACERTDIR"]) + 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***") diff --git a/src/lib/sos/plugins/named.py b/src/lib/sos/plugins/named.py index 421143cb..bb0d079e 100644 --- a/src/lib/sos/plugins/named.py +++ b/src/lib/sos/plugins/named.py @@ -20,21 +20,14 @@ class named(sos.plugintools.PluginBase): """named related information """ def checkenabled(self): - if self.cInfo["policy"].pkgByName("bind") or os.path.exists("/etc/named.conf") or os.path.exists("/etc/sysconfig/named"): - return True - return False + self.files = [ "/etc/named.conf", "/etc/sysconfig/named" ] + self.packages = [ "bind", "bind-chroot" ] + return sos.plugintools.PluginBase.checkenabled(self) 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 + self.addCopySpec("/etc/named.boot") + self.addCopySpec("/etc/named.conf") + self.addCopySpec("/etc/sysconfig/named") + self.addCopySpec("/var/named") + self.addForbiddenPath('/var/named/chroot/proc') + self.addForbiddenPath('/var/named/chroot/dev') diff --git a/src/lib/sos/plugins/networking.py b/src/lib/sos/plugins/networking.py index 080ae694..164ed43a 100644 --- a/src/lib/sos/plugins/networking.py +++ b/src/lib/sos/plugins/networking.py @@ -24,14 +24,7 @@ class networking(sos.plugintools.PluginBase): """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 + out=self.doRegexFindAll(r"^(eth\d+)\D", ifconfigFile) return out def collectIPTable(self,tablename): @@ -60,20 +53,19 @@ class networking(sos.plugintools.PluginBase): self.addCopySpec("/etc/resolv.conf") ifconfigFile=self.collectOutputNow("/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 -s") self.collectExtOutput("/bin/netstat -neopa", root_symlink = "netstat") - # FIXME: we should collect "ip route table <tablename>" for all tables (from "ip rule") + self.collectExtOutput("/sbin/ip route show table all") self.collectExtOutput("/sbin/ip link") self.collectExtOutput("/sbin/ip address") self.collectExtOutput("/sbin/ifenslave -a") if ifconfigFile: for eth in self.get_interface_name(ifconfigFile): self.collectExtOutput("/sbin/ethtool "+eth) - if self.isOptionEnabled("traceroute"): + if self.getOption("traceroute"): self.collectExtOutput("/bin/traceroute -n rhn.redhat.com") return diff --git a/src/lib/sos/plugins/nfsserver.py b/src/lib/sos/plugins/nfsserver.py index 9564aa22..1e352032 100644 --- a/src/lib/sos/plugins/nfsserver.py +++ b/src/lib/sos/plugins/nfsserver.py @@ -35,7 +35,10 @@ class nfsserver(sos.plugintools.PluginBase): def setup(self): self.addCopySpec("/etc/exports") + self.addCopySpec("/var/lib/nfs/etab") + self.addCopySpec("/var/lib/nfs/xtab") + self.addCopySpec("/var/lib/nfs/rmtab") self.collectExtOutput("/usr/sbin/rpcinfo -p localhost") - self.collectExtOutput("/usr/sbin/nfsstat") + self.collectExtOutput("/usr/sbin/nfsstat -a") return diff --git a/src/lib/sos/plugins/pam.py b/src/lib/sos/plugins/pam.py index 8164bba3..1a50811b 100644 --- a/src/lib/sos/plugins/pam.py +++ b/src/lib/sos/plugins/pam.py @@ -20,6 +20,6 @@ class pam(sos.plugintools.PluginBase): def setup(self): self.addCopySpec("/etc/pam.d") self.addCopySpec("/etc/security") - self.collectExtOutput("/bin/ls -laF /lib/security/pam_*so") + self.collectExtOutput("/bin/ls -laF /lib/security") return diff --git a/src/lib/sos/plugins/process.py b/src/lib/sos/plugins/process.py index 47e37edf..0d89e06b 100644 --- a/src/lib/sos/plugins/process.py +++ b/src/lib/sos/plugins/process.py @@ -25,6 +25,7 @@ class process(sos.plugintools.PluginBase): self.collectExtOutput("/bin/ps auxwwwm") self.collectExtOutput("/bin/ps alxwww") self.collectExtOutput("/usr/bin/pstree", root_symlink = "pstree") + self.collectExtOutput("/usr/sbin/lsof -b +M -n -l", root_symlink = "lsof") return def find_mountpoint(s): @@ -40,7 +41,7 @@ class process(sos.plugintools.PluginBase): line = line.split() if line[0] == "D": # keep an eye on the process to see if the stat changes - for inc in range(1,5): + for inc in range(1,10): try: if len(self.fileGrep("^State: D", " /proc/%d/status" % int(line[1]))) == 0: # status is not D, good. let's get out of the loop. diff --git a/src/lib/sos/plugins/rpm.py b/src/lib/sos/plugins/rpm.py index d9f1e79b..f1c09e33 100644 --- a/src/lib/sos/plugins/rpm.py +++ b/src/lib/sos/plugins/rpm.py @@ -22,12 +22,12 @@ class rpm(sos.plugintools.PluginBase): def setup(self): self.addCopySpec("/var/log/rpmpkgs") - - if self.isOptionEnabled("rpmq"): - self.collectExtOutput("/bin/rpm -qa --qf=\"%{NAME}-%{VERSION}-%{RELEASE}-%{ARCH}~~%{INSTALLTIME:date}\n\"|/bin/awk -F ~~ '{printf \"%-60s%s\\n\",$1,$2}'", 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") + + if self.getOption("rpmq"): + self.collectExtOutput("/bin/rpm -qa --qf \"%{NAME}-%{VERSION}-%{RELEASE}-%{ARCH}\n\"", root_symlink = "installed-rpms") + + if self.getOption("rpmva"): + self.eta_weight += 1500 # this plugins takes 200x longer (for ETA) + self.collectExtOutput("/bin/rpm -Va", root_symlink = "rpm-Va", timeout = 3600) return diff --git a/src/lib/sos/plugins/s390.py b/src/lib/sos/plugins/s390.py index 39c16c07..e9bfbb6f 100644 --- a/src/lib/sos/plugins/s390.py +++ b/src/lib/sos/plugins/s390.py @@ -38,10 +38,8 @@ class s390(sos.plugintools.PluginBase): self.addCopySpec("/proc/crypto") self.addCopySpec("/proc/dasd/devices") self.addCopySpec("/proc/dasd/statistics") - self.addCopySpec("/proc/misc") self.addCopySpec("/proc/qeth") self.addCopySpec("/proc/qeth_perf") - self.addCopySpec("/proc/qeth_ipa_takeover") self.addCopySpec("/proc/sys/appldata/*") self.addCopySpec("/proc/sys/kernel/hz_timer") self.addCopySpec("/proc/sysinfo") @@ -53,15 +51,11 @@ class s390(sos.plugintools.PluginBase): self.addCopySpec("/etc/zfcp.conf") self.addCopySpec("/etc/sysconfig/dumpconf") self.addCopySpec("/etc/src_vipa.conf") - self.addCopySpec("/etc/ccwgroup.conf") - self.addCopySpec("/etc/chandev.conf") self.collectExtOutput("/sbin/lscss") self.collectExtOutput("/sbin/lsdasd") self.collectExtOutput("/sbin/lsqeth") self.collectExtOutput("/sbin/lszfcp") self.collectExtOutput("/sbin/lstape") - self.collectExtOutput("find /sys -type -f") - self.collectExtOutput("find /proc/s390dbf -type -f") self.collectExtOutput("/sbin/qethconf list_all") dasdDev = commands.getoutput("ls /dev/dasd?") for x in dasdDev.split('\n'): diff --git a/src/lib/sos/plugins/selinux.py b/src/lib/sos/plugins/selinux.py index 897c3991..5f0c1f40 100644 --- a/src/lib/sos/plugins/selinux.py +++ b/src/lib/sos/plugins/selinux.py @@ -19,18 +19,32 @@ class selinux(sos.plugintools.PluginBase): """selinux related information """ def setup(self): - self.addCopySpec("/etc/selinux/*") + # sestatus is always collected in checkenabled() + 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") + self.eta_weight += 120 # this plugins takes 120x longer (for ETA) + self.collectExtOutput("/sbin/fixfiles check") + + self.addForbiddenPath("/etc/selinux/targeted") + return def checkenabled(self): # is selinux enabled ? try: - if commands.getoutput("/usr/sbin/sestatus").split(":")[1].strip() == "disabled": + if self.collectOutputNow("/usr/sbin/sestatus", root_symlink = "sestatus").split(":")[1].strip() == "disabled": return False except: pass return True + + def analyze(self): + # Check for SELinux denials and capture raw output from sealert + if self.cInfo["policy"].runlevelDefault() in self.cInfo["policy"].runlevelByService("setroubleshoot"): + # TODO: fixup regex for more precise matching + sealert=doRegexFindAll(r"^.*setroubleshoot:.*(sealert\s-l\s.*)","/var/log/messages") + if sealert: + for i in sealert: + self.collectExtOutput("%s" % i) + self.addAlert("There are numerous selinux errors present and "+ + "possible fixes stated in the sealert output.") diff --git a/src/lib/sos/plugins/squid.py b/src/lib/sos/plugins/squid.py index fdd3b8cf..3e9b3d8b 100644 --- a/src/lib/sos/plugins/squid.py +++ b/src/lib/sos/plugins/squid.py @@ -19,9 +19,9 @@ class squid(sos.plugintools.PluginBase): """squid related information """ def checkenabled(self): - if self.cInfo["policy"].pkgByName("squid") != None or os.path.exists("/etc/squid/squid.conf"): - return True - return False + self.files = [ "/etc/squid/squid.conf" ] + self.packages = [ "squid" ] + return sos.plugintools.PluginBase.checkenabled(self) def setup(self): self.addCopySpec("/etc/squid/squid.conf") diff --git a/src/lib/sos/plugins/startup.py b/src/lib/sos/plugins/startup.py index 59014aaa..a0d3e400 100644 --- a/src/lib/sos/plugins/startup.py +++ b/src/lib/sos/plugins/startup.py @@ -19,7 +19,6 @@ class startup(sos.plugintools.PluginBase): """ def setup(self): self.addCopySpec("/etc/rc.d") - self.collectExtOutput("/sbin/chkconfig --list", root_symlink = "chkconfig") self.collectExtOutput("/sbin/runlevel") return diff --git a/src/lib/sos/plugins/systemtap.py b/src/lib/sos/plugins/systemtap.py index b99ce0cf..beab832c 100644 --- a/src/lib/sos/plugins/systemtap.py +++ b/src/lib/sos/plugins/systemtap.py @@ -17,13 +17,18 @@ import sos.plugintools class systemtap(sos.plugintools.PluginBase): - """SystemTap pre-requisites information + """SystemTap information """ + def checkenabled(self): + self.files = [ "/usr/bin/stap" ] + self.packages = [ "systemtap", "systemtap-runtime" ] + return sos.plugintools.PluginBase.checkenabled(self) + def setup(self): # requires systemtap, systemtap-runtime, kernel-devel, # kernel-debuginfo, kernel-debuginfo-common + # FIXME: do not use rpm -qa 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/src/lib/sos/plugins/x11.py b/src/lib/sos/plugins/x11.py index 4abd8782..755352ef 100644 --- a/src/lib/sos/plugins/x11.py +++ b/src/lib/sos/plugins/x11.py @@ -19,16 +19,19 @@ class x11(sos.plugintools.PluginBase): """X related information """ def checkenabled(self): - try: os.stat("/etc/X11") - except: pass - else: return True - - return False + try:os.stat("/etc/X11") + except:pass + else:return True + return False 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.'") + + self.addForbiddenPath("/etc/X11/X") + self.addForbiddenPath("/etc/X11/fontpath.d") + return diff --git a/src/lib/sos/plugins/xen.py b/src/lib/sos/plugins/xen.py index 28c0ed43..d6daec63 100644 --- a/src/lib/sos/plugins/xen.py +++ b/src/lib/sos/plugins/xen.py @@ -38,11 +38,6 @@ class xen(sos.plugintools.PluginBase): return False return True - def is_running_xenstored(self): - xs_pid = os.popen("pidof xenstored").read() - xs_pidnum = re.split('\n$',xs_pid)[0] - return xs_pidnum.isdigit() - def domCollectProc(self): self.addCopySpec("/proc/xen/balloon") self.addCopySpec("/proc/xen/capabilities") @@ -73,17 +68,7 @@ class xen(sos.plugintools.PluginBase): self.collectExtOutput("/usr/sbin/xm info") self.collectExtOutput("/usr/sbin/brctl show") self.domCollectProc() - self.addCopySpec("/sys/hypervisor/version") - self.addCopySpec("/sys/hypervisor/compilation") - self.addCopySpec("/sys/hypervisor/properties") - self.addCopySpec("/sys/hypervisor/type") - if is_xenstored_running(): - self.addCopySpec("/sys/hypervisor/uuid") - self.collectExtOutput("/usr/bin/xenstore-ls") - else: - # we need tdb instead of xenstore-ls if cannot get it. - self.addCopySpec("/var/lib/xenstored/tdb") - + 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 diff --git a/src/lib/sos/plugins/yum.py b/src/lib/sos/plugins/yum.py index 1f389d30..672451ee 100644 --- a/src/lib/sos/plugins/yum.py +++ b/src/lib/sos/plugins/yum.py @@ -22,22 +22,17 @@ class yum(sos.plugintools.PluginBase): optionList = [("yumlist", "list repositories and packages", "slow", False)] def checkenabled(self): - if self.cInfo["policy"].pkgByName("yum") or os.path.exists("/etc/yum.conf"): - return True - return False + self.files = [ "/etc/yum.conf" ] + self.packages = [ "yum" ] + return sos.plugintools.PluginBase.checkenabled(self) - def diagnose(self): - # FIXME: diagnose should only report actual problems, disabling this for now. - return True + def analyze(self): # repo sanity checking # TODO: elaborate/validate actual repo files, however this directory should # be empty on RHEL 5+ systems. - try: rhelver = self.cInfo["policy"].pkgDictByName("redhat-release")[0] - except: rhelver = None - - if rhelver == "5" or True: - if len(os.listdir('/etc/yum.repos.d/')): - self.addDiagnose("/etc/yum.repos.d/ contains additional repository "+ + if self.cInfo["policy"].rhelVersion() == 5: + if len(os.listdir("/etc/yum.repos.d/")): + self.addAlert("/etc/yum.repos.d/ contains additional repository "+ "information and can cause rpm conflicts.") def setup(self): @@ -47,7 +42,7 @@ class yum(sos.plugintools.PluginBase): self.addCopySpec("/etc/yum.conf") self.addCopySpec("/var/log/yum.log") - if self.isOptionEnabled("yumlist"): + if self.getOption("yumlist"): # 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 diff --git a/src/lib/sos/plugintools.py b/src/lib/sos/plugintools.py index fc4d4b97..f9e10ae1 100644 --- a/src/lib/sos/plugintools.py +++ b/src/lib/sos/plugintools.py @@ -30,11 +30,31 @@ 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 +import os, os.path, sys, string, glob, re, traceback +import shutil from stat import * from time import time +# RHEL3 doesn't have a logging module, activate work-around +try: + import logging +except ImportError: + import sos.rhel3_logging + logging = sos.rhel3_logging + +# python < 2.4 (RHEL3 and RHEL4) doesn't have format_exc, activate work-around +if sys.version_info[0] <= 2 and sys.version_info[1] < 4: + def format_exc(): + import StringIO + + output = StringIO.StringIO() + traceback.print_exc(file = output) + toret = output.getvalue() + output.close() + return toret + + traceback.format_exc = format_exc + class PluginBase: """ Base class for plugins @@ -65,6 +85,11 @@ class PluginBase: self.time_start = None self.time_stop = None + self.packages = [] + self.files = [] + + self.must_exit = False + self.soslog = logging.getLogger('sos') # get the option list into a dictionary @@ -94,10 +119,10 @@ class PluginBase: except KeyboardInterrupt: raise KeyboardInterrupt except Exception, e: - self.soslog.log(logging.VERBOSE, "Problem at path %s (%s)" % (abspath,e)) + self.soslog.log(logging.VERBOSE, "problem at path %s (%s)" % (abspath,e)) break return False - + def doRegexFindAll(self,regex,fname): ''' Return a list of all non overlapping matches in the string(s) ''' @@ -109,7 +134,7 @@ class PluginBase: for i in reg.findall(content): out.append(i) return out - + # Methods for copying files and shelling out def doCopyFileOrDir(self, srcpath): # pylint: disable-msg = R0912 @@ -126,113 +151,103 @@ class PluginBase: if copyProhibited: return '' + if not os.path.exists(srcpath): + self.soslog.debug("file or directory %s does not exist" % srcpath) + 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 + + # FIXME: ignore directories for now + if os.path.isdir(srcpath): + return + link = os.readlink(srcpath) + # What's the name of the symlink on the dest tree? + dstslname = os.path.join(self.cInfo['dstroot'], srcpath.lstrip(os.path.sep)) + 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) + rpth = sosRelPath(os.path.dirname(dstslname), os.path.join(self.cInfo['dstroot'], link.lstrip(os.path.sep))) else: # no adjustment, symlink is the relative path - dstslname = link + rpth = link - if os.path.isdir(srcpath): # symlink to a directory - # FIXME: don't recurse symlinks until vicious loops are detected + # make sure the link doesn't already exists + if os.path.exists(dstslname): + self.soslog.log(logging.DEBUG, "skipping symlink creation: already exists (%s)" % dstslname) return - abslink = os.path.abspath(os.path.dirname(srcpath) + "/" + link) - self.soslog.log(logging.VERBOSE2, "DIRLINK %s to %s [%s]" % (srcpath,link,abslink)) - for tmplink in self.copiedDirs: - if tmplink["srcpath"] == abslink or tmplink["pointsto"] == abslink: - self.soslog.log(logging.VERBOSE2, "already copied [%s]" % srcpath) - return + # make sure the dst dir exists + if not (os.path.exists(os.path.dirname(dstslname)) and os.path.isdir(os.path.dirname(dstslname))): + os.makedirs(os.path.dirname(dstslname)) - for afile in os.listdir(srcpath): - if afile == '.' or afile == '..': - pass - else: - self.soslog.log(logging.VERBOSE2, "copying (file or dir) %s" % srcpath+'/'+afile) - try: - abspath = self.doCopyFileOrDir(srcpath+'/'+afile) - except SystemExit: - raise SystemExit - except KeyboardInterrupt: - raise KeyboardInterrupt - except Exception, e: - self.soslog.verbose(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':os.path.abspath(srcpath+'/'+afile) }) - else: - self.soslog.log(logging.VERBOSE2, "copying symlink %s" % srcpath) - try: - dstslname, abspath = self.__copyFile(srcpath) - self.copiedFiles.append({'srcpath':srcpath, 'dstpath':dstslname, 'symlink':"yes", 'pointsto':link}) - 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 + self.soslog.log(logging.VERBOSE3, "creating symlink %s -> %s" % (dstslname, rpth)) + os.symlink(rpth, dstslname) + self.copiedFiles.append({'srcpath':srcpath, 'dstpath':rpth, 'symlink':"yes", 'pointsto':link}) + self.doCopyFileOrDir(link) + return else: # not a symlink - if not os.path.exists(srcpath): - self.soslog.debug("File or directory %s does not exist\n" % srcpath) - elif os.path.isdir(srcpath): + if 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 + return + + # if we get here, it's definitely a regular file (not a symlink or dir) + + self.soslog.log(logging.VERBOSE3, "copying file %s" % srcpath) + try: + tdstpath, abspath = self.__copyFile(srcpath) + except "AlreadyExists": + self.soslog.log(logging.DEBUG, "error copying file %s (already exists)" % (srcpath)) + return + except IOError: + self.soslog.log(logging.VERBOSE2, "error copying file %s (IOError)" % (srcpath)) + return + except: + self.soslog.log(logging.VERBOSE2, "error copying file %s (SOMETHING HAPPENED)" % (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)) + rel_dir = os.path.dirname(src).lstrip(os.path.sep) +# if rel_dir[0] == "/": rel_dir = rel_dir[1:] + new_dir = os.path.join(self.cInfo['dstroot'], rel_dir) + new_fname = os.path.join(new_dir, os.path.basename(src)) + + if not os.path.exists(new_fname): + if not os.path.isdir(new_dir): + os.makedirs(new_dir) + + if os.path.islink(src): + linkto = os.readlink(src) + os.symlink(linkto, new_fname) + else: + shutil.copy2(src, new_dir) + else: + raise "AlreadyExists" + + abspath = os.path.join(self.cInfo['dstroot'], src.lstrip(os.path.sep)) + relpath = sosRelPath(self.cInfo['rptdir'], abspath) + return (relpath, abspath) 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) + # Glob case handling is such that a valid non-glob is a reduced glob + for filespec in glob.glob(forbiddenPath): + self.forbiddenPaths.append(filespec) def getAllOptions(self): """ @@ -240,14 +255,22 @@ class PluginBase: """ return (self.optNames, self.optParms) - def setOption(self, optionname, enable): - ''' enable or disable the named option. + def setOption(self, optionname, value): + ''' set the named option to value. ''' for name, parms in zip(self.optNames, self.optParms): if name == optionname: - parms['enabled'] = enable + parms['enabled'] = value + return True + else: + return False def isOptionEnabled(self, optionname): + ''' Deprecated, use getOption() instead + ''' + return self.getOption(optionname) + + def getOption(self, optionname): ''' see whether the named option is enabled. ''' for name, parms in zip(self.optNames, self.optParms): @@ -259,6 +282,9 @@ class PluginBase: def addCopySpecLimit(self,fname,sizelimit = None): """Add a file specification (with limits) """ + if not ( fname and len(fname) ): + self.soslog.warning("invalid file path") + return False files = glob.glob(fname) files.sort() cursize = 0 @@ -272,57 +298,26 @@ class PluginBase: """ Add a file specification (can be file, dir,or shell glob) to be copied into the sosreport by this module """ + if not ( copyspec and len(copyspec) ): + self.soslog.warning("invalid file path") + return False # 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) + 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): + def collectExtOutput(self, exe, suggest_filename = None, root_symlink = None, timeout = 300): """ Run a program and collect the output """ - self.collectProgs.append( (exe,suggest_filename,root_symlink) ) + self.collectProgs.append( (exe, suggest_filename, root_symlink, timeout) ) def fileGrep(self, regexp, fname): results = [] @@ -358,18 +353,13 @@ class PluginBase: return outfn - def collectOutputNow(self, exe, suggest_filename = None, root_symlink = False): + def collectOutputNow(self, exe, suggest_filename = None, root_symlink = False, timeout = 300): """ 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]) - - # FIXME: we should have a timeout or we may end waiting forever # pylint: disable-msg = W0612 - status, shout, runtime = sosGetCommandOutput(exe) + status, shout, runtime = sosGetCommandOutput(exe, timeout = timeout) if suggest_filename: outfn = self.makeCommandFilename(suggest_filename) @@ -379,7 +369,7 @@ class PluginBase: if not os.path.isdir(os.path.dirname(outfn)): os.mkdir(os.path.dirname(outfn)) - if not (status == 127 or status == 32512): + if not (status == 127 or status == 32512): # if not command_not_found outfd = open(outfn, "w") if len(shout): outfd.write(shout+"\n") outfd.close() @@ -387,7 +377,8 @@ class PluginBase: if root_symlink: curdir = os.getcwd() os.chdir(self.cInfo['dstroot']) - os.symlink(outfn[len(self.cInfo['dstroot'])+1:], root_symlink.strip("/.")) + try: os.symlink(outfn[len(self.cInfo['dstroot'])+1:], root_symlink.strip("/.")) + except: pass os.chdir(curdir) outfn_strip = outfn[len(self.cInfo['cmddir'])+1:] @@ -476,6 +467,10 @@ class PluginBase: if semaphore: semaphore.acquire() + if self.must_exit: + semaphore.release() + return + self.soslog.log(logging.VERBOSE, "starting threaded plugin %s" % self.piName) self.time_start = time() @@ -486,33 +481,29 @@ class PluginBase: try: self.doCopyFileOrDir(path) except SystemExit: - if threaded: - return SystemExit - else: - raise SystemExit + if semaphore: semaphore.release() + if threaded: return KeyboardInterrupt + else: raise KeyboardInterrupt except KeyboardInterrupt: - if threaded: - return KeyboardInterrupt - else: - raise KeyboardInterrupt + if semaphore: semaphore.release() + if threaded: return KeyboardInterrupt + else: raise KeyboardInterrupt except Exception, e: self.soslog.log(logging.VERBOSE2, "error copying from pathspec %s (%s), traceback follows:" % (path,e)) self.soslog.log(logging.VERBOSE2, traceback.format_exc()) - for (prog,suggest_filename,root_symlink) in self.collectProgs: + for (prog, suggest_filename, root_symlink, timeout) in self.collectProgs: self.soslog.debug("collecting output of '%s'" % prog) try: - self.collectOutputNow(prog, suggest_filename, root_symlink) + self.collectOutputNow(prog, suggest_filename, root_symlink, timeout) except SystemExit: - if threaded: - return SystemExit - else: - raise SystemExit + if semaphore: semaphore.release() + if threaded: return SystemExit + else: raise SystemExit except KeyboardInterrupt: - if threaded: - return KeyboardInterrupt - else: - raise KeyboardInterrupt - except: + if semaphore: semaphore.release() + if threaded: return KeyboardInterrupt + else: raise KeyboardInterrupt + except Exception, e: self.soslog.log(logging.VERBOSE2, "error collection output of '%s', traceback follows:" % prog) self.soslog.log(logging.VERBOSE2, traceback.format_exc()) @@ -521,6 +512,10 @@ class PluginBase: if semaphore: semaphore.release() self.soslog.log(logging.VERBOSE, "plugin %s returning" % self.piName) + def exit_please(self): + """ This function tells the plugin that it should exit ASAP""" + self.must_exit = True + def get_description(self): """ This function will return the description for the plugin""" try: @@ -532,7 +527,17 @@ class PluginBase: """ This function can be overidden to let the plugin decide whether it should run or not. """ - return True + # some files or packages have been specified for this package + if len(self.files) or len(self.packages): + for fname in self.files: + if os.path.exists(fname): + return True + for pkgname in self.packages: + if self.cInfo["policy"].pkgByName(pkgname): + return True + return False + + return True def defaultenabled(self): """This devices whether a plugin should be automatically loaded or @@ -604,11 +609,11 @@ class PluginBase: html = html + "<p>Commands Executed:<br><ul>\n" # convert file name to relative path from our root for cmd in self.executedCommands: - if cmd["file"] and len(cmd["file"]): - cmdOutRelPath = sosRelPath(self.cInfo['rptdir'], self.cInfo['cmddir'] + "/" + cmd['file']) - html = html + '<li><a href="%s">%s</a></li>\n' % (cmdOutRelPath, cmd['exe']) - else: - html = html + '<li>%s</li>\n' % (cmd['exe']) + if cmd["file"] and len(cmd["file"]): + cmdOutRelPath = sosRelPath(self.cInfo['rptdir'], self.cInfo['cmddir'] + "/" + cmd['file']) + html = html + '<li><a href="%s">%s</a></li>\n' % (cmdOutRelPath, cmd['exe']) + else: + html = html + '<li>%s</li>\n' % (cmd['exe']) html = html + "</ul></p>\n" # Alerts @@ -624,5 +629,3 @@ class PluginBase: html = html + self.customText + "</p>\n" return html - - diff --git a/src/lib/sos/policyredhat.py b/src/lib/sos/policyredhat.py index c25a9d55..a10e7dfb 100755 --- a/src/lib/sos/policyredhat.py +++ b/src/lib/sos/policyredhat.py @@ -26,22 +26,45 @@ from sos.helpers import * import random import re import md5 - -SOME_PATH = "/tmp/SomePath" +import rpm +import time + +sys.path.insert(0, "/usr/share/rhn/") +try: + from up2date_client import up2dateAuth + from up2date_client import config + from rhn import rpclib +except: + # might fail if non-RHEL + pass #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) +def memoized(function): + ''' function decorator to allow caching of return values + ''' + function.cache={} + def f(*args): + try: + return function.cache[args] + except KeyError: + result = function.cache[args] = function(*args) + return result + return f class SosPolicy: "This class implements various policies for sos" def __init__(self): - #print "Policy init" + self.report_file = "" + self.report_md5 = "" + self.reportName = "" + self.ticketNumber = "" return def setCommons(self, commons): @@ -55,35 +78,45 @@ class SosPolicy: #print "validating %s" % pluginpath return True + def pkgProvides(self, name): + pkg = self.pkgByName(name) + return pkg['providename'] + def pkgRequires(self, name): - # FIXME: we're relying on rpm to sort the output list - cmd = "/bin/rpm -q --requires %s" % (name) - return [requires[:-1].split() for requires in os.popen(cmd).readlines()] + pkg = self.pkgByName(name) + return pkg['requirename'] 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].split() for pkg in pkgs if pkg.startswith(name)] + return self.allPkgs("name", name) + + def allPkgsByNameRegex(self, regex_name): + reg = re.compile(regex_name) + return [pkg for pkg in self.allPkgs() if reg.match(pkg['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): - # FIXME: what does this do again ?? - pkgName = self.pkgByName(name) - print pkgName - if pkgName and len(pkgName) > len(name): - return pkgName[len(name)+1:].split("-") + return self.allPkgsByName(name)[-1] + except: + pass + return None + + def allPkgs(self, ds = None, value = None): + # if possible return the cached values + try: return self._cache_rpm[ "%s-%s" % (ds,value) ] + except AttributeError: self._cache_rpm = {} + except KeyError: pass + + ts = rpm.TransactionSet() + if ds and value: + mi = ts.dbMatch(ds, value) else: - return None + mi = ts.dbMatch() + + self._cache_rpm[ "%s-%s" % (ds,value) ] = [pkg for pkg in mi] + del mi, ts + return self._cache_rpm[ "%s-%s" % (ds,value) ] def runlevelByService(self, name): ret = [] @@ -91,7 +124,7 @@ class SosPolicy: for tabs in commands.getoutput("/sbin/chkconfig --list %s" % name).split(): try: (runlevel, onoff) = tabs.split(":", 1) - except ValueError: + except: pass else: if onoff == "on": @@ -102,7 +135,7 @@ class SosPolicy: def runlevelDefault(self): try: - reg=self.fileGrep(r"^id:(\d{1}):initdefault:", "/etc/inittab") + reg=self.doRegexFindAll(r"^id:(\d{1}):initdefault:", "/etc/inittab") for initlevel in reg: return initlevel except: @@ -111,9 +144,36 @@ class SosPolicy: def kernelVersion(self): return commands.getoutput("/bin/uname -r").strip("\n") + def hostName(self): + return commands.getoutput("/bin/hostname").split(".")[0] + + def rhelVersion(self): + try: + pkgname = self.pkgByName("redhat-release")["version"] + if pkgname[0] == "4": + return 4 + elif pkgname in [ "5Server", "5Client" ]: + return 5 + except: pass + return False + + def rhnUsername(self): + try: + cfg = config.initUp2dateConfig() + + return rpclib.xmlrpclib.loads(up2dateAuth.getSystemId())[0][0]['username'] + except: + # ignore any exception and return an empty username + return "" + def isKernelSMP(self): - if self.kernelVersion()[-3:]=="smp": return True - else: return False + if commands.getoutput("/bin/uname -v").split()[1] == "SMP": + return True + else: + return False + + def getArch(self): + return commands.getoutput("/bin/uname -m").strip() def pkgNVRA(self, pkg): fields = pkg.split("-") @@ -121,85 +181,188 @@ class SosPolicy: name = "-".join(fields[:-3]) return (name, version, release, arch) - def preWork(self, name="", ticket=""): + def getDstroot(self): + """Find a temp directory to form the root for our gathered information + and reports. + """ + dstroot = "/tmp/%s-%s" % (self.hostName(), time.strftime("%Y%m%d%H%M%S")) + try: + os.mkdir(dstroot, 0700) + except: + return False + return dstroot + + def preWork(self): # this method will be called before the gathering begins - localname = commands.getoutput("/bin/uname -n").split(".")[0] + localname = self.rhnUsername() + if len(localname) == 0: localname = self.hostName() - try: - if len(name) == 0: + if not self.cInfo['cmdlineopts'].batch: + try: self.reportName = raw_input(_("Please enter your first initial and last name [%s]: ") % localname) - else: - self.reportName = str(name) - self.reportName = re.sub(r"[^a-zA-Z.0-9]", "", self.reportName) - - if len(self.reportName) == 0: - self.reportName = localname - - if len(ticket) == 0: - self.ticketNumber = raw_input(_("Please enter the case number that you are generating this report for: ")) - else: - self.ticketNumber = str(ticket) - self.ticketNumber = re.sub(r"[^0-9]", "", self.ticketNumber) + self.reportName = re.sub(r"[^a-zA-Z.0-9]", "", self.reportName) - print - except KeyboardInterrupt: - print - sys.exit(0) + self.ticketNumber = raw_input(_("Please enter the case number that you are generating this report for: ")) + self.ticketNumber = re.sub(r"[^0-9]", "", self.ticketNumber) + print + except: + print + sys.exit(0) - def packageResults(self): + if len(self.reportName) == 0: + self.reportName = localname - if len(self.ticketNumber): - namestr = self.reportName + "." + self.ticketNumber - else: - namestr = self.reportName + return - ourtempdir = gettempdir() - tarballName = os.path.join(ourtempdir, "sosreport-" + namestr + ".tar.bz2") + def renameResults(self, newName): + newName = os.path.join(gettempdir(), newName) + if len(self.report_file) and os.path.isfile(self.report_file): + try: os.rename(self.report_file, newName) + except: return False + self.report_file = newName - namestr = namestr + "-" + str(random.randint(1, 999999)) + def packageResults(self): - aliasdir = os.path.join(ourtempdir, namestr) + self.renameResults("sosreport-%s-%s.tar.bz2" % (self.reportName, time.strftime("%Y%m%d%H%M%S"))) - tarcmd = "/bin/tar -jcf %s %s" % (tarballName, namestr) + tarcmd = "/bin/tar -jcf %s %s" % (self.report_file, os.path.basename(self.cInfo['dstroot'])) print _("Creating compressed 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) + os.chdir(os.path.dirname(self.cInfo['dstroot'])) oldmask = os.umask(077) - # pylint: disable-msg = W0612 - status, shout, runtime = sosGetCommandOutput(tarcmd) + status, shout = commands.getstatusoutput(tarcmd) os.umask(oldmask) os.chdir(curwd) - # FIXME: use python internal command - os.system("/bin/mv %s %s" % (aliasdir, self.cInfo['dstroot'])) - # add last 6 chars from md5sum to file name - fp = open(tarballName, "r") - md5out = md5.new(fp.read()).hexdigest() + return + + def cleanDstroot(self): + if not os.path.isdir(os.path.join(self.cInfo['dstroot'],"sos_commands")): + # doesn't look like a dstroot, refusing to clean + return False + os.system("/bin/rm -rf %s" % self.cInfo['dstroot']) + + def encryptResults(self): + # make sure a report exists + if not self.report_file: + return False + + print _("Encrypting archive...") + gpgname = self.report_file + ".gpg" + + try: + keyring = self.cInfo['config'].get("general", "gpg_keyring") + except: + keyring = "/usr/share/sos/rhsupport.pub" + + try: + recipient = self.cInfo['config'].get("general", "gpg_recipient") + except: + recipient = "support@redhat.com" + + status, output = commands.getstatusoutput("""/usr/bin/gpg --trust-model always --batch --keyring "%s" --no-default-keyring --compress-level 0 --encrypt --recipient "%s" --output "%s" "%s" """ % (keyring, recipient, gpgname, self.report_file)) + if status == 0: + os.unlink(self.report_file) + self.report_file = gpgname + else: + print _("There was a problem encrypting your report.") + sys.exit(1) + + def displayResults(self): + # make sure a report exists + if not self.report_file: + return False + + # calculate md5 + fp = open(self.report_file, "r") + self.report_md5 = md5.new(fp.read()).hexdigest() fp.close() - oldtarballName = tarballName - tarballName = os.path.join(ourtempdir, "sosreport-%s-%s.tar.bz2" % (namestr, md5out[-6:]) ) - os.system("/bin/mv %s %s" % (oldtarballName, tarballName) ) - # store md5 to a file - fp = open(tarballName + ".md5", "w") - fp.write(md5out + "\n") + + self.renameResults("sosreport-%s-%s-%s.tar.bz2" % (self.reportName, time.strftime("%Y%m%d%H%M%S"), self.report_md5[-4:])) + + # store md5 into file + fp = open(self.report_file + ".md5", "w") + fp.write(self.report_md5 + "\n") fp.close() - sys.stdout.write("\n") - print _("Your sosreport has been generated and saved in:\n %s") % tarballName print - if md5out: - print _("The md5sum is: ") + md5out + print _("Your sosreport has been generated and saved in:\n %s") % self.report_file + print + if len(self.report_md5): + print _("The md5sum is: ") + self.report_md5 print print _("Please send this file to your support representative.") - sys.stdout.write("\n") + print - return + def uploadResults(self): + # make sure a report exists + if not self.report_file: + return False + + print + # make sure it's readable + try: + fp = open(self.report_file, "r") + except: + return False + + # read ftp URL from configuration + try: + upload_url = self.cInfo['config'].get("general", "upload_url") + except: + upload_url = "ftp://dropbox.redhat.com/incoming" + + from urlparse import urlparse + url = urlparse(upload_url) + + if url[0] != "ftp": + print _("Cannot upload to specified URL.") + return + + # extract username and password from URL, if present + if url[1].find("@") > 0: + username, host = url[1].split("@", 1) + if username.find(":") > 0: + username, passwd = username.split(":", 1) + else: + passwd = None + else: + username, passwd, host = None, None, url[1] + + # extract port, if present + if host.find(":") > 0: + host, port = host.split(":", 1) + port = int(port) + else: + port = 21 + + path = url[2] + + try: + from ftplib import FTP + upload_name = os.path.basename(self.report_file) + + ftp = FTP() + ftp.connect(host, port) + if username and passwd: + ftp.login(username, passwd) + else: + ftp.login() + ftp.cwd(path) + ftp.set_pasv(True) + ftp.storbinary('STOR %s' % upload_name, fp) + ftp.quit() + except: + print _("There was a problem uploading your report to Red Hat support.") + else: + print _("Your report was successfully uploaded to Red Hat's ftp server with name:") + print " " + upload_name + print + print _("Please communicate this name to your support representative.") + print + + fp.close() diff --git a/src/locale/fr/LC_MESSAGES/sos.po b/src/locale/fr/LC_MESSAGES/sos.po index cbc0634e..752355ab 100644 --- a/src/locale/fr/LC_MESSAGES/sos.po +++ b/src/locale/fr/LC_MESSAGES/sos.po @@ -1,160 +1,192 @@ -# sosreport Fench translation file -# Copyright (C) Red Hat UK, Ltd. -# Imed Chihi <ichihi@redhat.com>, 2007. +# translation of fr.po to French +# French translations for PACKAGE package. +# Copyright (C) 2007 ORGANIZATION # +# Automatically generated, 2007. +# Decroux Fabien <fdecroux@redhat.com>, 2007. msgid "" msgstr "" -"Project-Id-Version: sos 1.7\n" -"POT-Creation-Date: 2007-08-02 10:20\n" -"PO-Revision-Date: 2007-08-02 13:00+01\n" -"Last-Translator: Imed Chihi <ichihi@redhat.com>\n" -"Language-Team: FR <navid@redhat.com>\n" +"Project-Id-Version: fr\n" +"POT-Creation-Date: 2007-10-24 08:45\n" +"PO-Revision-Date: 2007-11-08 10:11+1000\n" +"Last-Translator: Decroux Fabien <fdecroux@redhat.com>\n" +"Language-Team: French <fr@li.org>\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: utf-8\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: KBabel 1.11.4\n" -#: lib/sos/policyredhat.py:127 +#: lib/sos/policyredhat.py:203 msgid "Please enter your first initial and last name [%s]: " -msgstr "Prière d'entrer votre première initiale et votre nom [%s]: " +msgstr "Veuillez saisir votre premier prénom (si vous en avez plusieurs) et votre nom [%s] :" -#: lib/sos/policyredhat.py:132 +#: lib/sos/policyredhat.py:206 msgid "Please enter the case number that you are generating this report for: " -msgstr "Prière de donner le numéro de ticket pour lequel vous avez généré ce rapport: " +msgstr "Veuillez saisir le numéro de cas pour lequel vous générez ce rapport :" -#: lib/sos/policyredhat.py:155 +#: lib/sos/policyredhat.py:231 msgid "Creating compressed archive..." -msgstr "Création de l'archive compressée en cours..." +msgstr "Création d'une archive compressée..." -#: lib/sos/policyredhat.py:185 +#: lib/sos/policyredhat.py:253 +msgid "Encrypting archive..." +msgstr "Cryptage de l'archive..." + +#: lib/sos/policyredhat.py:271 +msgid "There was a problem encrypting your report." +msgstr "Une erreur s'est produire lors du cryptage de votre rapport." + +#: lib/sos/policyredhat.py:292 msgid "" "Your sosreport has been generated and saved in:\n" " %s" -"Votre sosreport a été généré et copié dans:\n" +msgstr "" +"Votre rapport sos a été généré et enregistré dans :\n" " %s" -#: lib/sos/policyredhat.py:188 +#: lib/sos/policyredhat.py:295 msgid "The md5sum is: " -msgstr "La somme md5sum est: " +msgstr "Le md5sum est :" -#: lib/sos/policyredhat.py:190 +#: lib/sos/policyredhat.py:297 msgid "Please send this file to your support representative." -msgstr "Prière d'envoyer ce rapport à votre représentant de Support." +msgstr "Veuillez envoyer ce fichier à votre représentant de support." + +#: lib/sos/policyredhat.py:323 +msgid "Cannot upload to specified URL." +msgstr "Impossible de le télécharger vers l'URL spécifié." + +#: lib/sos/policyredhat.py:360 +msgid "There was a problem uploading your report to Red Hat support." +msgstr "Une erreur s'est produite lors du téléchargement de votre rapport vers le support Red Hat." + +#: lib/sos/policyredhat.py:362 +msgid "Your report was successfully uploaded to Red Hat's ftp server with name:" +msgstr "Votre rapport a été téléchargé avec succès vers le serveur ftp de Red Hat avec le nom :" + +#: lib/sos/policyredhat.py:365 +msgid "Please communicate this name to your support representative." +msgstr "Veuillez communiquer ce nom à votre représentant de support." + +#: sosreport:395 +msgid "Could not create temporary directory." +msgstr "Impossible de créer un répertoire temporaire." -#: sosreport:370 +#: sosreport:451 msgid "sosreport (version %s)" -msgstr "" +msgstr "sosreport (version %s)" -#: sosreport:388 +#: sosreport:468 msgid "plugin %s does not validate, skipping" -msgstr "plugin %s ne s'applique pas, desactivé" +msgstr "le plugin %s n'a pas été validé, ignoré" -#: sosreport:392 +#: sosreport:474 msgid "plugin %s skipped (--skip-plugins)" -msgstr "plugin %s desactivé (--skip-plugins)" +msgstr "le plugin %s a été ignoré (--skip-plugins)" -#: sosreport:396 +#: sosreport:478 msgid "plugin %s is inactive (use -e or -o to enable)." -msgstr "plugin %s a été desactivé (utiliser -e ou -o pour réactiver)." +msgstr "le plugin %s est inactif (utilisez -e ou -o pour l'activer)." -#: sosreport:404 +#: sosreport:486 msgid "plugin %s not specified in --only-plugins list" -msgstr "plugin %s non specifié dans la liste --only-plugins" +msgstr "le plugin %s n'a pas été spécifié dans la liste --only-plugins" -#: sosreport:409 +#: sosreport:491 msgid "plugin %s does not install, skipping" -msgstr "Plugin %s ne s'installe pas, déscativé" - -#: sosreport:412 -msgid "could not load plugin %s" -msgstr "Echec du chargement du plugin %s" +msgstr "le plugin %s ne s'installe pas, ignoré" -#: sosreport:455 +#: sosreport:554 msgid "processing options from plugin: %s" -msgstr "Traitement des options du plugin: %s" +msgstr "traitement des options du plugin : %s" -#: sosreport:466 +#: sosreport:565 msgid "no valid plugins found" -msgstr "Aucun plugin valide trouvé" +msgstr "aucun plugin valide n'a été trouvé" -#: sosreport:471 +#: sosreport:570 msgid "The following plugins are currently enabled:" -msgstr "Les plugins suivants sont actuellement activés:" +msgstr "Les plugins suivants sont activés :" -#: sosreport:476 +#: sosreport:575 msgid "No plugin enabled." -msgstr "Aucun plugin activé." +msgstr "Aucun plugin n'est activé." -#: sosreport:480 +#: sosreport:579 msgid "The following plugins are currently disabled:" -msgstr "Les plugins suivants sont actuellment déscativés:" +msgstr "Les plugins suivants sont désactivés :" -#: sosreport:487 +#: sosreport:586 msgid "The following plugin options are available:" -msgstr "Les options de plugin suivantes sont disponibles:" +msgstr "Les options du plugin suivant sont disponibles :" -#: sosreport:507 +#: sosreport:606 msgid "No plugin options available." -msgstr "Pas d'options de plugin configurables." +msgstr "Aucune option n'est disponible pour ce plugin." -#: sosreport:515 +#: sosreport:614 msgid "sosreport requires root permissions to run." -msgstr "sosreport doit être lancé avec les privilèges root." +msgstr "sosreport requiert des permissions root pour démarrer." -#: sosreport:522 +#: sosreport:621 msgid "no valid plugins were enabled" -msgstr "Aucun plugin valide n'a été activé" +msgstr "aucun plugin valide n'a été activé" -#: sosreport:526 +#: sosreport:624 msgid "" "This utility will collect some detailed information about the\n" "hardware and setup of your Red Hat Enterprise Linux system.\n" "The information is collected and an archive is packaged under\n" -"/tmp, which you can send to a support rappresentative.\n" +"/tmp, which you can send to a support representative.\n" "Red Hat will use this information for diagnostic purposes ONLY\n" "and it will be considered confidential information.\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 "" -"Cet outil va collecter des informations détaillées sur le matériel\n" -"et la configuration de votre système Red Hat Enterprise Linux.\n" -"Les informations seront assemblées dans une archive sous /tmp,\n" -"que vous pouvez renvoyer à un représentant du Support Red Hat.\n" -"Red Hat utilisera ces informations à fin d'anaylser les problèmes UNIQUEMENT\n" -"et elles seront considérées comme confidentielles.\n" +"Cet utilitaire collectera des informations détaillées à propos du\n" +"matériel et de l'installation de votre système Red Hat Enterprise Linux.\n" +"Les informations sont collectées et une archive est créée sous /tmp.\n" +"Vous pouvez l'envoyer à un représentant de support.\n" +"Red Hat utilisera ces informations à des fins de diagnostique SEULEMENT\n" +"et elles seront considérées commes des informations confidentielles.\n" "\n" -"Ce processus pourrait prendre du temps pour finir.\n" -"Aucun changement ne sera apporté à votre système.\n" +"Ce processus peut prendre un certain temps avant d'être terminé.\n" +"Aucun changement ne sera effectué sur votre système.\n" "\n" -"Tapez ENTREE pour continuer, ou CTRL-C pour quitter.\n.\n" -#: sosreport:555 +#: sosreport:638 +msgid "Press ENTER to continue, or CTRL-C to quit.\n" +msgstr "Appuyez sur Entrée pour continuer ou CTRL-C pour quitter.\n" + +#: sosreport:654 msgid "One or more plugins have detected a problem in your configuration." -msgstr "Un ou plusieurs plugins ont détecté un problème avec votre configuration." +msgstr "Un ou plusieurs plugins ont détecté un problème dans votre configuration." -#: sosreport:556 +#: sosreport:655 msgid "Please review the following messages:" -msgstr "Prière de vérifier les messages suivants:" +msgstr "Veuillez revoir les messages suivants :" -#: sosreport:571 +#: sosreport:671 msgid "Are you sure you would like to continue (y/n) ? " -msgstr "Voulez vous continuer (y/n)?" +msgstr "Êtes-vous sûr de vouloir continuer (y/n) ?" -#: sosreport:572 +#: sosreport:672 msgid "Y" -msgstr "O" +msgstr "Y" -#: sosreport:572 +#: sosreport:672 msgid "y" -msgstr "o" +msgstr "y" -#: sosreport:575 +#: sosreport:675 msgid "N" msgstr "N" -#: sosreport:575 +#: sosreport:675 msgid "n" msgstr "n" + diff --git a/src/locale/it/LC_MESSAGES/sos.po b/src/locale/it/LC_MESSAGES/sos.po index cafd4c42..7e6ee4c2 100644 --- a/src/locale/it/LC_MESSAGES/sos.po +++ b/src/locale/it/LC_MESSAGES/sos.po @@ -1,33 +1,41 @@ -# SOME DESCRIPTIVE TITLE. +# translation of it.po to # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2007-08-03 09:39\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" +"Project-Id-Version: it\n" +"POT-Creation-Date: 2007-10-24 08:45\n" +"PO-Revision-Date: 2007-11-06 15:02+1000\n" +"Last-Translator: \n" +"Language-Team: <it@li.org>\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: utf-8\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" +"X-Generator: KBabel 1.11.4\n" - -#: lib/sos/policyredhat.py:127 +#: lib/sos/policyredhat.py:203 msgid "Please enter your first initial and last name [%s]: " msgstr "Inserire l'iniziale del nome e il cognome [%s]: " -#: lib/sos/policyredhat.py:132 +#: lib/sos/policyredhat.py:206 msgid "Please enter the case number that you are generating this report for: " msgstr "Inserire il numero del caso a cui si riferisce questo report: " -#: lib/sos/policyredhat.py:155 +#: lib/sos/policyredhat.py:231 msgid "Creating compressed archive..." msgstr "Creazione di un archivio compresso in corso..." -#: lib/sos/policyredhat.py:185 +#: lib/sos/policyredhat.py:253 +msgid "Encrypting archive..." +msgstr "Cifratura archivio in corso..." + +#: lib/sos/policyredhat.py:271 +msgid "There was a problem encrypting your report." +msgstr "Si è verificato un problema durante la cifratura di questo report." + +#: lib/sos/policyredhat.py:292 msgid "" "Your sosreport has been generated and saved in:\n" " %s" @@ -35,129 +43,147 @@ msgstr "" "Il sosreport é stato creato e salvato in:\n" " %s" -#: lib/sos/policyredhat.py:188 +#: lib/sos/policyredhat.py:295 msgid "The md5sum is: " msgstr "L'md5sum é: " -#: lib/sos/policyredhat.py:190 +#: lib/sos/policyredhat.py:297 msgid "Please send this file to your support representative." -msgstr "E' pregato di inviare questo file al rappresentante del supporto tecnico." +msgstr "Si prega di inviare questo file al rappresentante del supporto tecnico." + +#: lib/sos/policyredhat.py:323 +msgid "Cannot upload to specified URL." +msgstr "Impossibile caricare sull'URL specificato." + +#: lib/sos/policyredhat.py:360 +msgid "There was a problem uploading your report to Red Hat support." +msgstr "Si è verificato un problema durante il caricamento del vostro report su Red Hat support." -#: sosreport:372 +#: lib/sos/policyredhat.py:362 +msgid "Your report was successfully uploaded to Red Hat's ftp server with name:" +msgstr "Il vostro report è stato caricato con successo sul server ftp di Red Hat con il nome:" + +#: lib/sos/policyredhat.py:365 +msgid "Please communicate this name to your support representative." +msgstr "Si prega di inviare questo nome al rappresentante del supporto tecnico." + +#: sosreport:395 +msgid "Could not create temporary directory." +msgstr "Impossibile creare le directory temporanea." + +#: sosreport:451 msgid "sosreport (version %s)" msgstr "sosreport (versione %s)" -#: sosreport:390 +#: sosreport:468 msgid "plugin %s does not validate, skipping" msgstr "la plugin %s non é valida e verrà ignorata" -#: sosreport:394 +#: sosreport:474 msgid "plugin %s skipped (--skip-plugins)" msgstr "plugin %s disattivata (--skip-plugins)" -#: sosreport:398 +#: sosreport:478 msgid "plugin %s is inactive (use -e or -o to enable)." msgstr "plugin %s non é attiva (utilizzare -e o -o per riattivarla)." -#: sosreport:406 +#: sosreport:486 msgid "plugin %s not specified in --only-plugins list" msgstr "plugin %s non specificata nella lista --only-plugins" -#: sosreport:411 +#: sosreport:491 msgid "plugin %s does not install, skipping" msgstr "non é stato possibile installare la plugin %s" -#: sosreport:414 -msgid "could not load plugin %s" -msgstr "impossibile caricare la plugin %s" - -#: sosreport:457 +#: sosreport:554 msgid "processing options from plugin: %s" msgstr "sto processando le opzioni della plugin: %s" -#: sosreport:468 +#: sosreport:565 msgid "no valid plugins found" msgstr "nessuna plugin valida é stata trovata" -#: sosreport:473 +#: sosreport:570 msgid "The following plugins are currently enabled:" msgstr "Le seguenti plugin sono attive:" -#: sosreport:478 +#: sosreport:575 msgid "No plugin enabled." msgstr "Nessuna plugin abilitata." -#: sosreport:482 +#: sosreport:579 msgid "The following plugins are currently disabled:" msgstr "Le seguenti plugin sono disattivate:" -#: sosreport:489 +#: sosreport:586 msgid "The following plugin options are available:" msgstr "Sono disponibili le seguenti opzioni per la plugin:" -#: sosreport:509 +#: sosreport:606 msgid "No plugin options available." msgstr "Nessuna opzione disponibile per le plugins attive." -#: sosreport:517 +#: sosreport:614 msgid "sosreport requires root permissions to run." msgstr "sosreport necessita dei permessi di root" -#: sosreport:524 +#: sosreport:621 msgid "no valid plugins were enabled" msgstr "non é stata attivata nessuna plugin valida" -#: sosreport:528 +#: sosreport:624 msgid "" "This utility will collect some detailed information about the\n" "hardware and setup of your Red Hat Enterprise Linux system.\n" "The information is collected and an archive is packaged under\n" -"/tmp, which you can send to a support rappresentative.\n" +"/tmp, which you can send to a support representative.\n" "Red Hat will use this information for diagnostic purposes ONLY\n" "and it will be considered confidential information.\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 "" -"Questo programma collezionerà delle informazioni dettagliate\n" -"su hardware e configurazione del vostro sistema Red Hat\n" -"Enterprise Linux. Queste informazioni verranno usate per\n" -"diagnosticare i problemi del vostro sistema e sono considerate\n" -"strettamente confidenziali. Red Hat utilizzerà questi dati\n" -"ESCLUSIVAMENTE per scopi diagnostici.\n" +"Questa utilità raccoglierà alcune informazioni dettagliate\n" +"sull'hardware e sulla impostazione del vostro sistema Red Hat\n" +"Enterprise Linux. Queste informazioni verranno archiviate in\n" +"/tmp, e possono essere inviate ad un rappresentante del supporto tecnico.\n" +"Red Hat utilizzerà questi dati ESCLUSIVAMENTE per scopi diagnostici e\n" +"verranno considerate informazioni riservate\n" "\n" "Questo processo potrebbe durare alcuni minuti.\n" -"Non verrà apportata nessuna modifica al vostro sistema.\n" +"Non verrà apportata alcuna modifica al vostro sistema.\n" "\n" -"Premere ENTER per continuare, o CTRL-C per uscire.\n" -#: sosreport:555 +#: sosreport:638 +msgid "Press ENTER to continue, or CTRL-C to quit.\n" +msgstr "Premere INVIO per continuare, o CTRL-C per usicre.\n" + +#: sosreport:654 msgid "One or more plugins have detected a problem in your configuration." -msgstr "Una o più plugin hanno determinato problemi nella configurazione del sistema:" +msgstr "Una o più plugin hanno causato problemi nella configurazione del sistema:" -#: sosreport:556 +#: sosreport:655 msgid "Please review the following messages:" -msgstr "Verifichi i seguenti messaggi:" +msgstr "Si prega di verificare i seguenti messaggi:" -#: sosreport:571 +#: sosreport:671 msgid "Are you sure you would like to continue (y/n) ? " msgstr "E' sicuro di voler continuare (s/n) ? " -#: sosreport:572 +#: sosreport:672 msgid "Y" msgstr "S" -#: sosreport:572 +#: sosreport:672 msgid "y" msgstr "s" -#: sosreport:575 +#: sosreport:675 msgid "N" -msgstr "" +msgstr "N" -#: sosreport:575 +#: sosreport:675 msgid "n" -msgstr "" +msgstr "n" diff --git a/src/sos-internal.spec b/src/sos-internal.spec index 603508fb..55920b1c 100644 --- a/src/sos-internal.spec +++ b/src/sos-internal.spec @@ -2,7 +2,7 @@ %define name sos-internal %define version 1.8 -%define release 0 +%define release 0pre0 Summary: SoS internal utilities for report analysis Name: %{name} @@ -17,6 +17,8 @@ License: GPL BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot BuildArch: noarch Url: https://hosted.fedoraproject.org/projects/sos +Requires: python-cherrypy +Requires: python-sqlite2 %description Sos is a set of tools that gathers information about system @@ -33,6 +35,14 @@ rm -rf ${RPM_BUILD_ROOT} cd sos-%{version} install -D -m755 extras/sos-open ${RPM_BUILD_ROOT}/usr/bin/sos-open +install -D -m755 extras/sos-html-logs/sos-html-logs ${RPM_BUILD_ROOT}/usr/bin/sos-html-logs + +mkdir -p ${RPM_BUILD_ROOT}/%{python_sitelib}/soshtmllogs/parsers +cp extras/sos-html-logs/lib/*.py ${RPM_BUILD_ROOT}/%{python_sitelib}/soshtmllogs +cp extras/sos-html-logs/lib/parsers/*.py extras/sos-html-logs/lib/parsers/*.rules ${RPM_BUILD_ROOT}/%{python_sitelib}/soshtmllogs/parsers + +mkdir -p ${RPM_BUILD_ROOT}/usr/share +cp -r extras/sos-html-logs/share ${RPM_BUILD_ROOT}/usr/share/sos-html-logs install -D -m644 %{SOURCE1} ${RPM_BUILD_ROOT}/usr/share/sos/rhsupport.pub install -D -m600 %{SOURCE2} ${RPM_BUILD_ROOT}/usr/share/sos/rhsupport.key @@ -50,7 +60,10 @@ echo "done." %defattr(-,root,root,-) /usr/share/sos/rhsupport.pub /usr/share/sos/rhsupport.key +/usr/bin/sos-html-logs /usr/bin/sos-open +/%{python_sitelib}/soshtmllogs +/usr/share/sos-html-logs %changelog * Wed Sep 4 2007 Navid Sheikhol-Eslami <navid at redhat dot com> - 1.0 diff --git a/src/sos.spec b/src/sos.spec index 3b3f8534..c579ec1e 100644 --- a/src/sos.spec +++ b/src/sos.spec @@ -2,7 +2,7 @@ %define name sos %define version 1.8 -%define release 0pre2 +%define release 0 %define _localedir %_datadir/locale @@ -23,10 +23,8 @@ BuildArch: noarch Url: https://hosted.fedoraproject.org/projects/sos BuildRequires: python-devel Requires: libxml2-python -%if 0%{?rhel} Provides: sysreport = 1.4.3-13 Obsoletes: sysreport -%endif %description Sos is a set of tools that gathers information about system @@ -44,16 +42,7 @@ python setup.py build rm -rf ${RPM_BUILD_ROOT} install -D -m644 %{SOURCE1} ${RPM_BUILD_ROOT}/usr/share/sos/rhsupport.pub python setup.py install --optimize 1 --root=$RPM_BUILD_ROOT - -%if 0%{?rhel} ln -s /usr/sbin/sosreport $RPM_BUILD_ROOT/usr/sbin/sysreport -%endif -%if ! 0%{?rhel} -rm -f $RPM_BUILD_ROOT/usr/sbin/sysreport.legacy -rm -f $RPM_BUILD_ROOT/usr/share/sysreport/functions -rm -f $RPM_BUILD_ROOT/usr/share/sysreport/sysreport-fdisk -rm -f $RPM_BUILD_ROOT/usr/share/sysreport/text.xsl -%endif %clean rm -rf ${RPM_BUILD_ROOT} @@ -63,11 +52,9 @@ rm -rf ${RPM_BUILD_ROOT} %{_sbindir}/sosreport /usr/share/sos/rhsupport.pub /usr/bin/rh-upload-core -%if 0%{?rhel} /usr/sbin/sysreport /usr/sbin/sysreport.legacy /usr/share/sysreport -%endif %{python_sitelib}/sos/ %{_mandir}/man1/sosreport.1* %{_localedir}/*/LC_MESSAGES/sos.mo diff --git a/src/sosreport b/src/sosreport index 516cda22..37a88a47 100755 --- a/src/sosreport +++ b/src/sosreport @@ -209,9 +209,9 @@ def textcolor(text, fg, raw=0): if __cmdLineOpts__.nocolors: return text 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" } + "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 diff --git a/src/sosreport.1 b/src/sosreport.1 index 3ae187a8..495ce416 100644 --- a/src/sosreport.1 +++ b/src/sosreport.1 @@ -36,6 +36,9 @@ If no value is specified and the option is a boolean (on/off), it will be set to .B \-a, \--alloptions Enable all (boolean) options for all loaded plugins. .TP +.B \--upload +Upload the report to Red Hat (use exclusively if advised from a Red Hat support representative). +.TP .B \-v, \--verbose Increase the verbosity of the output as sosreport is running. Multiple -v mean more verbosity. .TP diff --git a/src/tools/msgfmt.py b/src/tools/msgfmt.py index 277901c0..8a2d4e66 100644 --- a/src/tools/msgfmt.py +++ b/src/tools/msgfmt.py @@ -1,4 +1,4 @@ -#! /usr/bin/python +#! /usr/bin/env python # -*- coding: iso-8859-1 -*- # Written by Martin v. Löwis <loewis@informatik.hu-berlin.de> diff --git a/src/tools/pygettext.py b/src/tools/pygettext.py index e44b61b4..040b5c7f 100644 --- a/src/tools/pygettext.py +++ b/src/tools/pygettext.py @@ -1,4 +1,4 @@ -#! /usr/bin/python +#! /usr/bin/env python # -*- coding: iso-8859-1 -*- # Originally written by Barry Warsaw <barry@zope.com> # |