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