import logging import os import re import tarfile from StringIO import StringIO from lxml import etree class CrashReport(object): """This class represents a crash report.""" valid_name = re.compile('^crashreport\.[A-Za-z0-9]{6}\.tar\.gz$') def __init__(self, path): name = os.path.basename(path) self.name = name self.path = path self.confirmation_code = None self.data = CrashData() def has_valid_name(self): """Returns True is the report's name matches the name of a valid crash report. Otherwise it returns implicit False.""" match = re.match(self.__class__.valid_name, self.name) if not match: logging.info('Invalid crash report name: %s' % self.name) return return True def has_valid_type(self): """Returns True if the report's file type matches the file type of a valid crash report. Otherwise it returns implicit False.""" if not tarfile.is_tarfile(self.path): logging.info('The report %s cannot be read from the tarfile module' % self.path) return try: tarfileobj = tarfile.open(self.path, 'r:gz') except tarfile.ReadError: logging.info('The provided mode is not suitable to open for reading' ' the report %s' % self.path) return except tarfile.CompressionError: logging.info('The compression method for the report %s is not ' 'supported' % self.path) return finally: tarfileobj.close() return True def has_valid_contents_number(self): """Returns True is the report contains the same number of files that a valid crash report has. Othewise it returns implicit False.""" try: tarfileobj = tarfile.open(self.path, 'r:gz') except tarfile.ReadError: return except tarfile.CompressionError: return else: contents_list = tarfileobj.getnames() if not len(contents_list) == 1: logging.info('The report %s has invalid number of contents' % self.path) return self.data.name = contents_list[0] finally: tarfileobj.close() return True class CrashData(object): """This class represents the crash data that a crash report contains.""" valid_name = re.compile('^crashreport\.[A-Za-z0-9]{6}\.xml$') def __init__(self): self.name = None self.path = None self.info = {} self.commands = {'crashtype': None, 'crashdate': None, 'hostname': None, 'ostype': None, 'osrelease': None, 'version': None, 'machine': None, 'panic': None, 'backtrace': None, 'ps_axl': None, 'vmstat_s': None, 'vmstat_m': None, 'vmstat_z': None, 'vmstat_i': None, 'pstat_T': None, 'pstat_s': None, 'iostat': None, 'ipcs_a': None, 'ipcs_T': None, 'nfsstat': None, 'netstat_s': None, 'netstat_m': None, 'netstat_id': None, 'netstat_anr': None, 'netstat_anA': None, 'netstat_aL': None, 'fstat': None, 'dmesg': None, 'kernelconfig': None, 'ddbcapturebuffer': None } def has_valid_name(self): """Returns True if the report's crash data name matches the name of a valid crash data. Otherwise it returns implicit False.""" match = re.match(self.__class__.valid_name, self.name) if not match: logging.info('Invalid crash data name: %s' % self.name) return return True def has_valid_crashdata(self): """Returns True if the crash data is a well formed and valid XML file. Otherwise implicit False.""" dtdfile = StringIO("""<!ELEMENT crashreport (header, body)> <!ELEMENT header (email)> <!ELEMENT email (#PCDATA)> <!ELEMENT body (command+)> <!ELEMENT command (name, result)> <!ELEMENT name (#PCDATA)> <!ELEMENT result (#PCDATA)>""") try: elemtree = etree.parse(self.path) except: logging.info('%s is not a well formed crash report data.' % (self.path)) return else: dtd = etree.DTD(dtdfile) if not dtd.validate(elemtree): logging.info('%s is not a valid crash report data.' % (self.path)) return return True