ovs-bugtool: Provide a separate capability to openvswitch logs.
[sliver-openvswitch.git] / utilities / bugtool / ovs-bugtool.in
1 #! @PYTHON@
2
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.
6 #
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.
11 #
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
15 #
16 # Copyright (c) 2005, 2007 XenSource Ltd.
17 # Copyright (c) 2010, 2011, 2012 Nicira, Inc.
18
19 #
20 # To add new entries to the bugtool, you need to:
21 #
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:
27 #
28 #   A new CAP_ constant.
29 #   A cap() invocation to declare the capability.
30 #
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(),
33 # or func_output().
34 #
35
36 import warnings
37 warnings.filterwarnings(action="ignore", category=DeprecationWarning)
38
39 import getopt
40 import re
41 import os
42 import StringIO
43 import sys
44 import tarfile
45 import time
46 import commands
47 import pprint
48 from xml.dom.minidom import parse, getDOMImplementation
49 import zipfile
50 from subprocess import Popen, PIPE
51 from select import select
52 from signal import SIGTERM, SIGUSR1
53 import md5
54 import platform
55 import fcntl
56 import glob
57 import urllib
58 import socket
59 import base64
60
61 OS_RELEASE = platform.release()
62
63 #
64 # Files & directories
65 #
66
67 APT_SOURCES_LIST = "/etc/apt/sources.list"
68 APT_SOURCES_LIST_D = "/etc/apt/sources.list.d"
69 BUG_DIR = "/var/log/ovs-bugtool"
70 PLUGIN_DIR = "@pkgdatadir@/bugtool-plugins"
71 GRUB_CONFIG = '/boot/grub/menu.lst'
72 BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
73 BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
74 PROC_PARTITIONS = '/proc/partitions'
75 FSTAB = '/etc/fstab'
76 PROC_MOUNTS = '/proc/mounts'
77 ISCSI_CONF = '/etc/iscsi/iscsid.conf'
78 ISCSI_INITIATOR = '/etc/iscsi/initiatorname.iscsi'
79 LVM_CACHE = '/etc/lvm/cache/.cache'
80 LVM_CONFIG = '/etc/lvm/lvm.conf'
81 PROC_CPUINFO = '/proc/cpuinfo'
82 PROC_MEMINFO = '/proc/meminfo'
83 PROC_IOPORTS = '/proc/ioports'
84 PROC_INTERRUPTS = '/proc/interrupts'
85 PROC_SCSI = '/proc/scsi/scsi'
86 PROC_VERSION = '/proc/version'
87 PROC_MODULES = '/proc/modules'
88 PROC_DEVICES = '/proc/devices'
89 PROC_FILESYSTEMS = '/proc/filesystems'
90 PROC_CMDLINE = '/proc/cmdline'
91 PROC_CONFIG = '/proc/config.gz'
92 PROC_USB_DEV = '/proc/bus/usb/devices'
93 PROC_XEN_BALLOON = '/proc/xen/balloon'
94 PROC_NET_BONDING_DIR = '/proc/net/bonding'
95 IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
96 ROUTE_RE = re.compile(r'^.*/route-.*')
97 SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
98 SYSCONFIG_NETWORK = '/etc/sysconfig/network'
99 SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
100 PROC_NET_VLAN_DIR = '/proc/net/vlan'
101 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
102 MODPROBE_CONF = '/etc/modprobe.conf'
103 MODPROBE_DIR = '/etc/modprobe.d'
104 RESOLV_CONF = '/etc/resolv.conf'
105 MPP_CONF = '/etc/mpp.conf'
106 MULTIPATH_CONF = '/etc/multipath.conf'
107 NSSWITCH_CONF = '/etc/nsswitch.conf'
108 NTP_CONF = '/etc/ntp.conf'
109 IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
110 HOSTS = '/etc/hosts'
111 HOSTS_ALLOW = '/etc/hosts.allow'
112 HOSTS_DENY = '/etc/hosts.deny'
113 DHCP_LEASE_DIR = ['/var/lib/dhclient', '/var/lib/dhcp3']
114 OPENVSWITCH_LOG_DIR = '@LOGDIR@/'
115 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch' # Debian
116 OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch'    # RHEL
117 OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
118 OPENVSWITCH_CONF_DB = '@DBDIR@/conf.db'
119 OPENVSWITCH_VSWITCHD_PID = '@RUNDIR@/ovs-vswitchd.pid'
120 COLLECTD_LOGS_DIR = '/var/lib/collectd/rrd'
121 VAR_LOG_DIR = '/var/log/'
122 VAR_LOG_CORE_DIR = '/var/log/core'
123 X11_LOGS_DIR = VAR_LOG_DIR
124 X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
125 X11_AUTH_DIR = '/root/'
126 X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
127 YUM_LOG = '/var/log/yum.log'
128 YUM_REPOS_DIR = '/etc/yum.repos.d'
129 PAM_DIR = '/etc/pam.d'
130 KRB5_CONF = '/etc/krb5.conf'
131
132 #
133 # External programs
134 #
135
136 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:@pkgdatadir@/scripts'
137 ARP = 'arp'
138 CAT = 'cat'
139 CHKCONFIG = 'chkconfig'
140 DF = 'df'
141 DMESG = 'dmesg'
142 DMIDECODE = 'dmidecode'
143 DMSETUP = 'dmsetup'
144 DPKG_QUERY = 'dpkg-query'
145 ETHTOOL = 'ethtool'
146 FDISK = 'fdisk'
147 FIND = 'find'
148 HDPARM = 'hdparm'
149 IFCONFIG = 'ifconfig'
150 IPTABLES = 'iptables'
151 ISCSIADM = 'iscsiadm'
152 LOSETUP = 'losetup'
153 LS = 'ls'
154 LSPCI = 'lspci'
155 LVDISPLAY = 'lvdisplay'
156 LVS = 'lvs'
157 MD5SUM = 'md5sum'
158 MODINFO = 'modinfo'
159 MPPUTIL = 'mppUtil'
160 MULTIPATHD = 'multipathd'
161 NETSTAT = 'netstat'
162 OVS_DPCTL = 'ovs-dpctl'
163 OVS_OFCTL = 'ovs-ofctl'
164 OVS_VSCTL = 'ovs-vsctl'
165 OVS_APPCTL = 'ovs-appctl'
166 PS = 'ps'
167 PVS = 'pvs'
168 ROUTE = 'route'
169 RPM = 'rpm'
170 SG_MAP = 'sg_map'
171 SYSCTL = 'sysctl'
172 TC = 'tc'
173 UPTIME = 'uptime'
174 VGS = 'vgs'
175 VGSCAN = 'vgscan'
176 ZCAT = 'zcat'
177
178 #
179 # PII -- Personally identifiable information.  Of particular concern are
180 # things that would identify customers, or their network topology.
181 # Passwords are never to be included in any bug report, regardless of any PII
182 # declaration.
183 #
184 # NO            -- No PII will be in these entries.
185 # YES           -- PII will likely or certainly be in these entries.
186 # MAYBE         -- The user may wish to audit these entries for PII.
187 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
188 # but since we encourage customers to edit these files, PII may have been
189 # introduced by the customer.  This is used in particular for the networking
190 # scripts in dom0.
191 #
192
193 PII_NO            = 'no'
194 PII_YES           = 'yes'
195 PII_MAYBE         = 'maybe'
196 PII_IF_CUSTOMIZED = 'if_customized'
197 KEY      = 0
198 PII      = 1
199 MIN_SIZE = 2
200 MAX_SIZE = 3
201 MIN_TIME = 4
202 MAX_TIME = 5
203 MIME     = 6
204 CHECKED  = 7
205 HIDDEN   = 8
206
207 MIME_DATA = 'application/data'
208 MIME_TEXT = 'text/plain'
209
210 INVENTORY_XML_ROOT = "system-status-inventory"
211 INVENTORY_XML_SUMMARY = 'system-summary'
212 INVENTORY_XML_ELEMENT = 'inventory-entry'
213 CAP_XML_ROOT = "system-status-capabilities"
214 CAP_XML_ELEMENT = 'capability'
215
216
217 CAP_BLOBS                = 'blobs'
218 CAP_BOOT_LOADER          = 'boot-loader'
219 CAP_COLLECTD_LOGS        = 'collectd-logs'
220 CAP_DISK_INFO            = 'disk-info'
221 CAP_FIRSTBOOT            = 'firstboot'
222 CAP_HARDWARE_INFO        = 'hardware-info'
223 CAP_HDPARM_T             = 'hdparm-t'
224 CAP_HIGH_AVAILABILITY    = 'high-availability'
225 CAP_KERNEL_INFO          = 'kernel-info'
226 CAP_LOSETUP_A            = 'loopback-devices'
227 CAP_MULTIPATH            = 'multipath'
228 CAP_NETWORK_CONFIG       = 'network-config'
229 CAP_NETWORK_STATUS       = 'network-status'
230 CAP_OEM                  = 'oem'
231 CAP_OPENVSWITCH_LOGS     = 'ovs-system-logs'
232 CAP_PAM                  = 'pam'
233 CAP_PROCESS_LIST         = 'process-list'
234 CAP_PERSISTENT_STATS     = 'persistent-stats'
235 CAP_SYSTEM_LOGS          = 'system-logs'
236 CAP_SYSTEM_SERVICES      = 'system-services'
237 CAP_VNCTERM              = 'vncterm'
238 CAP_WLB                  = 'wlb'
239 CAP_X11_LOGS             = 'X11'
240 CAP_X11_AUTH             = 'X11-auth'
241 CAP_YUM                  = 'yum'
242
243 KB = 1024
244 MB = 1024 * 1024
245
246 caps = {}
247 cap_sizes = {}
248 unlimited_data = False
249 dbg = False
250
251 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
252         max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
253     caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
254                  checked, hidden)
255     cap_sizes[key] = 0
256
257
258 cap(CAP_BLOBS,               PII_NO,                    max_size=5*MB)
259 cap(CAP_BOOT_LOADER,         PII_NO,                    max_size=3*KB,
260     max_time=5)
261 cap(CAP_COLLECTD_LOGS,       PII_MAYBE,                 max_size=50*MB,
262     max_time=5)
263 cap(CAP_DISK_INFO,           PII_MAYBE,                 max_size=50*KB,
264     max_time=20)
265 cap(CAP_FIRSTBOOT,           PII_YES,   min_size=60*KB, max_size=80*KB)
266 cap(CAP_HARDWARE_INFO,       PII_MAYBE,                 max_size=30*KB,
267     max_time=20)
268 cap(CAP_HDPARM_T,            PII_NO,    min_size=0,     max_size=5*KB,
269     min_time=20, max_time=90, checked=False, hidden=True)
270 cap(CAP_HIGH_AVAILABILITY,   PII_MAYBE,                 max_size=5*MB)
271 cap(CAP_KERNEL_INFO,         PII_MAYBE,                 max_size=120*KB,
272     max_time=5)
273 cap(CAP_LOSETUP_A,           PII_MAYBE,                 max_size=KB, max_time=5)
274 cap(CAP_MULTIPATH,           PII_MAYBE,                 max_size=20*KB,
275     max_time=10)
276 cap(CAP_NETWORK_CONFIG,      PII_IF_CUSTOMIZED,
277                                         min_size=0,     max_size=40*KB)
278 cap(CAP_NETWORK_STATUS,      PII_YES,                   max_size=50*KB,
279     max_time=30)
280 cap(CAP_OPENVSWITCH_LOGS,    PII_MAYBE,                 max_size=-1,
281     max_time=5)
282 cap(CAP_PAM,                 PII_NO,                    max_size=50*KB)
283 cap(CAP_PERSISTENT_STATS,    PII_MAYBE,                 max_size=50*MB,
284     max_time=60)
285 cap(CAP_PROCESS_LIST,        PII_YES,                   max_size=30*KB,
286     max_time=20)
287 cap(CAP_SYSTEM_LOGS,         PII_MAYBE,                 max_size=200*MB,
288     max_time=5)
289 cap(CAP_SYSTEM_SERVICES,     PII_NO,                    max_size=5*KB,
290     max_time=20)
291 cap(CAP_VNCTERM,             PII_MAYBE, checked = False)
292 cap(CAP_WLB,                 PII_NO,                    max_size=3*MB,
293     max_time=20)
294 cap(CAP_X11_LOGS,            PII_NO,                    max_size=100*KB)
295 cap(CAP_X11_AUTH,            PII_NO,                    max_size=100*KB)
296 cap(CAP_YUM,                 PII_IF_CUSTOMIZED,         max_size=10*KB,
297     max_time=30)
298
299 ANSWER_YES_TO_ALL = False
300 SILENT_MODE = False
301 entries = None
302 data = {}
303 dev_null = open('/dev/null', 'r+')
304
305 def output(x):
306     global SILENT_MODE
307     if not SILENT_MODE:
308         print x
309
310 def output_ts(x):
311     output("[%s]  %s" % (time.strftime("%x %X %Z"), x))
312
313 def cmd_output(cap, args, label=None, filter=None):
314     if cap in entries:
315         if not label:
316             if isinstance(args, list):
317                 a = [aa for aa in args]
318                 a[0] = os.path.basename(a[0])
319                 label = ' '.join(a)
320             else:
321                 label = args
322         data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
323
324 def file_output(cap, path_list, newest_first=False):
325     """
326     If newest_first is True, the list of files in path_list is sorted
327     by file modification time in descending order, else its sorted
328     in ascending order.
329     """
330     if cap in entries:
331         path_entries = []
332         for path in path_list:
333             try:
334                 s = os.stat(path)
335             except OSError, e:
336                 continue
337             path_entries.append((path, s))
338
339         mtime = lambda(path, stat): stat.st_mtime
340         path_entries.sort(key=mtime, reverse=newest_first)
341         for p in path_entries:
342             if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
343                     cap_sizes[cap] < caps[cap][MAX_SIZE]:
344                 data[p] = {'cap': cap, 'filename': p[0]}
345                 cap_sizes[cap] += p[1].st_size
346             else:
347                 output("Omitting %s, size constraint of %s exceeded" % (p[0], cap))
348
349 def tree_output(cap, path, pattern=None, negate=False, newest_first=False):
350     """
351     Walks the directory tree rooted at path. Files in current dir are processed
352     before files in sub-dirs.
353     """
354     if cap in entries:
355         if os.path.exists(path):
356             for root, dirs, files in os.walk(path):
357                 fns = [fn for fn in [os.path.join(root, f) for f in files]
358                        if os.path.isfile(fn) and matches(fn, pattern, negate)]
359                 file_output(cap, fns, newest_first=newest_first)
360
361 def func_output(cap, label, func):
362     if cap in entries:
363         t = str(func).split()
364         data[label] = {'cap': cap, 'func': func}
365
366 def collect_data():
367     process_lists = {}
368
369     for (k, v) in data.items():
370         cap = v['cap']
371         if v.has_key('cmd_args'):
372             v['output'] = StringIOmtime()
373             if not process_lists.has_key(cap):
374                 process_lists[cap] = []
375             process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
376         elif v.has_key('filename') and v['filename'].startswith('/proc/'):
377             # proc files must be read into memory
378             try:
379                 f = open(v['filename'], 'r')
380                 s = f.read()
381                 f.close()
382                 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
383                         cap_sizes[cap] < caps[cap][MAX_SIZE]:
384                     v['output'] = StringIOmtime(s)
385                     cap_sizes[cap] += len(s)
386                 else:
387                     output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
388             except:
389                 pass
390         elif v.has_key('func'):
391             try:
392                 s = v['func'](cap)
393             except Exception, e:
394                 s = str(e)
395             if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
396                     cap_sizes[cap] < caps[cap][MAX_SIZE]:
397                 v['output'] = StringIOmtime(s)
398                 cap_sizes[cap] += len(s)
399             else:
400                 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
401
402     run_procs(process_lists.values())
403
404
405 def main(argv=None):
406     global ANSWER_YES_TO_ALL, SILENT_MODE
407     global entries, data, dbg, unlimited_data
408
409     # Filter flags
410     only_ovs_info = False
411     collect_all_info = True
412
413     # we need access to privileged files, exit if we are not running as root
414     if os.getuid() != 0:
415         print >>sys.stderr, "Error: ovs-bugtool must be run as root"
416         return 1
417
418     output_file = None
419     output_type = 'tar.bz2'
420     output_fd = -1
421
422     if argv is None:
423         argv = sys.argv
424
425     try:
426         (options, params) = getopt.gnu_getopt(
427             argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
428                          'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
429                          'debug', 'ovs'])
430     except getopt.GetoptError, opterr:
431         print >>sys.stderr, opterr
432         return 2
433
434     try:
435         load_plugins(True)
436     except:
437         pass
438
439     entries = [e for e in caps.keys() if caps[e][CHECKED]]
440
441     for (k, v) in options:
442         if k == '--capabilities':
443             update_capabilities()
444             print_capabilities()
445             return 0
446
447         if k == '--output':
448             if  v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
449                 output_type = v
450             else:
451                 print >>sys.stderr, "Invalid output format '%s'" % v
452                 return 2
453
454         # "-s" or "--silent" means suppress output (except for the final
455         # output filename at the end)
456         if k in ['-s', '--silent']:
457             SILENT_MODE = True
458
459         if k == '--entries' and v != '':
460             entries = v.split(',')
461
462         # If the user runs the script with "-y" or "--yestoall" we don't ask
463         # all the really annoying questions.
464         if k in ['-y', '--yestoall']:
465             ANSWER_YES_TO_ALL = True
466
467         if k == '--outfd':
468             output_fd = int(v)
469             try:
470                 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
471                 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
472             except:
473                 print >>sys.stderr, "Invalid output file descriptor", output_fd
474                 return 2
475
476         if k == '--outfile':
477             output_file = v
478
479         elif k == '--all':
480             entries = caps.keys()
481         elif k == '--unlimited':
482             unlimited_data = True
483         elif k == '--debug':
484             dbg = True
485             ProcOutput.debug = True
486
487         if k == '--ovs':
488             only_ovs_info = True
489             collect_all_info = False
490
491     if len(params) != 1:
492         print >>sys.stderr, "Invalid additional arguments", str(params)
493         return 2
494
495     if output_fd != -1 and output_type != 'tar':
496         print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
497         return 2
498
499     if output_fd != -1 and output_file is not None:
500         print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
501         return 2
502
503     if ANSWER_YES_TO_ALL:
504         output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
505
506     output('''
507 This application will collate dmesg output, details of the
508 hardware configuration of your machine, information about the build of
509 openvswitch that you are using, plus, if you allow it, various logs.
510
511 The collated information will be saved as a .%s for archiving or
512 sending to a Technical Support Representative.
513
514 The logs may contain private information, and if you are at all
515 worried about that, you should exit now, or you should explicitly
516 exclude those logs from the archive.
517
518 ''' % output_type)
519
520     # assemble potential data
521
522     file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
523     cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
524     cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
525
526     tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
527     cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
528     file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
529     file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
530     cmd_output(CAP_DISK_INFO, [DF, '-alT'])
531     cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
532     for d in disk_list():
533         cmd_output(CAP_DISK_INFO, [HDPARM, '-I', '/dev/%s' % d])
534     if len(pidof('iscsid')) != 0:
535         cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
536     cmd_output(CAP_DISK_INFO, [VGSCAN])
537     cmd_output(CAP_DISK_INFO, [PVS])
538     cmd_output(CAP_DISK_INFO, [VGS])
539     cmd_output(CAP_DISK_INFO, [LVS])
540     file_output(CAP_DISK_INFO, [LVM_CACHE, LVM_CONFIG])
541     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
542     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
543     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
544     cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
545     func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
546     cmd_output(CAP_DISK_INFO, [LVDISPLAY, '--map'])
547
548     file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
549     cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
550     cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
551     cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
552     file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
553     file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
554     cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
555     # FIXME IDE?
556
557     for d in disk_list():
558         cmd_output(CAP_HDPARM_T, [HDPARM, '-tT', '/dev/%s' % d])
559
560     file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
561                                   PROC_FILESYSTEMS, PROC_CMDLINE])
562     cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
563     cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
564     file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
565     tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
566     func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
567
568     cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
569
570     file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
571     cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
572     func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
573     cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
574     if CAP_MULTIPATH in entries and collect_all_info:
575         dump_rdac_groups(CAP_MULTIPATH)
576
577     tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
578     tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
579     file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
580     file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
581     file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
582
583     cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
584     cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
585     cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
586     cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
587     for dir in DHCP_LEASE_DIR:
588         tree_output(CAP_NETWORK_STATUS, dir)
589     cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
590     for p in os.listdir('/sys/class/net/'):
591         try:
592             f = open('/sys/class/net/%s/type' % p, 'r')
593             t = f.readline()
594             f.close()
595             if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
596                 # ARPHRD_ETHER
597                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
598                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
599                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
600                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
601                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
602             if int(t) == 1:
603                 cmd_output(CAP_NETWORK_STATUS,
604                            [TC, '-s', '-d', 'class', 'show', 'dev', p])
605         except:
606             pass
607     tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
608     tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
609     cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
610     file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
611     if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
612         cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
613         for d in dp_list():
614             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
615             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
616             cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
617         try:
618             vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
619             vspid = int(vspidfile.readline().strip())
620             vspidfile.close()
621             for b in bond_list(vspid):
622                 cmd_output(CAP_NETWORK_STATUS,
623                            [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
624                            'ovs-appctl-bond-show-%s.out' % b)
625         except e:
626             pass
627
628     tree_output(CAP_PAM, PAM_DIR)
629     file_output(CAP_PAM, [KRB5_CONF])
630
631     cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
632     func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
633
634     system_logs = (CAP_SYSTEM_LOGS, [ VAR_LOG_DIR + x for x in
635         ['crit.log', 'kern.log', 'daemon.log', 'user.log',
636         'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
637     ovs_logs = (CAP_OPENVSWITCH_LOGS, [ OPENVSWITCH_LOG_DIR + x for x in
638         ['ovs-vswitchd.log', 'ovsdb-server.log',
639         'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log']])
640     for cap, logs in [system_logs, ovs_logs]:
641         file_output(cap, logs)
642         file_output(cap, ['%s.%d' % (f, n) for n in range(20) for f in logs])
643         file_output(cap, ['%s.%d.gz' % (f, n) for n in range(20) for f in logs])
644
645     if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
646         cmd_output(CAP_SYSTEM_LOGS, [DMESG])
647
648     cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
649
650     tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
651     tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
652     tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
653
654     file_output(CAP_YUM, [YUM_LOG])
655     tree_output(CAP_YUM, YUM_REPOS_DIR)
656     cmd_output(CAP_YUM, [RPM, '-qa'])
657     file_output(CAP_YUM, [APT_SOURCES_LIST])
658     tree_output(CAP_YUM, APT_SOURCES_LIST_D)
659     cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
660
661     # Filter out ovs relevant information if --ovs option passed
662     # else collect all information
663     filters = set()
664     if only_ovs_info:
665         filters.add('ovs')
666         ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
667                          CAP_NETWORK_CONFIG]
668         ovs_info_list = ['process-tree']
669         # We cannot use iteritems, since we modify 'data' as we pass through
670         for (k, v) in data.items():
671             cap = v['cap']
672             if 'filename' in v:
673                 info = k[0]
674             else:
675                 info = k
676             if info not in ovs_info_list and cap not in ovs_info_caps:
677                 del data[k]
678
679     if filters:
680         filter = ",".join(filters)
681     else:
682         filter = None
683
684     try:
685         load_plugins(filter=filter)
686     except:
687         pass
688
689     # permit the user to filter out data
690     # We cannot use iteritems, since we modify 'data' as we pass through
691     for (k, v) in sorted(data.items()):
692        cap = v['cap']
693        if 'filename' in v:
694            key = k[0]
695        else:
696            key = k
697        if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
698            del data[k]
699
700     # collect selected data now
701     output_ts('Running commands to collect data')
702     collect_data()
703
704     subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
705
706     # include inventory
707     data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
708
709     # create archive
710     if output_fd == -1:
711         if output_file is None:
712             dirname = BUG_DIR
713         else:
714             dirname = os.path.dirname(output_file)
715         if dirname and not os.path.exists(dirname):
716             try:
717                 os.makedirs(dirname)
718             except:
719                 pass
720
721     if output_fd == -1:
722         output_ts('Creating output file')
723
724     if output_type.startswith('tar'):
725         make_tar(subdir, output_type, output_fd, output_file)
726     else:
727         make_zip(subdir, output_file)
728
729     clean_tapdisk_logs()
730
731     if dbg:
732         print >>sys.stderr, "Category sizes (max, actual):\n"
733         for c in caps.keys():
734             print >>sys.stderr, "    %s (%d, %d)" % (c, caps[c][MAX_SIZE],
735                                                      cap_sizes[c])
736     return 0
737
738 def find_tapdisk_logs():
739     return glob.glob('/var/log/blktap/*.log*')
740
741 def generate_tapdisk_logs():
742     for pid in pidof('tapdisk'):
743         try:
744             os.kill(pid, SIGUSR1)
745             output_ts("Including logs for tapdisk process %d" % pid)
746         except :
747             pass
748     # give processes a second to write their logs
749     time.sleep(1)
750
751 def clean_tapdisk_logs():
752     for filename in find_tapdisk_logs():
753         try:
754             os.remove(filename)
755         except :
756             pass
757
758 def filter_db_pii(str, state):
759     if 'in_secret_table' not in state:
760         state['in_secret_table'] = False
761
762     if str.startswith('<table ') and 'name="secret"' in str:
763         state['in_secret_table'] = True
764     elif str.startswith('</table>'):
765         state['in_secret_table'] = False
766
767     if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
768         str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
769     return str
770
771 def dump_scsi_hosts(cap):
772     output = ''
773     l = os.listdir('/sys/class/scsi_host')
774     l.sort()
775
776     for h in l:
777         procname = ''
778         try:
779                 f = open('/sys/class/scsi_host/%s/proc_name' % h)
780                 procname = f.readline().strip("\n")
781                 f.close()
782         except:
783                 pass
784         modelname = None
785         try:
786                 f = open('/sys/class/scsi_host/%s/model_name' % h)
787                 modelname = f.readline().strip("\n")
788                 f.close()
789         except:
790                 pass
791
792         output += "%s:\n" %h
793         output += "    %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
794
795     return output
796
797 def module_info(cap):
798     output = StringIO.StringIO()
799     modules = open(PROC_MODULES, 'r')
800     procs = []
801
802     for line in modules:
803         module = line.split()[0]
804         procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
805     modules.close()
806
807     run_procs([procs])
808
809     return output.getvalue()
810
811
812 def multipathd_topology(cap):
813     pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
814                      stdout=PIPE, stderr=dev_null)
815     stdout, stderr = pipe.communicate('show topology')
816
817     return stdout
818
819 def dp_list():
820     output = StringIO.StringIO()
821     procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
822
823     run_procs([procs])
824
825     if not procs[0].timed_out:
826         return output.getvalue().splitlines()
827     return []
828
829 def bond_list(pid):
830     output = StringIO.StringIO()
831     procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
832
833     run_procs([procs])
834
835     if not procs[0].timed_out:
836         bonds = output.getvalue().splitlines()[1:]
837         return [x.split('\t')[1] for x in bonds]
838     return []
839
840 def fd_usage(cap):
841     output = ''
842     fd_dict = {}
843     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
844         try:
845             fh = open('/proc/'+d+'/cmdline')
846             name = fh.readline()
847             num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
848             if num_fds > 0:
849                 if not num_fds in fd_dict:
850                     fd_dict[num_fds] = []
851                 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
852         finally:
853             fh.close()
854     keys = fd_dict.keys()
855     keys.sort(lambda a, b: int(b) - int(a))
856     for k in keys:
857         output += "%s: %s\n" % (k, str(fd_dict[k]))
858     return output
859
860 def dump_rdac_groups(cap):
861     output = StringIO.StringIO()
862     procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
863
864     run_procs([procs])
865
866     if not procs[0].timed_out:
867         proc_line = 0
868         for line in output.getvalue().splitlines():
869             if line.startswith('ID'):
870                 proc_line = 2
871             elif line.startswith('----'):
872                 proc_line -= 1
873             elif proc_line > 0:
874                 group, _ = line.split(None, 1)
875                 cmd_output(cap, [MPPUTIL, '-g', group])
876
877 def load_plugins(just_capabilities=False, filter=None):
878     def getText(nodelist):
879         rc = ""
880         for node in nodelist:
881             if node.nodeType == node.TEXT_NODE:
882                 rc += node.data
883         return rc.encode()
884
885     def getBoolAttr(el, attr, default=False):
886         ret = default
887         val = el.getAttribute(attr).lower()
888         if val in ['true', 'false', 'yes', 'no']:
889             ret = val in ['true', 'yes']
890         return ret
891
892     for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
893         if not caps.has_key(dir):
894             if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
895                 continue
896             xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
897             assert xmldoc.documentElement.tagName == "capability"
898
899             pii, min_size, max_size, min_time, max_time, mime = \
900                  PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
901
902             if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
903                 pii = xmldoc.documentElement.getAttribute("pii")
904             if xmldoc.documentElement.getAttribute("min_size") != '':
905                 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
906             if xmldoc.documentElement.getAttribute("max_size") != '':
907                 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
908             if xmldoc.documentElement.getAttribute("min_time") != '':
909                 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
910             if xmldoc.documentElement.getAttribute("max_time") != '':
911                 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
912             if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
913                 mime = xmldoc.documentElement.getAttribute("mime")
914             checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
915             hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
916
917             cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
918
919         if just_capabilities:
920             continue
921
922         plugdir = os.path.join(PLUGIN_DIR, dir)
923         for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
924             xmldoc = parse(os.path.join(plugdir, file))
925             assert xmldoc.documentElement.tagName == "collect"
926
927             for el in xmldoc.documentElement.getElementsByTagName("*"):
928                 filters_tmp = el.getAttribute("filters")
929                 if filters_tmp == '':
930                     filters = []
931                 else:
932                     filters = filters_tmp.split(',')
933                 if not(filter is None or filter in filters):
934                     continue
935                 if el.tagName == "files":
936                     newest_first = getBoolAttr(el, 'newest_first')
937                     file_output(dir, getText(el.childNodes).split(),
938                                 newest_first=newest_first)
939                 elif el.tagName == "directory":
940                     pattern = el.getAttribute("pattern")
941                     if pattern == '': pattern = None
942                     negate = getBoolAttr(el, 'negate')
943                     newest_first = getBoolAttr(el, 'newest_first')
944                     tree_output(dir, getText(el.childNodes),
945                                 pattern and re.compile(pattern) or None,
946                                 negate=negate, newest_first=newest_first)
947                 elif el.tagName == "command":
948                     label = el.getAttribute("label")
949                     if label == '': label = None
950                     cmd_output(dir, getText(el.childNodes), label)
951
952 def make_tar(subdir, suffix, output_fd, output_file):
953     global SILENT_MODE, data
954
955     mode = 'w'
956     if suffix == 'tar.bz2':
957         mode = 'w:bz2'
958     elif suffix == 'tar.gz':
959         mode = 'w:gz'
960
961     if output_fd == -1:
962         if output_file is None:
963             filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
964         else:
965             filename = output_file
966         old_umask = os.umask(0077)
967         tf = tarfile.open(filename, mode)
968         os.umask(old_umask)
969     else:
970         tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
971
972     try:
973         for (k, v) in data.items():
974             try:
975                 tar_filename = os.path.join(subdir, construct_filename(k, v))
976                 ti = tarfile.TarInfo(tar_filename)
977
978                 ti.uname = 'root'
979                 ti.gname = 'root'
980
981                 if v.has_key('output'):
982                     ti.mtime = v['output'].mtime
983                     ti.size = len(v['output'].getvalue())
984                     v['output'].seek(0)
985                     tf.addfile(ti, v['output'])
986                 elif v.has_key('filename'):
987                     s = os.stat(v['filename'])
988                     ti.mtime = s.st_mtime
989                     ti.size = s.st_size
990                     tf.addfile(ti, file(v['filename']))
991             except:
992                 pass
993     finally:
994         tf.close()
995
996     if output_fd == -1:
997         output ('Writing tarball %s successful.' % filename)
998         if SILENT_MODE:
999             print filename
1000
1001
1002 def make_zip(subdir, output_file):
1003     global SILENT_MODE, data
1004
1005     if output_file is None:
1006         filename = "%s/%s.zip" % (BUG_DIR, subdir)
1007     else:
1008         filename = output_file
1009     old_umask = os.umask(0077)
1010     zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
1011     os.umask(old_umask)
1012
1013     try:
1014         for (k, v) in data.items():
1015             try:
1016                 dest = os.path.join(subdir, construct_filename(k, v))
1017
1018                 if v.has_key('output'):
1019                     zf.writestr(dest, v['output'].getvalue())
1020                 else:
1021                     if os.stat(v['filename']).st_size < 50:
1022                         compress_type = zipfile.ZIP_STORED
1023                     else:
1024                         compress_type = zipfile.ZIP_DEFLATED
1025                     zf.write(v['filename'], dest, compress_type)
1026             except:
1027                 pass
1028     finally:
1029         zf.close()
1030
1031     output ('Writing archive %s successful.' % filename)
1032     if SILENT_MODE:
1033         print filename
1034
1035
1036 def make_inventory(inventory, subdir):
1037     document = getDOMImplementation().createDocument(
1038         None, INVENTORY_XML_ROOT, None)
1039
1040     # create summary entry
1041     s = document.createElement(INVENTORY_XML_SUMMARY)
1042     user = os.getenv('SUDO_USER', os.getenv('USER'))
1043     if user:
1044         s.setAttribute('user', user)
1045     s.setAttribute('date', time.strftime('%c'))
1046     s.setAttribute('hostname', platform.node())
1047     s.setAttribute('uname', ' '.join(platform.uname()))
1048     s.setAttribute('uptime', commands.getoutput(UPTIME))
1049     document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1050
1051     map(lambda (k, v): inventory_entry(document, subdir, k, v),
1052         inventory.items())
1053     return document.toprettyxml()
1054
1055 def inventory_entry(document, subdir, k, v):
1056     try:
1057         el = document.createElement(INVENTORY_XML_ELEMENT)
1058         el.setAttribute('capability', v['cap'])
1059         el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1060         el.setAttribute('md5sum', md5sum(v))
1061         document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1062     except:
1063         pass
1064
1065
1066 def md5sum(d):
1067     m = md5.new()
1068     if d.has_key('filename'):
1069         f = open(d['filename'])
1070         data = f.read(1024)
1071         while len(data) > 0:
1072             m.update(data)
1073             data = f.read(1024)
1074         f.close()
1075     elif d.has_key('output'):
1076         m.update(d['output'].getvalue())
1077     return m.hexdigest()
1078
1079
1080 def construct_filename(k, v):
1081     if v.has_key('filename'):
1082         if v['filename'][0] == '/':
1083             return v['filename'][1:]
1084         else:
1085             return v['filename']
1086     s = k.replace(' ', '-')
1087     s = s.replace('--', '-')
1088     s = s.replace('/', '%')
1089     if s.find('.') == -1:
1090         s += '.out'
1091
1092     return s
1093
1094 def update_capabilities():
1095     pass
1096
1097 def update_cap_size(cap, size):
1098     update_cap(cap, MIN_SIZE, size)
1099     update_cap(cap, MAX_SIZE, size)
1100     update_cap(cap, CHECKED, size > 0)
1101
1102
1103 def update_cap(cap, k, v):
1104     global caps
1105     l = list(caps[cap])
1106     l[k] = v
1107     caps[cap] = tuple(l)
1108
1109
1110 def size_of_dir(d, pattern=None, negate=False):
1111     if os.path.isdir(d):
1112         return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1113                            pattern, negate)
1114     else:
1115         return 0
1116
1117
1118 def size_of_all(files, pattern=None, negate=False):
1119     return sum([size_of(f, pattern, negate) for f in files])
1120
1121
1122 def matches(f, pattern, negate):
1123     if negate:
1124         return not matches(f, pattern, False)
1125     else:
1126         return pattern is None or pattern.match(f)
1127
1128
1129 def size_of(f, pattern, negate):
1130     if os.path.isfile(f) and matches(f, pattern, negate):
1131         return os.stat(f)[6]
1132     else:
1133         return size_of_dir(f, pattern, negate)
1134
1135
1136 def print_capabilities():
1137     document = getDOMImplementation().createDocument(
1138         "ns", CAP_XML_ROOT, None)
1139     map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1140     print document.toprettyxml()
1141
1142 def capability(document, key):
1143     c = caps[key]
1144     el = document.createElement(CAP_XML_ELEMENT)
1145     el.setAttribute('key', c[KEY])
1146     el.setAttribute('pii', c[PII])
1147     el.setAttribute('min-size', str(c[MIN_SIZE]))
1148     el.setAttribute('max-size', str(c[MAX_SIZE]))
1149     el.setAttribute('min-time', str(c[MIN_TIME]))
1150     el.setAttribute('max-time', str(c[MAX_TIME]))
1151     el.setAttribute('content-type', c[MIME])
1152     el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1153     document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1154
1155
1156 def prettyDict(d):
1157     format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1158     return '\n'.join([format % i for i in d.items()]) + '\n'
1159
1160
1161 def yes(prompt):
1162     yn = raw_input(prompt)
1163
1164     return len(yn) == 0 or yn.lower()[0] == 'y'
1165
1166
1167 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1168
1169 def disk_list():
1170     disks = []
1171     try:
1172         f = open('/proc/partitions')
1173         f.readline()
1174         f.readline()
1175         for line in f.readlines():
1176             (major, minor, blocks, name) = line.split()
1177             if int(major) < 254 and not partition_re.match(name):
1178                 disks.append(name)
1179         f.close()
1180     except:
1181         pass
1182     return disks
1183
1184
1185 class ProcOutput:
1186     debug = False
1187
1188     def __init__(self, command, max_time, inst=None, filter=None):
1189         self.command = command
1190         self.max_time = max_time
1191         self.inst = inst
1192         self.running = False
1193         self.status = None
1194         self.timed_out = False
1195         self.failed = False
1196         self.timeout = int(time.time()) + self.max_time
1197         self.filter = filter
1198         self.filter_state = {}
1199
1200     def __del__(self):
1201         self.terminate()
1202
1203     def cmdAsStr(self):
1204         return isinstance(self.command, list) and ' '.join(self.command) or self.command
1205
1206     def run(self):
1207         self.timed_out = False
1208         try:
1209             if ProcOutput.debug:
1210                 output_ts("Starting '%s'" % self.cmdAsStr())
1211             self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1212             old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1213             fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1214             self.running = True
1215             self.failed = False
1216         except:
1217             output_ts("'%s' failed" % self.cmdAsStr())
1218             self.running = False
1219             self.failed = True
1220
1221     def terminate(self):
1222         if self.running:
1223             try:
1224                 self.proc.stdout.close()
1225                 os.kill(self.proc.pid, SIGTERM)
1226             except:
1227                 pass
1228             self.proc = None
1229             self.running = False
1230             self.status = SIGTERM
1231
1232     def read_line(self):
1233         assert self.running
1234         line = self.proc.stdout.readline()
1235         if line == '':
1236             # process exited
1237             self.proc.stdout.close()
1238             self.status = self.proc.wait()
1239             self.proc = None
1240             self.running = False
1241         else:
1242             if self.filter:
1243                 line = self.filter(line, self.filter_state)
1244             if self.inst:
1245                 self.inst.write(line)
1246
1247 def run_procs(procs):
1248     while True:
1249         pipes = []
1250         active_procs = []
1251
1252         for pp in procs:
1253             for p in pp:
1254                 if p.running:
1255                     active_procs.append(p)
1256                     pipes.append(p.proc.stdout)
1257                     break
1258                 elif p.status == None and not p.failed and not p.timed_out:
1259                     p.run()
1260                     if p.running:
1261                         active_procs.append(p)
1262                         pipes.append(p.proc.stdout)
1263                         break
1264
1265         if len(pipes) == 0:
1266             # all finished
1267             break
1268
1269         (i, o, x) = select(pipes, [], [], 1.0)
1270         now = int(time.time())
1271
1272         # handle process output
1273         for p in active_procs:
1274             if p.proc.stdout in i:
1275                 p.read_line()
1276
1277             # handle timeout
1278             if p.running and now > p.timeout:
1279                 output_ts("'%s' timed out" % p.cmdAsStr())
1280                 if p.inst:
1281                     p.inst.write("\n** timeout **\n")
1282                 p.timed_out = True
1283                 p.terminate()
1284
1285
1286 def pidof(name):
1287     pids = []
1288
1289     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1290         try:
1291             if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1292                 pids.append(int(d))
1293         except:
1294             pass
1295
1296     return pids
1297
1298
1299 class StringIOmtime(StringIO.StringIO):
1300     def __init__(self, buf=''):
1301         StringIO.StringIO.__init__(self, buf)
1302         self.mtime = time.time()
1303
1304     def write(self, s):
1305         StringIO.StringIO.write(self, s)
1306         self.mtime = time.time()
1307
1308
1309 if __name__ == "__main__":
1310     try:
1311         sys.exit(main())
1312     except KeyboardInterrupt:
1313         print "\nInterrupted."
1314         sys.exit(3)