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