Bugfixes and improvements to crash handler script

* Fixed a bug on get_results_dir list (wrong indent level of a return
statement)
* Use GDB also to determine information about the core dump
* Keep a copy of the core file on a temp directory for safety
* After all reporting is done, compress core files generated
to save space (they are usually pretty large)

Signed-off-by: Lucas Meneghel Rodrigues <lmr@redhat.com>


git-svn-id: http://test.kernel.org/svn/autotest/trunk@4582 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/client/tools/crash_handler.py b/client/tools/crash_handler.py
index 9a94510..7ee9a78 100755
--- a/client/tools/crash_handler.py
+++ b/client/tools/crash_handler.py
@@ -5,7 +5,7 @@
 @copyright Red Hat Inc 2009
 @author Lucas Meneghel Rodrigues <lmr@redhat.com>
 """
-import sys, os, commands, glob, tempfile, shutil, syslog
+import sys, os, commands, glob, tempfile, shutil, syslog, re, time
 
 
 def get_parent_pid(pid):
@@ -24,17 +24,30 @@
     return ppid
 
 
-def write_to_file(file_path, contents):
+def write_to_file(filename, data, compress=False):
     """
     Write contents to a given file path specified. If not specified, the file
-    will be created.
+    will be created. Optionally, compress the destination file.
 
     @param file_path: Path to a given file.
-    @param contents: File contents.
+    @param data: File contents.
+    @param compress: Whether the file is going to be compressed at the end of
+            the data write process.
     """
-    file_object = open(file_path, 'w')
-    file_object.write(contents)
-    file_object.close()
+    f = open(filename, 'w')
+    try:
+        f.write(data)
+    finally:
+        f.close()
+
+    if compress:
+        s, o = commands.getstatusoutput('bzip2 %s' % filename)
+        if s:
+            syslog.syslog("File %s compression failed: %s" % (filename, o))
+        else:
+            filename += '.bz2'
+
+    return filename
 
 
 def get_results_dir_list(pid, core_dir_basename):
@@ -68,9 +81,9 @@
     else:
         results_dir_list = pid_dir_dict.values()
 
-        return (results_dir_list or
-               pid_dir_dict.values() or
-               [os.path.join("/tmp", core_dir_basename)])
+    return (results_dir_list or
+            pid_dir_dict.values() or
+            [os.path.join("/tmp", core_dir_basename)])
 
 
 def get_info_from_core(path):
@@ -80,17 +93,22 @@
 
     @param path: Path to core file.
     """
-    # Here we are getting the executable full path in a very inelegant way :(
-    # Since the 'right' solution for it is to make a library to get information
-    # from core dump files, properly written, I'll leave this as it is for now.
-    full_exe_path = commands.getoutput('strings %s | grep "_="' %
-                                       path).strip("_=")
-    if full_exe_path.startswith("./"):
-        pwd = commands.getoutput('strings %s | grep "^PWD="' %
-                                 path).strip("PWD=")
-        full_exe_path = os.path.join(pwd, full_exe_path.strip("./"))
+    full_exe_path = None
+    output = commands.getoutput('gdb -c %s batch' % path)
+    path_pattern = re.compile("Core was generated by `([^\0]+)'", re.IGNORECASE)
+    match = re.findall(path_pattern, output)
+    for m in match:
+        # Sometimes the command line args come with the core, so get rid of them
+        m = m.split(" ")[0]
+        if os.path.isfile(m):
+            full_exe_path = m
+            break
 
-    return {'core_file': path, 'full_exe_path': full_exe_path}
+    if full_exe_path is None:
+        syslog.syslog("Could not determine from which application core file %s "
+                      "is from" % path)
+
+    return {'full_exe_path': full_exe_path}
 
 
 if __name__ == "__main__":
@@ -99,7 +117,7 @@
         try:
             full_functionality = False
             try:
-                (crashed_pid, time, uid, signal, hostname, exe) = sys.argv[1:]
+                (crashed_pid, crash_time, uid, signal, hostname, exe) = sys.argv[1:]
                 full_functionality = True
             except ValueError, e:
                 # Probably due a kernel bug, we can't exactly map the parameters
@@ -128,74 +146,75 @@
             # Write the core file to the appropriate directory
             # (we are piping it to this script)
             core_file = sys.stdin.read()
-            # Write the core file to its temporary location
-            write_to_file(core_tmp_path, core_file)
+            # Write the core file to its temporary location, let's keep it
+            # there in case something goes wrong
+            core_tmp_path = write_to_file(core_tmp_path, core_file)
+            processing_succeed = False
 
             if not full_functionality:
-                syslog.syslog(syslog.LOG_INFO, "Writing core files to %s" %
+                syslog.syslog("Writing core files to %s" %
                               current_results_dir_list)
                 for result_dir in current_results_dir_list:
                     if not os.path.isdir(result_dir):
                         os.makedirs(result_dir)
                     core_path = os.path.join(result_dir, 'core')
-                    write_to_file(core_path, core_file)
+                    core_path = write_to_file(core_path, core_file,
+                                              compress=True)
+                    processing_succeed = True
                 raise ValueError("Incorrect params passed to handler "
                                  "script: %s." % sys.argv[1:])
 
-            # Write a command file for GDB
-            gdb_command = 'bt full\n'
-            write_to_file(gdb_command_path, gdb_command)
-
             # Get full command path
             exe_path = get_info_from_core(core_tmp_path)['full_exe_path']
 
-            # Take a backtrace from the running program
-            gdb_cmd = ('gdb -e %s -c %s -x %s -n -batch -quiet' %
-                       (exe_path, core_tmp_path, gdb_command_path))
-            backtrace = commands.getoutput(gdb_cmd)
-            # Sanitize output before passing it to the report
-            backtrace = backtrace.decode('utf-8', 'ignore')
+            if exe_path is not None:
+                # Write a command file for GDB
+                gdb_command = 'bt full\n'
+                write_to_file(gdb_command_path, gdb_command)
+
+                # Take a backtrace from the running program
+                gdb_cmd = ('gdb -e %s -c %s -x %s -n -batch -quiet' %
+                           (exe_path, core_tmp_path, gdb_command_path))
+                backtrace = commands.getoutput(gdb_cmd)
+                # Sanitize output before passing it to the report
+                backtrace = backtrace.decode('utf-8', 'ignore')
+            else:
+                exe_path = "Unknown"
+                backtrace = ("Could not determine backtrace for core file %s" %
+                             core_tmp_path)
 
             # Composing the format_dict
-            format_dict = {}
-            format_dict['program'] = exe_path
-            format_dict['pid'] = crashed_pid
-            format_dict['signal'] = signal
-            format_dict['hostname'] = hostname
-            format_dict['time'] = time
-            format_dict['backtrace'] = backtrace
+            report = "Program: %s\n" % exe_path
+            report += "PID: %s\n" % crashed_pid
+            report += "Signal: %s\n" % signal
+            report += "Hostname: %s\n" % hostname
+            report += "Time of the crash: %s\n" % time.ctime(float(crash_time))
+            report += "Program backtrace:\n%s\n" % backtrace
 
-            report = """Autotest crash report
-
-Program: %(program)s
-PID: %(pid)s
-Signal: %(signal)s
-Hostname: %(hostname)s
-Time of the crash: %(time)s
-Program backtrace:
-%(backtrace)s
-""" % format_dict
-
-            syslog.syslog(syslog.LOG_INFO,
-                          "Application %s, PID %s crashed" %
+            syslog.syslog("Application %s, PID %s crashed" %
                           (exe_path, crashed_pid))
 
             # Now, for all results dir, let's create the directory if it doesn't
             # exist, and write the core file and the report to it.
-            syslog.syslog(syslog.LOG_INFO,
-                          "Writing core files and reports to %s" %
+            syslog.syslog("Writing core files and reports to %s" %
                           current_results_dir_list)
             for result_dir in current_results_dir_list:
                 if not os.path.isdir(result_dir):
                     os.makedirs(result_dir)
                 core_path = os.path.join(result_dir, 'core')
-                write_to_file(core_path, core_file)
+                core_path = write_to_file(core_path, core_file, compress=True)
                 report_path = os.path.join(result_dir, 'report')
                 write_to_file(report_path, report)
+            processing_succeed = True
 
         except Exception, e:
             syslog.syslog("Crash handler had a problem: %s" % e)
 
     finally:
-        if os.path.isdir(core_tmp_dir):
-            shutil.rmtree(core_tmp_dir)
+        if processing_succeed:
+            if os.path.isdir(core_tmp_dir):
+                shutil.rmtree(core_tmp_dir)
+        else:
+            syslog.syslog("Crash handler failed to process the core file. "
+                          "A copy of the file was kept at %s" %
+                          core_tmp_path)