3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of version 2.1 of the GNU Lesser General Public
5 # License as published by the Free Software Foundation.
7 # This library is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 # Lesser General Public License for more details.
12 # You should have received a copy of the GNU Lesser General Public
13 # License along with this library; if not, write to the Free Software
14 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 # Copyright (c) 2005, 2007 XenSource Ltd.
17 # Copyright (c) 2010, Nicira Networks.
20 # To add new entries to the bugtool, you need to:
22 # Create a new capability. These declare the new entry to the GUI, including
23 # the expected size, time to collect, privacy implications, and whether the
24 # capability should be selected by default. One capability may refer to
25 # multiple files, assuming that they can be reasonably grouped together, and
26 # have the same privacy implications. You need:
28 # A new CAP_ constant.
29 # A cap() invocation to declare the capability.
31 # You then need to add calls to main() to collect the files. These will
32 # typically be calls to the helpers file_output(), tree_output(), cmd_output(),
45 from xml.dom.minidom import parse, getDOMImplementation
47 from subprocess import Popen, PIPE
48 from select import select
49 from signal import SIGTERM, SIGUSR1
58 sys.path.append('/usr/lib/python')
59 sys.path.append('/usr/lib64/python')
61 OS_RELEASE = platform.release()
67 BUG_DIR = "/var/log/openvswitch"
68 PLUGIN_DIR = "/etc/openvswitch/bugtool"
69 GRUB_CONFIG = '/boot/grub/menu.lst'
70 BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
71 BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
72 PROC_PARTITIONS = '/proc/partitions'
74 PROC_MOUNTS = '/proc/mounts'
75 PROC_CPUINFO = '/proc/cpuinfo'
76 PROC_MEMINFO = '/proc/meminfo'
77 PROC_IOPORTS = '/proc/ioports'
78 PROC_INTERRUPTS = '/proc/interrupts'
79 PROC_SCSI = '/proc/scsi/scsi'
80 PROC_VERSION = '/proc/version'
81 PROC_MODULES = '/proc/modules'
82 PROC_DEVICES = '/proc/devices'
83 PROC_FILESYSTEMS = '/proc/filesystems'
84 PROC_CMDLINE = '/proc/cmdline'
85 PROC_CONFIG = '/proc/config.gz'
86 PROC_USB_DEV = '/proc/bus/usb/devices'
87 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
88 MODPROBE_DIR = '/etc/modprobe.d'
89 RESOLV_CONF = '/etc/resolv.conf'
90 NSSWITCH_CONF = '/etc/nsswitch.conf'
91 NTP_CONF = '/etc/ntp.conf'
93 HOSTS_ALLOW = '/etc/hosts.allow'
94 HOSTS_DENY = '/etc/hosts.deny'
95 DHCP_LEASE_DIR = '/var/lib/dhcp3'
96 OPENVSWITCH_CORE_DIR = '/var/log/openvswitch/cores'
97 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch'
98 OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
99 OPENVSWITCH_CONF_DB = '/etc/openvswitch/conf.db'
100 OPENVSWITCH_VSWITCHD_PID = '/var/run/openvswitch/ovs-vswitchd.pid'
101 VAR_LOG_DIR = '/var/log/'
102 X11_LOGS_DIR = VAR_LOG_DIR
103 X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
104 X11_AUTH_DIR = '/root/'
105 X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
106 PAM_DIR = '/etc/pam.d'
112 ARP = '/usr/sbin/arp'
116 DMIDECODE = '/usr/sbin/dmidecode'
117 FDISK = '/sbin/fdisk'
118 FIND = '/usr/bin/find'
119 IFCONFIG = '/sbin/ifconfig'
120 IPTABLES = '/sbin/iptables'
121 LOSETUP = '/sbin/losetup'
123 LSPCI = '/usr/bin/lspci'
124 MD5SUM = '/usr/bin/md5sum'
125 MODINFO = '/sbin/modinfo'
126 NETSTAT = '/bin/netstat'
127 OVS_DPCTL = '/usr/sbin/ovs-dpctl'
128 OVS_OFCTL = '/usr/sbin/ovs-ofctl'
129 OVS_VSCTL = '/usr/sbin/ovs-vsctl'
130 OVS_APPCTL = '/usr/sbin/ovs-appctl'
132 ROUTE = '/sbin/route'
133 SYSCTL = '/sbin/sysctl'
135 UPTIME = '/usr/bin/uptime'
138 ETHTOOL = '/sbin/ethtool'
139 # ETHTOOL recently moved from /usr/sbin to /sbin in debian
140 if not os.path.isfile(ETHTOOL):
141 ETHTOOL = '/usr/sbin/ethtool'
144 # PII -- Personally identifiable information. Of particular concern are
145 # things that would identify customers, or their network topology.
146 # Passwords are never to be included in any bug report, regardless of any PII
149 # NO -- No PII will be in these entries.
150 # YES -- PII will likely or certainly be in these entries.
151 # MAYBE -- The user may wish to audit these entries for PII.
152 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
153 # but since we encourage customers to edit these files, PII may have been
154 # introduced by the customer. This is used in particular for the networking
161 PII_IF_CUSTOMIZED = 'if_customized'
172 MIME_DATA = 'application/data'
173 MIME_TEXT = 'text/plain'
175 INVENTORY_XML_ROOT = "system-status-inventory"
176 INVENTORY_XML_SUMMARY = 'system-summary'
177 INVENTORY_XML_ELEMENT = 'inventory-entry'
178 CAP_XML_ROOT = "system-status-capabilities"
179 CAP_XML_ELEMENT = 'capability'
183 CAP_BOOT_LOADER = 'boot-loader'
184 CAP_DISK_INFO = 'disk-info'
185 CAP_FIRSTBOOT = 'firstboot'
186 CAP_HARDWARE_INFO = 'hardware-info'
187 CAP_HIGH_AVAILABILITY = 'high-availability'
188 CAP_HOST_CRASHDUMP_DUMPS = 'host-crashdump-dumps'
189 CAP_HOST_CRASHDUMP_LOGS = 'host-crashdump-logs'
190 CAP_KERNEL_INFO = 'kernel-info'
191 CAP_LOSETUP_A = 'loopback-devices'
192 CAP_NETWORK_CONFIG = 'network-config'
193 CAP_NETWORK_STATUS = 'network-status'
196 CAP_PROCESS_LIST = 'process-list'
197 CAP_PERSISTENT_STATS = 'persistent-stats'
198 CAP_SYSTEM_LOGS = 'system-logs'
199 CAP_SYSTEM_SERVICES = 'system-services'
200 CAP_VNCTERM = 'vncterm'
203 CAP_X11_AUTH = 'X11-auth'
210 unlimited_data = False
213 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
214 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
215 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
220 cap(CAP_BLOBS, PII_NO, max_size=5*MB)
221 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
223 cap(CAP_DISK_INFO, PII_MAYBE, max_size=25*KB,
225 cap(CAP_FIRSTBOOT, PII_YES, min_size=60*KB, max_size=80*KB)
226 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB,
228 cap(CAP_HIGH_AVAILABILITY, PII_MAYBE, max_size=5*MB)
229 cap(CAP_HOST_CRASHDUMP_DUMPS,PII_YES, checked = False)
230 cap(CAP_HOST_CRASHDUMP_LOGS, PII_NO)
231 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
233 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
234 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
235 min_size=0, max_size=20*KB)
236 cap(CAP_NETWORK_STATUS, PII_YES, max_size=19*KB,
238 cap(CAP_PAM, PII_NO, max_size=30*KB)
239 cap(CAP_PERSISTENT_STATS, PII_MAYBE, max_size=50*MB,
241 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
243 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=50*MB,
245 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
247 cap(CAP_VNCTERM, PII_MAYBE, checked = False)
248 cap(CAP_WLB, PII_NO, max_size=3*MB,
250 cap(CAP_X11_LOGS, PII_NO, max_size=100*KB)
251 cap(CAP_X11_AUTH, PII_NO, max_size=100*KB)
253 ANSWER_YES_TO_ALL = False
257 dev_null = open('/dev/null', 'r+')
265 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
267 def cmd_output(cap, args, label = None, filter = None):
270 if isinstance(args, list):
271 a = [aa for aa in args]
272 a[0] = os.path.basename(a[0])
276 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
278 def file_output(cap, path_list):
281 if os.path.exists(p):
282 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
283 cap_sizes[cap] < caps[cap][MAX_SIZE]:
284 data[p] = {'cap': cap, 'filename': p}
287 cap_sizes[cap] += s.st_size
291 output("Omitting %s, size constraint of %s exceeded" % (p, cap))
293 def tree_output(cap, path, pattern = None, negate = False):
295 if os.path.exists(path):
296 for f in os.listdir(path):
297 fn = os.path.join(path, f)
298 if os.path.isfile(fn) and matches(fn, pattern, negate):
299 file_output(cap, [fn])
300 elif os.path.isdir(fn):
301 tree_output(cap, fn, pattern, negate)
303 def func_output(cap, label, func):
305 t = str(func).split()
306 data[label] = {'cap': cap, 'func': func}
311 for (k, v) in data.items():
313 if v.has_key('cmd_args'):
314 v['output'] = StringIOmtime()
315 if not process_lists.has_key(cap):
316 process_lists[cap] = []
317 process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
318 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
319 # proc files must be read into memory
321 f = open(v['filename'], 'r')
324 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
325 cap_sizes[cap] < caps[cap][MAX_SIZE]:
326 v['output'] = StringIOmtime(s)
327 cap_sizes[cap] += len(s)
329 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
332 elif v.has_key('func'):
337 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
338 cap_sizes[cap] < caps[cap][MAX_SIZE]:
339 v['output'] = StringIOmtime(s)
340 cap_sizes[cap] += len(s)
342 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
344 run_procs(process_lists.values())
347 def main(argv = None):
348 global ANSWER_YES_TO_ALL, SILENT_MODE
349 global entries, data, dbg
351 # we need access to privileged files, exit if we are not running as root
353 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
356 output_type = 'tar.bz2'
363 (options, params) = getopt.gnu_getopt(
364 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
365 'output=', 'outfd=', 'all', 'unlimited', 'debug'])
366 except getopt.GetoptError, opterr:
367 print >>sys.stderr, opterr
375 entries = [e for e in caps.keys() if caps[e][CHECKED]]
377 for (k, v) in options:
378 if k == '--capabilities':
379 update_capabilities()
384 if v in ['tar', 'tar.bz2', 'zip']:
387 print >>sys.stderr, "Invalid output format '%s'" % v
390 # "-s" or "--silent" means suppress output (except for the final
391 # output filename at the end)
392 if k in ['-s', '--silent']:
395 if k == '--entries' and v != '':
396 entries = v.split(',')
398 # If the user runs the script with "-y" or "--yestoall" we don't ask
399 # all the really annoying questions.
400 if k in ['-y', '--yestoall']:
401 ANSWER_YES_TO_ALL = True
406 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
407 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
409 print >>sys.stderr, "Invalid output file descriptor", output_fd
413 entries = caps.keys()
414 elif k == '--unlimited':
415 unlimited_data = True
418 ProcOutput.debug = True
421 print >>sys.stderr, "Invalid additional arguments", str(params)
424 if output_fd != -1 and output_type != 'tar':
425 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
428 if ANSWER_YES_TO_ALL:
429 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
432 This application will collate dmesg output, details of the
433 hardware configuration of your machine, information about the build of
434 openvswitch that you are using, plus, if you allow it, various logs.
436 The collated information will be saved as a .%s for archiving or
437 sending to a Technical Support Representative.
439 The logs may contain private information, and if you are at all
440 worried about that, you should exit now, or you should explicitly
441 exclude those logs from the archive.
445 # assemble potential data
447 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
448 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
449 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
451 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
452 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
453 file_output(CAP_DISK_INFO, [FSTAB])
454 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
455 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
456 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
457 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
458 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
459 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
462 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
463 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
464 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
465 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
466 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
467 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
469 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
470 PROC_FILESYSTEMS, PROC_CMDLINE])
471 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
472 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
473 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
474 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
476 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
478 file_output(CAP_NETWORK_CONFIG, [RESOLV_CONF, NSSWITCH_CONF, HOSTS])
479 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, HOSTS_ALLOW, HOSTS_DENY])
480 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
481 OPENVSWITCH_DEFAULT_CONTROLLER, OPENVSWITCH_CONF_DB])
483 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
484 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
485 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
486 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
487 tree_output(CAP_NETWORK_STATUS, DHCP_LEASE_DIR)
488 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
489 for p in os.listdir('/sys/class/net/'):
491 f = open('/sys/class/net/%s/type' % p, 'r')
496 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
497 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
498 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
499 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
500 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
501 cmd_output(CAP_NETWORK_STATUS,
502 [TC, '-s', '-d', 'class', 'show', 'dev', p])
505 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
506 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
507 tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_CORE_DIR)
508 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
509 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
511 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
512 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'status', d])
513 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
514 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
516 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
517 vspid = int(vspidfile.readline().strip())
519 for b in bond_list(vspid):
520 cmd_output(CAP_NETWORK_STATUS,
521 [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
522 'ovs-appctl-bond-show-%s.out' % b)
526 tree_output(CAP_PAM, PAM_DIR)
528 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
529 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
531 file_output(CAP_SYSTEM_LOGS,
532 [ VAR_LOG_DIR + x for x in
533 [ 'kern.log', 'daemon.log', 'user.log', 'syslog', 'messages',
534 'debug', 'dmesg', 'boot'] +
535 [ f % n for n in range(1, 20) \
536 for f in ['kern.log.%d', 'kern.log.%d.gz',
537 'daemon.log.%d', 'daemon.log.%d.gz',
538 'user.log.%d', 'user.log.%d.gz',
539 'messages.%d', 'messages.%d.gz']]])
540 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
541 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
544 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
545 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
553 # permit the user to filter out data
554 for k in sorted(data.keys()):
555 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
558 # collect selected data now
559 output_ts('Running commands to collect data')
562 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
565 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
568 if output_fd == -1 and not os.path.exists(BUG_DIR):
575 output_ts('Creating output file')
577 if output_type.startswith('tar'):
578 make_tar(subdir, output_type, output_fd)
585 print >>sys.stderr, "Category sizes (max, actual):\n"
586 for c in caps.keys():
587 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
591 def find_tapdisk_logs():
592 return glob.glob('/var/log/blktap/*.log*')
594 def generate_tapdisk_logs():
595 for pid in pidof('tapdisk'):
597 os.kill(pid, SIGUSR1)
598 output_ts("Including logs for tapdisk process %d" % pid)
601 # give processes a second to write their logs
604 def clean_tapdisk_logs():
605 for filename in find_tapdisk_logs():
611 def filter_db_pii(str, state):
612 if 'in_secret_table' not in state:
613 state['in_secret_table'] = False
615 if str.startswith('<table ') and 'name="secret"' in str:
616 state['in_secret_table'] = True
617 elif str.startswith('</table>'):
618 state['in_secret_table'] = False
620 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
621 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
624 def dump_scsi_hosts(cap):
626 l = os.listdir('/sys/class/scsi_host')
632 f = open('/sys/class/scsi_host/%s/proc_name' % h)
633 procname = f.readline().strip("\n")
639 f = open('/sys/class/scsi_host/%s/model_name' % h)
640 modelname = f.readline().strip("\n")
646 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
650 def module_info(cap):
651 output = StringIO.StringIO()
652 modules = open(PROC_MODULES, 'r')
656 module = line.split()[0]
657 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
662 return output.getvalue()
665 output = StringIO.StringIO()
666 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
670 if not procs[0].timed_out:
671 return output.getvalue().splitlines()
675 output = StringIO.StringIO()
676 procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
680 if not procs[0].timed_out:
681 bonds = output.getvalue().splitlines()[1:]
682 return [x.split('\t')[1] for x in bonds]
688 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
690 fh = open('/proc/'+d+'/cmdline')
692 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
694 if not num_fds in fd_dict:
695 fd_dict[num_fds] = []
696 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
699 keys = fd_dict.keys()
700 keys.sort(lambda a, b: int(b) - int(a))
702 output += "%s: %s\n" % (k, str(fd_dict[k]))
705 def load_plugins(just_capabilities = False):
706 def getText(nodelist):
708 for node in nodelist:
709 if node.nodeType == node.TEXT_NODE:
713 def getBoolAttr(el, attr, default = False):
715 val = el.getAttribute(attr).lower()
716 if val in ['true', 'false', 'yes', 'no']:
717 ret = val in ['true', 'yes']
720 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
721 if not caps.has_key(dir):
722 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
724 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
725 assert xmldoc.documentElement.tagName == "capability"
727 pii, min_size, max_size, min_time, max_time, mime = \
728 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
730 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
731 pii = xmldoc.documentElement.getAttribute("pii")
732 if xmldoc.documentElement.getAttribute("min_size") != '':
733 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
734 if xmldoc.documentElement.getAttribute("max_size") != '':
735 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
736 if xmldoc.documentElement.getAttribute("min_time") != '':
737 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
738 if xmldoc.documentElement.getAttribute("max_time") != '':
739 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
740 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
741 mime = xmldoc.documentElement.getAttribute("mime")
742 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
743 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
745 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
747 if just_capabilities:
750 plugdir = os.path.join(PLUGIN_DIR, dir)
751 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
752 xmldoc = parse(os.path.join(plugdir, file))
753 assert xmldoc.documentElement.tagName == "collect"
755 for el in xmldoc.documentElement.getElementsByTagName("*"):
756 if el.tagName == "files":
757 file_output(dir, getText(el.childNodes).split())
758 elif el.tagName == "directory":
759 pattern = el.getAttribute("pattern")
760 if pattern == '': pattern = None
761 negate = getBoolAttr(el, 'negate')
762 tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
763 elif el.tagName == "command":
764 label = el.getAttribute("label")
765 if label == '': label = None
766 cmd_output(dir, getText(el.childNodes), label)
768 def make_tar(subdir, suffix, output_fd):
769 global SILENT_MODE, data
772 if suffix == 'tar.bz2':
774 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
777 tf = tarfile.open(filename, mode)
779 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
782 for (k, v) in data.items():
784 tar_filename = os.path.join(subdir, construct_filename(k, v))
785 ti = tarfile.TarInfo(tar_filename)
790 if v.has_key('output'):
791 ti.mtime = v['output'].mtime
792 ti.size = len(v['output'].getvalue())
794 tf.addfile(ti, v['output'])
795 elif v.has_key('filename'):
796 s = os.stat(v['filename'])
797 ti.mtime = s.st_mtime
799 tf.addfile(ti, file(v['filename']))
806 output ('Writing tarball %s successful.' % filename)
811 def make_zip(subdir):
812 global SILENT_MODE, data
814 filename = "%s/%s.zip" % (BUG_DIR, subdir)
815 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
818 for (k, v) in data.items():
820 dest = os.path.join(subdir, construct_filename(k, v))
822 if v.has_key('output'):
823 zf.writestr(dest, v['output'].getvalue())
825 if os.stat(v['filename']).st_size < 50:
826 compress_type = zipfile.ZIP_STORED
828 compress_type = zipfile.ZIP_DEFLATED
829 zf.write(v['filename'], dest, compress_type)
835 output ('Writing archive %s successful.' % filename)
840 def make_inventory(inventory, subdir):
841 document = getDOMImplementation().createDocument(
842 None, INVENTORY_XML_ROOT, None)
844 # create summary entry
845 s = document.createElement(INVENTORY_XML_SUMMARY)
846 user = os.getenv('SUDO_USER', os.getenv('USER'))
848 s.setAttribute('user', user)
849 s.setAttribute('date', time.strftime('%c'))
850 s.setAttribute('hostname', platform.node())
851 s.setAttribute('uname', ' '.join(platform.uname()))
852 s.setAttribute('uptime', commands.getoutput(UPTIME))
853 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
855 map(lambda (k, v): inventory_entry(document, subdir, k, v),
857 return document.toprettyxml()
859 def inventory_entry(document, subdir, k, v):
861 el = document.createElement(INVENTORY_XML_ELEMENT)
862 el.setAttribute('capability', v['cap'])
863 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
864 el.setAttribute('md5sum', md5sum(v))
865 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
872 if d.has_key('filename'):
873 f = open(d['filename'])
879 elif d.has_key('output'):
880 m.update(d['output'].getvalue())
884 def construct_filename(k, v):
885 if v.has_key('filename'):
886 if v['filename'][0] == '/':
887 return v['filename'][1:]
890 s = k.replace(' ', '-')
891 s = s.replace('--', '-')
892 s = s.replace('/', '%')
893 if s.find('.') == -1:
898 def update_capabilities():
901 def update_cap_size(cap, size):
902 update_cap(cap, MIN_SIZE, size)
903 update_cap(cap, MAX_SIZE, size)
904 update_cap(cap, CHECKED, size > 0)
907 def update_cap(cap, k, v):
914 def size_of_dir(d, pattern = None, negate = False):
916 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
922 def size_of_all(files, pattern = None, negate = False):
923 return sum([size_of(f, pattern, negate) for f in files])
926 def matches(f, pattern, negate):
928 return not matches(f, pattern, False)
930 return pattern is None or pattern.match(f)
933 def size_of(f, pattern, negate):
934 if os.path.isfile(f) and matches(f, pattern, negate):
937 return size_of_dir(f, pattern, negate)
940 def print_capabilities():
941 document = getDOMImplementation().createDocument(
942 "ns", CAP_XML_ROOT, None)
943 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
944 print document.toprettyxml()
946 def capability(document, key):
948 el = document.createElement(CAP_XML_ELEMENT)
949 el.setAttribute('key', c[KEY])
950 el.setAttribute('pii', c[PII])
951 el.setAttribute('min-size', str(c[MIN_SIZE]))
952 el.setAttribute('max-size', str(c[MAX_SIZE]))
953 el.setAttribute('min-time', str(c[MIN_TIME]))
954 el.setAttribute('max-time', str(c[MAX_TIME]))
955 el.setAttribute('content-type', c[MIME])
956 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
957 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
961 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
962 return '\n'.join([format % i for i in d.items()]) + '\n'
966 yn = raw_input(prompt)
968 return len(yn) == 0 or yn.lower()[0] == 'y'
971 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
976 f = open('/proc/partitions')
979 for line in f.readlines():
980 (major, minor, blocks, name) = line.split()
981 if int(major) < 254 and not partition_re.match(name):
992 def __init__(self, command, max_time, inst=None, filter=None):
993 self.command = command
994 self.max_time = max_time
998 self.timed_out = False
1000 self.timeout = int(time.time()) + self.max_time
1001 self.filter = filter
1002 self.filter_state = {}
1008 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1011 self.timed_out = False
1013 if ProcOutput.debug:
1014 output_ts("Starting '%s'" % self.cmdAsStr())
1015 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1016 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1017 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1021 output_ts("'%s' failed" % self.cmdAsStr())
1022 self.running = False
1025 def terminate(self):
1028 os.kill(self.proc.pid, SIGTERM)
1032 self.running = False
1033 self.status = SIGTERM
1035 def read_line(self):
1037 line = self.proc.stdout.readline()
1040 self.status = self.proc.wait()
1042 self.running = False
1045 line = self.filter(line, self.filter_state)
1047 self.inst.write(line)
1049 def run_procs(procs):
1057 active_procs.append(p)
1058 pipes.append(p.proc.stdout)
1060 elif p.status == None and not p.failed and not p.timed_out:
1063 active_procs.append(p)
1064 pipes.append(p.proc.stdout)
1071 (i, o, x) = select(pipes, [], [], 1.0)
1072 now = int(time.time())
1074 # handle process output
1075 for p in active_procs:
1076 if p.proc.stdout in i:
1080 if p.running and now > p.timeout:
1081 output_ts("'%s' timed out" % p.cmdAsStr())
1083 p.inst.write("\n** timeout **\n")
1091 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1093 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1101 class StringIOmtime(StringIO.StringIO):
1102 def __init__(self, buf = ''):
1103 StringIO.StringIO.__init__(self, buf)
1104 self.mtime = time.time()
1107 StringIO.StringIO.write(self, s)
1108 self.mtime = time.time()
1111 if __name__ == "__main__":
1114 except KeyboardInterrupt:
1115 print "\nInterrupted."