From 7b1d5c1702e8eb465201eae5707be449d77e7c99 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Jun 2026 16:08:46 +0000 Subject: [PATCH 1/5] refactor(retracer): extract build_report from callback --- src/retracer.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/retracer.py b/src/retracer.py index cf26f952..e5f02dfb 100755 --- a/src/retracer.py +++ b/src/retracer.py @@ -364,6 +364,20 @@ def save_crash(self, report, oops_id, core_file): with open(failed_crash, "wb") as fp: report.write(fp) + def build_report(self, col): + report = Report() + + for k in col: + try: + report[k] = col[k] + except (AssertionError, ValueError): + # apport raises an ValueError if a key is invalid + # e.g. /usr/bin/media-hub-server became a key somehow, + # and this doesn't need to be part of the report used + # for retracing + continue + return report + @prefix_log_with_amqp_message def callback(self, msg): self._processing_callback = True @@ -455,17 +469,7 @@ def callback(self, msg): rm_eff(work_path) return - report = Report() - - for k in col: - try: - report[k] = col[k] - except (AssertionError, ValueError): - # apport raises an ValueError if a key is invalid - # e.g. /usr/bin/media-hub-server became a key somehow, - # and this doesn't need to be part of the report used - # for retracing - continue + report = self.build_report(col) # these will not change after retracing architecture = report.get("Architecture", "") From 05f3545741b7c183ba3268d4bc010b0d74b35677 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Jun 2026 16:09:28 +0000 Subject: [PATCH 2/5] refactor(retracer): extract decompress_core from callback --- src/retracer.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/retracer.py b/src/retracer.py index e5f02dfb..39ef769d 100755 --- a/src/retracer.py +++ b/src/retracer.py @@ -378,6 +378,20 @@ def build_report(self, col): continue return report + def decompress_core(self, report_path, core_file): + try: + with open(core_file, "wb") as fp: + log("Decompressing to %s" % core_file) + with open(report_path) as path_fp: + for block in CompressedValue.decode_compressed_stream( + _base64_decoder(path_fp) + ): + fp.write(block) + except Exception as e: + log("Failed to decompress core: %s" % str(e)) + return False + return True + @prefix_log_with_amqp_message def callback(self, msg): self._processing_callback = True @@ -430,16 +444,7 @@ def callback(self, msg): core_file = work_path / "core" report_path = work_path / "crash" - try: - with open(core_file, "wb") as fp: - log("Decompressing to %s" % core_file) - with open(report_path) as path_fp: - for block in CompressedValue.decode_compressed_stream( - _base64_decoder(path_fp) - ): - fp.write(block) - except Exception as e: - log("Failed to decompress core: %s" % str(e)) + if not self.decompress_core(report_path, core_file): # We couldn't decompress this, so there's no value in trying again. self.remove(oops_id) self.update_time_to_retrace(msg) From 00f675b6f48e0586f60774d744943726b0074ec8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Jun 2026 16:09:58 +0000 Subject: [PATCH 3/5] refactor(retracer): extract gdb_core_check from callback --- src/retracer.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/retracer.py b/src/retracer.py index 39ef769d..b307e937 100755 --- a/src/retracer.py +++ b/src/retracer.py @@ -392,6 +392,15 @@ def decompress_core(self, report_path, core_file): return False return True + def gdb_core_check(self, core_file): + # confirm that gdb thinks the core file is good + gdb_cmd = [self.gdb_path, "--batch", "--ex", "target core %s" % core_file] + proc = Popen(gdb_cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True, errors="ignore") + (out, err) = proc.communicate() + if "is truncated: expected core file size" in err or "not a core dump" in err: + return False + return True + @prefix_log_with_amqp_message def callback(self, msg): self._processing_callback = True @@ -457,10 +466,7 @@ def callback(self, msg): return # confirm that gdb thinks the core file is good - gdb_cmd = [self.gdb_path, "--batch", "--ex", "target core %s" % core_file] - proc = Popen(gdb_cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True, errors="ignore") - (out, err) = proc.communicate() - if "is truncated: expected core file size" in err or "not a core dump" in err: + if not self.gdb_core_check(core_file): # Not a core file, there's no value in trying again. self.remove(oops_id) self.update_time_to_retrace(msg) From a686bff5e6032f9c86c42f7c16962f6e49a64213 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Jun 2026 16:10:45 +0000 Subject: [PATCH 4/5] refactor(retracer): extract check_retraceable from callback --- src/retracer.py | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/retracer.py b/src/retracer.py index b307e937..ccfb3319 100755 --- a/src/retracer.py +++ b/src/retracer.py @@ -401,6 +401,31 @@ def gdb_core_check(self, core_file): return False return True + def check_retraceable(self, report, unreportable_reason): + release = report.get("DistroRelease", "") + bad = "[^-a-zA-Z0-9_.() ]+" + retraceable = utils.retraceable_release(release) + if not retraceable: + metrics.meter("retrace.failed.notretraceable") + if release in utils.EOL_RELEASES: + metrics.meter("retrace.failed.eolrelease") + log("Not retraced due to EoL release: %s" % release) + package = report.get("Package", "") + # there will not be a debug symbol version of the package + if not utils.retraceable_package(package): + log("Not retraced due to foreign origin.") + if unreportable_reason: + log("UnreportableReason is: %s" % unreportable_reason) + metrics.meter("retrace.failed.foreign") + retraceable = False + + invalid = re.search(bad, release) or len(release) > 1024 + if invalid: + metrics.meter("retrace.failed.invalid") + if not release or invalid or not retraceable: + return False + return True + @prefix_log_with_amqp_message def callback(self, msg): self._processing_callback = True @@ -485,21 +510,6 @@ def callback(self, msg): # these will not change after retracing architecture = report.get("Architecture", "") release = report.get("DistroRelease", "") - bad = "[^-a-zA-Z0-9_.() ]+" - retraceable = utils.retraceable_release(release) - if not retraceable: - metrics.meter("retrace.failed.notretraceable") - if release in utils.EOL_RELEASES: - metrics.meter("retrace.failed.eolrelease") - log("Not retraced due to EoL release: %s" % release) - package = report.get("Package", "") - # there will not be a debug symbol version of the package - if not utils.retraceable_package(package): - log("Not retraced due to foreign origin.") - if unreportable_reason: - log("UnreportableReason is: %s" % unreportable_reason) - metrics.meter("retrace.failed.foreign") - retraceable = False # srcpackage = report.get('SourcePackage', '') # if srcpackage in ['kodi', 'mysql-workbench'] and release == 'Ubuntu 18.04': # 2018-06-13 gdb is hanging trying to retrace these so put them at @@ -509,10 +519,7 @@ def callback(self, msg): # rm_eff(path) # return - invalid = re.search(bad, release) or len(release) > 1024 - if invalid: - metrics.meter("retrace.failed.invalid") - if not release or invalid or not retraceable: + if not self.check_retraceable(report, unreportable_reason): self.remove(oops_id) self.update_time_to_retrace(msg) rm_eff(work_path) From ca543c87911a8a19e75a5005117b2e016ac3ccf0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Jun 2026 16:12:23 +0000 Subject: [PATCH 5/5] refactor(retracer): extract run_apport_retrace from callback --- src/retracer.py | 150 +++++++++++++++++++++++++----------------------- 1 file changed, 78 insertions(+), 72 deletions(-) diff --git a/src/retracer.py b/src/retracer.py index ccfb3319..a5434e30 100755 --- a/src/retracer.py +++ b/src/retracer.py @@ -426,6 +426,81 @@ def check_retraceable(self, report, unreportable_reason): return False return True + def run_apport_retrace(self, report_path, core_file, release, architecture): + try: + retrace_msg = "Retracing {}".format(self.msg_body) + sandbox, cache = self.setup_cache(self.sandbox_dir, release) + day_key = time.strftime("%Y%m%d", time.gmtime()) + + retracing_start_time = time.time() + # the easiest way to test not using a sandbox is to make it another + # command line option like don't use sandbox even though we will + # provide it on the cli + cmd = [ + "timeout", + "45m", + "python3", + self.apport_retrace_path, + report_path, + "--core-file", + core_file, + "--remove-core", + "--sandbox", + self.config_dir, + "--gdb-sandbox", + "--output", + "%s.new" % report_path, + ] + if sandbox: + retrace_msg += " with sandbox-dir %s" % sandbox + cmd.extend(["--sandbox-dir", sandbox]) + if cache: + retrace_msg += " with cache %s" % cache + cmd.extend(["-C", cache]) + if not self.stacktrace_source: + cmd.extend(["--no-stacktrace-source"]) + if self.verbose: + cmd.append("-v") + log(retrace_msg) + # use our own crashdb config with all supported architectures + env = os.environ.copy() + env["APPORT_CRASHDB_CONF"] = os.path.join(self.config_dir, "crashdb.conf") + http_proxy = env.get("retracer_http_proxy") + if http_proxy: + env.update({"http_proxy": http_proxy}) + proc = Popen( + cmd, + env=env, + stdout=PIPE, + stderr=PIPE, + universal_newlines=True, + preexec_fn=os.setpgrp, + ) + out, err = proc.communicate() + except: + rm_eff("%s.new" % report_path) + log("Failure in retrace set up for {}".format(self.msg_body)) + log(traceback.format_exc()) + metrics.meter("retrace.failed") + metrics.meter("retrace.failed.%s" % release) + metrics.meter("retrace.failed.%s" % architecture) + metrics.meter("retrace.failed.%s.%s" % (release, architecture)) + metrics.meter("retrace.failed.to_setup") + metrics.meter("retrace.failed.to_setup.%s" % release) + metrics.meter("retrace.failed.to_setup.%s" % architecture) + metrics.meter("retrace.failed.to_setup.%s.%s" % (release, architecture)) + raise + finally: + if sandbox and self.cleanup_sandbox: + log("Removing %s" % sandbox) + shutil.rmtree(sandbox) + os.mkdir(sandbox) + if cache and self.cleanup_debs: + log("Removing %s" % cache) + shutil.rmtree(cache) + os.mkdir(cache) + return proc, out, err, day_key, retracing_start_time + @prefix_log_with_amqp_message def callback(self, msg): self._processing_callback = True @@ -528,78 +603,9 @@ def callback(self, msg): with open(report_path, "wb") as fp: report.write(fp) - try: - retrace_msg = "Retracing {}".format(self.msg_body) - sandbox, cache = self.setup_cache(self.sandbox_dir, release) - day_key = time.strftime("%Y%m%d", time.gmtime()) - - retracing_start_time = time.time() - # the easiest way to test not using a sandbox is to make it another - # command line option like don't use sandbox even though we will - # provide it on the cli - cmd = [ - "timeout", - "45m", - "python3", - self.apport_retrace_path, - report_path, - "--core-file", - core_file, - "--remove-core", - "--sandbox", - self.config_dir, - "--gdb-sandbox", - "--output", - "%s.new" % report_path, - ] - if sandbox: - retrace_msg += " with sandbox-dir %s" % sandbox - cmd.extend(["--sandbox-dir", sandbox]) - if cache: - retrace_msg += " with cache %s" % cache - cmd.extend(["-C", cache]) - if not self.stacktrace_source: - cmd.extend(["--no-stacktrace-source"]) - if self.verbose: - cmd.append("-v") - log(retrace_msg) - # use our own crashdb config with all supported architectures - env = os.environ.copy() - env["APPORT_CRASHDB_CONF"] = os.path.join(self.config_dir, "crashdb.conf") - http_proxy = env.get("retracer_http_proxy") - if http_proxy: - env.update({"http_proxy": http_proxy}) - proc = Popen( - cmd, - env=env, - stdout=PIPE, - stderr=PIPE, - universal_newlines=True, - preexec_fn=os.setpgrp, - ) - out, err = proc.communicate() - except: - rm_eff("%s.new" % report_path) - log("Failure in retrace set up for {}".format(self.msg_body)) - log(traceback.format_exc()) - metrics.meter("retrace.failed") - metrics.meter("retrace.failed.%s" % release) - metrics.meter("retrace.failed.%s" % architecture) - metrics.meter("retrace.failed.%s.%s" % (release, architecture)) - metrics.meter("retrace.failed.to_setup") - metrics.meter("retrace.failed.to_setup.%s" % release) - metrics.meter("retrace.failed.to_setup.%s" % architecture) - metrics.meter("retrace.failed.to_setup.%s.%s" % (release, architecture)) - raise - finally: - if sandbox and self.cleanup_sandbox: - log("Removing %s" % sandbox) - shutil.rmtree(sandbox) - os.mkdir(sandbox) - if cache and self.cleanup_debs: - log("Removing %s" % cache) - shutil.rmtree(cache) - os.mkdir(cache) + proc, out, err, day_key, retracing_start_time = self.run_apport_retrace( + report_path, core_file, release, architecture + ) try: if proc.returncode != 0: