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/ovs-bugtool"
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_LOG_DIR = '/var/log/openvswitch'
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 VAR_LOG_CORE_DIR = '/var/log/core'
104 X11_LOGS_DIR = VAR_LOG_DIR
105 X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
106 X11_AUTH_DIR = '/root/'
107 X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
108 PAM_DIR = '/etc/pam.d'
114 ARP = '/usr/sbin/arp'
118 DMIDECODE = '/usr/sbin/dmidecode'
119 FDISK = '/sbin/fdisk'
120 FIND = '/usr/bin/find'
121 IFCONFIG = '/sbin/ifconfig'
122 IPTABLES = '/sbin/iptables'
123 LOSETUP = '/sbin/losetup'
125 LSPCI = '/usr/bin/lspci'
126 MD5SUM = '/usr/bin/md5sum'
127 MODINFO = '/sbin/modinfo'
128 NETSTAT = '/bin/netstat'
129 OVS_DPCTL = '/usr/sbin/ovs-dpctl'
130 OVS_OFCTL = '/usr/sbin/ovs-ofctl'
131 OVS_VSCTL = '/usr/sbin/ovs-vsctl'
132 OVS_APPCTL = '/usr/sbin/ovs-appctl'
134 ROUTE = '/sbin/route'
135 SYSCTL = '/sbin/sysctl'
137 UPTIME = '/usr/bin/uptime'
140 ETHTOOL = '/sbin/ethtool'
141 # ETHTOOL recently moved from /usr/sbin to /sbin in debian
142 if not os.path.isfile(ETHTOOL):
143 ETHTOOL = '/usr/sbin/ethtool'
146 # PII -- Personally identifiable information. Of particular concern are
147 # things that would identify customers, or their network topology.
148 # Passwords are never to be included in any bug report, regardless of any PII
151 # NO -- No PII will be in these entries.
152 # YES -- PII will likely or certainly be in these entries.
153 # MAYBE -- The user may wish to audit these entries for PII.
154 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
155 # but since we encourage customers to edit these files, PII may have been
156 # introduced by the customer. This is used in particular for the networking
163 PII_IF_CUSTOMIZED = 'if_customized'
174 MIME_DATA = 'application/data'
175 MIME_TEXT = 'text/plain'
177 INVENTORY_XML_ROOT = "system-status-inventory"
178 INVENTORY_XML_SUMMARY = 'system-summary'
179 INVENTORY_XML_ELEMENT = 'inventory-entry'
180 CAP_XML_ROOT = "system-status-capabilities"
181 CAP_XML_ELEMENT = 'capability'
185 CAP_BOOT_LOADER = 'boot-loader'
186 CAP_COLLECTD_LOGS = 'collectd-logs'
187 CAP_DISK_INFO = 'disk-info'
188 CAP_FIRSTBOOT = 'firstboot'
189 CAP_HARDWARE_INFO = 'hardware-info'
190 CAP_HIGH_AVAILABILITY = 'high-availability'
191 CAP_HOST_CRASHDUMP_DUMPS = 'host-crashdump-dumps'
192 CAP_HOST_CRASHDUMP_LOGS = 'host-crashdump-logs'
193 CAP_KERNEL_INFO = 'kernel-info'
194 CAP_LOSETUP_A = 'loopback-devices'
195 CAP_NETWORK_CONFIG = 'network-config'
196 CAP_NETWORK_STATUS = 'network-status'
199 CAP_PROCESS_LIST = 'process-list'
200 CAP_PERSISTENT_STATS = 'persistent-stats'
201 CAP_SYSTEM_LOGS = 'system-logs'
202 CAP_SYSTEM_SERVICES = 'system-services'
203 CAP_VNCTERM = 'vncterm'
206 CAP_X11_AUTH = 'X11-auth'
213 unlimited_data = False
216 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
217 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
218 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
223 cap(CAP_BLOBS, PII_NO, max_size=5*MB)
224 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
226 cap(CAP_COLLECTD_LOGS, PII_MAYBE, max_size=50*MB,
228 cap(CAP_DISK_INFO, PII_MAYBE, max_size=25*KB,
230 cap(CAP_FIRSTBOOT, PII_YES, min_size=60*KB, max_size=80*KB)
231 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB,
233 cap(CAP_HIGH_AVAILABILITY, PII_MAYBE, max_size=5*MB)
234 cap(CAP_HOST_CRASHDUMP_DUMPS,PII_YES, checked = False)
235 cap(CAP_HOST_CRASHDUMP_LOGS, PII_NO)
236 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
238 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
239 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
240 min_size=0, max_size=20*KB)
241 cap(CAP_NETWORK_STATUS, PII_YES, max_size=19*KB,
243 cap(CAP_PAM, PII_NO, max_size=30*KB)
244 cap(CAP_PERSISTENT_STATS, PII_MAYBE, max_size=50*MB,
246 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
248 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=50*MB,
250 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
252 cap(CAP_VNCTERM, PII_MAYBE, checked = False)
253 cap(CAP_WLB, PII_NO, max_size=3*MB,
255 cap(CAP_X11_LOGS, PII_NO, max_size=100*KB)
256 cap(CAP_X11_AUTH, PII_NO, max_size=100*KB)
258 ANSWER_YES_TO_ALL = False
262 dev_null = open('/dev/null', 'r+')
270 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
272 def cmd_output(cap, args, label = None, filter = None):
275 if isinstance(args, list):
276 a = [aa for aa in args]
277 a[0] = os.path.basename(a[0])
281 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
283 def file_output(cap, path_list):
286 if os.path.exists(p):
287 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
288 cap_sizes[cap] < caps[cap][MAX_SIZE]:
289 data[p] = {'cap': cap, 'filename': p}
292 cap_sizes[cap] += s.st_size
296 output("Omitting %s, size constraint of %s exceeded" % (p, cap))
298 def tree_output(cap, path, pattern = None, negate = False):
300 if os.path.exists(path):
301 for f in os.listdir(path):
302 fn = os.path.join(path, f)
303 if os.path.isfile(fn) and matches(fn, pattern, negate):
304 file_output(cap, [fn])
305 elif os.path.isdir(fn):
306 tree_output(cap, fn, pattern, negate)
308 def func_output(cap, label, func):
310 t = str(func).split()
311 data[label] = {'cap': cap, 'func': func}
316 for (k, v) in data.items():
318 if v.has_key('cmd_args'):
319 v['output'] = StringIOmtime()
320 if not process_lists.has_key(cap):
321 process_lists[cap] = []
322 process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
323 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
324 # proc files must be read into memory
326 f = open(v['filename'], 'r')
329 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
330 cap_sizes[cap] < caps[cap][MAX_SIZE]:
331 v['output'] = StringIOmtime(s)
332 cap_sizes[cap] += len(s)
334 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
337 elif v.has_key('func'):
342 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
343 cap_sizes[cap] < caps[cap][MAX_SIZE]:
344 v['output'] = StringIOmtime(s)
345 cap_sizes[cap] += len(s)
347 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
349 run_procs(process_lists.values())
352 def main(argv = None):
353 global ANSWER_YES_TO_ALL, SILENT_MODE
354 global entries, data, dbg
356 # we need access to privileged files, exit if we are not running as root
358 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
361 output_type = 'tar.bz2'
368 (options, params) = getopt.gnu_getopt(
369 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
370 'output=', 'outfd=', 'all', 'unlimited', 'debug'])
371 except getopt.GetoptError, opterr:
372 print >>sys.stderr, opterr
380 entries = [e for e in caps.keys() if caps[e][CHECKED]]
382 for (k, v) in options:
383 if k == '--capabilities':
384 update_capabilities()
389 if v in ['tar', 'tar.bz2', 'zip']:
392 print >>sys.stderr, "Invalid output format '%s'" % v
395 # "-s" or "--silent" means suppress output (except for the final
396 # output filename at the end)
397 if k in ['-s', '--silent']:
400 if k == '--entries' and v != '':
401 entries = v.split(',')
403 # If the user runs the script with "-y" or "--yestoall" we don't ask
404 # all the really annoying questions.
405 if k in ['-y', '--yestoall']:
406 ANSWER_YES_TO_ALL = True
411 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
412 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
414 print >>sys.stderr, "Invalid output file descriptor", output_fd
418 entries = caps.keys()
419 elif k == '--unlimited':
420 unlimited_data = True
423 ProcOutput.debug = True
426 print >>sys.stderr, "Invalid additional arguments", str(params)
429 if output_fd != -1 and output_type != 'tar':
430 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
433 if ANSWER_YES_TO_ALL:
434 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
437 This application will collate dmesg output, details of the
438 hardware configuration of your machine, information about the build of
439 openvswitch that you are using, plus, if you allow it, various logs.
441 The collated information will be saved as a .%s for archiving or
442 sending to a Technical Support Representative.
444 The logs may contain private information, and if you are at all
445 worried about that, you should exit now, or you should explicitly
446 exclude those logs from the archive.
450 # assemble potential data
452 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
453 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
454 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
456 tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
457 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
458 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
459 file_output(CAP_DISK_INFO, [FSTAB])
460 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
461 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
462 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
463 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
464 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
465 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
468 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
469 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
470 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
471 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
472 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
473 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
475 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
476 PROC_FILESYSTEMS, PROC_CMDLINE])
477 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
478 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
479 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
480 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
482 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
484 file_output(CAP_NETWORK_CONFIG, [RESOLV_CONF, NSSWITCH_CONF, HOSTS])
485 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, HOSTS_ALLOW, HOSTS_DENY])
486 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
487 OPENVSWITCH_DEFAULT_CONTROLLER, OPENVSWITCH_CONF_DB])
489 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
490 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
491 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
492 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
493 tree_output(CAP_NETWORK_STATUS, DHCP_LEASE_DIR)
494 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
495 for p in os.listdir('/sys/class/net/'):
497 f = open('/sys/class/net/%s/type' % p, 'r')
502 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
503 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
504 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
505 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
506 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
507 cmd_output(CAP_NETWORK_STATUS,
508 [TC, '-s', '-d', 'class', 'show', 'dev', p])
511 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
512 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
513 tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_LOG_DIR)
514 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
515 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
517 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
518 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'status', d])
519 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
520 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
522 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
523 vspid = int(vspidfile.readline().strip())
525 for b in bond_list(vspid):
526 cmd_output(CAP_NETWORK_STATUS,
527 [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
528 'ovs-appctl-bond-show-%s.out' % b)
532 tree_output(CAP_PAM, PAM_DIR)
534 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
535 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
537 file_output(CAP_SYSTEM_LOGS,
538 [ VAR_LOG_DIR + x for x in
539 [ 'kern.log', 'daemon.log', 'user.log', 'syslog', 'messages',
540 'debug', 'dmesg', 'boot'] +
541 [ f % n for n in range(1, 20) \
542 for f in ['kern.log.%d', 'kern.log.%d.gz',
543 'daemon.log.%d', 'daemon.log.%d.gz',
544 'user.log.%d', 'user.log.%d.gz',
545 'messages.%d', 'messages.%d.gz']]])
546 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
547 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
550 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
551 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
552 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
560 # permit the user to filter out data
561 for k in sorted(data.keys()):
562 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
565 # collect selected data now
566 output_ts('Running commands to collect data')
569 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
572 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
575 if output_fd == -1 and not os.path.exists(BUG_DIR):
582 output_ts('Creating output file')
584 if output_type.startswith('tar'):
585 make_tar(subdir, output_type, output_fd)
592 print >>sys.stderr, "Category sizes (max, actual):\n"
593 for c in caps.keys():
594 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
598 def find_tapdisk_logs():
599 return glob.glob('/var/log/blktap/*.log*')
601 def generate_tapdisk_logs():
602 for pid in pidof('tapdisk'):
604 os.kill(pid, SIGUSR1)
605 output_ts("Including logs for tapdisk process %d" % pid)
608 # give processes a second to write their logs
611 def clean_tapdisk_logs():
612 for filename in find_tapdisk_logs():
618 def filter_db_pii(str, state):
619 if 'in_secret_table' not in state:
620 state['in_secret_table'] = False
622 if str.startswith('<table ') and 'name="secret"' in str:
623 state['in_secret_table'] = True
624 elif str.startswith('</table>'):
625 state['in_secret_table'] = False
627 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
628 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
631 def dump_scsi_hosts(cap):
633 l = os.listdir('/sys/class/scsi_host')
639 f = open('/sys/class/scsi_host/%s/proc_name' % h)
640 procname = f.readline().strip("\n")
646 f = open('/sys/class/scsi_host/%s/model_name' % h)
647 modelname = f.readline().strip("\n")
653 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
657 def module_info(cap):
658 output = StringIO.StringIO()
659 modules = open(PROC_MODULES, 'r')
663 module = line.split()[0]
664 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
669 return output.getvalue()
672 output = StringIO.StringIO()
673 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
677 if not procs[0].timed_out:
678 return output.getvalue().splitlines()
682 output = StringIO.StringIO()
683 procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
687 if not procs[0].timed_out:
688 bonds = output.getvalue().splitlines()[1:]
689 return [x.split('\t')[1] for x in bonds]
695 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
697 fh = open('/proc/'+d+'/cmdline')
699 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
701 if not num_fds in fd_dict:
702 fd_dict[num_fds] = []
703 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
706 keys = fd_dict.keys()
707 keys.sort(lambda a, b: int(b) - int(a))
709 output += "%s: %s\n" % (k, str(fd_dict[k]))
712 def load_plugins(just_capabilities = False):
713 def getText(nodelist):
715 for node in nodelist:
716 if node.nodeType == node.TEXT_NODE:
720 def getBoolAttr(el, attr, default = False):
722 val = el.getAttribute(attr).lower()
723 if val in ['true', 'false', 'yes', 'no']:
724 ret = val in ['true', 'yes']
727 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
728 if not caps.has_key(dir):
729 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
731 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
732 assert xmldoc.documentElement.tagName == "capability"
734 pii, min_size, max_size, min_time, max_time, mime = \
735 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
737 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
738 pii = xmldoc.documentElement.getAttribute("pii")
739 if xmldoc.documentElement.getAttribute("min_size") != '':
740 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
741 if xmldoc.documentElement.getAttribute("max_size") != '':
742 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
743 if xmldoc.documentElement.getAttribute("min_time") != '':
744 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
745 if xmldoc.documentElement.getAttribute("max_time") != '':
746 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
747 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
748 mime = xmldoc.documentElement.getAttribute("mime")
749 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
750 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
752 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
754 if just_capabilities:
757 plugdir = os.path.join(PLUGIN_DIR, dir)
758 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
759 xmldoc = parse(os.path.join(plugdir, file))
760 assert xmldoc.documentElement.tagName == "collect"
762 for el in xmldoc.documentElement.getElementsByTagName("*"):
763 if el.tagName == "files":
764 file_output(dir, getText(el.childNodes).split())
765 elif el.tagName == "directory":
766 pattern = el.getAttribute("pattern")
767 if pattern == '': pattern = None
768 negate = getBoolAttr(el, 'negate')
769 tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
770 elif el.tagName == "command":
771 label = el.getAttribute("label")
772 if label == '': label = None
773 cmd_output(dir, getText(el.childNodes), label)
775 def make_tar(subdir, suffix, output_fd):
776 global SILENT_MODE, data
779 if suffix == 'tar.bz2':
781 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
784 tf = tarfile.open(filename, mode)
786 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
789 for (k, v) in data.items():
791 tar_filename = os.path.join(subdir, construct_filename(k, v))
792 ti = tarfile.TarInfo(tar_filename)
797 if v.has_key('output'):
798 ti.mtime = v['output'].mtime
799 ti.size = len(v['output'].getvalue())
801 tf.addfile(ti, v['output'])
802 elif v.has_key('filename'):
803 s = os.stat(v['filename'])
804 ti.mtime = s.st_mtime
806 tf.addfile(ti, file(v['filename']))
813 output ('Writing tarball %s successful.' % filename)
818 def make_zip(subdir):
819 global SILENT_MODE, data
821 filename = "%s/%s.zip" % (BUG_DIR, subdir)
822 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
825 for (k, v) in data.items():
827 dest = os.path.join(subdir, construct_filename(k, v))
829 if v.has_key('output'):
830 zf.writestr(dest, v['output'].getvalue())
832 if os.stat(v['filename']).st_size < 50:
833 compress_type = zipfile.ZIP_STORED
835 compress_type = zipfile.ZIP_DEFLATED
836 zf.write(v['filename'], dest, compress_type)
842 output ('Writing archive %s successful.' % filename)
847 def make_inventory(inventory, subdir):
848 document = getDOMImplementation().createDocument(
849 None, INVENTORY_XML_ROOT, None)
851 # create summary entry
852 s = document.createElement(INVENTORY_XML_SUMMARY)
853 user = os.getenv('SUDO_USER', os.getenv('USER'))
855 s.setAttribute('user', user)
856 s.setAttribute('date', time.strftime('%c'))
857 s.setAttribute('hostname', platform.node())
858 s.setAttribute('uname', ' '.join(platform.uname()))
859 s.setAttribute('uptime', commands.getoutput(UPTIME))
860 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
862 map(lambda (k, v): inventory_entry(document, subdir, k, v),
864 return document.toprettyxml()
866 def inventory_entry(document, subdir, k, v):
868 el = document.createElement(INVENTORY_XML_ELEMENT)
869 el.setAttribute('capability', v['cap'])
870 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
871 el.setAttribute('md5sum', md5sum(v))
872 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
879 if d.has_key('filename'):
880 f = open(d['filename'])
886 elif d.has_key('output'):
887 m.update(d['output'].getvalue())
891 def construct_filename(k, v):
892 if v.has_key('filename'):
893 if v['filename'][0] == '/':
894 return v['filename'][1:]
897 s = k.replace(' ', '-')
898 s = s.replace('--', '-')
899 s = s.replace('/', '%')
900 if s.find('.') == -1:
905 def update_capabilities():
908 def update_cap_size(cap, size):
909 update_cap(cap, MIN_SIZE, size)
910 update_cap(cap, MAX_SIZE, size)
911 update_cap(cap, CHECKED, size > 0)
914 def update_cap(cap, k, v):
921 def size_of_dir(d, pattern = None, negate = False):
923 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
929 def size_of_all(files, pattern = None, negate = False):
930 return sum([size_of(f, pattern, negate) for f in files])
933 def matches(f, pattern, negate):
935 return not matches(f, pattern, False)
937 return pattern is None or pattern.match(f)
940 def size_of(f, pattern, negate):
941 if os.path.isfile(f) and matches(f, pattern, negate):
944 return size_of_dir(f, pattern, negate)
947 def print_capabilities():
948 document = getDOMImplementation().createDocument(
949 "ns", CAP_XML_ROOT, None)
950 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
951 print document.toprettyxml()
953 def capability(document, key):
955 el = document.createElement(CAP_XML_ELEMENT)
956 el.setAttribute('key', c[KEY])
957 el.setAttribute('pii', c[PII])
958 el.setAttribute('min-size', str(c[MIN_SIZE]))
959 el.setAttribute('max-size', str(c[MAX_SIZE]))
960 el.setAttribute('min-time', str(c[MIN_TIME]))
961 el.setAttribute('max-time', str(c[MAX_TIME]))
962 el.setAttribute('content-type', c[MIME])
963 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
964 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
968 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
969 return '\n'.join([format % i for i in d.items()]) + '\n'
973 yn = raw_input(prompt)
975 return len(yn) == 0 or yn.lower()[0] == 'y'
978 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
983 f = open('/proc/partitions')
986 for line in f.readlines():
987 (major, minor, blocks, name) = line.split()
988 if int(major) < 254 and not partition_re.match(name):
999 def __init__(self, command, max_time, inst=None, filter=None):
1000 self.command = command
1001 self.max_time = max_time
1003 self.running = False
1005 self.timed_out = False
1007 self.timeout = int(time.time()) + self.max_time
1008 self.filter = filter
1009 self.filter_state = {}
1015 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1018 self.timed_out = False
1020 if ProcOutput.debug:
1021 output_ts("Starting '%s'" % self.cmdAsStr())
1022 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1023 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1024 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1028 output_ts("'%s' failed" % self.cmdAsStr())
1029 self.running = False
1032 def terminate(self):
1035 os.kill(self.proc.pid, SIGTERM)
1039 self.running = False
1040 self.status = SIGTERM
1042 def read_line(self):
1044 line = self.proc.stdout.readline()
1047 self.status = self.proc.wait()
1049 self.running = False
1052 line = self.filter(line, self.filter_state)
1054 self.inst.write(line)
1056 def run_procs(procs):
1064 active_procs.append(p)
1065 pipes.append(p.proc.stdout)
1067 elif p.status == None and not p.failed and not p.timed_out:
1070 active_procs.append(p)
1071 pipes.append(p.proc.stdout)
1078 (i, o, x) = select(pipes, [], [], 1.0)
1079 now = int(time.time())
1081 # handle process output
1082 for p in active_procs:
1083 if p.proc.stdout in i:
1087 if p.running and now > p.timeout:
1088 output_ts("'%s' timed out" % p.cmdAsStr())
1090 p.inst.write("\n** timeout **\n")
1098 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1100 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1108 class StringIOmtime(StringIO.StringIO):
1109 def __init__(self, buf = ''):
1110 StringIO.StringIO.__init__(self, buf)
1111 self.mtime = time.time()
1114 StringIO.StringIO.write(self, s)
1115 self.mtime = time.time()
1118 if __name__ == "__main__":
1121 except KeyboardInterrupt:
1122 print "\nInterrupted."