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 ETHTOOL = '/sbin/ethtool'
118 FDISK = '/sbin/fdisk'
119 FIND = '/usr/bin/find'
120 IFCONFIG = '/sbin/ifconfig'
121 IPTABLES = '/sbin/iptables'
122 LOSETUP = '/sbin/losetup'
124 LSPCI = '/usr/bin/lspci'
125 MD5SUM = '/usr/bin/md5sum'
126 MODINFO = '/sbin/modinfo'
127 NETSTAT = '/bin/netstat'
128 OVS_DPCTL = '/usr/sbin/ovs-dpctl'
129 OVS_OFCTL = '/usr/sbin/ovs-ofctl'
130 OVS_VSCTL = '/usr/sbin/ovs-vsctl'
131 OVS_APPCTL = '/usr/sbin/ovs-appctl'
133 ROUTE = '/sbin/route'
134 SYSCTL = '/sbin/sysctl'
136 UPTIME = '/usr/bin/uptime'
140 # PII -- Personally identifiable information. Of particular concern are
141 # things that would identify customers, or their network topology.
142 # Passwords are never to be included in any bug report, regardless of any PII
145 # NO -- No PII will be in these entries.
146 # YES -- PII will likely or certainly be in these entries.
147 # MAYBE -- The user may wish to audit these entries for PII.
148 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
149 # but since we encourage customers to edit these files, PII may have been
150 # introduced by the customer. This is used in particular for the networking
157 PII_IF_CUSTOMIZED = 'if_customized'
168 MIME_DATA = 'application/data'
169 MIME_TEXT = 'text/plain'
171 INVENTORY_XML_ROOT = "system-status-inventory"
172 INVENTORY_XML_SUMMARY = 'system-summary'
173 INVENTORY_XML_ELEMENT = 'inventory-entry'
174 CAP_XML_ROOT = "system-status-capabilities"
175 CAP_XML_ELEMENT = 'capability'
179 CAP_BOOT_LOADER = 'boot-loader'
180 CAP_DISK_INFO = 'disk-info'
181 CAP_FIRSTBOOT = 'firstboot'
182 CAP_HARDWARE_INFO = 'hardware-info'
183 CAP_HIGH_AVAILABILITY = 'high-availability'
184 CAP_HOST_CRASHDUMP_DUMPS = 'host-crashdump-dumps'
185 CAP_HOST_CRASHDUMP_LOGS = 'host-crashdump-logs'
186 CAP_KERNEL_INFO = 'kernel-info'
187 CAP_LOSETUP_A = 'loopback-devices'
188 CAP_NETWORK_CONFIG = 'network-config'
189 CAP_NETWORK_STATUS = 'network-status'
192 CAP_PROCESS_LIST = 'process-list'
193 CAP_PERSISTENT_STATS = 'persistent-stats'
194 CAP_SYSTEM_LOGS = 'system-logs'
195 CAP_SYSTEM_SERVICES = 'system-services'
196 CAP_VNCTERM = 'vncterm'
199 CAP_X11_AUTH = 'X11-auth'
206 unlimited_data = False
209 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
210 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
211 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
216 cap(CAP_BLOBS, PII_NO, max_size=5*MB)
217 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
219 cap(CAP_DISK_INFO, PII_MAYBE, max_size=25*KB,
221 cap(CAP_FIRSTBOOT, PII_YES, min_size=60*KB, max_size=80*KB)
222 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB,
224 cap(CAP_HIGH_AVAILABILITY, PII_MAYBE, max_size=5*MB)
225 cap(CAP_HOST_CRASHDUMP_DUMPS,PII_YES, checked = False)
226 cap(CAP_HOST_CRASHDUMP_LOGS, PII_NO)
227 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
229 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
230 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
231 min_size=0, max_size=20*KB)
232 cap(CAP_NETWORK_STATUS, PII_YES, max_size=19*KB,
234 cap(CAP_PAM, PII_NO, max_size=30*KB)
235 cap(CAP_PERSISTENT_STATS, PII_MAYBE, max_size=50*MB,
237 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
239 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=50*MB,
241 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
243 cap(CAP_VNCTERM, PII_MAYBE, checked = False)
244 cap(CAP_WLB, PII_NO, max_size=3*MB,
246 cap(CAP_X11_LOGS, PII_NO, max_size=100*KB)
247 cap(CAP_X11_AUTH, PII_NO, max_size=100*KB)
249 ANSWER_YES_TO_ALL = False
253 dev_null = open('/dev/null', 'r+')
261 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
263 def cmd_output(cap, args, label = None, filter = None):
266 if isinstance(args, list):
267 a = [aa for aa in args]
268 a[0] = os.path.basename(a[0])
272 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
274 def file_output(cap, path_list):
277 if os.path.exists(p):
278 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
279 cap_sizes[cap] < caps[cap][MAX_SIZE]:
280 data[p] = {'cap': cap, 'filename': p}
283 cap_sizes[cap] += s.st_size
287 output("Omitting %s, size constraint of %s exceeded" % (p, cap))
289 def tree_output(cap, path, pattern = None, negate = False):
291 if os.path.exists(path):
292 for f in os.listdir(path):
293 fn = os.path.join(path, f)
294 if os.path.isfile(fn) and matches(fn, pattern, negate):
295 file_output(cap, [fn])
296 elif os.path.isdir(fn):
297 tree_output(cap, fn, pattern, negate)
299 def func_output(cap, label, func):
301 t = str(func).split()
302 data[label] = {'cap': cap, 'func': func}
307 for (k, v) in data.items():
309 if v.has_key('cmd_args'):
310 v['output'] = StringIOmtime()
311 if not process_lists.has_key(cap):
312 process_lists[cap] = []
313 process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
314 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
315 # proc files must be read into memory
317 f = open(v['filename'], 'r')
320 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
321 cap_sizes[cap] < caps[cap][MAX_SIZE]:
322 v['output'] = StringIOmtime(s)
323 cap_sizes[cap] += len(s)
325 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
328 elif v.has_key('func'):
333 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
334 cap_sizes[cap] < caps[cap][MAX_SIZE]:
335 v['output'] = StringIOmtime(s)
336 cap_sizes[cap] += len(s)
338 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
340 run_procs(process_lists.values())
343 def main(argv = None):
344 global ANSWER_YES_TO_ALL, SILENT_MODE
345 global entries, data, dbg
347 # we need access to privileged files, exit if we are not running as root
349 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
352 output_type = 'tar.bz2'
359 (options, params) = getopt.gnu_getopt(
360 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
361 'output=', 'outfd=', 'all', 'unlimited', 'debug'])
362 except getopt.GetoptError, opterr:
363 print >>sys.stderr, opterr
371 entries = [e for e in caps.keys() if caps[e][CHECKED]]
373 for (k, v) in options:
374 if k == '--capabilities':
375 update_capabilities()
380 if v in ['tar', 'tar.bz2', 'zip']:
383 print >>sys.stderr, "Invalid output format '%s'" % v
386 # "-s" or "--silent" means suppress output (except for the final
387 # output filename at the end)
388 if k in ['-s', '--silent']:
391 if k == '--entries' and v != '':
392 entries = v.split(',')
394 # If the user runs the script with "-y" or "--yestoall" we don't ask
395 # all the really annoying questions.
396 if k in ['-y', '--yestoall']:
397 ANSWER_YES_TO_ALL = True
402 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
403 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
405 print >>sys.stderr, "Invalid output file descriptor", output_fd
409 entries = caps.keys()
410 elif k == '--unlimited':
411 unlimited_data = True
414 ProcOutput.debug = True
417 print >>sys.stderr, "Invalid additional arguments", str(params)
420 if output_fd != -1 and output_type != 'tar':
421 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
424 if ANSWER_YES_TO_ALL:
425 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
428 This application will collate dmesg output, details of the
429 hardware configuration of your machine, information about the build of
430 openvswitch that you are using, plus, if you allow it, various logs.
432 The collated information will be saved as a .%s for archiving or
433 sending to a Technical Support Representative.
435 The logs may contain private information, and if you are at all
436 worried about that, you should exit now, or you should explicitly
437 exclude those logs from the archive.
441 # assemble potential data
443 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
444 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
445 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
447 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
448 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
449 file_output(CAP_DISK_INFO, [FSTAB])
450 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
451 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
452 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
453 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
454 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
455 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
458 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
459 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
460 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
461 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
462 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
463 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
465 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
466 PROC_FILESYSTEMS, PROC_CMDLINE])
467 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
468 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
469 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
470 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
472 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
474 file_output(CAP_NETWORK_CONFIG, [RESOLV_CONF, NSSWITCH_CONF, HOSTS])
475 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, HOSTS_ALLOW, HOSTS_DENY])
476 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
477 OPENVSWITCH_DEFAULT_CONTROLLER, OPENVSWITCH_CONF_DB])
479 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
480 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
481 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
482 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
483 tree_output(CAP_NETWORK_STATUS, DHCP_LEASE_DIR)
484 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
485 for p in os.listdir('/sys/class/net/'):
487 f = open('/sys/class/net/%s/type' % p, 'r')
492 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
493 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
494 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
495 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
496 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
499 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
500 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
501 tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_CORE_DIR)
502 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
503 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
505 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
506 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'status', d])
507 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
508 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
510 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
511 vspid = int(vspidfile.readline().strip())
513 for b in bond_list(vspid):
514 cmd_output(CAP_NETWORK_STATUS,
515 [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
516 'ovs-appctl-bond-show-%s.out' % b)
520 tree_output(CAP_PAM, PAM_DIR)
522 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
523 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
525 file_output(CAP_SYSTEM_LOGS,
526 [ VAR_LOG_DIR + x for x in
527 [ 'kern.log', 'daemon.log', 'user.log', 'syslog', 'messages',
528 'debug', 'dmesg', 'boot'] +
529 [ f % n for n in range(1, 20) \
530 for f in ['kern.log.%d', 'kern.log.%d.gz',
531 'daemon.log.%d', 'daemon.log.%d.gz',
532 'user.log.%d', 'user.log.%d.gz',
533 'messages.%d', 'messages.%d.gz']]])
534 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
535 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
538 tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
539 tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
547 # permit the user to filter out data
548 for k in sorted(data.keys()):
549 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
552 # collect selected data now
553 output_ts('Running commands to collect data')
556 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
559 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
562 if output_fd == -1 and not os.path.exists(BUG_DIR):
569 output_ts('Creating output file')
571 if output_type.startswith('tar'):
572 make_tar(subdir, output_type, output_fd)
579 print >>sys.stderr, "Category sizes (max, actual):\n"
580 for c in caps.keys():
581 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
585 def find_tapdisk_logs():
586 return glob.glob('/var/log/blktap/*.log*')
588 def generate_tapdisk_logs():
589 for pid in pidof('tapdisk'):
591 os.kill(pid, SIGUSR1)
592 output_ts("Including logs for tapdisk process %d" % pid)
595 # give processes a second to write their logs
598 def clean_tapdisk_logs():
599 for filename in find_tapdisk_logs():
605 def filter_db_pii(str, state):
606 if 'in_secret_table' not in state:
607 state['in_secret_table'] = False
609 if str.startswith('<table ') and 'name="secret"' in str:
610 state['in_secret_table'] = True
611 elif str.startswith('</table>'):
612 state['in_secret_table'] = False
614 if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
615 str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
618 def dump_scsi_hosts(cap):
620 l = os.listdir('/sys/class/scsi_host')
626 f = open('/sys/class/scsi_host/%s/proc_name' % h)
627 procname = f.readline().strip("\n")
633 f = open('/sys/class/scsi_host/%s/model_name' % h)
634 modelname = f.readline().strip("\n")
640 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
644 def module_info(cap):
645 output = StringIO.StringIO()
646 modules = open(PROC_MODULES, 'r')
650 module = line.split()[0]
651 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
656 return output.getvalue()
659 output = StringIO.StringIO()
660 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
664 if not procs[0].timed_out:
665 return output.getvalue().splitlines()
669 output = StringIO.StringIO()
670 procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
674 if not procs[0].timed_out:
675 bonds = output.getvalue().splitlines()[1:]
676 return [x.split('\t')[1] for x in bonds]
682 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
684 fh = open('/proc/'+d+'/cmdline')
686 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
688 if not num_fds in fd_dict:
689 fd_dict[num_fds] = []
690 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
693 keys = fd_dict.keys()
694 keys.sort(lambda a, b: int(b) - int(a))
696 output += "%s: %s\n" % (k, str(fd_dict[k]))
699 def load_plugins(just_capabilities = False):
700 def getText(nodelist):
702 for node in nodelist:
703 if node.nodeType == node.TEXT_NODE:
707 def getBoolAttr(el, attr, default = False):
709 val = el.getAttribute(attr).lower()
710 if val in ['true', 'false', 'yes', 'no']:
711 ret = val in ['true', 'yes']
714 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
715 if not caps.has_key(dir):
716 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
718 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
719 assert xmldoc.documentElement.tagName == "capability"
721 pii, min_size, max_size, min_time, max_time, mime = \
722 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
724 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
725 pii = xmldoc.documentElement.getAttribute("pii")
726 if xmldoc.documentElement.getAttribute("min_size") != '':
727 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
728 if xmldoc.documentElement.getAttribute("max_size") != '':
729 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
730 if xmldoc.documentElement.getAttribute("min_time") != '':
731 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
732 if xmldoc.documentElement.getAttribute("max_time") != '':
733 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
734 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
735 mime = xmldoc.documentElement.getAttribute("mime")
736 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
737 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
739 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
741 if just_capabilities:
744 plugdir = os.path.join(PLUGIN_DIR, dir)
745 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
746 xmldoc = parse(os.path.join(plugdir, file))
747 assert xmldoc.documentElement.tagName == "collect"
749 for el in xmldoc.documentElement.getElementsByTagName("*"):
750 if el.tagName == "files":
751 file_output(dir, getText(el.childNodes).split())
752 elif el.tagName == "directory":
753 pattern = el.getAttribute("pattern")
754 if pattern == '': pattern = None
755 negate = getBoolAttr(el, 'negate')
756 tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
757 elif el.tagName == "command":
758 label = el.getAttribute("label")
759 if label == '': label = None
760 cmd_output(dir, getText(el.childNodes), label)
762 def make_tar(subdir, suffix, output_fd):
763 global SILENT_MODE, data
766 if suffix == 'tar.bz2':
768 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
771 tf = tarfile.open(filename, mode)
773 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
776 for (k, v) in data.items():
778 tar_filename = os.path.join(subdir, construct_filename(k, v))
779 ti = tarfile.TarInfo(tar_filename)
784 if v.has_key('output'):
785 ti.mtime = v['output'].mtime
786 ti.size = len(v['output'].getvalue())
788 tf.addfile(ti, v['output'])
789 elif v.has_key('filename'):
790 s = os.stat(v['filename'])
791 ti.mtime = s.st_mtime
793 tf.addfile(ti, file(v['filename']))
800 output ('Writing tarball %s successful.' % filename)
805 def make_zip(subdir):
806 global SILENT_MODE, data
808 filename = "%s/%s.zip" % (BUG_DIR, subdir)
809 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
812 for (k, v) in data.items():
814 dest = os.path.join(subdir, construct_filename(k, v))
816 if v.has_key('output'):
817 zf.writestr(dest, v['output'].getvalue())
819 if os.stat(v['filename']).st_size < 50:
820 compress_type = zipfile.ZIP_STORED
822 compress_type = zipfile.ZIP_DEFLATED
823 zf.write(v['filename'], dest, compress_type)
829 output ('Writing archive %s successful.' % filename)
834 def make_inventory(inventory, subdir):
835 document = getDOMImplementation().createDocument(
836 None, INVENTORY_XML_ROOT, None)
838 # create summary entry
839 s = document.createElement(INVENTORY_XML_SUMMARY)
840 user = os.getenv('SUDO_USER', os.getenv('USER'))
842 s.setAttribute('user', user)
843 s.setAttribute('date', time.strftime('%c'))
844 s.setAttribute('hostname', platform.node())
845 s.setAttribute('uname', ' '.join(platform.uname()))
846 s.setAttribute('uptime', commands.getoutput(UPTIME))
847 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
849 map(lambda (k, v): inventory_entry(document, subdir, k, v),
851 return document.toprettyxml()
853 def inventory_entry(document, subdir, k, v):
855 el = document.createElement(INVENTORY_XML_ELEMENT)
856 el.setAttribute('capability', v['cap'])
857 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
858 el.setAttribute('md5sum', md5sum(v))
859 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
866 if d.has_key('filename'):
867 f = open(d['filename'])
873 elif d.has_key('output'):
874 m.update(d['output'].getvalue())
878 def construct_filename(k, v):
879 if v.has_key('filename'):
880 if v['filename'][0] == '/':
881 return v['filename'][1:]
884 s = k.replace(' ', '-')
885 s = s.replace('--', '-')
886 s = s.replace('/', '%')
887 if s.find('.') == -1:
892 def update_capabilities():
895 def update_cap_size(cap, size):
896 update_cap(cap, MIN_SIZE, size)
897 update_cap(cap, MAX_SIZE, size)
898 update_cap(cap, CHECKED, size > 0)
901 def update_cap(cap, k, v):
908 def size_of_dir(d, pattern = None, negate = False):
910 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
916 def size_of_all(files, pattern = None, negate = False):
917 return sum([size_of(f, pattern, negate) for f in files])
920 def matches(f, pattern, negate):
922 return not matches(f, pattern, False)
924 return pattern is None or pattern.match(f)
927 def size_of(f, pattern, negate):
928 if os.path.isfile(f) and matches(f, pattern, negate):
931 return size_of_dir(f, pattern, negate)
934 def print_capabilities():
935 document = getDOMImplementation().createDocument(
936 "ns", CAP_XML_ROOT, None)
937 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
938 print document.toprettyxml()
940 def capability(document, key):
942 el = document.createElement(CAP_XML_ELEMENT)
943 el.setAttribute('key', c[KEY])
944 el.setAttribute('pii', c[PII])
945 el.setAttribute('min-size', str(c[MIN_SIZE]))
946 el.setAttribute('max-size', str(c[MAX_SIZE]))
947 el.setAttribute('min-time', str(c[MIN_TIME]))
948 el.setAttribute('max-time', str(c[MAX_TIME]))
949 el.setAttribute('content-type', c[MIME])
950 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
951 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
955 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
956 return '\n'.join([format % i for i in d.items()]) + '\n'
960 yn = raw_input(prompt)
962 return len(yn) == 0 or yn.lower()[0] == 'y'
965 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
970 f = open('/proc/partitions')
973 for line in f.readlines():
974 (major, minor, blocks, name) = line.split()
975 if int(major) < 254 and not partition_re.match(name):
986 def __init__(self, command, max_time, inst=None, filter=None):
987 self.command = command
988 self.max_time = max_time
992 self.timed_out = False
994 self.timeout = int(time.time()) + self.max_time
996 self.filter_state = {}
1002 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1005 self.timed_out = False
1007 if ProcOutput.debug:
1008 output_ts("Starting '%s'" % self.cmdAsStr())
1009 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1010 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1011 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1015 output_ts("'%s' failed" % self.cmdAsStr())
1016 self.running = False
1019 def terminate(self):
1022 os.kill(self.proc.pid, SIGTERM)
1026 self.running = False
1027 self.status = SIGTERM
1029 def read_line(self):
1031 line = self.proc.stdout.readline()
1034 self.status = self.proc.wait()
1036 self.running = False
1039 line = self.filter(line, self.filter_state)
1041 self.inst.write(line)
1043 def run_procs(procs):
1051 active_procs.append(p)
1052 pipes.append(p.proc.stdout)
1054 elif p.status == None and not p.failed and not p.timed_out:
1057 active_procs.append(p)
1058 pipes.append(p.proc.stdout)
1065 (i, o, x) = select(pipes, [], [], 1.0)
1066 now = int(time.time())
1068 # handle process output
1069 for p in active_procs:
1070 if p.proc.stdout in i:
1074 if p.running and now > p.timeout:
1075 output_ts("'%s' timed out" % p.cmdAsStr())
1077 p.inst.write("\n** timeout **\n")
1085 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1087 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1095 class StringIOmtime(StringIO.StringIO):
1096 def __init__(self, buf = ''):
1097 StringIO.StringIO.__init__(self, buf)
1098 self.mtime = time.time()
1101 StringIO.StringIO.write(self, s)
1102 self.mtime = time.time()
1105 if __name__ == "__main__":
1108 except KeyboardInterrupt:
1109 print "\nInterrupted."