3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of version 2.1 of the GNU Lesser General Public
5 # License as published by the Free Software Foundation.
7 # This library is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 # Lesser General Public License for more details.
12 # You should have received a copy of the GNU Lesser General Public
13 # License along with this library; if not, write to the Free Software
14 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 # Copyright (c) 2005, 2007 XenSource Ltd.
17 # Copyright (c) 2010, Nicira Networks.
20 # To add new entries to the bugtool, you need to:
22 # Create a new capability. These declare the new entry to the GUI, including
23 # the expected size, time to collect, privacy implications, and whether the
24 # capability should be selected by default. One capability may refer to
25 # multiple files, assuming that they can be reasonably grouped together, and
26 # have the same privacy implications. You need:
28 # A new CAP_ constant.
29 # A cap() invocation to declare the capability.
31 # You then need to add calls to main() to collect the files. These will
32 # typically be calls to the helpers file_output(), tree_output(), cmd_output(),
45 from xml.dom.minidom import parse, getDOMImplementation
47 from subprocess import Popen, PIPE
48 from select import select
49 from signal import SIGTERM, SIGUSR1
58 sys.path.append('/usr/lib/python')
59 sys.path.append('/usr/lib64/python')
61 OS_RELEASE = platform.release()
67 BUG_DIR = "/var/log/openvswitch"
68 PLUGIN_DIR = "/etc/openvswitch/bugtool"
69 GRUB_CONFIG = '/boot/grub/menu.lst'
70 BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
71 BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
72 PROC_PARTITIONS = '/proc/partitions'
74 PROC_MOUNTS = '/proc/mounts'
75 PROC_CPUINFO = '/proc/cpuinfo'
76 PROC_MEMINFO = '/proc/meminfo'
77 PROC_IOPORTS = '/proc/ioports'
78 PROC_INTERRUPTS = '/proc/interrupts'
79 PROC_SCSI = '/proc/scsi/scsi'
80 PROC_VERSION = '/proc/version'
81 PROC_MODULES = '/proc/modules'
82 PROC_DEVICES = '/proc/devices'
83 PROC_FILESYSTEMS = '/proc/filesystems'
84 PROC_CMDLINE = '/proc/cmdline'
85 PROC_CONFIG = '/proc/config.gz'
86 PROC_USB_DEV = '/proc/bus/usb/devices'
87 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
88 MODPROBE_DIR = '/etc/modprobe.d'
89 RESOLV_CONF = '/etc/resolv.conf'
90 NSSWITCH_CONF = '/etc/nsswitch.conf'
91 NTP_CONF = '/etc/ntp.conf'
93 HOSTS_ALLOW = '/etc/hosts.allow'
94 HOSTS_DENY = '/etc/hosts.deny'
95 DHCP_LEASE_DIR = '/var/lib/dhcp3'
96 OPENVSWITCH_CORE_DIR = '/var/log/openvswitch/cores'
97 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch'
98 OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
99 OPENVSWITCH_CONF_DB = '/etc/openvswitch/conf.db'
100 OPENVSWITCH_VSWITCHD_PID = '/var/run/openvswitch/ovs-vswitchd.pid'
101 COLLECTD_LOGS_DIR = '/var/lib/collectd/rrd'
102 VAR_LOG_DIR = '/var/log/'
103 X11_LOGS_DIR = VAR_LOG_DIR
104 X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
105 X11_AUTH_DIR = '/root/'
106 X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
107 PAM_DIR = '/etc/pam.d'
113 ARP = '/usr/sbin/arp'
117 DMIDECODE = '/usr/sbin/dmidecode'
118 FDISK = '/sbin/fdisk'
119 FIND = '/usr/bin/find'
120 IFCONFIG = '/sbin/ifconfig'
121 IPTABLES = '/sbin/iptables'
122 LOSETUP = '/sbin/losetup'
124 LSPCI = '/usr/bin/lspci'
125 MD5SUM = '/usr/bin/md5sum'
126 MODINFO = '/sbin/modinfo'
127 NETSTAT = '/bin/netstat'
128 OVS_DPCTL = '/usr/sbin/ovs-dpctl'
129 OVS_OFCTL = '/usr/sbin/ovs-ofctl'
130 OVS_VSCTL = '/usr/sbin/ovs-vsctl'
131 OVS_APPCTL = '/usr/sbin/ovs-appctl'
133 ROUTE = '/sbin/route'
134 SYSCTL = '/sbin/sysctl'
136 UPTIME = '/usr/bin/uptime'
139 ETHTOOL = '/sbin/ethtool'
140 # ETHTOOL recently moved from /usr/sbin to /sbin in debian
141 if not os.path.isfile(ETHTOOL):
142 ETHTOOL = '/usr/sbin/ethtool'
145 # PII -- Personally identifiable information. Of particular concern are
146 # things that would identify customers, or their network topology.
147 # Passwords are never to be included in any bug report, regardless of any PII
150 # NO -- No PII will be in these entries.
151 # YES -- PII will likely or certainly be in these entries.
152 # MAYBE -- The user may wish to audit these entries for PII.
153 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
154 # but since we encourage customers to edit these files, PII may have been
155 # introduced by the customer. This is used in particular for the networking
162 PII_IF_CUSTOMIZED = 'if_customized'
173 MIME_DATA = 'application/data'
174 MIME_TEXT = 'text/plain'
176 INVENTORY_XML_ROOT = "system-status-inventory"
177 INVENTORY_XML_SUMMARY = 'system-summary'
178 INVENTORY_XML_ELEMENT = 'inventory-entry'
179 CAP_XML_ROOT = "system-status-capabilities"
180 CAP_XML_ELEMENT = 'capability'
184 CAP_BOOT_LOADER = 'boot-loader'
185 CAP_COLLECTD_LOGS = 'collectd-logs'
186 CAP_DISK_INFO = 'disk-info'
187 CAP_FIRSTBOOT = 'firstboot'
188 CAP_HARDWARE_INFO = 'hardware-info'
189 CAP_HIGH_AVAILABILITY = 'high-availability'
190 CAP_HOST_CRASHDUMP_DUMPS = 'host-crashdump-dumps'
191 CAP_HOST_CRASHDUMP_LOGS = 'host-crashdump-logs'
192 CAP_KERNEL_INFO = 'kernel-info'
193 CAP_LOSETUP_A = 'loopback-devices'
194 CAP_NETWORK_CONFIG = 'network-config'
195 CAP_NETWORK_STATUS = 'network-status'
198 CAP_PROCESS_LIST = 'process-list'
199 CAP_PERSISTENT_STATS = 'persistent-stats'
200 CAP_SYSTEM_LOGS = 'system-logs'
201 CAP_SYSTEM_SERVICES = 'system-services'
202 CAP_VNCTERM = 'vncterm'
205 CAP_X11_AUTH = 'X11-auth'
212 unlimited_data = False
215 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
216 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
217 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
222 cap(CAP_BLOBS, PII_NO, max_size=5*MB)
223 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
225 cap(CAP_COLLECTD_LOGS, PII_MAYBE, max_size=50*MB,
227 cap(CAP_DISK_INFO, PII_MAYBE, max_size=25*KB,
229 cap(CAP_FIRSTBOOT, PII_YES, min_size=60*KB, max_size=80*KB)
230 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB,
232 cap(CAP_HIGH_AVAILABILITY, PII_MAYBE, max_size=5*MB)
233 cap(CAP_HOST_CRASHDUMP_DUMPS,PII_YES, checked = False)
234 cap(CAP_HOST_CRASHDUMP_LOGS, PII_NO)
235 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
237 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
238 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
239 min_size=0, max_size=20*KB)
240 cap(CAP_NETWORK_STATUS, PII_YES, max_size=19*KB,
242 cap(CAP_PAM, PII_NO, max_size=30*KB)
243 cap(CAP_PERSISTENT_STATS, PII_MAYBE, max_size=50*MB,
245 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
247 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=50*MB,
249 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
251 cap(CAP_VNCTERM, PII_MAYBE, checked = False)
252 cap(CAP_WLB, PII_NO, max_size=3*MB,
254 cap(CAP_X11_LOGS, PII_NO, max_size=100*KB)
255 cap(CAP_X11_AUTH, PII_NO, max_size=100*KB)
257 ANSWER_YES_TO_ALL = False
261 dev_null = open('/dev/null', 'r+')
269 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
271 def cmd_output(cap, args, label = None, filter = None):
274 if isinstance(args, list):
275 a = [aa for aa in args]
276 a[0] = os.path.basename(a[0])
280 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
282 def file_output(cap, path_list):
285 if os.path.exists(p):
286 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
287 cap_sizes[cap] < caps[cap][MAX_SIZE]:
288 data[p] = {'cap': cap, 'filename': p}
291 cap_sizes[cap] += s.st_size
295 output("Omitting %s, size constraint of %s exceeded" % (p, cap))
297 def tree_output(cap, path, pattern = None, negate = False):
299 if os.path.exists(path):
300 for f in os.listdir(path):
301 fn = os.path.join(path, f)
302 if os.path.isfile(fn) and matches(fn, pattern, negate):
303 file_output(cap, [fn])
304 elif os.path.isdir(fn):
305 tree_output(cap, fn, pattern, negate)
307 def func_output(cap, label, func):
309 t = str(func).split()
310 data[label] = {'cap': cap, 'func': func}
315 for (k, v) in data.items():
317 if v.has_key('cmd_args'):
318 v['output'] = StringIOmtime()
319 if not process_lists.has_key(cap):
320 process_lists[cap] = []
321 process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
322 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
323 # proc files must be read into memory
325 f = open(v['filename'], 'r')
328 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
329 cap_sizes[cap] < caps[cap][MAX_SIZE]:
330 v['output'] = StringIOmtime(s)
331 cap_sizes[cap] += len(s)
333 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
336 elif v.has_key('func'):
341 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
342 cap_sizes[cap] < caps[cap][MAX_SIZE]:
343 v['output'] = StringIOmtime(s)
344 cap_sizes[cap] += len(s)
346 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
348 run_procs(process_lists.values())
351 def main(argv = None):
352 global ANSWER_YES_TO_ALL, SILENT_MODE
353 global entries, data, dbg
355 # we need access to privileged files, exit if we are not running as root
357 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
360 output_type = 'tar.bz2'
367 (options, params) = getopt.gnu_getopt(
368 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
369 'output=', 'outfd=', 'all', 'unlimited', 'debug'])
370 except getopt.GetoptError, opterr:
371 print >>sys.stderr, opterr
379 entries = [e for e in caps.keys() if caps[e][CHECKED]]
381 for (k, v) in options:
382 if k == '--capabilities':
383 update_capabilities()
388 if v in ['tar', 'tar.bz2', 'zip']:
391 print >>sys.stderr, "Invalid output format '%s'" % v
394 # "-s" or "--silent" means suppress output (except for the final
395 # output filename at the end)
396 if k in ['-s', '--silent']:
399 if k == '--entries' and v != '':
400 entries = v.split(',')
402 # If the user runs the script with "-y" or "--yestoall" we don't ask
403 # all the really annoying questions.
404 if k in ['-y', '--yestoall']:
405 ANSWER_YES_TO_ALL = True
410 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
411 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
413 print >>sys.stderr, "Invalid output file descriptor", output_fd
417 entries = caps.keys()
418 elif k == '--unlimited':
419 unlimited_data = True
422 ProcOutput.debug = True
425 print >>sys.stderr, "Invalid additional arguments", str(params)
428 if output_fd != -1 and output_type != 'tar':
429 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
432 if ANSWER_YES_TO_ALL:
433 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
436 This application will collate dmesg output, details of the
437 hardware configuration of your machine, information about the build of
438 openvswitch that you are using, plus, if you allow it, various logs.
440 The collated information will be saved as a .%s for archiving or
441 sending to a Technical Support Representative.
443 The logs may contain private information, and if you are at all
444 worried about that, you should exit now, or you should explicitly
445 exclude those logs from the archive.
449 # assemble potential data
451 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
452 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
453 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
455 tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
456 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
457 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
458 file_output(CAP_DISK_INFO, [FSTAB])
459 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
460 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
461 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
462 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
463 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
464 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
467 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
468 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
469 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
470 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
471 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
472 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
474 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
475 PROC_FILESYSTEMS, PROC_CMDLINE])
476 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
477 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
478 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
479 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
481 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
483 file_output(CAP_NETWORK_CONFIG, [RESOLV_CONF, NSSWITCH_CONF, HOSTS])
484 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, HOSTS_ALLOW, HOSTS_DENY])
485 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
486 OPENVSWITCH_DEFAULT_CONTROLLER, OPENVSWITCH_CONF_DB])
488 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
489 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
490 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
491 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
492 tree_output(CAP_NETWORK_STATUS, DHCP_LEASE_DIR)
493 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
494 for p in os.listdir('/sys/class/net/'):
496 f = open('/sys/class/net/%s/type' % p, 'r')
501 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
502 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
503 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
504 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
505 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
506 cmd_output(CAP_NETWORK_STATUS,
507 [TC, '-s', '-d', 'class', 'show', 'dev', p])
510 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
511 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
512 tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_CORE_DIR)
513 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
514 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
516 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
517 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'status', d])
518 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
519 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
521 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
522 vspid = int(vspidfile.readline().strip())
524 for b in bond_list(vspid):
525 cmd_output(CAP_NETWORK_STATUS,
526 [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
527 'ovs-appctl-bond-show-%s.out' % b)
531 tree_output(CAP_PAM, PAM_DIR)
533 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
534 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
536 file_output(CAP_SYSTEM_LOGS,
537 [ VAR_LOG_DIR + x for x in
538 [ 'kern.log', 'daemon.log', 'user.log', 'syslog', 'messages',
539 'debug', 'dmesg', 'boot'] +
540 [ f % n for n in range(1, 20) \
541 for f in ['kern.log.%d', 'kern.log.%d.gz',
542 'daemon.log.%d', 'daemon.log.%d.gz',
543 'user.log.%d', 'user.log.%d.gz',
544 'messages.%d', 'messages.%d.gz']]])
545 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
546 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
549 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
550 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
558 # permit the user to filter out data
559 for k in sorted(data.keys()):
560 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
563 # collect selected data now
564 output_ts('Running commands to collect data')
567 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
570 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
573 if output_fd == -1 and not os.path.exists(BUG_DIR):
580 output_ts('Creating output file')
582 if output_type.startswith('tar'):
583 make_tar(subdir, output_type, output_fd)
590 print >>sys.stderr, "Category sizes (max, actual):\n"
591 for c in caps.keys():
592 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
596 def find_tapdisk_logs():
597 return glob.glob('/var/log/blktap/*.log*')
599 def generate_tapdisk_logs():
600 for pid in pidof('tapdisk'):
602 os.kill(pid, SIGUSR1)
603 output_ts("Including logs for tapdisk process %d" % pid)
606 # give processes a second to write their logs
609 def clean_tapdisk_logs():
610 for filename in find_tapdisk_logs():
616 def filter_db_pii(str, state):
617 if 'in_secret_table' not in state:
618 state['in_secret_table'] = False
620 if str.startswith('<table ') and 'name="secret"' in str:
621 state['in_secret_table'] = True
622 elif str.startswith('</table>'):
623 state['in_secret_table'] = False
625 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
626 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
629 def dump_scsi_hosts(cap):
631 l = os.listdir('/sys/class/scsi_host')
637 f = open('/sys/class/scsi_host/%s/proc_name' % h)
638 procname = f.readline().strip("\n")
644 f = open('/sys/class/scsi_host/%s/model_name' % h)
645 modelname = f.readline().strip("\n")
651 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
655 def module_info(cap):
656 output = StringIO.StringIO()
657 modules = open(PROC_MODULES, 'r')
661 module = line.split()[0]
662 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
667 return output.getvalue()
670 output = StringIO.StringIO()
671 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
675 if not procs[0].timed_out:
676 return output.getvalue().splitlines()
680 output = StringIO.StringIO()
681 procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
685 if not procs[0].timed_out:
686 bonds = output.getvalue().splitlines()[1:]
687 return [x.split('\t')[1] for x in bonds]
693 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
695 fh = open('/proc/'+d+'/cmdline')
697 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
699 if not num_fds in fd_dict:
700 fd_dict[num_fds] = []
701 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
704 keys = fd_dict.keys()
705 keys.sort(lambda a, b: int(b) - int(a))
707 output += "%s: %s\n" % (k, str(fd_dict[k]))
710 def load_plugins(just_capabilities = False):
711 def getText(nodelist):
713 for node in nodelist:
714 if node.nodeType == node.TEXT_NODE:
718 def getBoolAttr(el, attr, default = False):
720 val = el.getAttribute(attr).lower()
721 if val in ['true', 'false', 'yes', 'no']:
722 ret = val in ['true', 'yes']
725 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
726 if not caps.has_key(dir):
727 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
729 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
730 assert xmldoc.documentElement.tagName == "capability"
732 pii, min_size, max_size, min_time, max_time, mime = \
733 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
735 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
736 pii = xmldoc.documentElement.getAttribute("pii")
737 if xmldoc.documentElement.getAttribute("min_size") != '':
738 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
739 if xmldoc.documentElement.getAttribute("max_size") != '':
740 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
741 if xmldoc.documentElement.getAttribute("min_time") != '':
742 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
743 if xmldoc.documentElement.getAttribute("max_time") != '':
744 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
745 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
746 mime = xmldoc.documentElement.getAttribute("mime")
747 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
748 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
750 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
752 if just_capabilities:
755 plugdir = os.path.join(PLUGIN_DIR, dir)
756 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
757 xmldoc = parse(os.path.join(plugdir, file))
758 assert xmldoc.documentElement.tagName == "collect"
760 for el in xmldoc.documentElement.getElementsByTagName("*"):
761 if el.tagName == "files":
762 file_output(dir, getText(el.childNodes).split())
763 elif el.tagName == "directory":
764 pattern = el.getAttribute("pattern")
765 if pattern == '': pattern = None
766 negate = getBoolAttr(el, 'negate')
767 tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
768 elif el.tagName == "command":
769 label = el.getAttribute("label")
770 if label == '': label = None
771 cmd_output(dir, getText(el.childNodes), label)
773 def make_tar(subdir, suffix, output_fd):
774 global SILENT_MODE, data
777 if suffix == 'tar.bz2':
779 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
782 tf = tarfile.open(filename, mode)
784 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
787 for (k, v) in data.items():
789 tar_filename = os.path.join(subdir, construct_filename(k, v))
790 ti = tarfile.TarInfo(tar_filename)
795 if v.has_key('output'):
796 ti.mtime = v['output'].mtime
797 ti.size = len(v['output'].getvalue())
799 tf.addfile(ti, v['output'])
800 elif v.has_key('filename'):
801 s = os.stat(v['filename'])
802 ti.mtime = s.st_mtime
804 tf.addfile(ti, file(v['filename']))
811 output ('Writing tarball %s successful.' % filename)
816 def make_zip(subdir):
817 global SILENT_MODE, data
819 filename = "%s/%s.zip" % (BUG_DIR, subdir)
820 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
823 for (k, v) in data.items():
825 dest = os.path.join(subdir, construct_filename(k, v))
827 if v.has_key('output'):
828 zf.writestr(dest, v['output'].getvalue())
830 if os.stat(v['filename']).st_size < 50:
831 compress_type = zipfile.ZIP_STORED
833 compress_type = zipfile.ZIP_DEFLATED
834 zf.write(v['filename'], dest, compress_type)
840 output ('Writing archive %s successful.' % filename)
845 def make_inventory(inventory, subdir):
846 document = getDOMImplementation().createDocument(
847 None, INVENTORY_XML_ROOT, None)
849 # create summary entry
850 s = document.createElement(INVENTORY_XML_SUMMARY)
851 user = os.getenv('SUDO_USER', os.getenv('USER'))
853 s.setAttribute('user', user)
854 s.setAttribute('date', time.strftime('%c'))
855 s.setAttribute('hostname', platform.node())
856 s.setAttribute('uname', ' '.join(platform.uname()))
857 s.setAttribute('uptime', commands.getoutput(UPTIME))
858 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
860 map(lambda (k, v): inventory_entry(document, subdir, k, v),
862 return document.toprettyxml()
864 def inventory_entry(document, subdir, k, v):
866 el = document.createElement(INVENTORY_XML_ELEMENT)
867 el.setAttribute('capability', v['cap'])
868 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
869 el.setAttribute('md5sum', md5sum(v))
870 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
877 if d.has_key('filename'):
878 f = open(d['filename'])
884 elif d.has_key('output'):
885 m.update(d['output'].getvalue())
889 def construct_filename(k, v):
890 if v.has_key('filename'):
891 if v['filename'][0] == '/':
892 return v['filename'][1:]
895 s = k.replace(' ', '-')
896 s = s.replace('--', '-')
897 s = s.replace('/', '%')
898 if s.find('.') == -1:
903 def update_capabilities():
906 def update_cap_size(cap, size):
907 update_cap(cap, MIN_SIZE, size)
908 update_cap(cap, MAX_SIZE, size)
909 update_cap(cap, CHECKED, size > 0)
912 def update_cap(cap, k, v):
919 def size_of_dir(d, pattern = None, negate = False):
921 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
927 def size_of_all(files, pattern = None, negate = False):
928 return sum([size_of(f, pattern, negate) for f in files])
931 def matches(f, pattern, negate):
933 return not matches(f, pattern, False)
935 return pattern is None or pattern.match(f)
938 def size_of(f, pattern, negate):
939 if os.path.isfile(f) and matches(f, pattern, negate):
942 return size_of_dir(f, pattern, negate)
945 def print_capabilities():
946 document = getDOMImplementation().createDocument(
947 "ns", CAP_XML_ROOT, None)
948 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
949 print document.toprettyxml()
951 def capability(document, key):
953 el = document.createElement(CAP_XML_ELEMENT)
954 el.setAttribute('key', c[KEY])
955 el.setAttribute('pii', c[PII])
956 el.setAttribute('min-size', str(c[MIN_SIZE]))
957 el.setAttribute('max-size', str(c[MAX_SIZE]))
958 el.setAttribute('min-time', str(c[MIN_TIME]))
959 el.setAttribute('max-time', str(c[MAX_TIME]))
960 el.setAttribute('content-type', c[MIME])
961 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
962 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
966 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
967 return '\n'.join([format % i for i in d.items()]) + '\n'
971 yn = raw_input(prompt)
973 return len(yn) == 0 or yn.lower()[0] == 'y'
976 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
981 f = open('/proc/partitions')
984 for line in f.readlines():
985 (major, minor, blocks, name) = line.split()
986 if int(major) < 254 and not partition_re.match(name):
997 def __init__(self, command, max_time, inst=None, filter=None):
998 self.command = command
999 self.max_time = max_time
1001 self.running = False
1003 self.timed_out = False
1005 self.timeout = int(time.time()) + self.max_time
1006 self.filter = filter
1007 self.filter_state = {}
1013 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1016 self.timed_out = False
1018 if ProcOutput.debug:
1019 output_ts("Starting '%s'" % self.cmdAsStr())
1020 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1021 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1022 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1026 output_ts("'%s' failed" % self.cmdAsStr())
1027 self.running = False
1030 def terminate(self):
1033 os.kill(self.proc.pid, SIGTERM)
1037 self.running = False
1038 self.status = SIGTERM
1040 def read_line(self):
1042 line = self.proc.stdout.readline()
1045 self.status = self.proc.wait()
1047 self.running = False
1050 line = self.filter(line, self.filter_state)
1052 self.inst.write(line)
1054 def run_procs(procs):
1062 active_procs.append(p)
1063 pipes.append(p.proc.stdout)
1065 elif p.status == None and not p.failed and not p.timed_out:
1068 active_procs.append(p)
1069 pipes.append(p.proc.stdout)
1076 (i, o, x) = select(pipes, [], [], 1.0)
1077 now = int(time.time())
1079 # handle process output
1080 for p in active_procs:
1081 if p.proc.stdout in i:
1085 if p.running and now > p.timeout:
1086 output_ts("'%s' timed out" % p.cmdAsStr())
1088 p.inst.write("\n** timeout **\n")
1096 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1098 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1106 class StringIOmtime(StringIO.StringIO):
1107 def __init__(self, buf = ''):
1108 StringIO.StringIO.__init__(self, buf)
1109 self.mtime = time.time()
1112 StringIO.StringIO.write(self, s)
1113 self.mtime = time.time()
1116 if __name__ == "__main__":
1119 except KeyboardInterrupt:
1120 print "\nInterrupted."