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