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