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