Back to index

apport  2.4
Public Member Functions | Public Attributes | Private Member Functions
problem_report.ProblemReport Class Reference
Inheritance diagram for problem_report.ProblemReport:
Inheritance graph
[legend]
Collaboration diagram for problem_report.ProblemReport:
Collaboration graph
[legend]

List of all members.

Public Member Functions

def __init__
def load
def has_removed_fields
def write
def add_to_existing
def write_mime
def __setitem__
def new_keys

Public Attributes

 data
 old_keys

Private Member Functions

def _is_binary
def _is_binary_py2
def _try_unicode
def _strip_gzip_header
def _strip_gzip_header_py2
def _assert_bin_mode

Detailed Description

Definition at line 94 of file problem_report.py.


Constructor & Destructor Documentation

def problem_report.ProblemReport.__init__ (   self,
  type = 'Crash',
  date = None 
)
Initialize a fresh problem report.

type can be 'Crash', 'Packaging', 'KernelCrash' or 'KernelOops'.
date is the desired date/time string; if None (default), the
current local time is used.

Reimplemented in apport.report.Report.

Definition at line 95 of file problem_report.py.

00095 
00096     def __init__(self, type='Crash', date=None):
00097         '''Initialize a fresh problem report.
00098 
00099         type can be 'Crash', 'Packaging', 'KernelCrash' or 'KernelOops'.
00100         date is the desired date/time string; if None (default), the
00101         current local time is used.
00102         '''
00103         if date is None:
00104             date = time.asctime()
00105         self.data = {'ProblemType': type, 'Date': date}
00106 
00107         # keeps track of keys which were added since the last ctor or load()
00108         self.old_keys = set()

Here is the caller graph for this function:


Member Function Documentation

def problem_report.ProblemReport.__setitem__ (   self,
  k,
  v 
)

Definition at line 559 of file problem_report.py.

00559 
00560     def __setitem__(self, k, v):
00561         assert hasattr(k, 'isalnum')
00562         assert k.replace('.', '').replace('-', '').replace('_', '').isalnum()
00563         # value must be a string or a CompressedValue or a file reference
00564         # (tuple (string|file [, bool]))
00565         assert (isinstance(v, CompressedValue) or hasattr(v, 'isalnum') or
00566                 (hasattr(v, '__getitem__') and (
00567                     len(v) == 1 or (len(v) >= 2 and v[1] in (True, False)))
00568                     and (hasattr(v[0], 'isalnum') or hasattr(v[0], 'read'))))
00569 
00570         return self.data.__setitem__(k, v)

def problem_report.ProblemReport._assert_bin_mode (   klass,
  file 
) [private]
Assert that given file object is in binary mode

Definition at line 625 of file problem_report.py.

00625 
00626     def _assert_bin_mode(klass, file):
00627         '''Assert that given file object is in binary mode'''
00628 
00629         if _python2:
00630             assert (type(file) == BytesIO or 'b' in file.mode), 'file stream must be in binary mode'
00631         else:
00632             assert not hasattr(file, 'encoding'), 'file stream must be in binary mode'

Here is the caller graph for this function:

def problem_report.ProblemReport._is_binary (   klass,
  string 
) [private]
Check if the given strings contains binary data.

Definition at line 199 of file problem_report.py.

00199 
00200     def _is_binary(klass, string):
00201         '''Check if the given strings contains binary data.'''
00202 
00203         if _python2:
00204             return klass._is_binary_py2(string)
00205 
00206         if type(string) == bytes:
00207             for c in string:
00208                 if c < 32 and not chr(c).isspace():
00209                     return True
00210         return False

Here is the caller graph for this function:

def problem_report.ProblemReport._is_binary_py2 (   klass,
  string 
) [private]
Check if the given strings contains binary data. (Python 2)

Definition at line 212 of file problem_report.py.

00212 
00213     def _is_binary_py2(klass, string):
00214         '''Check if the given strings contains binary data. (Python 2)'''
00215 
00216         for c in string:
00217             if c < ' ' and not c.isspace():
00218                 return True
00219         return False

def problem_report.ProblemReport._strip_gzip_header (   klass,
  line 
) [private]
Strip gzip header from line and return the rest.

Definition at line 580 of file problem_report.py.

00580 
00581     def _strip_gzip_header(klass, line):
00582         '''Strip gzip header from line and return the rest.'''
00583 
00584         if _python2:
00585             return klass._strip_gzip_header_py2(line)
00586 
00587         flags = line[3]
00588         offset = 10
00589         if flags & 4:  # FLG.FEXTRA
00590             offset += line[offset] + 1
00591         if flags & 8:  # FLG.FNAME
00592             while line[offset] != 0:
00593                 offset += 1
00594             offset += 1
00595         if flags & 16:  # FLG.FCOMMENT
00596             while line[offset] != 0:
00597                 offset += 1
00598             offset += 1
00599         if flags & 2:  # FLG.FHCRC
00600             offset += 2
00601 
00602         return line[offset:]

Here is the caller graph for this function:

def problem_report.ProblemReport._strip_gzip_header_py2 (   klass,
  line 
) [private]
Strip gzip header from line and return the rest. (Python 2)

Definition at line 604 of file problem_report.py.

00604 
00605     def _strip_gzip_header_py2(klass, line):
00606         '''Strip gzip header from line and return the rest. (Python 2)'''
00607 
00608         flags = ord(line[3])
00609         offset = 10
00610         if flags & 4:  # FLG.FEXTRA
00611             offset += line[offset] + 1
00612         if flags & 8:  # FLG.FNAME
00613             while ord(line[offset]) != 0:
00614                 offset += 1
00615             offset += 1
00616         if flags & 16:  # FLG.FCOMMENT
00617             while ord(line[offset]) != 0:
00618                 offset += 1
00619             offset += 1
00620         if flags & 2:  # FLG.FHCRC
00621             offset += 2
00622 
00623         return line[offset:]

def problem_report.ProblemReport._try_unicode (   klass,
  value 
) [private]
Try to convert bytearray value to unicode

Definition at line 221 of file problem_report.py.

00221 
00222     def _try_unicode(klass, value):
00223         '''Try to convert bytearray value to unicode'''
00224 
00225         if type(value) == bytes and not klass._is_binary(value):
00226             try:
00227                 return value.decode('UTF-8')
00228             except UnicodeDecodeError:
00229                 return value
00230         return value

Here is the caller graph for this function:

def problem_report.ProblemReport.add_to_existing (   self,
  reportfile,
  keep_times = False 
)
Add this report's data to an already existing report file.

The file will be temporarily chmod'ed to 000 to prevent frontends
from picking up a hal-updated report file. If keep_times
is True, then the file's atime and mtime restored after updating.

Definition at line 402 of file problem_report.py.

00402 
00403     def add_to_existing(self, reportfile, keep_times=False):
00404         '''Add this report's data to an already existing report file.
00405 
00406         The file will be temporarily chmod'ed to 000 to prevent frontends
00407         from picking up a hal-updated report file. If keep_times
00408         is True, then the file's atime and mtime restored after updating.
00409         '''
00410         st = os.stat(reportfile)
00411         try:
00412             f = open(reportfile, 'ab')
00413             os.chmod(reportfile, 0)
00414             self.write(f)
00415             f.close()
00416         finally:
00417             if keep_times:
00418                 os.utime(reportfile, (st.st_atime, st.st_mtime))
00419             os.chmod(reportfile, st.st_mode)

Here is the call graph for this function:

Check if the report has any keys which were not loaded.

This could happen when using binary=False in load().

Definition at line 191 of file problem_report.py.

00191 
00192     def has_removed_fields(self):
00193         '''Check if the report has any keys which were not loaded.
00194 
00195         This could happen when using binary=False in load().
00196         '''
00197         return ('' in self.values())

def problem_report.ProblemReport.load (   self,
  file,
  binary = True 
)
Initialize problem report from a file-like object.

If binary is False, binary data is not loaded; the dictionary key is
created, but its value will be an empty string. If it is True, it is
transparently uncompressed and available as dictionary byte array values.
If binary is 'compressed', the compressed value is retained, and the
dictionary value will be a CompressedValue object. This is useful if
the compressed value is still useful (to avoid recompression if the
file needs to be written back).

file needs to be opened in binary mode.

Files are in RFC822 format.

Definition at line 109 of file problem_report.py.

00109 
00110     def load(self, file, binary=True):
00111         '''Initialize problem report from a file-like object.
00112 
00113         If binary is False, binary data is not loaded; the dictionary key is
00114         created, but its value will be an empty string. If it is True, it is
00115         transparently uncompressed and available as dictionary byte array values.
00116         If binary is 'compressed', the compressed value is retained, and the
00117         dictionary value will be a CompressedValue object. This is useful if
00118         the compressed value is still useful (to avoid recompression if the
00119         file needs to be written back).
00120 
00121         file needs to be opened in binary mode.
00122 
00123         Files are in RFC822 format.
00124         '''
00125         self._assert_bin_mode(file)
00126         self.data.clear()
00127         key = None
00128         value = None
00129         b64_block = False
00130         bd = None
00131         for line in file:
00132             # continuation line
00133             if line.startswith(b' '):
00134                 if b64_block and not binary:
00135                     continue
00136                 assert (key is not None and value is not None)
00137                 if b64_block:
00138                     l = base64.b64decode(line)
00139                     if bd:
00140                         value += bd.decompress(l)
00141                     else:
00142                         if binary == 'compressed':
00143                             # check gzip header; if absent, we have legacy zlib
00144                             # data
00145                             if value.gzipvalue == b'' and not l.startswith(b'\037\213\010'):
00146                                 value.legacy_zlib = True
00147                             value.gzipvalue += l
00148                         else:
00149                             # lazy initialization of bd
00150                             # skip gzip header, if present
00151                             if l.startswith(b'\037\213\010'):
00152                                 bd = zlib.decompressobj(-zlib.MAX_WBITS)
00153                                 value = bd.decompress(self._strip_gzip_header(l))
00154                             else:
00155                                 # legacy zlib-only format used default block
00156                                 # size
00157                                 bd = zlib.decompressobj()
00158                                 value += bd.decompress(l)
00159                 else:
00160                     if len(value) > 0:
00161                         value += b'\n'
00162                     if line.endswith(b'\n'):
00163                         value += line[1:-1]
00164                     else:
00165                         value += line[1:]
00166             else:
00167                 if b64_block:
00168                     if bd:
00169                         value += bd.flush()
00170                     b64_block = False
00171                     bd = None
00172                 if key:
00173                     assert value is not None
00174                     self.data[key] = self._try_unicode(value)
00175                 (key, value) = line.split(b':', 1)
00176                 if not _python2:
00177                     key = key.decode('ASCII')
00178                 value = value.strip()
00179                 if value == b'base64':
00180                     if binary == 'compressed':
00181                         value = CompressedValue(key.encode())
00182                         value.gzipvalue = b''
00183                     else:
00184                         value = b''
00185                     b64_block = True
00186 
00187         if key is not None:
00188             self.data[key] = self._try_unicode(value)
00189 
00190         self.old_keys = set(self.data.keys())

Here is the call graph for this function:

Return newly added keys.

Return the set of keys which have been added to the report since it
was constructed or loaded.

Definition at line 571 of file problem_report.py.

00571 
00572     def new_keys(self):
00573         '''Return newly added keys.
00574 
00575         Return the set of keys which have been added to the report since it
00576         was constructed or loaded.
00577         '''
00578         return set(self.data.keys()) - self.old_keys

def problem_report.ProblemReport.write (   self,
  file,
  only_new = False 
)
Write information into the given file-like object.

If only_new is True, only keys which have been added since the last
load() are written (i. e. those returned by new_keys()).

If a value is a string, it is written directly. Otherwise it must be a
tuple of the form (file, encode=True, limit=None, fail_on_empty=False).
The first argument can be a file name or a file-like object,
which will be read and its content will become the value of this key.
'encode' specifies whether the contents will be
gzip compressed and base64-encoded (this defaults to True). If limit is
set to a positive integer, the file is not attached if it's larger
than the given limit, and the entire key will be removed. If
fail_on_empty is True, reading zero bytes will cause an IOError.

file needs to be opened in binary mode.

Files are written in RFC822 format.

Definition at line 231 of file problem_report.py.

00231 
00232     def write(self, file, only_new=False):
00233         '''Write information into the given file-like object.
00234 
00235         If only_new is True, only keys which have been added since the last
00236         load() are written (i. e. those returned by new_keys()).
00237 
00238         If a value is a string, it is written directly. Otherwise it must be a
00239         tuple of the form (file, encode=True, limit=None, fail_on_empty=False).
00240         The first argument can be a file name or a file-like object,
00241         which will be read and its content will become the value of this key.
00242         'encode' specifies whether the contents will be
00243         gzip compressed and base64-encoded (this defaults to True). If limit is
00244         set to a positive integer, the file is not attached if it's larger
00245         than the given limit, and the entire key will be removed. If
00246         fail_on_empty is True, reading zero bytes will cause an IOError.
00247 
00248         file needs to be opened in binary mode.
00249 
00250         Files are written in RFC822 format.
00251         '''
00252         self._assert_bin_mode(file)
00253 
00254         # sort keys into ASCII non-ASCII/binary attachment ones, so that
00255         # the base64 ones appear last in the report
00256         asckeys = []
00257         binkeys = []
00258         for k in self.data.keys():
00259             if only_new and k in self.old_keys:
00260                 continue
00261             v = self.data[k]
00262             if hasattr(v, 'find'):
00263                 if self._is_binary(v):
00264                     binkeys.append(k)
00265                 else:
00266                     asckeys.append(k)
00267             else:
00268                 if not isinstance(v, CompressedValue) and len(v) >= 2 and not v[1]:
00269                     # force uncompressed
00270                     asckeys.append(k)
00271                 else:
00272                     binkeys.append(k)
00273 
00274         asckeys.sort()
00275         if 'ProblemType' in asckeys:
00276             asckeys.remove('ProblemType')
00277             asckeys.insert(0, 'ProblemType')
00278         binkeys.sort()
00279 
00280         # write the ASCII keys first
00281         for k in asckeys:
00282             v = self.data[k]
00283 
00284             # if it's a tuple, we have a file reference; read the contents
00285             if not hasattr(v, 'find'):
00286                 if len(v) >= 3 and v[2] is not None:
00287                     limit = v[2]
00288                 else:
00289                     limit = None
00290 
00291                 fail_on_empty = len(v) >= 4 and v[3]
00292 
00293                 if hasattr(v[0], 'read'):
00294                     v = v[0].read()  # file-like object
00295                 else:
00296                     with open(v[0], 'rb') as f:  # file name
00297                         v = f.read()
00298 
00299                 if fail_on_empty and len(v) == 0:
00300                     raise IOError('did not get any data for field ' + k)
00301 
00302                 if limit is not None and len(v) > limit:
00303                     del self.data[k]
00304                     continue
00305 
00306             if _python2:
00307                 if isinstance(v, unicode):
00308                     # unicode → str
00309                     v = v.encode('UTF-8')
00310             else:
00311                 if isinstance(v, str):
00312                     # unicode → str
00313                     v = v.encode('UTF-8')
00314 
00315             file.write(k.encode('ASCII'))
00316             if b'\n' in v:
00317                 # multiline value
00318                 file.write(b':\n ')
00319                 file.write(v.replace(b'\n', b'\n '))
00320             else:
00321                 file.write(b': ')
00322                 file.write(v)
00323             file.write(b'\n')
00324 
00325         # now write the binary keys with gzip compression and base64 encoding
00326         for k in binkeys:
00327             v = self.data[k]
00328             limit = None
00329             size = 0
00330 
00331             curr_pos = file.tell()
00332             file.write(k.encode('ASCII'))
00333             file.write(b': base64\n ')
00334 
00335             # CompressedValue
00336             if isinstance(v, CompressedValue):
00337                 file.write(base64.b64encode(v.gzipvalue))
00338                 file.write(b'\n')
00339                 continue
00340 
00341             # write gzip header
00342             gzip_header = b'\037\213\010\010\000\000\000\000\002\377' + k.encode('UTF-8') + b'\000'
00343             file.write(base64.b64encode(gzip_header))
00344             file.write(b'\n ')
00345             crc = zlib.crc32(b'')
00346 
00347             bc = zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS,
00348                                   zlib.DEF_MEM_LEVEL, 0)
00349             # direct value
00350             if hasattr(v, 'find'):
00351                 size += len(v)
00352                 crc = zlib.crc32(v, crc)
00353                 outblock = bc.compress(v)
00354                 if outblock:
00355                     file.write(base64.b64encode(outblock))
00356                     file.write(b'\n ')
00357             # file reference
00358             else:
00359                 if len(v) >= 3 and v[2] is not None:
00360                     limit = v[2]
00361 
00362                 if hasattr(v[0], 'read'):
00363                     f = v[0]  # file-like object
00364                 else:
00365                     f = open(v[0], 'rb')  # file name
00366                 while True:
00367                     block = f.read(1048576)
00368                     size += len(block)
00369                     crc = zlib.crc32(block, crc)
00370                     if limit is not None:
00371                         if size > limit:
00372                             # roll back
00373                             file.seek(curr_pos)
00374                             file.truncate(curr_pos)
00375                             del self.data[k]
00376                             crc = None
00377                             break
00378                     if block:
00379                         outblock = bc.compress(block)
00380                         if outblock:
00381                             file.write(base64.b64encode(outblock))
00382                             file.write(b'\n ')
00383                     else:
00384                         break
00385                 if not hasattr(v[0], 'read'):
00386                     f.close()
00387 
00388                 if len(v) >= 4 and v[3]:
00389                     if size == 0:
00390                         raise IOError('did not get any data for field %s from %s' % (k, str(v[0])))
00391 
00392             # flush compressor and write the rest
00393             if not limit or size <= limit:
00394                 block = bc.flush()
00395                 # append gzip trailer: crc (32 bit) and size (32 bit)
00396                 if crc:
00397                     block += struct.pack('<L', crc & 0xFFFFFFFF)
00398                     block += struct.pack('<L', size & 0xFFFFFFFF)
00399 
00400                 file.write(base64.b64encode(block))
00401                 file.write(b'\n')

Here is the call graph for this function:

Here is the caller graph for this function:

def problem_report.ProblemReport.write_mime (   self,
  file,
  attach_treshold = 5,
  extra_headers = {},
  skip_keys = None,
  priority_fields = None 
)
Write MIME/Multipart RFC 2822 formatted data into file.

file must be a file-like object, not a path.  It needs to be opened in
binary mode.

If a value is a string or a CompressedValue, it is written directly.
Otherwise it must be a tuple containing the source file and an optional
boolean value (in that order); the first argument can be a file name or
a file-like object, which will be read and its content will become the
value of this key.  The file will be gzip compressed, unless the key
already ends in .gz.

attach_treshold specifies the maximum number of lines for a value to be
included into the first inline text part. All bigger values (as well as
all non-ASCII ones) will become an attachment, as well as text
values bigger than 1 kB.

Extra MIME preamble headers can be specified, too, as a dictionary.

skip_keys is a set/list specifying keys which are filtered out and not
written to the destination file.

priority_fields is a set/list specifying the order in which keys should
appear in the destination file.

Definition at line 421 of file problem_report.py.

00421 
00422                    skip_keys=None, priority_fields=None):
00423         '''Write MIME/Multipart RFC 2822 formatted data into file.
00424 
00425         file must be a file-like object, not a path.  It needs to be opened in
00426         binary mode.
00427 
00428         If a value is a string or a CompressedValue, it is written directly.
00429         Otherwise it must be a tuple containing the source file and an optional
00430         boolean value (in that order); the first argument can be a file name or
00431         a file-like object, which will be read and its content will become the
00432         value of this key.  The file will be gzip compressed, unless the key
00433         already ends in .gz.
00434 
00435         attach_treshold specifies the maximum number of lines for a value to be
00436         included into the first inline text part. All bigger values (as well as
00437         all non-ASCII ones) will become an attachment, as well as text
00438         values bigger than 1 kB.
00439 
00440         Extra MIME preamble headers can be specified, too, as a dictionary.
00441 
00442         skip_keys is a set/list specifying keys which are filtered out and not
00443         written to the destination file.
00444 
00445         priority_fields is a set/list specifying the order in which keys should
00446         appear in the destination file.
00447         '''
00448         self._assert_bin_mode(file)
00449 
00450         keys = sorted(self.data.keys())
00451 
00452         text = b''
00453         attachments = []
00454 
00455         if 'ProblemType' in keys:
00456             keys.remove('ProblemType')
00457             keys.insert(0, 'ProblemType')
00458 
00459         if priority_fields:
00460             counter = 0
00461             for priority_field in priority_fields:
00462                 if priority_field in keys:
00463                     keys.remove(priority_field)
00464                     keys.insert(counter, priority_field)
00465                     counter += 1
00466 
00467         for k in keys:
00468             if skip_keys and k in skip_keys:
00469                 continue
00470             v = self.data[k]
00471             attach_value = None
00472 
00473             # compressed values are ready for attaching in gzip form
00474             if isinstance(v, CompressedValue):
00475                 attach_value = v.gzipvalue
00476 
00477             # if it's a tuple, we have a file reference; read the contents
00478             # and gzip it
00479             elif not hasattr(v, 'find'):
00480                 attach_value = ''
00481                 if hasattr(v[0], 'read'):
00482                     f = v[0]  # file-like object
00483                 else:
00484                     f = open(v[0], 'rb')  # file name
00485                 if k.endswith('.gz'):
00486                     attach_value = f.read()
00487                 else:
00488                     io = BytesIO()
00489                     gf = gzip.GzipFile(k, mode='wb', fileobj=io)
00490                     while True:
00491                         block = f.read(1048576)
00492                         if block:
00493                             gf.write(block)
00494                         else:
00495                             gf.close()
00496                             break
00497                     attach_value = io.getvalue()
00498                 f.close()
00499 
00500             # binary value
00501             elif self._is_binary(v):
00502                 if k.endswith('.gz'):
00503                     attach_value = v
00504                 else:
00505                     attach_value = CompressedValue(v, k).gzipvalue
00506 
00507             # if we have an attachment value, create an attachment
00508             if attach_value:
00509                 att = MIMEBase('application', 'x-gzip')
00510                 if k.endswith('.gz'):
00511                     att.add_header('Content-Disposition', 'attachment', filename=k)
00512                 else:
00513                     att.add_header('Content-Disposition', 'attachment', filename=k + '.gz')
00514                 att.set_payload(attach_value)
00515                 encode_base64(att)
00516                 attachments.append(att)
00517             else:
00518                 # plain text value
00519                 size = len(v)
00520 
00521                 # ensure that byte arrays are valid UTF-8
00522                 if type(v) == bytes:
00523                     v = v.decode('UTF-8', 'replace')
00524                 # convert unicode to UTF-8 str
00525                 if _python2:
00526                     assert isinstance(v, unicode)
00527                 else:
00528                     assert isinstance(v, str)
00529                 v = v.encode('UTF-8')
00530 
00531                 lines = len(v.splitlines())
00532                 if size <= 1000 and lines == 1:
00533                     v = v.rstrip()
00534                     text += k.encode() + b': ' + v + b'\n'
00535                 elif size <= 1000 and lines <= attach_treshold:
00536                     text += k.encode() + b':\n '
00537                     if not v.endswith(b'\n'):
00538                         v += b'\n'
00539                     text += v.strip().replace(b'\n', b'\n ') + b'\n'
00540                 else:
00541                     # too large, separate attachment
00542                     att = MIMEText(v, _charset='UTF-8')
00543                     att.add_header('Content-Disposition', 'attachment', filename=k + '.txt')
00544                     attachments.append(att)
00545 
00546         # create initial text attachment
00547         att = MIMEText(text, _charset='UTF-8')
00548         att.add_header('Content-Disposition', 'inline')
00549         attachments.insert(0, att)
00550 
00551         msg = MIMEMultipart()
00552         for k, v in extra_headers.items():
00553             msg.add_header(k, v)
00554         for a in attachments:
00555             msg.attach(a)
00556 
00557         file.write(msg.as_string().encode('UTF-8'))
00558         file.write(b'\n')

Here is the call graph for this function:

Here is the caller graph for this function:


Member Data Documentation

Definition at line 104 of file problem_report.py.

Definition at line 107 of file problem_report.py.


The documentation for this class was generated from the following file: