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, 2011 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(),
37 warnings.filterwarnings(action="ignore", category=DeprecationWarning)
48 from xml.dom.minidom import parse, getDOMImplementation
50 from subprocess import Popen, PIPE
51 from select import select
52 from signal import SIGTERM, SIGUSR1
61 sys.path.append('/usr/lib/python')
62 sys.path.append('/usr/lib64/python')
64 OS_RELEASE = platform.release()
70 BUG_DIR = "/var/log/ovs-bugtool"
71 PLUGIN_DIR = "/etc/openvswitch/bugtool"
72 GRUB_CONFIG = '/boot/grub/menu.lst'
73 BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
74 BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
75 PROC_PARTITIONS = '/proc/partitions'
77 PROC_MOUNTS = '/proc/mounts'
78 PROC_CPUINFO = '/proc/cpuinfo'
79 PROC_MEMINFO = '/proc/meminfo'
80 PROC_IOPORTS = '/proc/ioports'
81 PROC_INTERRUPTS = '/proc/interrupts'
82 PROC_SCSI = '/proc/scsi/scsi'
83 PROC_VERSION = '/proc/version'
84 PROC_MODULES = '/proc/modules'
85 PROC_DEVICES = '/proc/devices'
86 PROC_FILESYSTEMS = '/proc/filesystems'
87 PROC_CMDLINE = '/proc/cmdline'
88 PROC_CONFIG = '/proc/config.gz'
89 PROC_USB_DEV = '/proc/bus/usb/devices'
90 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
91 MODPROBE_DIR = '/etc/modprobe.d'
92 RESOLV_CONF = '/etc/resolv.conf'
93 NSSWITCH_CONF = '/etc/nsswitch.conf'
94 NTP_CONF = '/etc/ntp.conf'
96 HOSTS_ALLOW = '/etc/hosts.allow'
97 HOSTS_DENY = '/etc/hosts.deny'
98 DHCP_LEASE_DIR = '/var/lib/dhcp3'
99 OPENVSWITCH_LOG_DIR = '/var/log/openvswitch'
100 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch'
101 OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
102 OPENVSWITCH_CONF_DB = '/etc/openvswitch/conf.db'
103 OPENVSWITCH_VSWITCHD_PID = '/var/run/openvswitch/ovs-vswitchd.pid'
104 COLLECTD_LOGS_DIR = '/var/lib/collectd/rrd'
105 VAR_LOG_DIR = '/var/log/'
106 VAR_LOG_CORE_DIR = '/var/log/core'
107 X11_LOGS_DIR = VAR_LOG_DIR
108 X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
109 X11_AUTH_DIR = '/root/'
110 X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
111 PAM_DIR = '/etc/pam.d'
117 ARP = '/usr/sbin/arp'
121 DMIDECODE = '/usr/sbin/dmidecode'
122 FDISK = '/sbin/fdisk'
123 FIND = '/usr/bin/find'
124 IFCONFIG = '/sbin/ifconfig'
125 IPTABLES = '/sbin/iptables'
126 LOSETUP = '/sbin/losetup'
128 LSPCI = '/usr/bin/lspci'
129 MD5SUM = '/usr/bin/md5sum'
130 MODINFO = '/sbin/modinfo'
131 NETSTAT = '/bin/netstat'
132 OVS_DPCTL = '/usr/sbin/ovs-dpctl'
133 OVS_OFCTL = '/usr/sbin/ovs-ofctl'
134 OVS_VSCTL = '/usr/sbin/ovs-vsctl'
135 OVS_APPCTL = '/usr/sbin/ovs-appctl'
137 ROUTE = '/sbin/route'
138 SYSCTL = '/sbin/sysctl'
140 UPTIME = '/usr/bin/uptime'
143 ETHTOOL = '/sbin/ethtool'
144 # ETHTOOL recently moved from /usr/sbin to /sbin in debian
145 if not os.path.isfile(ETHTOOL):
146 ETHTOOL = '/usr/sbin/ethtool'
149 # PII -- Personally identifiable information. Of particular concern are
150 # things that would identify customers, or their network topology.
151 # Passwords are never to be included in any bug report, regardless of any PII
154 # NO -- No PII will be in these entries.
155 # YES -- PII will likely or certainly be in these entries.
156 # MAYBE -- The user may wish to audit these entries for PII.
157 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
158 # but since we encourage customers to edit these files, PII may have been
159 # introduced by the customer. This is used in particular for the networking
166 PII_IF_CUSTOMIZED = 'if_customized'
177 MIME_DATA = 'application/data'
178 MIME_TEXT = 'text/plain'
180 INVENTORY_XML_ROOT = "system-status-inventory"
181 INVENTORY_XML_SUMMARY = 'system-summary'
182 INVENTORY_XML_ELEMENT = 'inventory-entry'
183 CAP_XML_ROOT = "system-status-capabilities"
184 CAP_XML_ELEMENT = 'capability'
188 CAP_BOOT_LOADER = 'boot-loader'
189 CAP_COLLECTD_LOGS = 'collectd-logs'
190 CAP_DISK_INFO = 'disk-info'
191 CAP_FIRSTBOOT = 'firstboot'
192 CAP_HARDWARE_INFO = 'hardware-info'
193 CAP_HIGH_AVAILABILITY = 'high-availability'
194 CAP_HOST_CRASHDUMP_DUMPS = 'host-crashdump-dumps'
195 CAP_HOST_CRASHDUMP_LOGS = 'host-crashdump-logs'
196 CAP_KERNEL_INFO = 'kernel-info'
197 CAP_LOSETUP_A = 'loopback-devices'
198 CAP_NETWORK_CONFIG = 'network-config'
199 CAP_NETWORK_STATUS = 'network-status'
202 CAP_PROCESS_LIST = 'process-list'
203 CAP_PERSISTENT_STATS = 'persistent-stats'
204 CAP_SYSTEM_LOGS = 'system-logs'
205 CAP_SYSTEM_SERVICES = 'system-services'
206 CAP_VNCTERM = 'vncterm'
209 CAP_X11_AUTH = 'X11-auth'
216 unlimited_data = False
219 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
220 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
221 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
226 cap(CAP_BLOBS, PII_NO, max_size=5*MB)
227 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
229 cap(CAP_COLLECTD_LOGS, PII_MAYBE, max_size=50*MB,
231 cap(CAP_DISK_INFO, PII_MAYBE, max_size=25*KB,
233 cap(CAP_FIRSTBOOT, PII_YES, min_size=60*KB, max_size=80*KB)
234 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB,
236 cap(CAP_HIGH_AVAILABILITY, PII_MAYBE, max_size=5*MB)
237 cap(CAP_HOST_CRASHDUMP_DUMPS,PII_YES, checked = False)
238 cap(CAP_HOST_CRASHDUMP_LOGS, PII_NO)
239 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
241 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
242 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
243 min_size=0, max_size=20*KB)
244 cap(CAP_NETWORK_STATUS, PII_YES, max_size=19*KB,
246 cap(CAP_PAM, PII_NO, max_size=30*KB)
247 cap(CAP_PERSISTENT_STATS, PII_MAYBE, max_size=50*MB,
249 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
251 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=50*MB,
253 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
255 cap(CAP_VNCTERM, PII_MAYBE, checked = False)
256 cap(CAP_WLB, PII_NO, max_size=3*MB,
258 cap(CAP_X11_LOGS, PII_NO, max_size=100*KB)
259 cap(CAP_X11_AUTH, PII_NO, max_size=100*KB)
261 ANSWER_YES_TO_ALL = False
265 dev_null = open('/dev/null', 'r+')
273 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
275 def cmd_output(cap, args, label = None, filter = None):
278 if isinstance(args, list):
279 a = [aa for aa in args]
280 a[0] = os.path.basename(a[0])
284 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
286 def file_output(cap, path_list):
289 if os.path.exists(p):
290 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
291 cap_sizes[cap] < caps[cap][MAX_SIZE]:
292 data[p] = {'cap': cap, 'filename': p}
295 cap_sizes[cap] += s.st_size
299 output("Omitting %s, size constraint of %s exceeded" % (p, cap))
301 def tree_output(cap, path, pattern = None, negate = False):
303 if os.path.exists(path):
304 for f in os.listdir(path):
305 fn = os.path.join(path, f)
306 if os.path.isfile(fn) and matches(fn, pattern, negate):
307 file_output(cap, [fn])
308 elif os.path.isdir(fn):
309 tree_output(cap, fn, pattern, negate)
311 def func_output(cap, label, func):
313 t = str(func).split()
314 data[label] = {'cap': cap, 'func': func}
319 for (k, v) in data.items():
321 if v.has_key('cmd_args'):
322 v['output'] = StringIOmtime()
323 if not process_lists.has_key(cap):
324 process_lists[cap] = []
325 process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
326 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
327 # proc files must be read into memory
329 f = open(v['filename'], 'r')
332 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
333 cap_sizes[cap] < caps[cap][MAX_SIZE]:
334 v['output'] = StringIOmtime(s)
335 cap_sizes[cap] += len(s)
337 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
340 elif v.has_key('func'):
345 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
346 cap_sizes[cap] < caps[cap][MAX_SIZE]:
347 v['output'] = StringIOmtime(s)
348 cap_sizes[cap] += len(s)
350 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
352 run_procs(process_lists.values())
355 def main(argv = None):
356 global ANSWER_YES_TO_ALL, SILENT_MODE
357 global entries, data, dbg
359 # we need access to privileged files, exit if we are not running as root
361 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
364 output_type = 'tar.bz2'
371 (options, params) = getopt.gnu_getopt(
372 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
373 'output=', 'outfd=', 'all', 'unlimited', 'debug'])
374 except getopt.GetoptError, opterr:
375 print >>sys.stderr, opterr
383 entries = [e for e in caps.keys() if caps[e][CHECKED]]
385 for (k, v) in options:
386 if k == '--capabilities':
387 update_capabilities()
392 if v in ['tar', 'tar.bz2', 'zip']:
395 print >>sys.stderr, "Invalid output format '%s'" % v
398 # "-s" or "--silent" means suppress output (except for the final
399 # output filename at the end)
400 if k in ['-s', '--silent']:
403 if k == '--entries' and v != '':
404 entries = v.split(',')
406 # If the user runs the script with "-y" or "--yestoall" we don't ask
407 # all the really annoying questions.
408 if k in ['-y', '--yestoall']:
409 ANSWER_YES_TO_ALL = True
414 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
415 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
417 print >>sys.stderr, "Invalid output file descriptor", output_fd
421 entries = caps.keys()
422 elif k == '--unlimited':
423 unlimited_data = True
426 ProcOutput.debug = True
429 print >>sys.stderr, "Invalid additional arguments", str(params)
432 if output_fd != -1 and output_type != 'tar':
433 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
436 if ANSWER_YES_TO_ALL:
437 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
440 This application will collate dmesg output, details of the
441 hardware configuration of your machine, information about the build of
442 openvswitch that you are using, plus, if you allow it, various logs.
444 The collated information will be saved as a .%s for archiving or
445 sending to a Technical Support Representative.
447 The logs may contain private information, and if you are at all
448 worried about that, you should exit now, or you should explicitly
449 exclude those logs from the archive.
453 # assemble potential data
455 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
456 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
457 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
459 tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
460 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
461 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
462 file_output(CAP_DISK_INFO, [FSTAB])
463 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
464 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
465 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
466 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
467 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
468 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
471 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
472 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
473 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
474 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
475 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
476 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
478 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
479 PROC_FILESYSTEMS, PROC_CMDLINE])
480 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
481 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
482 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
483 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
485 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
487 file_output(CAP_NETWORK_CONFIG, [RESOLV_CONF, NSSWITCH_CONF, HOSTS])
488 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, HOSTS_ALLOW, HOSTS_DENY])
489 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
490 OPENVSWITCH_DEFAULT_CONTROLLER, OPENVSWITCH_CONF_DB])
492 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
493 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
494 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
495 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
496 tree_output(CAP_NETWORK_STATUS, DHCP_LEASE_DIR)
497 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
498 for p in os.listdir('/sys/class/net/'):
500 f = open('/sys/class/net/%s/type' % p, 'r')
505 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
506 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
507 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
508 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
509 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
510 cmd_output(CAP_NETWORK_STATUS,
511 [TC, '-s', '-d', 'class', 'show', 'dev', p])
514 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
515 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
516 tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_LOG_DIR)
517 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
518 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
520 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
521 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
522 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
524 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
525 vspid = int(vspidfile.readline().strip())
527 for b in bond_list(vspid):
528 cmd_output(CAP_NETWORK_STATUS,
529 [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
530 'ovs-appctl-bond-show-%s.out' % b)
534 tree_output(CAP_PAM, PAM_DIR)
536 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
537 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
539 file_output(CAP_SYSTEM_LOGS,
540 [ VAR_LOG_DIR + x for x in
541 [ 'kern.log', 'daemon.log', 'user.log', 'syslog', 'messages',
542 'debug', 'dmesg', 'boot'] +
543 [ f % n for n in range(1, 20) \
544 for f in ['kern.log.%d', 'kern.log.%d.gz',
545 'daemon.log.%d', 'daemon.log.%d.gz',
546 'user.log.%d', 'user.log.%d.gz',
547 'messages.%d', 'messages.%d.gz']]])
548 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
549 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
552 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
553 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
554 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
562 # permit the user to filter out data
563 for k in sorted(data.keys()):
564 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
567 # collect selected data now
568 output_ts('Running commands to collect data')
571 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
574 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
577 if output_fd == -1 and not os.path.exists(BUG_DIR):
584 output_ts('Creating output file')
586 if output_type.startswith('tar'):
587 make_tar(subdir, output_type, output_fd)
594 print >>sys.stderr, "Category sizes (max, actual):\n"
595 for c in caps.keys():
596 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
600 def find_tapdisk_logs():
601 return glob.glob('/var/log/blktap/*.log*')
603 def generate_tapdisk_logs():
604 for pid in pidof('tapdisk'):
606 os.kill(pid, SIGUSR1)
607 output_ts("Including logs for tapdisk process %d" % pid)
610 # give processes a second to write their logs
613 def clean_tapdisk_logs():
614 for filename in find_tapdisk_logs():
620 def filter_db_pii(str, state):
621 if 'in_secret_table' not in state:
622 state['in_secret_table'] = False
624 if str.startswith('<table ') and 'name="secret"' in str:
625 state['in_secret_table'] = True
626 elif str.startswith('</table>'):
627 state['in_secret_table'] = False
629 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
630 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
633 def dump_scsi_hosts(cap):
635 l = os.listdir('/sys/class/scsi_host')
641 f = open('/sys/class/scsi_host/%s/proc_name' % h)
642 procname = f.readline().strip("\n")
648 f = open('/sys/class/scsi_host/%s/model_name' % h)
649 modelname = f.readline().strip("\n")
655 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
659 def module_info(cap):
660 output = StringIO.StringIO()
661 modules = open(PROC_MODULES, 'r')
665 module = line.split()[0]
666 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
671 return output.getvalue()
674 output = StringIO.StringIO()
675 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
679 if not procs[0].timed_out:
680 return output.getvalue().splitlines()
684 output = StringIO.StringIO()
685 procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
689 if not procs[0].timed_out:
690 bonds = output.getvalue().splitlines()[1:]
691 return [x.split('\t')[1] for x in bonds]
697 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
699 fh = open('/proc/'+d+'/cmdline')
701 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
703 if not num_fds in fd_dict:
704 fd_dict[num_fds] = []
705 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
708 keys = fd_dict.keys()
709 keys.sort(lambda a, b: int(b) - int(a))
711 output += "%s: %s\n" % (k, str(fd_dict[k]))
714 def load_plugins(just_capabilities = False):
715 def getText(nodelist):
717 for node in nodelist:
718 if node.nodeType == node.TEXT_NODE:
722 def getBoolAttr(el, attr, default = False):
724 val = el.getAttribute(attr).lower()
725 if val in ['true', 'false', 'yes', 'no']:
726 ret = val in ['true', 'yes']
729 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
730 if not caps.has_key(dir):
731 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
733 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
734 assert xmldoc.documentElement.tagName == "capability"
736 pii, min_size, max_size, min_time, max_time, mime = \
737 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
739 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
740 pii = xmldoc.documentElement.getAttribute("pii")
741 if xmldoc.documentElement.getAttribute("min_size") != '':
742 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
743 if xmldoc.documentElement.getAttribute("max_size") != '':
744 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
745 if xmldoc.documentElement.getAttribute("min_time") != '':
746 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
747 if xmldoc.documentElement.getAttribute("max_time") != '':
748 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
749 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
750 mime = xmldoc.documentElement.getAttribute("mime")
751 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
752 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
754 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
756 if just_capabilities:
759 plugdir = os.path.join(PLUGIN_DIR, dir)
760 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
761 xmldoc = parse(os.path.join(plugdir, file))
762 assert xmldoc.documentElement.tagName == "collect"
764 for el in xmldoc.documentElement.getElementsByTagName("*"):
765 if el.tagName == "files":
766 file_output(dir, getText(el.childNodes).split())
767 elif el.tagName == "directory":
768 pattern = el.getAttribute("pattern")
769 if pattern == '': pattern = None
770 negate = getBoolAttr(el, 'negate')
771 tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
772 elif el.tagName == "command":
773 label = el.getAttribute("label")
774 if label == '': label = None
775 cmd_output(dir, getText(el.childNodes), label)
777 def make_tar(subdir, suffix, output_fd):
778 global SILENT_MODE, data
781 if suffix == 'tar.bz2':
783 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
786 tf = tarfile.open(filename, mode)
788 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
791 for (k, v) in data.items():
793 tar_filename = os.path.join(subdir, construct_filename(k, v))
794 ti = tarfile.TarInfo(tar_filename)
799 if v.has_key('output'):
800 ti.mtime = v['output'].mtime
801 ti.size = len(v['output'].getvalue())
803 tf.addfile(ti, v['output'])
804 elif v.has_key('filename'):
805 s = os.stat(v['filename'])
806 ti.mtime = s.st_mtime
808 tf.addfile(ti, file(v['filename']))
815 output ('Writing tarball %s successful.' % filename)
820 def make_zip(subdir):
821 global SILENT_MODE, data
823 filename = "%s/%s.zip" % (BUG_DIR, subdir)
824 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
827 for (k, v) in data.items():
829 dest = os.path.join(subdir, construct_filename(k, v))
831 if v.has_key('output'):
832 zf.writestr(dest, v['output'].getvalue())
834 if os.stat(v['filename']).st_size < 50:
835 compress_type = zipfile.ZIP_STORED
837 compress_type = zipfile.ZIP_DEFLATED
838 zf.write(v['filename'], dest, compress_type)
844 output ('Writing archive %s successful.' % filename)
849 def make_inventory(inventory, subdir):
850 document = getDOMImplementation().createDocument(
851 None, INVENTORY_XML_ROOT, None)
853 # create summary entry
854 s = document.createElement(INVENTORY_XML_SUMMARY)
855 user = os.getenv('SUDO_USER', os.getenv('USER'))
857 s.setAttribute('user', user)
858 s.setAttribute('date', time.strftime('%c'))
859 s.setAttribute('hostname', platform.node())
860 s.setAttribute('uname', ' '.join(platform.uname()))
861 s.setAttribute('uptime', commands.getoutput(UPTIME))
862 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
864 map(lambda (k, v): inventory_entry(document, subdir, k, v),
866 return document.toprettyxml()
868 def inventory_entry(document, subdir, k, v):
870 el = document.createElement(INVENTORY_XML_ELEMENT)
871 el.setAttribute('capability', v['cap'])
872 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
873 el.setAttribute('md5sum', md5sum(v))
874 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
881 if d.has_key('filename'):
882 f = open(d['filename'])
888 elif d.has_key('output'):
889 m.update(d['output'].getvalue())
893 def construct_filename(k, v):
894 if v.has_key('filename'):
895 if v['filename'][0] == '/':
896 return v['filename'][1:]
899 s = k.replace(' ', '-')
900 s = s.replace('--', '-')
901 s = s.replace('/', '%')
902 if s.find('.') == -1:
907 def update_capabilities():
910 def update_cap_size(cap, size):
911 update_cap(cap, MIN_SIZE, size)
912 update_cap(cap, MAX_SIZE, size)
913 update_cap(cap, CHECKED, size > 0)
916 def update_cap(cap, k, v):
923 def size_of_dir(d, pattern = None, negate = False):
925 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
931 def size_of_all(files, pattern = None, negate = False):
932 return sum([size_of(f, pattern, negate) for f in files])
935 def matches(f, pattern, negate):
937 return not matches(f, pattern, False)
939 return pattern is None or pattern.match(f)
942 def size_of(f, pattern, negate):
943 if os.path.isfile(f) and matches(f, pattern, negate):
946 return size_of_dir(f, pattern, negate)
949 def print_capabilities():
950 document = getDOMImplementation().createDocument(
951 "ns", CAP_XML_ROOT, None)
952 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
953 print document.toprettyxml()
955 def capability(document, key):
957 el = document.createElement(CAP_XML_ELEMENT)
958 el.setAttribute('key', c[KEY])
959 el.setAttribute('pii', c[PII])
960 el.setAttribute('min-size', str(c[MIN_SIZE]))
961 el.setAttribute('max-size', str(c[MAX_SIZE]))
962 el.setAttribute('min-time', str(c[MIN_TIME]))
963 el.setAttribute('max-time', str(c[MAX_TIME]))
964 el.setAttribute('content-type', c[MIME])
965 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
966 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
970 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
971 return '\n'.join([format % i for i in d.items()]) + '\n'
975 yn = raw_input(prompt)
977 return len(yn) == 0 or yn.lower()[0] == 'y'
980 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
985 f = open('/proc/partitions')
988 for line in f.readlines():
989 (major, minor, blocks, name) = line.split()
990 if int(major) < 254 and not partition_re.match(name):
1001 def __init__(self, command, max_time, inst=None, filter=None):
1002 self.command = command
1003 self.max_time = max_time
1005 self.running = False
1007 self.timed_out = False
1009 self.timeout = int(time.time()) + self.max_time
1010 self.filter = filter
1011 self.filter_state = {}
1017 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1020 self.timed_out = False
1022 if ProcOutput.debug:
1023 output_ts("Starting '%s'" % self.cmdAsStr())
1024 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1025 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1026 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1030 output_ts("'%s' failed" % self.cmdAsStr())
1031 self.running = False
1034 def terminate(self):
1037 os.kill(self.proc.pid, SIGTERM)
1041 self.running = False
1042 self.status = SIGTERM
1044 def read_line(self):
1046 line = self.proc.stdout.readline()
1049 self.status = self.proc.wait()
1051 self.running = False
1054 line = self.filter(line, self.filter_state)
1056 self.inst.write(line)
1058 def run_procs(procs):
1066 active_procs.append(p)
1067 pipes.append(p.proc.stdout)
1069 elif p.status == None and not p.failed and not p.timed_out:
1072 active_procs.append(p)
1073 pipes.append(p.proc.stdout)
1080 (i, o, x) = select(pipes, [], [], 1.0)
1081 now = int(time.time())
1083 # handle process output
1084 for p in active_procs:
1085 if p.proc.stdout in i:
1089 if p.running and now > p.timeout:
1090 output_ts("'%s' timed out" % p.cmdAsStr())
1092 p.inst.write("\n** timeout **\n")
1100 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1102 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1110 class StringIOmtime(StringIO.StringIO):
1111 def __init__(self, buf = ''):
1112 StringIO.StringIO.__init__(self, buf)
1113 self.mtime = time.time()
1116 StringIO.StringIO.write(self, s)
1117 self.mtime = time.time()
1120 if __name__ == "__main__":
1123 except KeyboardInterrupt:
1124 print "\nInterrupted."