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