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