3bafa134a969d130965f9c8446f5bd01db4a83b0
[openvswitch] / 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 os.path.islink('/sys/class/net/%s/device' % p) and 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             if int(t) == 1:
594                 cmd_output(CAP_NETWORK_STATUS,
595                            [TC, '-s', '-d', 'class', 'show', 'dev', p])
596         except:
597             pass
598     tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
599     tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
600     cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
601     file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
602     if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
603         cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
604         for d in dp_list():
605             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
606             cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
607             cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
608         try:
609             vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
610             vspid = int(vspidfile.readline().strip())
611             vspidfile.close()
612             for b in bond_list(vspid):
613                 cmd_output(CAP_NETWORK_STATUS,
614                            [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
615                            'ovs-appctl-bond-show-%s.out' % b)
616         except e:
617             pass
618
619     tree_output(CAP_PAM, PAM_DIR)
620     file_output(CAP_PAM, [KRB5_CONF])
621
622     cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
623     func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
624
625     logs = ([ VAR_LOG_DIR + x for x in
626              [ 'crit.log', 'kern.log', 'daemon.log', 'user.log',
627              'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot' ]]
628             + [ OPENVSWITCH_LOG_DIR + x for x in
629                 [ 'ovs-vswitchd.log', 'ovs-brcompatd.log', 'ovsdb-server.log',
630                   'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log' ]])
631     file_output(CAP_SYSTEM_LOGS, logs)
632     file_output(CAP_SYSTEM_LOGS,
633                 [ '%s.%d' % (f, n) for n in range(20) for f in logs ])
634     file_output(CAP_SYSTEM_LOGS,
635                 [ '%s.%d.gz' % (f, n) for n in range(20) for f in logs ])
636
637     if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
638         cmd_output(CAP_SYSTEM_LOGS, [DMESG])
639
640     cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
641
642     tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE)
643     tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE)
644     tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
645
646     file_output(CAP_YUM, [YUM_LOG])
647     tree_output(CAP_YUM, YUM_REPOS_DIR)
648     cmd_output(CAP_YUM, [RPM, '-qa'])
649     file_output(CAP_YUM, [APT_SOURCES_LIST])
650     tree_output(CAP_YUM, APT_SOURCES_LIST_D)
651     cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
652
653     try:
654         load_plugins()
655     except:
656         pass
657     
658     # permit the user to filter out data
659     for k in sorted(data.keys()):
660         if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k):
661             del data[k]
662
663     # collect selected data now
664     output_ts('Running commands to collect data')
665     collect_data()
666
667     subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
668
669     # include inventory
670     data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
671
672     # create archive
673     if output_fd == -1:
674         if output_file is None:
675             dirname = BUG_DIR
676         else:
677             dirname = os.path.dirname(output_file)
678         if dirname and not os.path.exists(dirname):
679             try:
680                 os.makedirs(dirname)
681             except:
682                 pass
683
684     if output_fd == -1:
685         output_ts('Creating output file')
686
687     if output_type.startswith('tar'):
688         make_tar(subdir, output_type, output_fd, output_file)
689     else:
690         make_zip(subdir, output_file)
691
692     clean_tapdisk_logs()
693
694     if dbg:
695         print >>sys.stderr, "Category sizes (max, actual):\n"
696         for c in caps.keys():
697             print >>sys.stderr, "    %s (%d, %d)" % (c, caps[c][MAX_SIZE],
698                                                      cap_sizes[c])
699     return 0
700
701 def find_tapdisk_logs():
702     return glob.glob('/var/log/blktap/*.log*')
703
704 def generate_tapdisk_logs():
705     for pid in pidof('tapdisk'):
706         try:
707             os.kill(pid, SIGUSR1)
708             output_ts("Including logs for tapdisk process %d" % pid)
709         except :
710             pass
711     # give processes a second to write their logs
712     time.sleep(1)
713
714 def clean_tapdisk_logs():
715     for filename in find_tapdisk_logs():
716         try:
717             os.remove(filename)
718         except :
719             pass
720
721 def filter_db_pii(str, state):
722     if 'in_secret_table' not in state:
723         state['in_secret_table'] = False
724
725     if str.startswith('<table ') and 'name="secret"' in str:
726         state['in_secret_table'] = True
727     elif str.startswith('</table>'):
728         state['in_secret_table'] = False
729
730     if state['in_secret_table'] and str.startswith("<row"): # match only on DB rows
731         str = re.sub(r'(value=")[^"]+(")', r'\1REMOVED\2', str)
732     return str
733
734 def dump_scsi_hosts(cap):
735     output = ''
736     l = os.listdir('/sys/class/scsi_host')
737     l.sort()
738
739     for h in l:
740         procname = ''
741         try:
742                 f = open('/sys/class/scsi_host/%s/proc_name' % h)
743                 procname = f.readline().strip("\n")
744                 f.close()
745         except:
746                 pass
747         modelname = None
748         try:
749                 f = open('/sys/class/scsi_host/%s/model_name' % h)
750                 modelname = f.readline().strip("\n")
751                 f.close()
752         except:
753                 pass
754
755         output += "%s:\n" %h
756         output += "    %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
757
758     return output
759
760 def module_info(cap):
761     output = StringIO.StringIO()
762     modules = open(PROC_MODULES, 'r')
763     procs = []
764
765     for line in modules:
766         module = line.split()[0]
767         procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
768     modules.close()
769
770     run_procs([procs])
771
772     return output.getvalue()
773
774
775 def multipathd_topology(cap):
776     pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE, 
777                      stdout=PIPE, stderr=dev_null)
778     stdout, stderr = pipe.communicate('show topology')
779
780     return stdout
781
782 def dp_list():
783     output = StringIO.StringIO()
784     procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
785
786     run_procs([procs])
787
788     if not procs[0].timed_out:
789         return output.getvalue().splitlines()
790     return []
791
792 def bond_list(pid):
793     output = StringIO.StringIO()
794     procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
795
796     run_procs([procs])
797
798     if not procs[0].timed_out:
799         bonds = output.getvalue().splitlines()[1:]
800         return [x.split('\t')[1] for x in bonds]
801     return []
802
803 def fd_usage(cap):
804     output = ''
805     fd_dict = {}
806     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
807         try:
808             fh = open('/proc/'+d+'/cmdline')
809             name = fh.readline()
810             num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
811             if num_fds > 0:
812                 if not num_fds in fd_dict:
813                     fd_dict[num_fds] = []
814                 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
815         finally:
816             fh.close()
817     keys = fd_dict.keys()
818     keys.sort(lambda a, b: int(b) - int(a))
819     for k in keys:
820         output += "%s: %s\n" % (k, str(fd_dict[k]))
821     return output
822
823 def dump_rdac_groups(cap):
824     output = StringIO.StringIO()
825     procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
826
827     run_procs([procs])
828
829     if not procs[0].timed_out:
830         proc_line = 0
831         for line in output.getvalue().splitlines():
832             if line.startswith('ID'):
833                 proc_line = 2
834             elif line.startswith('----'):
835                 proc_line -= 1
836             elif proc_line > 0:
837                 group, _ = line.split(None, 1)
838                 cmd_output(cap, [MPPUTIL, '-g', group])
839
840 def load_plugins(just_capabilities = False):
841     def getText(nodelist):
842         rc = ""
843         for node in nodelist:
844             if node.nodeType == node.TEXT_NODE:
845                 rc += node.data
846         return rc.encode()
847
848     def getBoolAttr(el, attr, default = False):
849         ret = default
850         val = el.getAttribute(attr).lower()
851         if val in ['true', 'false', 'yes', 'no']:
852             ret = val in ['true', 'yes']
853         return ret
854         
855     for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
856         if not caps.has_key(dir):
857             if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
858                 continue
859             xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
860             assert xmldoc.documentElement.tagName == "capability"
861
862             pii, min_size, max_size, min_time, max_time, mime = \
863                  PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
864
865             if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
866                 pii = xmldoc.documentElement.getAttribute("pii")
867             if xmldoc.documentElement.getAttribute("min_size") != '':
868                 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
869             if xmldoc.documentElement.getAttribute("max_size") != '':
870                 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
871             if xmldoc.documentElement.getAttribute("min_time") != '':
872                 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
873             if xmldoc.documentElement.getAttribute("max_time") != '':
874                 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
875             if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
876                 mime = xmldoc.documentElement.getAttribute("mime")
877             checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
878             hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
879
880             cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
881
882         if just_capabilities:
883             continue
884                     
885         plugdir = os.path.join(PLUGIN_DIR, dir)
886         for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
887             xmldoc = parse(os.path.join(plugdir, file))
888             assert xmldoc.documentElement.tagName == "collect"
889
890             for el in xmldoc.documentElement.getElementsByTagName("*"):
891                 if el.tagName == "files":
892                     newest_first = getBoolAttr(el, 'newest_first')
893                     file_output(dir, getText(el.childNodes).split(),
894                                 newest_first=newest_first)
895                 elif el.tagName == "directory":
896                     pattern = el.getAttribute("pattern")
897                     if pattern == '': pattern = None
898                     negate = getBoolAttr(el, 'negate')
899                     newest_first = getBoolAttr(el, 'newest_first')
900                     tree_output(dir, getText(el.childNodes), pattern and re.compile(pattern) or None,
901                                 negate=negate, newest_first=newest_first)
902                 elif el.tagName == "command":
903                     label = el.getAttribute("label")
904                     if label == '': label = None
905                     cmd_output(dir, getText(el.childNodes), label)
906
907 def make_tar(subdir, suffix, output_fd, output_file):
908     global SILENT_MODE, data
909
910     mode = 'w'
911     if suffix == 'tar.bz2':
912         mode = 'w:bz2'
913     elif suffix == 'tar.gz':
914         mode = 'w:gz'
915
916     if output_fd == -1:
917         if output_file is None:
918             filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
919         else:
920             filename = output_file
921         old_umask = os.umask(0077)
922         tf = tarfile.open(filename, mode)
923         os.umask(old_umask)
924     else:
925         tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
926
927     try:
928         for (k, v) in data.items():
929             try:
930                 tar_filename = os.path.join(subdir, construct_filename(k, v))
931                 ti = tarfile.TarInfo(tar_filename)
932
933                 ti.uname = 'root'
934                 ti.gname = 'root'
935
936                 if v.has_key('output'):
937                     ti.mtime = v['output'].mtime
938                     ti.size = len(v['output'].getvalue())
939                     v['output'].seek(0)
940                     tf.addfile(ti, v['output'])
941                 elif v.has_key('filename'):
942                     s = os.stat(v['filename'])
943                     ti.mtime = s.st_mtime
944                     ti.size = s.st_size
945                     tf.addfile(ti, file(v['filename']))
946             except:
947                 pass
948     finally:
949         tf.close()
950
951     if output_fd == -1:
952         output ('Writing tarball %s successful.' % filename)
953         if SILENT_MODE:
954             print filename
955
956
957 def make_zip(subdir, output_file):
958     global SILENT_MODE, data
959
960     if output_file is None:
961         filename = "%s/%s.zip" % (BUG_DIR, subdir)
962     else:
963         filename = output_file
964     old_umask = os.umask(0077)
965     zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
966     os.umask(old_umask)
967
968     try:
969         for (k, v) in data.items():
970             try:
971                 dest = os.path.join(subdir, construct_filename(k, v))
972
973                 if v.has_key('output'):
974                     zf.writestr(dest, v['output'].getvalue())
975                 else:
976                     if os.stat(v['filename']).st_size < 50:
977                         compress_type = zipfile.ZIP_STORED
978                     else:
979                         compress_type = zipfile.ZIP_DEFLATED
980                     zf.write(v['filename'], dest, compress_type)
981             except:
982                 pass
983     finally:
984         zf.close()
985     
986     output ('Writing archive %s successful.' % filename)
987     if SILENT_MODE:
988         print filename
989
990
991 def make_inventory(inventory, subdir):
992     document = getDOMImplementation().createDocument(
993         None, INVENTORY_XML_ROOT, None)
994
995     # create summary entry
996     s = document.createElement(INVENTORY_XML_SUMMARY)
997     user = os.getenv('SUDO_USER', os.getenv('USER'))
998     if user:
999         s.setAttribute('user', user)
1000     s.setAttribute('date', time.strftime('%c'))
1001     s.setAttribute('hostname', platform.node())
1002     s.setAttribute('uname', ' '.join(platform.uname()))
1003     s.setAttribute('uptime', commands.getoutput(UPTIME))
1004     document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
1005
1006     map(lambda (k, v): inventory_entry(document, subdir, k, v),
1007         inventory.items())
1008     return document.toprettyxml()
1009
1010 def inventory_entry(document, subdir, k, v):
1011     try:
1012         el = document.createElement(INVENTORY_XML_ELEMENT)
1013         el.setAttribute('capability', v['cap'])
1014         el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
1015         el.setAttribute('md5sum', md5sum(v))
1016         document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
1017     except:
1018         pass
1019
1020
1021 def md5sum(d):
1022     m = md5.new()
1023     if d.has_key('filename'):
1024         f = open(d['filename'])
1025         data = f.read(1024)
1026         while len(data) > 0:
1027             m.update(data)
1028             data = f.read(1024)
1029         f.close()
1030     elif d.has_key('output'):
1031         m.update(d['output'].getvalue())
1032     return m.hexdigest()
1033
1034
1035 def construct_filename(k, v):
1036     if v.has_key('filename'):
1037         if v['filename'][0] == '/':
1038             return v['filename'][1:]
1039         else:
1040             return v['filename']
1041     s = k.replace(' ', '-')
1042     s = s.replace('--', '-')
1043     s = s.replace('/', '%')
1044     if s.find('.') == -1:
1045         s += '.out'
1046
1047     return s
1048
1049 def update_capabilities():
1050     pass
1051
1052 def update_cap_size(cap, size):
1053     update_cap(cap, MIN_SIZE, size)
1054     update_cap(cap, MAX_SIZE, size)
1055     update_cap(cap, CHECKED, size > 0)
1056
1057
1058 def update_cap(cap, k, v):
1059     global caps
1060     l = list(caps[cap])
1061     l[k] = v
1062     caps[cap] = tuple(l)
1063
1064
1065 def size_of_dir(d, pattern = None, negate = False):
1066     if os.path.isdir(d):
1067         return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1068                            pattern, negate)
1069     else:
1070         return 0
1071
1072
1073 def size_of_all(files, pattern = None, negate = False):
1074     return sum([size_of(f, pattern, negate) for f in files])
1075
1076
1077 def matches(f, pattern, negate):
1078     if negate:
1079         return not matches(f, pattern, False)
1080     else:
1081         return pattern is None or pattern.match(f)
1082
1083
1084 def size_of(f, pattern, negate):
1085     if os.path.isfile(f) and matches(f, pattern, negate):
1086         return os.stat(f)[6]
1087     else:
1088         return size_of_dir(f, pattern, negate)
1089
1090
1091 def print_capabilities():
1092     document = getDOMImplementation().createDocument(
1093         "ns", CAP_XML_ROOT, None)
1094     map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1095     print document.toprettyxml()
1096
1097 def capability(document, key):
1098     c = caps[key]
1099     el = document.createElement(CAP_XML_ELEMENT)
1100     el.setAttribute('key', c[KEY])
1101     el.setAttribute('pii', c[PII])
1102     el.setAttribute('min-size', str(c[MIN_SIZE]))
1103     el.setAttribute('max-size', str(c[MAX_SIZE]))
1104     el.setAttribute('min-time', str(c[MIN_TIME]))
1105     el.setAttribute('max-time', str(c[MAX_TIME]))
1106     el.setAttribute('content-type', c[MIME])
1107     el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1108     document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1109
1110
1111 def prettyDict(d):
1112     format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1113     return '\n'.join([format % i for i in d.items()]) + '\n'
1114
1115
1116 def yes(prompt):
1117     yn = raw_input(prompt)
1118
1119     return len(yn) == 0 or yn.lower()[0] == 'y'
1120
1121
1122 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1123
1124 def disk_list():
1125     disks = []
1126     try:
1127         f = open('/proc/partitions')
1128         f.readline()
1129         f.readline()
1130         for line in f.readlines():
1131             (major, minor, blocks, name) = line.split()
1132             if int(major) < 254 and not partition_re.match(name):
1133                 disks.append(name)
1134         f.close()
1135     except:
1136         pass
1137     return disks
1138
1139
1140 class ProcOutput:
1141     debug = False
1142
1143     def __init__(self, command, max_time, inst=None, filter=None):
1144         self.command = command
1145         self.max_time = max_time
1146         self.inst = inst
1147         self.running = False
1148         self.status = None
1149         self.timed_out = False
1150         self.failed = False
1151         self.timeout = int(time.time()) + self.max_time
1152         self.filter = filter
1153         self.filter_state = {}
1154
1155     def __del__(self):
1156         self.terminate()
1157
1158     def cmdAsStr(self):
1159         return isinstance(self.command, list) and ' '.join(self.command) or self.command
1160
1161     def run(self):
1162         self.timed_out = False
1163         try:
1164             if ProcOutput.debug:
1165                 output_ts("Starting '%s'" % self.cmdAsStr())
1166             self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1167             old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1168             fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1169             self.running = True
1170             self.failed = False
1171         except:
1172             output_ts("'%s' failed" % self.cmdAsStr())
1173             self.running = False
1174             self.failed = True
1175
1176     def terminate(self):
1177         if self.running:
1178             try:
1179                 self.proc.stdout.close()
1180                 os.kill(self.proc.pid, SIGTERM)
1181             except:
1182                 pass
1183             self.proc = None
1184             self.running = False
1185             self.status = SIGTERM
1186
1187     def read_line(self):
1188         assert self.running
1189         line = self.proc.stdout.readline()
1190         if line == '':
1191             # process exited
1192             self.proc.stdout.close()
1193             self.status = self.proc.wait()
1194             self.proc = None
1195             self.running = False
1196         else:
1197             if self.filter:
1198                 line = self.filter(line, self.filter_state)
1199             if self.inst:
1200                 self.inst.write(line)
1201
1202 def run_procs(procs):
1203     while True:
1204         pipes = []
1205         active_procs = []
1206
1207         for pp in procs:
1208             for p in pp:
1209                 if p.running:
1210                     active_procs.append(p)
1211                     pipes.append(p.proc.stdout)
1212                     break
1213                 elif p.status == None and not p.failed and not p.timed_out:
1214                     p.run()
1215                     if p.running:
1216                         active_procs.append(p)
1217                         pipes.append(p.proc.stdout)
1218                         break
1219
1220         if len(pipes) == 0:
1221             # all finished
1222             break
1223
1224         (i, o, x) = select(pipes, [], [], 1.0)
1225         now = int(time.time())
1226
1227         # handle process output
1228         for p in active_procs:
1229             if p.proc.stdout in i:
1230                 p.read_line()
1231
1232             # handle timeout
1233             if p.running and now > p.timeout:
1234                 output_ts("'%s' timed out" % p.cmdAsStr())
1235                 if p.inst:
1236                     p.inst.write("\n** timeout **\n")
1237                 p.timed_out = True
1238                 p.terminate()
1239
1240
1241 def pidof(name):
1242     pids = []
1243
1244     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1245         try:
1246             if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1247                 pids.append(int(d))
1248         except:
1249             pass
1250
1251     return pids
1252
1253
1254 class StringIOmtime(StringIO.StringIO):
1255     def __init__(self, buf = ''):
1256         StringIO.StringIO.__init__(self, buf)
1257         self.mtime = time.time()
1258
1259     def write(self, s):
1260         StringIO.StringIO.write(self, s)
1261         self.mtime = time.time()
1262
1263
1264 if __name__ == "__main__":
1265     try:
1266         sys.exit(main())
1267     except KeyboardInterrupt:
1268         print "\nInterrupted."
1269         sys.exit(3)