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