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(),
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, 'status', d])
522 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
523 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
525 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
526 vspid = int(vspidfile.readline().strip())
528 for b in bond_list(vspid):
529 cmd_output(CAP_NETWORK_STATUS,
530 [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
531 'ovs-appctl-bond-show-%s.out' % b)
535 tree_output(CAP_PAM, PAM_DIR)
537 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
538 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
540 file_output(CAP_SYSTEM_LOGS,
541 [ VAR_LOG_DIR + x for x in
542 [ 'kern.log', 'daemon.log', 'user.log', 'syslog', 'messages',
543 'debug', 'dmesg', 'boot'] +
544 [ f % n for n in range(1, 20) \
545 for f in ['kern.log.%d', 'kern.log.%d.gz',
546 'daemon.log.%d', 'daemon.log.%d.gz',
547 'user.log.%d', 'user.log.%d.gz',
548 'messages.%d', 'messages.%d.gz']]])
549 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
550 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
553 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
554 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
555 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
563 # permit the user to filter out data
564 for k in sorted(data.keys()):
565 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
568 # collect selected data now
569 output_ts('Running commands to collect data')
572 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
575 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
578 if output_fd == -1 and not os.path.exists(BUG_DIR):
585 output_ts('Creating output file')
587 if output_type.startswith('tar'):
588 make_tar(subdir, output_type, output_fd)
595 print >>sys.stderr, "Category sizes (max, actual):\n"
596 for c in caps.keys():
597 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
601 def find_tapdisk_logs():
602 return glob.glob('/var/log/blktap/*.log*')
604 def generate_tapdisk_logs():
605 for pid in pidof('tapdisk'):
607 os.kill(pid, SIGUSR1)
608 output_ts("Including logs for tapdisk process %d" % pid)
611 # give processes a second to write their logs
614 def clean_tapdisk_logs():
615 for filename in find_tapdisk_logs():
621 def filter_db_pii(str, state):
622 if 'in_secret_table' not in state:
623 state['in_secret_table'] = False
625 if str.startswith('<table ') and 'name="secret"' in str:
626 state['in_secret_table'] = True
627 elif str.startswith('</table>'):
628 state['in_secret_table'] = False
630 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
631 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
634 def dump_scsi_hosts(cap):
636 l = os.listdir('/sys/class/scsi_host')
642 f = open('/sys/class/scsi_host/%s/proc_name' % h)
643 procname = f.readline().strip("\n")
649 f = open('/sys/class/scsi_host/%s/model_name' % h)
650 modelname = f.readline().strip("\n")
656 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
660 def module_info(cap):
661 output = StringIO.StringIO()
662 modules = open(PROC_MODULES, 'r')
666 module = line.split()[0]
667 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
672 return output.getvalue()
675 output = StringIO.StringIO()
676 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
680 if not procs[0].timed_out:
681 return output.getvalue().splitlines()
685 output = StringIO.StringIO()
686 procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
690 if not procs[0].timed_out:
691 bonds = output.getvalue().splitlines()[1:]
692 return [x.split('\t')[1] for x in bonds]
698 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
700 fh = open('/proc/'+d+'/cmdline')
702 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
704 if not num_fds in fd_dict:
705 fd_dict[num_fds] = []
706 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
709 keys = fd_dict.keys()
710 keys.sort(lambda a, b: int(b) - int(a))
712 output += "%s: %s\n" % (k, str(fd_dict[k]))
715 def load_plugins(just_capabilities = False):
716 def getText(nodelist):
718 for node in nodelist:
719 if node.nodeType == node.TEXT_NODE:
723 def getBoolAttr(el, attr, default = False):
725 val = el.getAttribute(attr).lower()
726 if val in ['true', 'false', 'yes', 'no']:
727 ret = val in ['true', 'yes']
730 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
731 if not caps.has_key(dir):
732 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
734 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
735 assert xmldoc.documentElement.tagName == "capability"
737 pii, min_size, max_size, min_time, max_time, mime = \
738 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
740 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
741 pii = xmldoc.documentElement.getAttribute("pii")
742 if xmldoc.documentElement.getAttribute("min_size") != '':
743 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
744 if xmldoc.documentElement.getAttribute("max_size") != '':
745 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
746 if xmldoc.documentElement.getAttribute("min_time") != '':
747 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
748 if xmldoc.documentElement.getAttribute("max_time") != '':
749 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
750 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
751 mime = xmldoc.documentElement.getAttribute("mime")
752 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
753 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
755 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
757 if just_capabilities:
760 plugdir = os.path.join(PLUGIN_DIR, dir)
761 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
762 xmldoc = parse(os.path.join(plugdir, file))
763 assert xmldoc.documentElement.tagName == "collect"
765 for el in xmldoc.documentElement.getElementsByTagName("*"):
766 if el.tagName == "files":
767 file_output(dir, getText(el.childNodes).split())
768 elif el.tagName == "directory":
769 pattern = el.getAttribute("pattern")
770 if pattern == '': pattern = None
771 negate = getBoolAttr(el, 'negate')
772 tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
773 elif el.tagName == "command":
774 label = el.getAttribute("label")
775 if label == '': label = None
776 cmd_output(dir, getText(el.childNodes), label)
778 def make_tar(subdir, suffix, output_fd):
779 global SILENT_MODE, data
782 if suffix == 'tar.bz2':
784 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
787 tf = tarfile.open(filename, mode)
789 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
792 for (k, v) in data.items():
794 tar_filename = os.path.join(subdir, construct_filename(k, v))
795 ti = tarfile.TarInfo(tar_filename)
800 if v.has_key('output'):
801 ti.mtime = v['output'].mtime
802 ti.size = len(v['output'].getvalue())
804 tf.addfile(ti, v['output'])
805 elif v.has_key('filename'):
806 s = os.stat(v['filename'])
807 ti.mtime = s.st_mtime
809 tf.addfile(ti, file(v['filename']))
816 output ('Writing tarball %s successful.' % filename)
821 def make_zip(subdir):
822 global SILENT_MODE, data
824 filename = "%s/%s.zip" % (BUG_DIR, subdir)
825 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
828 for (k, v) in data.items():
830 dest = os.path.join(subdir, construct_filename(k, v))
832 if v.has_key('output'):
833 zf.writestr(dest, v['output'].getvalue())
835 if os.stat(v['filename']).st_size < 50:
836 compress_type = zipfile.ZIP_STORED
838 compress_type = zipfile.ZIP_DEFLATED
839 zf.write(v['filename'], dest, compress_type)
845 output ('Writing archive %s successful.' % filename)
850 def make_inventory(inventory, subdir):
851 document = getDOMImplementation().createDocument(
852 None, INVENTORY_XML_ROOT, None)
854 # create summary entry
855 s = document.createElement(INVENTORY_XML_SUMMARY)
856 user = os.getenv('SUDO_USER', os.getenv('USER'))
858 s.setAttribute('user', user)
859 s.setAttribute('date', time.strftime('%c'))
860 s.setAttribute('hostname', platform.node())
861 s.setAttribute('uname', ' '.join(platform.uname()))
862 s.setAttribute('uptime', commands.getoutput(UPTIME))
863 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
865 map(lambda (k, v): inventory_entry(document, subdir, k, v),
867 return document.toprettyxml()
869 def inventory_entry(document, subdir, k, v):
871 el = document.createElement(INVENTORY_XML_ELEMENT)
872 el.setAttribute('capability', v['cap'])
873 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
874 el.setAttribute('md5sum', md5sum(v))
875 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
882 if d.has_key('filename'):
883 f = open(d['filename'])
889 elif d.has_key('output'):
890 m.update(d['output'].getvalue())
894 def construct_filename(k, v):
895 if v.has_key('filename'):
896 if v['filename'][0] == '/':
897 return v['filename'][1:]
900 s = k.replace(' ', '-')
901 s = s.replace('--', '-')
902 s = s.replace('/', '%')
903 if s.find('.') == -1:
908 def update_capabilities():
911 def update_cap_size(cap, size):
912 update_cap(cap, MIN_SIZE, size)
913 update_cap(cap, MAX_SIZE, size)
914 update_cap(cap, CHECKED, size > 0)
917 def update_cap(cap, k, v):
924 def size_of_dir(d, pattern = None, negate = False):
926 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
932 def size_of_all(files, pattern = None, negate = False):
933 return sum([size_of(f, pattern, negate) for f in files])
936 def matches(f, pattern, negate):
938 return not matches(f, pattern, False)
940 return pattern is None or pattern.match(f)
943 def size_of(f, pattern, negate):
944 if os.path.isfile(f) and matches(f, pattern, negate):
947 return size_of_dir(f, pattern, negate)
950 def print_capabilities():
951 document = getDOMImplementation().createDocument(
952 "ns", CAP_XML_ROOT, None)
953 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
954 print document.toprettyxml()
956 def capability(document, key):
958 el = document.createElement(CAP_XML_ELEMENT)
959 el.setAttribute('key', c[KEY])
960 el.setAttribute('pii', c[PII])
961 el.setAttribute('min-size', str(c[MIN_SIZE]))
962 el.setAttribute('max-size', str(c[MAX_SIZE]))
963 el.setAttribute('min-time', str(c[MIN_TIME]))
964 el.setAttribute('max-time', str(c[MAX_TIME]))
965 el.setAttribute('content-type', c[MIME])
966 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
967 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
971 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
972 return '\n'.join([format % i for i in d.items()]) + '\n'
976 yn = raw_input(prompt)
978 return len(yn) == 0 or yn.lower()[0] == 'y'
981 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
986 f = open('/proc/partitions')
989 for line in f.readlines():
990 (major, minor, blocks, name) = line.split()
991 if int(major) < 254 and not partition_re.match(name):
1002 def __init__(self, command, max_time, inst=None, filter=None):
1003 self.command = command
1004 self.max_time = max_time
1006 self.running = False
1008 self.timed_out = False
1010 self.timeout = int(time.time()) + self.max_time
1011 self.filter = filter
1012 self.filter_state = {}
1018 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1021 self.timed_out = False
1023 if ProcOutput.debug:
1024 output_ts("Starting '%s'" % self.cmdAsStr())
1025 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1026 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1027 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1031 output_ts("'%s' failed" % self.cmdAsStr())
1032 self.running = False
1035 def terminate(self):
1038 os.kill(self.proc.pid, SIGTERM)
1042 self.running = False
1043 self.status = SIGTERM
1045 def read_line(self):
1047 line = self.proc.stdout.readline()
1050 self.status = self.proc.wait()
1052 self.running = False
1055 line = self.filter(line, self.filter_state)
1057 self.inst.write(line)
1059 def run_procs(procs):
1067 active_procs.append(p)
1068 pipes.append(p.proc.stdout)
1070 elif p.status == None and not p.failed and not p.timed_out:
1073 active_procs.append(p)
1074 pipes.append(p.proc.stdout)
1081 (i, o, x) = select(pipes, [], [], 1.0)
1082 now = int(time.time())
1084 # handle process output
1085 for p in active_procs:
1086 if p.proc.stdout in i:
1090 if p.running and now > p.timeout:
1091 output_ts("'%s' timed out" % p.cmdAsStr())
1093 p.inst.write("\n** timeout **\n")
1101 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1103 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1111 class StringIOmtime(StringIO.StringIO):
1112 def __init__(self, buf = ''):
1113 StringIO.StringIO.__init__(self, buf)
1114 self.mtime = time.time()
1117 StringIO.StringIO.write(self, s)
1118 self.mtime = time.time()
1121 if __name__ == "__main__":
1124 except KeyboardInterrupt:
1125 print "\nInterrupted."