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