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