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])
503 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
504 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
505 tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_CORE_DIR)
506 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
507 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
509 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
510 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'status', d])
511 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
512 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
514 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
515 vspid = int(vspidfile.readline().strip())
517 for b in bond_list(vspid):
518 cmd_output(CAP_NETWORK_STATUS,
519 [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
520 'ovs-appctl-bond-show-%s.out' % b)
524 tree_output(CAP_PAM, PAM_DIR)
526 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
527 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
529 file_output(CAP_SYSTEM_LOGS,
530 [ VAR_LOG_DIR + x for x in
531 [ 'kern.log', 'daemon.log', 'user.log', 'syslog', 'messages',
532 'debug', 'dmesg', 'boot'] +
533 [ f % n for n in range(1, 20) \
534 for f in ['kern.log.%d', 'kern.log.%d.gz',
535 'daemon.log.%d', 'daemon.log.%d.gz',
536 'user.log.%d', 'user.log.%d.gz',
537 'messages.%d', 'messages.%d.gz']]])
538 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
539 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
542 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
543 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
551 # permit the user to filter out data
552 for k in sorted(data.keys()):
553 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
556 # collect selected data now
557 output_ts('Running commands to collect data')
560 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
563 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
566 if output_fd == -1 and not os.path.exists(BUG_DIR):
573 output_ts('Creating output file')
575 if output_type.startswith('tar'):
576 make_tar(subdir, output_type, output_fd)
583 print >>sys.stderr, "Category sizes (max, actual):\n"
584 for c in caps.keys():
585 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
589 def find_tapdisk_logs():
590 return glob.glob('/var/log/blktap/*.log*')
592 def generate_tapdisk_logs():
593 for pid in pidof('tapdisk'):
595 os.kill(pid, SIGUSR1)
596 output_ts("Including logs for tapdisk process %d" % pid)
599 # give processes a second to write their logs
602 def clean_tapdisk_logs():
603 for filename in find_tapdisk_logs():
609 def filter_db_pii(str, state):
610 if 'in_secret_table' not in state:
611 state['in_secret_table'] = False
613 if str.startswith('<table ') and 'name="secret"' in str:
614 state['in_secret_table'] = True
615 elif str.startswith('</table>'):
616 state['in_secret_table'] = False
618 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
619 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
622 def dump_scsi_hosts(cap):
624 l = os.listdir('/sys/class/scsi_host')
630 f = open('/sys/class/scsi_host/%s/proc_name' % h)
631 procname = f.readline().strip("\n")
637 f = open('/sys/class/scsi_host/%s/model_name' % h)
638 modelname = f.readline().strip("\n")
644 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
648 def module_info(cap):
649 output = StringIO.StringIO()
650 modules = open(PROC_MODULES, 'r')
654 module = line.split()[0]
655 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
660 return output.getvalue()
663 output = StringIO.StringIO()
664 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
668 if not procs[0].timed_out:
669 return output.getvalue().splitlines()
673 output = StringIO.StringIO()
674 procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
678 if not procs[0].timed_out:
679 bonds = output.getvalue().splitlines()[1:]
680 return [x.split('\t')[1] for x in bonds]
686 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
688 fh = open('/proc/'+d+'/cmdline')
690 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
692 if not num_fds in fd_dict:
693 fd_dict[num_fds] = []
694 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
697 keys = fd_dict.keys()
698 keys.sort(lambda a, b: int(b) - int(a))
700 output += "%s: %s\n" % (k, str(fd_dict[k]))
703 def load_plugins(just_capabilities = False):
704 def getText(nodelist):
706 for node in nodelist:
707 if node.nodeType == node.TEXT_NODE:
711 def getBoolAttr(el, attr, default = False):
713 val = el.getAttribute(attr).lower()
714 if val in ['true', 'false', 'yes', 'no']:
715 ret = val in ['true', 'yes']
718 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
719 if not caps.has_key(dir):
720 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
722 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
723 assert xmldoc.documentElement.tagName == "capability"
725 pii, min_size, max_size, min_time, max_time, mime = \
726 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
728 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
729 pii = xmldoc.documentElement.getAttribute("pii")
730 if xmldoc.documentElement.getAttribute("min_size") != '':
731 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
732 if xmldoc.documentElement.getAttribute("max_size") != '':
733 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
734 if xmldoc.documentElement.getAttribute("min_time") != '':
735 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
736 if xmldoc.documentElement.getAttribute("max_time") != '':
737 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
738 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
739 mime = xmldoc.documentElement.getAttribute("mime")
740 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
741 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
743 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
745 if just_capabilities:
748 plugdir = os.path.join(PLUGIN_DIR, dir)
749 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
750 xmldoc = parse(os.path.join(plugdir, file))
751 assert xmldoc.documentElement.tagName == "collect"
753 for el in xmldoc.documentElement.getElementsByTagName("*"):
754 if el.tagName == "files":
755 file_output(dir, getText(el.childNodes).split())
756 elif el.tagName == "directory":
757 pattern = el.getAttribute("pattern")
758 if pattern == '': pattern = None
759 negate = getBoolAttr(el, 'negate')
760 tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
761 elif el.tagName == "command":
762 label = el.getAttribute("label")
763 if label == '': label = None
764 cmd_output(dir, getText(el.childNodes), label)
766 def make_tar(subdir, suffix, output_fd):
767 global SILENT_MODE, data
770 if suffix == 'tar.bz2':
772 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
775 tf = tarfile.open(filename, mode)
777 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
780 for (k, v) in data.items():
782 tar_filename = os.path.join(subdir, construct_filename(k, v))
783 ti = tarfile.TarInfo(tar_filename)
788 if v.has_key('output'):
789 ti.mtime = v['output'].mtime
790 ti.size = len(v['output'].getvalue())
792 tf.addfile(ti, v['output'])
793 elif v.has_key('filename'):
794 s = os.stat(v['filename'])
795 ti.mtime = s.st_mtime
797 tf.addfile(ti, file(v['filename']))
804 output ('Writing tarball %s successful.' % filename)
809 def make_zip(subdir):
810 global SILENT_MODE, data
812 filename = "%s/%s.zip" % (BUG_DIR, subdir)
813 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
816 for (k, v) in data.items():
818 dest = os.path.join(subdir, construct_filename(k, v))
820 if v.has_key('output'):
821 zf.writestr(dest, v['output'].getvalue())
823 if os.stat(v['filename']).st_size < 50:
824 compress_type = zipfile.ZIP_STORED
826 compress_type = zipfile.ZIP_DEFLATED
827 zf.write(v['filename'], dest, compress_type)
833 output ('Writing archive %s successful.' % filename)
838 def make_inventory(inventory, subdir):
839 document = getDOMImplementation().createDocument(
840 None, INVENTORY_XML_ROOT, None)
842 # create summary entry
843 s = document.createElement(INVENTORY_XML_SUMMARY)
844 user = os.getenv('SUDO_USER', os.getenv('USER'))
846 s.setAttribute('user', user)
847 s.setAttribute('date', time.strftime('%c'))
848 s.setAttribute('hostname', platform.node())
849 s.setAttribute('uname', ' '.join(platform.uname()))
850 s.setAttribute('uptime', commands.getoutput(UPTIME))
851 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
853 map(lambda (k, v): inventory_entry(document, subdir, k, v),
855 return document.toprettyxml()
857 def inventory_entry(document, subdir, k, v):
859 el = document.createElement(INVENTORY_XML_ELEMENT)
860 el.setAttribute('capability', v['cap'])
861 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
862 el.setAttribute('md5sum', md5sum(v))
863 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
870 if d.has_key('filename'):
871 f = open(d['filename'])
877 elif d.has_key('output'):
878 m.update(d['output'].getvalue())
882 def construct_filename(k, v):
883 if v.has_key('filename'):
884 if v['filename'][0] == '/':
885 return v['filename'][1:]
888 s = k.replace(' ', '-')
889 s = s.replace('--', '-')
890 s = s.replace('/', '%')
891 if s.find('.') == -1:
896 def update_capabilities():
899 def update_cap_size(cap, size):
900 update_cap(cap, MIN_SIZE, size)
901 update_cap(cap, MAX_SIZE, size)
902 update_cap(cap, CHECKED, size > 0)
905 def update_cap(cap, k, v):
912 def size_of_dir(d, pattern = None, negate = False):
914 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
920 def size_of_all(files, pattern = None, negate = False):
921 return sum([size_of(f, pattern, negate) for f in files])
924 def matches(f, pattern, negate):
926 return not matches(f, pattern, False)
928 return pattern is None or pattern.match(f)
931 def size_of(f, pattern, negate):
932 if os.path.isfile(f) and matches(f, pattern, negate):
935 return size_of_dir(f, pattern, negate)
938 def print_capabilities():
939 document = getDOMImplementation().createDocument(
940 "ns", CAP_XML_ROOT, None)
941 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
942 print document.toprettyxml()
944 def capability(document, key):
946 el = document.createElement(CAP_XML_ELEMENT)
947 el.setAttribute('key', c[KEY])
948 el.setAttribute('pii', c[PII])
949 el.setAttribute('min-size', str(c[MIN_SIZE]))
950 el.setAttribute('max-size', str(c[MAX_SIZE]))
951 el.setAttribute('min-time', str(c[MIN_TIME]))
952 el.setAttribute('max-time', str(c[MAX_TIME]))
953 el.setAttribute('content-type', c[MIME])
954 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
955 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
959 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
960 return '\n'.join([format % i for i in d.items()]) + '\n'
964 yn = raw_input(prompt)
966 return len(yn) == 0 or yn.lower()[0] == 'y'
969 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
974 f = open('/proc/partitions')
977 for line in f.readlines():
978 (major, minor, blocks, name) = line.split()
979 if int(major) < 254 and not partition_re.match(name):
990 def __init__(self, command, max_time, inst=None, filter=None):
991 self.command = command
992 self.max_time = max_time
996 self.timed_out = False
998 self.timeout = int(time.time()) + self.max_time
1000 self.filter_state = {}
1006 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1009 self.timed_out = False
1011 if ProcOutput.debug:
1012 output_ts("Starting '%s'" % self.cmdAsStr())
1013 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1014 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1015 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1019 output_ts("'%s' failed" % self.cmdAsStr())
1020 self.running = False
1023 def terminate(self):
1026 os.kill(self.proc.pid, SIGTERM)
1030 self.running = False
1031 self.status = SIGTERM
1033 def read_line(self):
1035 line = self.proc.stdout.readline()
1038 self.status = self.proc.wait()
1040 self.running = False
1043 line = self.filter(line, self.filter_state)
1045 self.inst.write(line)
1047 def run_procs(procs):
1055 active_procs.append(p)
1056 pipes.append(p.proc.stdout)
1058 elif p.status == None and not p.failed and not p.timed_out:
1061 active_procs.append(p)
1062 pipes.append(p.proc.stdout)
1069 (i, o, x) = select(pipes, [], [], 1.0)
1070 now = int(time.time())
1072 # handle process output
1073 for p in active_procs:
1074 if p.proc.stdout in i:
1078 if p.running and now > p.timeout:
1079 output_ts("'%s' timed out" % p.cmdAsStr())
1081 p.inst.write("\n** timeout **\n")
1089 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1091 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1099 class StringIOmtime(StringIO.StringIO):
1100 def __init__(self, buf = ''):
1101 StringIO.StringIO.__init__(self, buf)
1102 self.mtime = time.time()
1105 StringIO.StringIO.write(self, s)
1106 self.mtime = time.time()
1109 if __name__ == "__main__":
1112 except KeyboardInterrupt:
1113 print "\nInterrupted."