ovs-bugtool: Restore RHEL support.
[sliver-openvswitch.git] / debian / ovs-bugtool
1 #!/usr/bin/env 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 Networks.
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 sys.path.append('/usr/lib/python')
62 sys.path.append('/usr/lib64/python')
63
64 OS_RELEASE = platform.release()
65
66 #
67 # Files & directories
68 #
69
70 BUG_DIR = "/var/log/ovs-bugtool"
71 PLUGIN_DIR = "/etc/openvswitch/bugtool"
72 GRUB_CONFIG = '/boot/grub/menu.lst'
73 BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
74 BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
75 PROC_PARTITIONS = '/proc/partitions'
76 FSTAB = '/etc/fstab'
77 PROC_MOUNTS = '/proc/mounts'
78 ISCSI_CONF = '/etc/iscsi/iscsid.conf'
79 ISCSI_INITIATOR = '/etc/iscsi/initiatorname.iscsi'
80 LVM_CACHE = '/etc/lvm/cache/.cache'
81 LVM_CONFIG = '/etc/lvm/lvm.conf'
82 PROC_CPUINFO = '/proc/cpuinfo'
83 PROC_MEMINFO = '/proc/meminfo'
84 PROC_IOPORTS = '/proc/ioports'
85 PROC_INTERRUPTS = '/proc/interrupts'
86 PROC_SCSI = '/proc/scsi/scsi'
87 PROC_VERSION = '/proc/version'
88 PROC_MODULES = '/proc/modules'
89 PROC_DEVICES = '/proc/devices'
90 PROC_FILESYSTEMS = '/proc/filesystems'
91 PROC_CMDLINE = '/proc/cmdline'
92 PROC_CONFIG = '/proc/config.gz'
93 PROC_USB_DEV = '/proc/bus/usb/devices'
94 PROC_XEN_BALLOON = '/proc/xen/balloon'
95 PROC_NET_BONDING_DIR = '/proc/net/bonding'
96 IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
97 ROUTE_RE = re.compile(r'^.*/route-.*')
98 SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
99 SYSCONFIG_NETWORK = '/etc/sysconfig/network'
100 SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
101 PROC_NET_VLAN_DIR = '/proc/net/vlan'
102 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
103 MODPROBE_CONF = '/etc/modprobe.conf'
104 MODPROBE_DIR = '/etc/modprobe.d'
105 RESOLV_CONF = '/etc/resolv.conf'
106 MPP_CONF = '/etc/mpp.conf'
107 MULTIPATH_CONF = '/etc/multipath.conf'
108 NSSWITCH_CONF = '/etc/nsswitch.conf'
109 NTP_CONF = '/etc/ntp.conf'
110 IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
111 HOSTS = '/etc/hosts'
112 HOSTS_ALLOW = '/etc/hosts.allow'
113 HOSTS_DENY = '/etc/hosts.deny'
114 DHCP_LEASE_DIR = ['/var/lib/dhclient', '/var/lib/dhcp3']
115 OPENVSWITCH_LOG_DIR = '/var/log/openvswitch'
116 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch' # Debian
117 OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch'    # RHEL
118 OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
119 OPENVSWITCH_CONF_DB = '/etc/openvswitch/conf.db'
120 OPENVSWITCH_VSWITCHD_PID = '/var/run/openvswitch/ovs-vswitchd.pid'
121 COLLECTD_LOGS_DIR = '/var/lib/collectd/rrd'
122 VAR_LOG_DIR = '/var/log/'
123 VAR_LOG_CORE_DIR = '/var/log/core'
124 X11_LOGS_DIR = VAR_LOG_DIR
125 X11_LOGS_RE = re.compile(r'.*/Xorg\..*$')
126 X11_AUTH_DIR = '/root/'
127 X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$')
128 YUM_LOG = '/var/log/yum.log'
129 YUM_REPOS_DIR = '/etc/yum.repos.d'
130 PAM_DIR = '/etc/pam.d'
131 KRB5_CONF = '/etc/krb5.conf'
132
133 #
134 # External programs
135 #
136
137 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
138 ARP = 'arp'
139 BRCTL = 'brctl'
140 CAT = 'cat'
141 CHKCONFIG = 'chkconfig'
142 DF = 'df'
143 DMESG = 'dmesg'
144 DMIDECODE = 'dmidecode'
145 DMSETUP = 'dmsetup'
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):
323     if cap in entries:
324         for p in path_list:
325             if os.path.exists(p):
326                 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
327                         cap_sizes[cap] < caps[cap][MAX_SIZE]:
328                     data[p] = {'cap': cap, 'filename': p}
329                     try:
330                         s = os.stat(p)
331                         cap_sizes[cap] += s.st_size
332                     except:
333                         pass
334                 else:
335                     output("Omitting %s, size constraint of %s exceeded" % (p, cap))
336
337 def tree_output(cap, path, pattern = None, negate = False):
338     if cap in entries:
339         if os.path.exists(path):
340             for f in os.listdir(path):
341                 fn = os.path.join(path, f)
342                 if os.path.isfile(fn) and matches(fn, pattern, negate):
343                     file_output(cap, [fn])
344                 elif os.path.isdir(fn):
345                     tree_output(cap, fn, pattern, negate)
346
347 def func_output(cap, label, func):
348     if cap in entries:
349         t = str(func).split()
350         data[label] = {'cap': cap, 'func': func}
351
352 def collect_data():
353     process_lists = {}
354
355     for (k, v) in data.items():
356         cap = v['cap']
357         if v.has_key('cmd_args'):
358             v['output'] = StringIOmtime()
359             if not process_lists.has_key(cap):
360                 process_lists[cap] = []
361             process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
362         elif v.has_key('filename') and v['filename'].startswith('/proc/'):
363             # proc files must be read into memory
364             try:
365                 f = open(v['filename'], 'r')
366                 s = f.read()
367                 f.close()
368                 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
369                         cap_sizes[cap] < caps[cap][MAX_SIZE]:
370                     v['output'] = StringIOmtime(s)
371                     cap_sizes[cap] += len(s)
372                 else:
373                     output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
374             except:
375                 pass
376         elif v.has_key('func'):
377             try:
378                 s = v['func'](cap)
379             except Exception, e:
380                 s = str(e)
381             if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
382                     cap_sizes[cap] < caps[cap][MAX_SIZE]:
383                 v['output'] = StringIOmtime(s)
384                 cap_sizes[cap] += len(s)
385             else:
386                 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
387
388     run_procs(process_lists.values())
389
390
391 def main(argv = None):
392     global ANSWER_YES_TO_ALL, SILENT_MODE
393     global entries, data, dbg
394
395     # we need access to privileged files, exit if we are not running as root
396     if os.getuid() != 0:
397         print >>sys.stderr, "Error: ovs-bugtool must be run as root"
398         return 1
399
400     output_file = None
401     output_type = 'tar.bz2'
402     output_fd = -1
403
404     if argv is None:
405         argv = sys.argv
406
407     try:
408         (options, params) = getopt.gnu_getopt(
409             argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
410                          'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
411                          'debug'])
412     except getopt.GetoptError, opterr:
413         print >>sys.stderr, opterr
414         return 2
415
416     try:
417         load_plugins(True)
418     except:
419         pass
420
421     entries = [e for e in caps.keys() if caps[e][CHECKED]]
422
423     for (k, v) in options:
424         if k == '--capabilities':
425             update_capabilities()
426             print_capabilities()
427             return 0
428
429         if k == '--output':
430             if  v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
431                 output_type = v
432             else:
433                 print >>sys.stderr, "Invalid output format '%s'" % v
434                 return 2
435
436         # "-s" or "--silent" means suppress output (except for the final
437         # output filename at the end)
438         if k in ['-s', '--silent']:
439             SILENT_MODE = True
440
441         if k == '--entries' and v != '':
442             entries = v.split(',')
443
444         # If the user runs the script with "-y" or "--yestoall" we don't ask
445         # all the really annoying questions.
446         if k in ['-y', '--yestoall']:
447             ANSWER_YES_TO_ALL = True
448
449         if k == '--outfd':
450             output_fd = int(v)
451             try:
452                 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
453                 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
454             except:
455                 print >>sys.stderr, "Invalid output file descriptor", output_fd
456                 return 2
457
458         if k == '--outfile':
459             output_file = v
460
461         elif k == '--all':
462             entries = caps.keys()
463         elif k == '--unlimited':
464             unlimited_data = True
465         elif k == '--debug':
466             dbg = True
467             ProcOutput.debug = True
468
469     if len(params) != 1:
470         print >>sys.stderr, "Invalid additional arguments", str(params)
471         return 2
472
473     if output_fd != -1 and output_type != 'tar':
474         print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
475         return 2
476
477     if output_fd != -1 and output_file is not None:
478         print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
479         return 2
480
481     if ANSWER_YES_TO_ALL:
482         output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
483
484     output('''
485 This application will collate dmesg output, details of the
486 hardware configuration of your machine, information about the build of
487 openvswitch that you are using, plus, if you allow it, various logs.
488
489 The collated information will be saved as a .%s for archiving or
490 sending to a Technical Support Representative.
491
492 The logs may contain private information, and if you are at all
493 worried about that, you should exit now, or you should explicitly
494 exclude those logs from the archive.
495
496 ''' % output_type)
497
498     # assemble potential data
499
500     file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
501     cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
502     cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
503
504     tree_output(CAP_COLLECTD_LOGS, COLLECTD_LOGS_DIR)
505     cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
506     file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
507     file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
508     cmd_output(CAP_DISK_INFO, [DF, '-alT'])
509     cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
510     for d in disk_list():
511         cmd_output(CAP_DISK_INFO, [HDPARM, '-I', '/dev/%s' % d])
512     if len(pidof('iscsid')) != 0:
513         cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
514     cmd_output(CAP_DISK_INFO, [VGSCAN])
515     cmd_output(CAP_DISK_INFO, [PVS])
516     cmd_output(CAP_DISK_INFO, [VGS])
517     cmd_output(CAP_DISK_INFO, [LVS])
518     file_output(CAP_DISK_INFO, [LVM_CACHE, LVM_CONFIG])
519     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
520     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
521     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
522     cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
523     func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
524     cmd_output(CAP_DISK_INFO, [LVDISPLAY, '--map'])
525
526     file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
527     cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
528     cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
529     cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
530     file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
531     file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
532     cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
533     # FIXME IDE?
534
535     for d in disk_list():
536         cmd_output(CAP_HDPARM_T, [HDPARM, '-tT', '/dev/%s' % d])
537
538     file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES, 
539                                   PROC_FILESYSTEMS, PROC_CMDLINE])
540     cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
541     cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
542     file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
543     tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
544     func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
545
546     cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
547
548     file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
549     cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
550     func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
551     cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
552     if CAP_MULTIPATH in entries:
553         dump_rdac_groups(CAP_MULTIPATH)
554
555     tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
556     tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
557     file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
558     file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
559     file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
560
561     cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
562     cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
563     cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
564     cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
565     for dir in DHCP_LEASE_DIR:
566         tree_output(CAP_NETWORK_STATUS, dir)
567     cmd_output(CAP_NETWORK_STATUS, [BRCTL, 'show'])
568     cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
569     for p in os.listdir('/sys/class/net/'):
570         try:
571             f = open('/sys/class/net/%s/type' % p, 'r')
572             t = f.readline()
573             f.close()
574             if int(t) == 1:
575                 # ARPHRD_ETHER
576                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
577                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
578                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
579                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
580                 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
581                 cmd_output(CAP_NETWORK_STATUS,
582                            [TC, '-s', '-d', 'class', 'show', 'dev', p])
583         except:
584             pass
585     tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
586     tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
587     cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
588     file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
589     tree_output(CAP_NETWORK_STATUS, OPENVSWITCH_LOG_DIR)
590     if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
591         cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show'])
592         for d in dp_list():
593             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
594             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
595             cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
596         try:
597             vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
598             vspid = int(vspidfile.readline().strip())
599             vspidfile.close()
600             for b in bond_list(vspid):
601                 cmd_output(CAP_NETWORK_STATUS,
602                            [OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
603                            'ovs-appctl-bond-show-%s.out' % b)
604         except e:
605             pass
606
607     tree_output(CAP_PAM, PAM_DIR)
608     file_output(CAP_PAM, [KRB5_CONF])
609
610     cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
611     func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
612
613     file_output(CAP_SYSTEM_LOGS,
614          [ VAR_LOG_DIR + x for x in
615            [ 'crit.log', 'kern.log', 'daemon.log', 'user.log', 'syslog', 
616              'messages', 'secure', 'debug', 'dmesg', 'boot'] +
617            [ f % n for n in range(1, 20) \
618                  for f in ['crit.log.%d', 'crit.log.%d.gz',
619                            'kern.log.%d', 'kern.log.%d.gz',
620                            'daemon.log.%d', 'daemon.log.%d.gz',
621                            'user.log.%d', 'user.log.%d.gz',
622                            'messages.%d', 'messages.%d.gz',
623                            'syslog.%d', 'syslog.%d.gz']]])
624     if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
625         cmd_output(CAP_SYSTEM_LOGS, [DMESG])
626
627     cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
628
629     tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
630     tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
631     tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
632
633     file_output(CAP_YUM, [YUM_LOG])
634     tree_output(CAP_YUM, YUM_REPOS_DIR)
635     cmd_output(CAP_YUM, [RPM, '-qa'])
636
637     try:
638         load_plugins()
639     except:
640         pass
641     
642     # permit the user to filter out data
643     for k in sorted(data.keys()):
644         if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
645             del data[k]
646
647     # collect selected data now
648     output_ts('Running commands to collect data')
649     collect_data()
650
651     subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
652
653     # include inventory
654     data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
655
656     # create archive
657     if output_fd == -1:
658         if output_file is None:
659             dirname = BUG_DIR
660         else:
661             dirname = os.path.dirname(output_file)
662         if dirname and not os.path.exists(dirname):
663             try:
664                 os.makedirs(dirname)
665             except:
666                 pass
667
668     if output_fd == -1:
669         output_ts('Creating output file')
670
671     if output_type.startswith('tar'):
672         make_tar(subdir, output_type, output_fd, output_file)
673     else:
674         make_zip(subdir, output_file)
675
676     clean_tapdisk_logs()
677
678     if dbg:
679         print >>sys.stderr, "Category sizes (max, actual):\n"
680         for c in caps.keys():
681             print >>sys.stderr, "    %s (%d, %d)" % (c, caps[c][MAX_SIZE],
682                                                      cap_sizes[c])
683     return 0
684
685 def find_tapdisk_logs():
686     return glob.glob('/var/log/blktap/*.log*')
687
688 def generate_tapdisk_logs():
689     for pid in pidof('tapdisk'):
690         try:
691             os.kill(pid, SIGUSR1)
692             output_ts("Including logs for tapdisk process %d" % pid)
693         except :
694             pass
695     # give processes a second to write their logs
696     time.sleep(1)
697
698 def clean_tapdisk_logs():
699     for filename in find_tapdisk_logs():
700         try:
701             os.remove(filename)
702         except :
703             pass
704
705 def filter_db_pii(str, state):
706     if 'in_secret_table' not in state:
707         state['in_secret_table'] = False
708
709     if str.startswith('<table ') and 'name="secret"' in str:
710         state['in_secret_table'] = True
711     elif str.startswith('</table>'):
712         state['in_secret_table'] = False
713
714     if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
715         str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
716     return str
717
718 def dump_scsi_hosts(cap):
719     output = ''
720     l = os.listdir('/sys/class/scsi_host')
721     l.sort()
722
723     for h in l:
724         procname = ''
725         try:
726                 f = open('/sys/class/scsi_host/%s/proc_name' % h)
727                 procname = f.readline().strip("\n")
728                 f.close()
729         except:
730                 pass
731         modelname = None
732         try:
733                 f = open('/sys/class/scsi_host/%s/model_name' % h)
734                 modelname = f.readline().strip("\n")
735                 f.close()
736         except:
737                 pass
738
739         output += "%s:\n" %h
740         output += "    %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
741
742     return output
743
744 def module_info(cap):
745     output = StringIO.StringIO()
746     modules = open(PROC_MODULES, 'r')
747     procs = []
748
749     for line in modules:
750         module = line.split()[0]
751         procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
752     modules.close()
753
754     run_procs([procs])
755
756     return output.getvalue()
757
758
759 def multipathd_topology(cap):
760     pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE, 
761                      stdout=PIPE, stderr=dev_null)
762     stdout, stderr = pipe.communicate('show topology')
763
764     return stdout
765
766 def dp_list():
767     output = StringIO.StringIO()
768     procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
769
770     run_procs([procs])
771
772     if not procs[0].timed_out:
773         return output.getvalue().splitlines()
774     return []
775
776 def bond_list(pid):
777     output = StringIO.StringIO()
778     procs = [ProcOutput([OVS_APPCTL, '-t', '/var/run/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
779
780     run_procs([procs])
781
782     if not procs[0].timed_out:
783         bonds = output.getvalue().splitlines()[1:]
784         return [x.split('\t')[1] for x in bonds]
785     return []
786
787 def fd_usage(cap):
788     output = ''
789     fd_dict = {}
790     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
791         try:
792             fh = open('/proc/'+d+'/cmdline')
793             name = fh.readline()
794             num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
795             if num_fds > 0:
796                 if not num_fds in fd_dict:
797                     fd_dict[num_fds] = []
798                 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
799         finally:
800             fh.close()
801     keys = fd_dict.keys()
802     keys.sort(lambda a, b: int(b) - int(a))
803     for k in keys:
804         output += "%s: %s\n" % (k, str(fd_dict[k]))
805     return output
806
807 def dump_rdac_groups(cap):
808     output = StringIO.StringIO()
809     procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
810
811     run_procs([procs])
812
813     if not procs[0].timed_out:
814         proc_line = 0
815         for line in output.getvalue().splitlines():
816             if line.startswith('ID'):
817                 proc_line = 2
818             elif line.startswith('----'):
819                 proc_line -= 1
820             elif proc_line > 0:
821                 group, _ = line.split(None, 1)
822                 cmd_output(cap, [MPPUTIL, '-g', group])
823
824 def load_plugins(just_capabilities = False):
825     def getText(nodelist):
826         rc = ""
827         for node in nodelist:
828             if node.nodeType == node.TEXT_NODE:
829                 rc += node.data
830         return rc.encode()
831
832     def getBoolAttr(el, attr, default = False):
833         ret = default
834         val = el.getAttribute(attr).lower()
835         if val in ['true', 'false', 'yes', 'no']:
836             ret = val in ['true', 'yes']
837         return ret
838         
839     for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
840         if not caps.has_key(dir):
841             if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
842                 continue
843             xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
844             assert xmldoc.documentElement.tagName == "capability"
845
846             pii, min_size, max_size, min_time, max_time, mime = \
847                  PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
848
849             if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
850                 pii = xmldoc.documentElement.getAttribute("pii")
851             if xmldoc.documentElement.getAttribute("min_size") != '':
852                 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
853             if xmldoc.documentElement.getAttribute("max_size") != '':
854                 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
855             if xmldoc.documentElement.getAttribute("min_time") != '':
856                 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
857             if xmldoc.documentElement.getAttribute("max_time") != '':
858                 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
859             if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
860                 mime = xmldoc.documentElement.getAttribute("mime")
861             checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
862             hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
863
864             cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
865
866         if just_capabilities:
867             continue
868                     
869         plugdir = os.path.join(PLUGIN_DIR, dir)
870         for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
871             xmldoc = parse(os.path.join(plugdir, file))
872             assert xmldoc.documentElement.tagName == "collect"
873
874             for el in xmldoc.documentElement.getElementsByTagName("*"):
875                 if el.tagName == "files":
876                     file_output(dir, getText(el.childNodes).split())
877                 elif el.tagName == "directory":
878                     pattern = el.getAttribute("pattern")
879                     if pattern == '': pattern = None
880                     negate = getBoolAttr(el, 'negate')
881                     tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None, negate)
882                 elif el.tagName == "command":
883                     label = el.getAttribute("label")
884                     if label == '': label = None
885                     cmd_output(dir, getText(el.childNodes), label)
886
887 def make_tar(subdir, suffix, output_fd, output_file):
888     global SILENT_MODE, data
889
890     mode = 'w'
891     if suffix == 'tar.bz2':
892         mode = 'w:bz2'
893     elif suffix == 'tar.gz':
894         mode = 'w:gz'
895
896     if output_fd == -1:
897         if output_file is None:
898             filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
899         else:
900             filename = output_file
901         tf = tarfile.open(filename, mode)
902     else:
903         tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
904
905     try:
906         for (k, v) in data.items():
907             try:
908                 tar_filename = os.path.join(subdir, construct_filename(k, v))
909                 ti = tarfile.TarInfo(tar_filename)
910
911                 ti.uname = 'root'
912                 ti.gname = 'root'
913
914                 if v.has_key('output'):
915                     ti.mtime = v['output'].mtime
916                     ti.size = len(v['output'].getvalue())
917                     v['output'].seek(0)
918                     tf.addfile(ti, v['output'])
919                 elif v.has_key('filename'):
920                     s = os.stat(v['filename'])
921                     ti.mtime = s.st_mtime
922                     ti.size = s.st_size
923                     tf.addfile(ti, file(v['filename']))
924             except:
925                 pass
926     finally:
927         tf.close()
928
929     if output_fd == -1:
930         output ('Writing tarball %s successful.' % filename)
931         if SILENT_MODE:
932             print filename
933
934
935 def make_zip(subdir, output_file):
936     global SILENT_MODE, data
937
938     if output_file is None:
939         filename = "%s/%s.zip" % (BUG_DIR, subdir)
940     else:
941         filename = output_file
942     zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
943
944     try:
945         for (k, v) in data.items():
946             try:
947                 dest = os.path.join(subdir, construct_filename(k, v))
948
949                 if v.has_key('output'):
950                     zf.writestr(dest, v['output'].getvalue())
951                 else:
952                     if os.stat(v['filename']).st_size < 50:
953                         compress_type = zipfile.ZIP_STORED
954                     else:
955                         compress_type = zipfile.ZIP_DEFLATED
956                     zf.write(v['filename'], dest, compress_type)
957             except:
958                 pass
959     finally:
960         zf.close()
961     
962     output ('Writing archive %s successful.' % filename)
963     if SILENT_MODE:
964         print filename
965
966
967 def make_inventory(inventory, subdir):
968     document = getDOMImplementation().createDocument(
969         None, INVENTORY_XML_ROOT, None)
970
971     # create summary entry
972     s = document.createElement(INVENTORY_XML_SUMMARY)
973     user = os.getenv('SUDO_USER', os.getenv('USER'))
974     if user:
975         s.setAttribute('user', user)
976     s.setAttribute('date', time.strftime('%c'))
977     s.setAttribute('hostname', platform.node())
978     s.setAttribute('uname', ' '.join(platform.uname()))
979     s.setAttribute('uptime', commands.getoutput(UPTIME))
980     document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
981
982     map(lambda (k, v): inventory_entry(document, subdir, k, v),
983         inventory.items())
984     return document.toprettyxml()
985
986 def inventory_entry(document, subdir, k, v):
987     try:
988         el = document.createElement(INVENTORY_XML_ELEMENT)
989         el.setAttribute('capability', v['cap'])
990         el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
991         el.setAttribute('md5sum', md5sum(v))
992         document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
993     except:
994         pass
995
996
997 def md5sum(d):
998     m = md5.new()
999     if d.has_key('filename'):
1000         f = open(d['filename'])
1001         data = f.read(1024)
1002         while len(data) > 0:
1003             m.update(data)
1004             data = f.read(1024)
1005         f.close()
1006     elif d.has_key('output'):
1007         m.update(d['output'].getvalue())
1008     return m.hexdigest()
1009
1010
1011 def construct_filename(k, v):
1012     if v.has_key('filename'):
1013         if v['filename'][0] == '/':
1014             return v['filename'][1:]
1015         else:
1016             return v['filename']
1017     s = k.replace(' ', '-')
1018     s = s.replace('--', '-')
1019     s = s.replace('/', '%')
1020     if s.find('.') == -1:
1021         s += '.out'
1022
1023     return s
1024
1025 def update_capabilities():
1026     pass
1027
1028 def update_cap_size(cap, size):
1029     update_cap(cap, MIN_SIZE, size)
1030     update_cap(cap, MAX_SIZE, size)
1031     update_cap(cap, CHECKED, size > 0)
1032
1033
1034 def update_cap(cap, k, v):
1035     global caps
1036     l = list(caps[cap])
1037     l[k] = v
1038     caps[cap] = tuple(l)
1039
1040
1041 def size_of_dir(d, pattern = None, negate = False):
1042     if os.path.isdir(d):
1043         return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1044                            pattern, negate)
1045     else:
1046         return 0
1047
1048
1049 def size_of_all(files, pattern = None, negate = False):
1050     return sum([size_of(f, pattern, negate) for f in files])
1051
1052
1053 def matches(f, pattern, negate):
1054     if negate:
1055         return not matches(f, pattern, False)
1056     else:
1057         return pattern is None or pattern.match(f)
1058
1059
1060 def size_of(f, pattern, negate):
1061     if os.path.isfile(f) and matches(f, pattern, negate):
1062         return os.stat(f)[6]
1063     else:
1064         return size_of_dir(f, pattern, negate)
1065
1066
1067 def print_capabilities():
1068     document = getDOMImplementation().createDocument(
1069         "ns", CAP_XML_ROOT, None)
1070     map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1071     print document.toprettyxml()
1072
1073 def capability(document, key):
1074     c = caps[key]
1075     el = document.createElement(CAP_XML_ELEMENT)
1076     el.setAttribute('key', c[KEY])
1077     el.setAttribute('pii', c[PII])
1078     el.setAttribute('min-size', str(c[MIN_SIZE]))
1079     el.setAttribute('max-size', str(c[MAX_SIZE]))
1080     el.setAttribute('min-time', str(c[MIN_TIME]))
1081     el.setAttribute('max-time', str(c[MAX_TIME]))
1082     el.setAttribute('content-type', c[MIME])
1083     el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1084     document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1085
1086
1087 def prettyDict(d):
1088     format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1089     return '\n'.join([format % i for i in d.items()]) + '\n'
1090
1091
1092 def yes(prompt):
1093     yn = raw_input(prompt)
1094
1095     return len(yn) == 0 or yn.lower()[0] == 'y'
1096
1097
1098 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1099
1100 def disk_list():
1101     disks = []
1102     try:
1103         f = open('/proc/partitions')
1104         f.readline()
1105         f.readline()
1106         for line in f.readlines():
1107             (major, minor, blocks, name) = line.split()
1108             if int(major) < 254 and not partition_re.match(name):
1109                 disks.append(name)
1110         f.close()
1111     except:
1112         pass
1113     return disks
1114
1115
1116 class ProcOutput:
1117     debug = False
1118
1119     def __init__(self, command, max_time, inst=None, filter=None):
1120         self.command = command
1121         self.max_time = max_time
1122         self.inst = inst
1123         self.running = False
1124         self.status = None
1125         self.timed_out = False
1126         self.failed = False
1127         self.timeout = int(time.time()) + self.max_time
1128         self.filter = filter
1129         self.filter_state = {}
1130
1131     def __del__(self):
1132         self.terminate()
1133
1134     def cmdAsStr(self):
1135         return isinstance(self.command, list) and ' '.join(self.command) or self.command
1136
1137     def run(self):
1138         self.timed_out = False
1139         try:
1140             if ProcOutput.debug:
1141                 output_ts("Starting '%s'" % self.cmdAsStr())
1142             self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1143             old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1144             fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1145             self.running = True
1146             self.failed = False
1147         except:
1148             output_ts("'%s' failed" % self.cmdAsStr())
1149             self.running = False
1150             self.failed = True
1151
1152     def terminate(self):
1153         if self.running:
1154             try:
1155                 os.kill(self.proc.pid, SIGTERM)
1156             except:
1157                 pass
1158             self.proc = None
1159             self.running = False
1160             self.status = SIGTERM
1161
1162     def read_line(self):
1163         assert self.running
1164         line = self.proc.stdout.readline()
1165         if line == '':
1166             # process exited
1167             self.status = self.proc.wait()
1168             self.proc = None
1169             self.running = False
1170         else:
1171             if self.filter:
1172                 line = self.filter(line, self.filter_state)
1173             if self.inst:
1174                 self.inst.write(line)
1175
1176 def run_procs(procs):
1177     while True:
1178         pipes = []
1179         active_procs = []
1180
1181         for pp in procs:
1182             for p in pp:
1183                 if p.running:
1184                     active_procs.append(p)
1185                     pipes.append(p.proc.stdout)
1186                     break
1187                 elif p.status == None and not p.failed and not p.timed_out:
1188                     p.run()
1189                     if p.running:
1190                         active_procs.append(p)
1191                         pipes.append(p.proc.stdout)
1192                         break
1193
1194         if len(pipes) == 0:
1195             # all finished
1196             break
1197
1198         (i, o, x) = select(pipes, [], [], 1.0)
1199         now = int(time.time())
1200
1201         # handle process output
1202         for p in active_procs:
1203             if p.proc.stdout in i:
1204                 p.read_line()
1205
1206             # handle timeout
1207             if p.running and now > p.timeout:
1208                 output_ts("'%s' timed out" % p.cmdAsStr())
1209                 if p.inst:
1210                     p.inst.write("\n** timeout **\n")
1211                 p.timed_out = True
1212                 p.terminate()
1213
1214
1215 def pidof(name):
1216     pids = []
1217
1218     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1219         try:
1220             if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1221                 pids.append(int(d))
1222         except:
1223             pass
1224
1225     return pids
1226
1227
1228 class StringIOmtime(StringIO.StringIO):
1229     def __init__(self, buf = ''):
1230         StringIO.StringIO.__init__(self, buf)
1231         self.mtime = time.time()
1232
1233     def write(self, s):
1234         StringIO.StringIO.write(self, s)
1235         self.mtime = time.time()
1236
1237
1238 if __name__ == "__main__":
1239     try:
1240         sys.exit(main())
1241     except KeyboardInterrupt:
1242         print "\nInterrupted."
1243         sys.exit(3)