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"
365 output_type = 'tar.bz2'
372 (options, params) = getopt.gnu_getopt(
373 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
374 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
376 except getopt.GetoptError, opterr:
377 print >>sys.stderr, opterr
385 entries = [e for e in caps.keys() if caps[e][CHECKED]]
387 for (k, v) in options:
388 if k == '--capabilities':
389 update_capabilities()
394 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
397 print >>sys.stderr, "Invalid output format '%s'" % v
400 # "-s" or "--silent" means suppress output (except for the final
401 # output filename at the end)
402 if k in ['-s', '--silent']:
405 if k == '--entries' and v != '':
406 entries = v.split(',')
408 # If the user runs the script with "-y" or "--yestoall" we don't ask
409 # all the really annoying questions.
410 if k in ['-y', '--yestoall']:
411 ANSWER_YES_TO_ALL = True
416 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
417 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
419 print >>sys.stderr, "Invalid output file descriptor", output_fd
426 entries = caps.keys()
427 elif k == '--unlimited':
428 unlimited_data = True
431 ProcOutput.debug = True
434 print >>sys.stderr, "Invalid additional arguments", str(params)
437 if output_fd != -1 and output_type != 'tar':
438 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
441 if output_fd != -1 and output_file is not None:
442 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
445 if ANSWER_YES_TO_ALL:
446 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
449 This application will collate dmesg output, details of the
450 hardware configuration of your machine, information about the build of
451 openvswitch that you are using, plus, if you allow it, various logs.
453 The collated information will be saved as a .%s for archiving or
454 sending to a Technical Support Representative.
456 The logs may contain private information, and if you are at all
457 worried about that, you should exit now, or you should explicitly
458 exclude those logs from the archive.
462 # assemble potential data
464 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
465 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
466 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
468 tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
469 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
470 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
471 file_output(CAP_DISK_INFO, [FSTAB])
472 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
473 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
474 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
475 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
476 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
477 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
480 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
481 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
482 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
483 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
484 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
485 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
487 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
488 PROC_FILESYSTEMS, PROC_CMDLINE])
489 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
490 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
491 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
492 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
494 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
496 file_output(CAP_NETWORK_CONFIG, [RESOLV_CONF, NSSWITCH_CONF, HOSTS])
497 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, HOSTS_ALLOW, HOSTS_DENY])
498 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
499 OPENVSWITCH_DEFAULT_CONTROLLER, OPENVSWITCH_CONF_DB])
501 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
502 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
503 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
504 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
505 tree_output(CAP_NETWORK_STATUS, DHCP_LEASE_DIR)
506 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
507 for p in os.listdir('/sys/class/net/'):
509 f = open('/sys/class/net/%s/type' % p, 'r')
514 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
515 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
516 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
517 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
518 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
519 cmd_output(CAP_NETWORK_STATUS,
520 [TC, '-s', '-d', 'class', 'show', 'dev', p])
523 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
524 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
525 tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_LOG_DIR)
526 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
527 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
529 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
530 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
531 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
533 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
534 vspid = int(vspidfile.readline().strip())
536 for b in bond_list(vspid):
537 cmd_output(CAP_NETWORK_STATUS,
538 [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
539 'ovs-appctl-bond-show-%s.out' % b)
543 tree_output(CAP_PAM, PAM_DIR)
545 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
546 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
548 file_output(CAP_SYSTEM_LOGS,
549 [ VAR_LOG_DIR + x for x in
550 [ 'kern.log', 'daemon.log', 'user.log', 'syslog', 'messages',
551 'debug', 'dmesg', 'boot'] +
552 [ f % n for n in range(1, 20) \
553 for f in ['kern.log.%d', 'kern.log.%d.gz',
554 'daemon.log.%d', 'daemon.log.%d.gz',
555 'user.log.%d', 'user.log.%d.gz',
556 'messages.%d', 'messages.%d.gz']]])
557 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
558 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
561 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
562 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
563 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
571 # permit the user to filter out data
572 for k in sorted(data.keys()):
573 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
576 # collect selected data now
577 output_ts('Running commands to collect data')
580 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
583 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
587 if output_file is None:
590 dirname = os.path.dirname(output_file)
591 if dirname and not os.path.exists(dirname):
598 output_ts('Creating output file')
600 if output_type.startswith('tar'):
601 make_tar(subdir, output_type, output_fd, output_file)
603 make_zip(subdir, output_file)
608 print >>sys.stderr, "Category sizes (max, actual):\n"
609 for c in caps.keys():
610 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
614 def find_tapdisk_logs():
615 return glob.glob('/var/log/blktap/*.log*')
617 def generate_tapdisk_logs():
618 for pid in pidof('tapdisk'):
620 os.kill(pid, SIGUSR1)
621 output_ts("Including logs for tapdisk process %d" % pid)
624 # give processes a second to write their logs
627 def clean_tapdisk_logs():
628 for filename in find_tapdisk_logs():
634 def filter_db_pii(str, state):
635 if 'in_secret_table' not in state:
636 state['in_secret_table'] = False
638 if str.startswith('<table ') and 'name="secret"' in str:
639 state['in_secret_table'] = True
640 elif str.startswith('</table>'):
641 state['in_secret_table'] = False
643 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
644 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
647 def dump_scsi_hosts(cap):
649 l = os.listdir('/sys/class/scsi_host')
655 f = open('/sys/class/scsi_host/%s/proc_name' % h)
656 procname = f.readline().strip("\n")
662 f = open('/sys/class/scsi_host/%s/model_name' % h)
663 modelname = f.readline().strip("\n")
669 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
673 def module_info(cap):
674 output = StringIO.StringIO()
675 modules = open(PROC_MODULES, 'r')
679 module = line.split()[0]
680 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
685 return output.getvalue()
688 output = StringIO.StringIO()
689 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
693 if not procs[0].timed_out:
694 return output.getvalue().splitlines()
698 output = StringIO.StringIO()
699 procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
703 if not procs[0].timed_out:
704 bonds = output.getvalue().splitlines()[1:]
705 return [x.split('\t')[1] for x in bonds]
711 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
713 fh = open('/proc/'+d+'/cmdline')
715 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
717 if not num_fds in fd_dict:
718 fd_dict[num_fds] = []
719 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
722 keys = fd_dict.keys()
723 keys.sort(lambda a, b: int(b) - int(a))
725 output += "%s: %s\n" % (k, str(fd_dict[k]))
728 def load_plugins(just_capabilities = False):
729 def getText(nodelist):
731 for node in nodelist:
732 if node.nodeType == node.TEXT_NODE:
736 def getBoolAttr(el, attr, default = False):
738 val = el.getAttribute(attr).lower()
739 if val in ['true', 'false', 'yes', 'no']:
740 ret = val in ['true', 'yes']
743 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
744 if not caps.has_key(dir):
745 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
747 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
748 assert xmldoc.documentElement.tagName == "capability"
750 pii, min_size, max_size, min_time, max_time, mime = \
751 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
753 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
754 pii = xmldoc.documentElement.getAttribute("pii")
755 if xmldoc.documentElement.getAttribute("min_size") != '':
756 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
757 if xmldoc.documentElement.getAttribute("max_size") != '':
758 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
759 if xmldoc.documentElement.getAttribute("min_time") != '':
760 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
761 if xmldoc.documentElement.getAttribute("max_time") != '':
762 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
763 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
764 mime = xmldoc.documentElement.getAttribute("mime")
765 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
766 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
768 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
770 if just_capabilities:
773 plugdir = os.path.join(PLUGIN_DIR, dir)
774 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
775 xmldoc = parse(os.path.join(plugdir, file))
776 assert xmldoc.documentElement.tagName == "collect"
778 for el in xmldoc.documentElement.getElementsByTagName("*"):
779 if el.tagName == "files":
780 file_output(dir, getText(el.childNodes).split())
781 elif el.tagName == "directory":
782 pattern = el.getAttribute("pattern")
783 if pattern == '': pattern = None
784 negate = getBoolAttr(el, 'negate')
785 tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
786 elif el.tagName == "command":
787 label = el.getAttribute("label")
788 if label == '': label = None
789 cmd_output(dir, getText(el.childNodes), label)
791 def make_tar(subdir, suffix, output_fd, output_file):
792 global SILENT_MODE, data
795 if suffix == 'tar.bz2':
797 elif suffix == 'tar.gz':
801 if output_file is None:
802 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
804 filename = output_file
805 tf = tarfile.open(filename, mode)
807 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
810 for (k, v) in data.items():
812 tar_filename = os.path.join(subdir, construct_filename(k, v))
813 ti = tarfile.TarInfo(tar_filename)
818 if v.has_key('output'):
819 ti.mtime = v['output'].mtime
820 ti.size = len(v['output'].getvalue())
822 tf.addfile(ti, v['output'])
823 elif v.has_key('filename'):
824 s = os.stat(v['filename'])
825 ti.mtime = s.st_mtime
827 tf.addfile(ti, file(v['filename']))
834 output ('Writing tarball %s successful.' % filename)
839 def make_zip(subdir, output_file):
840 global SILENT_MODE, data
842 if output_file is None:
843 filename = "%s/%s.zip" % (BUG_DIR, subdir)
845 filename = output_file
846 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
849 for (k, v) in data.items():
851 dest = os.path.join(subdir, construct_filename(k, v))
853 if v.has_key('output'):
854 zf.writestr(dest, v['output'].getvalue())
856 if os.stat(v['filename']).st_size < 50:
857 compress_type = zipfile.ZIP_STORED
859 compress_type = zipfile.ZIP_DEFLATED
860 zf.write(v['filename'], dest, compress_type)
866 output ('Writing archive %s successful.' % filename)
871 def make_inventory(inventory, subdir):
872 document = getDOMImplementation().createDocument(
873 None, INVENTORY_XML_ROOT, None)
875 # create summary entry
876 s = document.createElement(INVENTORY_XML_SUMMARY)
877 user = os.getenv('SUDO_USER', os.getenv('USER'))
879 s.setAttribute('user', user)
880 s.setAttribute('date', time.strftime('%c'))
881 s.setAttribute('hostname', platform.node())
882 s.setAttribute('uname', ' '.join(platform.uname()))
883 s.setAttribute('uptime', commands.getoutput(UPTIME))
884 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
886 map(lambda (k, v): inventory_entry(document, subdir, k, v),
888 return document.toprettyxml()
890 def inventory_entry(document, subdir, k, v):
892 el = document.createElement(INVENTORY_XML_ELEMENT)
893 el.setAttribute('capability', v['cap'])
894 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
895 el.setAttribute('md5sum', md5sum(v))
896 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
903 if d.has_key('filename'):
904 f = open(d['filename'])
910 elif d.has_key('output'):
911 m.update(d['output'].getvalue())
915 def construct_filename(k, v):
916 if v.has_key('filename'):
917 if v['filename'][0] == '/':
918 return v['filename'][1:]
921 s = k.replace(' ', '-')
922 s = s.replace('--', '-')
923 s = s.replace('/', '%')
924 if s.find('.') == -1:
929 def update_capabilities():
932 def update_cap_size(cap, size):
933 update_cap(cap, MIN_SIZE, size)
934 update_cap(cap, MAX_SIZE, size)
935 update_cap(cap, CHECKED, size > 0)
938 def update_cap(cap, k, v):
945 def size_of_dir(d, pattern = None, negate = False):
947 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
953 def size_of_all(files, pattern = None, negate = False):
954 return sum([size_of(f, pattern, negate) for f in files])
957 def matches(f, pattern, negate):
959 return not matches(f, pattern, False)
961 return pattern is None or pattern.match(f)
964 def size_of(f, pattern, negate):
965 if os.path.isfile(f) and matches(f, pattern, negate):
968 return size_of_dir(f, pattern, negate)
971 def print_capabilities():
972 document = getDOMImplementation().createDocument(
973 "ns", CAP_XML_ROOT, None)
974 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
975 print document.toprettyxml()
977 def capability(document, key):
979 el = document.createElement(CAP_XML_ELEMENT)
980 el.setAttribute('key', c[KEY])
981 el.setAttribute('pii', c[PII])
982 el.setAttribute('min-size', str(c[MIN_SIZE]))
983 el.setAttribute('max-size', str(c[MAX_SIZE]))
984 el.setAttribute('min-time', str(c[MIN_TIME]))
985 el.setAttribute('max-time', str(c[MAX_TIME]))
986 el.setAttribute('content-type', c[MIME])
987 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
988 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
992 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
993 return '\n'.join([format % i for i in d.items()]) + '\n'
997 yn = raw_input(prompt)
999 return len(yn) == 0 or yn.lower()[0] == 'y'
1002 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1007 f = open('/proc/partitions')
1010 for line in f.readlines():
1011 (major, minor, blocks, name) = line.split()
1012 if int(major) < 254 and not partition_re.match(name):
1023 def __init__(self, command, max_time, inst=None, filter=None):
1024 self.command = command
1025 self.max_time = max_time
1027 self.running = False
1029 self.timed_out = False
1031 self.timeout = int(time.time()) + self.max_time
1032 self.filter = filter
1033 self.filter_state = {}
1039 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1042 self.timed_out = False
1044 if ProcOutput.debug:
1045 output_ts("Starting '%s'" % self.cmdAsStr())
1046 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1047 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1048 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1052 output_ts("'%s' failed" % self.cmdAsStr())
1053 self.running = False
1056 def terminate(self):
1059 os.kill(self.proc.pid, SIGTERM)
1063 self.running = False
1064 self.status = SIGTERM
1066 def read_line(self):
1068 line = self.proc.stdout.readline()
1071 self.status = self.proc.wait()
1073 self.running = False
1076 line = self.filter(line, self.filter_state)
1078 self.inst.write(line)
1080 def run_procs(procs):
1088 active_procs.append(p)
1089 pipes.append(p.proc.stdout)
1091 elif p.status == None and not p.failed and not p.timed_out:
1094 active_procs.append(p)
1095 pipes.append(p.proc.stdout)
1102 (i, o, x) = select(pipes, [], [], 1.0)
1103 now = int(time.time())
1105 # handle process output
1106 for p in active_procs:
1107 if p.proc.stdout in i:
1111 if p.running and now > p.timeout:
1112 output_ts("'%s' timed out" % p.cmdAsStr())
1114 p.inst.write("\n** timeout **\n")
1122 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1124 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1132 class StringIOmtime(StringIO.StringIO):
1133 def __init__(self, buf = ''):
1134 StringIO.StringIO.__init__(self, buf)
1135 self.mtime = time.time()
1138 StringIO.StringIO.write(self, s)
1139 self.mtime = time.time()
1142 if __name__ == "__main__":
1145 except KeyboardInterrupt:
1146 print "\nInterrupted."