Merge ~ubuntu-core-dev/ubuntu/+source/apport:ubuntu/devel-core-dump-handler into ~ubuntu-core-dev/ubuntu/+source/apport:ubuntu/devel

Proposed by Benjamin Drung
Status: Merged
Merged at revision: daeea20711e0bbd2f4341b3be5e002609f780766
Proposed branch: ~ubuntu-core-dev/ubuntu/+source/apport:ubuntu/devel-core-dump-handler
Merge into: ~ubuntu-core-dev/ubuntu/+source/apport:ubuntu/devel
Diff against target: 449 lines (+318/-22)
10 files modified
debian/apport-core-dump-handler.install (+2/-0)
debian/apport.install (+4/-2)
debian/changelog (+15/-0)
debian/control (+18/-3)
debian/patches/apport-retrace-split-out-report-loading-into-its-own-func.patch (+132/-0)
debian/patches/fix-use-context-manager-when-manipulating-GzipFiles-LP-20.patch (+109/-0)
debian/patches/retrace-directly-test-crashid-relevance.patch (+34/-0)
debian/patches/series (+3/-0)
debian/rules (+1/-16)
dev/null (+0/-1)
Reviewer Review Type Date Requested Status
Simon Chopin Needs Fixing
git-ubuntu import Pending
Review via email: mp+458513@code.launchpad.net

This proposal supersedes a proposal from 2024-01-12.

To post a comment you must log in.
Revision history for this message
Benjamin Drung (bdrung) : Posted in a previous version of this proposal
Revision history for this message
Simon Chopin (schopin) wrote :

LGTM except for the circular deps thing, which should really not be happening.

review: Needs Fixing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/debian/apport-core-dump-handler.install b/debian/apport-core-dump-handler.install
2new file mode 100644
3index 0000000..124c9fd
4--- /dev/null
5+++ b/debian/apport-core-dump-handler.install
6@@ -0,0 +1,2 @@
7+debian/tmp/etc/init.d/apport
8+usr/lib/systemd/system/apport.service
9diff --git a/debian/apport.init b/debian/apport.init
10deleted file mode 120000
11index 4c697d9..0000000
12--- a/debian/apport.init
13+++ /dev/null
14@@ -1 +0,0 @@
15-../etc/init.d/apport
16\ No newline at end of file
17diff --git a/debian/apport.install b/debian/apport.install
18index da993f8..00e96b0 100644
19--- a/debian/apport.install
20+++ b/debian/apport.install
21@@ -4,8 +4,9 @@ debian/apport-autoreport.path /lib/systemd/system
22 debian/apport-autoreport.service /lib/systemd/system
23 debian/apport-autoreport.timer /lib/systemd/system
24 debian/package-hooks usr/share/apport
25-debian/tmp/etc
26-lib/systemd/
27+debian/tmp/etc/apport
28+debian/tmp/etc/cron.daily
29+debian/tmp/etc/default
30 lib/udev/
31 usr/bin/apport-bug
32 usr/bin/apport-cli
33@@ -13,6 +14,7 @@ usr/bin/apport-collect
34 usr/bin/apport-unpack
35 usr/bin/oem-getlogs
36 usr/lib/pm-utils
37+usr/lib/systemd/system/apport-forward*
38 usr/lib/tmpfiles.d/
39 usr/share/apport/apport
40 usr/share/apport/apport-checkreports
41diff --git a/debian/changelog b/debian/changelog
42index 8c290b6..e4e5854 100644
43--- a/debian/changelog
44+++ b/debian/changelog
45@@ -1,3 +1,18 @@
46+apport (2.27.0-0ubuntu7) noble; urgency=medium
47+
48+ [ Benjamin Drung ]
49+ * Rely on pybuild in dh_auto_* targets
50+ * Introduce the separate apport-core-dump-handler package that registers as
51+ kernel crash dump handler. This is needed for the upcoming systemd-coredump
52+ support.
53+ * Move systemd units from /lib to /usr/lib
54+
55+ [ Simon Chopin ]
56+ * Rework apport-retrace to handle unbound crashid (LP: #2051512)
57+ * fix: use context manager when manipulating GzipFiles (LP: #2051512)
58+
59+ -- Benjamin Drung <bdrung@ubuntu.com> Wed, 14 Feb 2024 16:51:44 +0100
60+
61 apport (2.27.0-0ubuntu6) noble; urgency=medium
62
63 * Move additional package hooks to debian/package-hooks
64diff --git a/debian/control b/debian/control
65index 630358f..d4c43fc 100644
66--- a/debian/control
67+++ b/debian/control
68@@ -39,6 +39,7 @@ Homepage: https://wiki.ubuntu.com/Apport
69 Package: apport
70 Architecture: all
71 Depends:
72+ apport-core-dump-handler,
73 gir1.2-glib-2.0 (>= 1.29.17),
74 python3,
75 python3-apport (>= ${source:Version}),
76@@ -47,10 +48,8 @@ Depends:
77 ${misc:Depends},
78 Recommends: apport-symptoms, python3-systemd
79 Suggests: apport-gtk | apport-kde, pkexec, polkitd
80-Replaces: core-dump-handler, python-apport (<< 2.2-0ubuntu1)
81+Replaces: python-apport (<< 2.2-0ubuntu1)
82 Breaks: python-apport (<< 2.2-0ubuntu1)
83-Conflicts: core-dump-handler
84-Provides: core-dump-handler
85 Pre-Depends: ${misc:Pre-Depends}
86 Description: automatically generate crash reports for debugging
87 apport automatically collects data from crashed processes and
88@@ -96,6 +95,22 @@ Description: Python 3 library for Apport crash report handling
89 * Various frontend utility functions.
90 * Python hook to generate crash reports when Python scripts fail.
91
92+Package: apport-core-dump-handler
93+Architecture: all
94+Depends: apport (>= 2.27.0-0ubuntu7~), ${misc:Depends}
95+Breaks: apport (<< 2.27.0-0ubuntu7~)
96+Replaces: apport (<< 2.27.0-0ubuntu7~), core-dump-handler
97+Conflicts: core-dump-handler
98+Provides: core-dump-handler
99+Pre-Depends: ${misc:Pre-Depends}
100+Description: Kernel core dump handler for Apport
101+ apport automatically collects data from crashed processes and
102+ compiles a problem report in /var/crash/. This utilizes the crashdump
103+ helper hook provided by the Ubuntu kernel.
104+ .
105+ This package provides the service that configures the kernel (via
106+ /proc/sys/kernel/core_pattern) to forward the crash dumps to apport.
107+
108 Package: apport-retrace
109 Section: devel
110 Architecture: all
111diff --git a/debian/patches/apport-retrace-split-out-report-loading-into-its-own-func.patch b/debian/patches/apport-retrace-split-out-report-loading-into-its-own-func.patch
112new file mode 100644
113index 0000000..c9e8529
114--- /dev/null
115+++ b/debian/patches/apport-retrace-split-out-report-loading-into-its-own-func.patch
116@@ -0,0 +1,132 @@
117+From: Simon Chopin <simon.chopin@canonical.com>
118+Date: Tue, 13 Feb 2024 19:23:14 +0100
119+Subject: apport-retrace: split out report loading into its own function
120+
121+This reduces complexity in main() while also addressing the unbound crashid
122+issue that showed up in Noble, albeit only partially: now it's not
123+unbound, but it can be None, which is another can of worms.
124+
125+Baby steps!
126+
127+Origin: upstream, https://github.com/canonical/apport/pull/295
128+---
129+ bin/apport-retrace | 55 ++++++++++++++++++++++++++++++++----------------------
130+ 1 file changed, 33 insertions(+), 22 deletions(-)
131+
132+diff --git a/bin/apport-retrace b/bin/apport-retrace
133+index f8082c6..246a065 100755
134+--- a/bin/apport-retrace
135++++ b/bin/apport-retrace
136+@@ -26,12 +26,14 @@ import tempfile
137+ import termios
138+ import tty
139+ import zlib
140++from argparse import Namespace
141+ from gettext import gettext as _
142+
143+ import apport
144+ import apport.fileutils
145+ import apport.sandboxutils
146+-from apport.crashdb import get_crashdb
147++from apport import Report
148++from apport.crashdb import CrashDatabase, get_crashdb
149+
150+
151+ def parse_args():
152+@@ -323,20 +325,10 @@ def print_traces(report):
153+ print(report["StacktraceSource"])
154+
155+
156+-# pylint: disable-next=missing-function-docstring
157+-def main():
158+- # TODO: Split into smaller functions/methods
159+- # pylint: disable=too-many-branches,too-many-locals,too-many-nested-blocks
160+- # pylint: disable=too-many-statements
161+- apport.memdbg("start")
162+-
163+- gettext.textdomain("apport")
164+-
165+- options = parse_args()
166+-
167+- crashdb = get_crashdb(options.auth)
168+- apport.memdbg("got crash DB")
169+-
170++def load_report(
171++ options: Namespace, crashdb: CrashDatabase
172++) -> tuple[Report, int | None]:
173++ """Load the initial report for the given CLI options"""
174+ # load the report
175+ if os.path.exists(options.report):
176+ try:
177+@@ -344,13 +336,17 @@ def main():
178+ with open(options.report, "rb") as f:
179+ report.load(f, binary="compressed")
180+ apport.memdbg("loaded report from file")
181++ return report, None
182+ except (MemoryError, TypeError, ValueError, OSError, zlib.error) as error:
183+ apport.fatal("Cannot open report file: %s", str(error))
184+ elif options.report.isdigit():
185+ # crash ID
186+ try:
187+- report = crashdb.download(int(options.report))
188++ crashid = int(options.report)
189++ report = crashdb.download(crashid)
190+ apport.memdbg("downloaded report from crash DB")
191++ options.report = None
192++ return report, crashid
193+ except AssertionError as error:
194+ if "apport format data" in str(error):
195+ apport.error("Broken report: %s", str(error))
196+@@ -389,14 +385,29 @@ Thank you for your understanding, and sorry for the inconvenience!
197+ sys.exit(0)
198+ else:
199+ raise
200+-
201+- crashid = options.report
202+- options.report = None
203+ else:
204+ apport.fatal(
205+ '"%s" is neither an existing report file nor a crash ID', options.report
206+ )
207+
208++
209++# pylint: disable-next=missing-function-docstring
210++def main():
211++ # TODO: Split into smaller functions/methods
212++ # pylint: disable=too-many-branches,too-many-locals,too-many-nested-blocks
213++ # pylint: disable=too-many-statements
214++ apport.memdbg("start")
215++
216++ gettext.textdomain("apport")
217++
218++ options = parse_args()
219++
220++ crashdb = get_crashdb(options.auth)
221++ apport.memdbg("got crash DB")
222++
223++ # load the report
224++ report, crashid = load_report(options, crashdb)
225++
226+ if options.core_file:
227+ report["CoreDump"] = (os.path.abspath(options.core_file),)
228+ if options.executable:
229+@@ -619,7 +630,7 @@ Thank you for your understanding, and sorry for the inconvenience!
230+ update_bug = True
231+ if options.duplicate_db:
232+ crashdb.init_duplicate_db(options.duplicate_db)
233+- res = crashdb.check_duplicate(int(crashid), report)
234++ res = crashdb.check_duplicate(crashid, report)
235+ if res:
236+ if res[1] is None:
237+ version = "not fixed yet"
238+@@ -639,8 +650,8 @@ Thank you for your understanding, and sorry for the inconvenience!
239+ if "Stacktrace" in report:
240+ crashdb.update_traces(crashid, report)
241+ apport.log(
242+- "New attachments uploaded to crash database LP: #"
243+- + crashid,
244++ f"New attachments uploaded to crash database"
245++ f" LP: #{crashid}",
246+ options.timestamps,
247+ )
248+ else:
249diff --git a/debian/patches/fix-use-context-manager-when-manipulating-GzipFiles-LP-20.patch b/debian/patches/fix-use-context-manager-when-manipulating-GzipFiles-LP-20.patch
250new file mode 100644
251index 0000000..9945554
252--- /dev/null
253+++ b/debian/patches/fix-use-context-manager-when-manipulating-GzipFiles-LP-20.patch
254@@ -0,0 +1,109 @@
255+From: Simon Chopin <simon.chopin@canonical.com>
256+Date: Fri, 9 Feb 2024 11:25:13 +0100
257+Subject: fix: use context manager when manipulating GzipFiles (LP: #2051512)
258+
259+Those objects are technically file-like objects, with the associated
260+semantics. In particular, starting with Python 3.12 the class using
261+internal buffering, which means write() calls won't necessarily directly
262+be reflected in the underlying storage object.
263+
264+For the sake of consistency, the context manager approach has been
265+generalized to read() calls as well, except when we could get away with
266+removing the use of GzipFile altogether.
267+
268+Origin: upstream, https://github.com/canonical/apport/pull/294
269+---
270+ apport/crashdb_impl/launchpad.py | 3 ++-
271+ problem_report.py | 32 ++++++++++++++++----------------
272+ tests/integration/test_problem_report.py | 3 ++-
273+ 3 files changed, 20 insertions(+), 18 deletions(-)
274+
275+diff --git a/apport/crashdb_impl/launchpad.py b/apport/crashdb_impl/launchpad.py
276+index 6d7e8b4..9fb44fb 100644
277+--- a/apport/crashdb_impl/launchpad.py
278++++ b/apport/crashdb_impl/launchpad.py
279+@@ -369,7 +369,8 @@ class CrashDatabase(apport.crashdb.CrashDatabase):
280+ pass
281+ elif ext == ".gz":
282+ try:
283+- report[key] = gzip.GzipFile(fileobj=attachment).read()
284++ with gzip.GzipFile(fileobj=attachment) as gz:
285++ report[key] = gz.read()
286+ except OSError as error:
287+ # some attachments are only called .gz, but are
288+ # uncompressed (LP #574360)
289+diff --git a/problem_report.py b/problem_report.py
290+index 9b39a3b..5f65aad 100644
291+--- a/problem_report.py
292++++ b/problem_report.py
293+@@ -65,7 +65,8 @@ class CompressedValue:
294+ def set_value(self, value):
295+ """Set uncompressed value."""
296+ out = io.BytesIO()
297+- gzip.GzipFile(self.name, mode="wb", fileobj=out, mtime=0).write(value)
298++ with gzip.GzipFile(self.name, mode="wb", fileobj=out, mtime=0) as gz:
299++ gz.write(value)
300+ self.gzipvalue = out.getvalue()
301+ self.legacy_zlib = False
302+
303+@@ -76,7 +77,7 @@ class CompressedValue:
304+
305+ if self.legacy_zlib:
306+ return zlib.decompress(self.gzipvalue)
307+- return gzip.GzipFile(fileobj=io.BytesIO(self.gzipvalue)).read()
308++ return gzip.decompress(self.gzipvalue)
309+
310+ def write(self, file):
311+ """Write uncompressed value into given file-like object."""
312+@@ -86,12 +87,12 @@ class CompressedValue:
313+ file.write(zlib.decompress(self.gzipvalue))
314+ return
315+
316+- gz = gzip.GzipFile(fileobj=io.BytesIO(self.gzipvalue))
317+- while True:
318+- block = gz.read(1048576)
319+- if not block:
320+- break
321+- file.write(block)
322++ with gzip.GzipFile(fileobj=io.BytesIO(self.gzipvalue)) as gz:
323++ while True:
324++ block = gz.read(1048576)
325++ if not block:
326++ break
327++ file.write(block)
328+
329+ def __len__(self):
330+ """Return length of uncompressed value."""
331+@@ -635,14 +636,13 @@ class ProblemReport(collections.UserDict):
332+ attach_value = f.read()
333+ else:
334+ out = io.BytesIO()
335+- gf = gzip.GzipFile(k, mode="wb", fileobj=out, mtime=0)
336+- while True:
337+- block = f.read(1048576)
338+- if block:
339+- gf.write(block)
340+- else:
341+- gf.close()
342+- break
343++ with gzip.GzipFile(k, mode="wb", fileobj=out, mtime=0) as gz:
344++ while True:
345++ block = f.read(1048576)
346++ if block:
347++ gz.write(block)
348++ else:
349++ break
350+ attach_value = out.getvalue()
351+ f.close()
352+
353+diff --git a/tests/integration/test_problem_report.py b/tests/integration/test_problem_report.py
354+index 7f705dd..f020021 100644
355+--- a/tests/integration/test_problem_report.py
356++++ b/tests/integration/test_problem_report.py
357+@@ -537,4 +537,5 @@ class T(unittest.TestCase):
358+ with tempfile.TemporaryFile() as payload:
359+ payload.write(message.get_payload(decode=True))
360+ payload.seek(0)
361+- return gzip.GzipFile(mode="rb", fileobj=payload).read()
362++ with gzip.GzipFile(mode="rb", fileobj=payload) as gz:
363++ return gz.read()
364diff --git a/debian/patches/retrace-directly-test-crashid-relevance.patch b/debian/patches/retrace-directly-test-crashid-relevance.patch
365new file mode 100644
366index 0000000..92df430
367--- /dev/null
368+++ b/debian/patches/retrace-directly-test-crashid-relevance.patch
369@@ -0,0 +1,34 @@
370+From: Simon Chopin <simon.chopin@canonical.com>
371+Date: Tue, 13 Feb 2024 20:05:37 +0100
372+Subject: retrace: directly test crashid relevance
373+
374+As it turns out testing for `options.report` being falsy meant that
375+crashid was defined, but I'd rather test it directly as it's a bit
376+cumbersome to trace (and it made pylint crazy).
377+
378+Origin: upstream, https://github.com/canonical/apport/pull/295
379+---
380+ bin/apport-retrace | 3 +--
381+ 1 file changed, 1 insertion(+), 2 deletions(-)
382+
383+diff --git a/bin/apport-retrace b/bin/apport-retrace
384+index 246a065..f12f8da 100755
385+--- a/bin/apport-retrace
386++++ b/bin/apport-retrace
387+@@ -345,7 +345,6 @@ def load_report(
388+ crashid = int(options.report)
389+ report = crashdb.download(crashid)
390+ apport.memdbg("downloaded report from crash DB")
391+- options.report = None
392+ return report, crashid
393+ except AssertionError as error:
394+ if "apport format data" in str(error):
395+@@ -619,7 +618,7 @@ Thank you for your understanding, and sorry for the inconvenience!
396+ modified = True
397+
398+ if modified:
399+- if not options.report and not options.output:
400++ if crashid is not None and not options.output:
401+ if not options.auth:
402+ apport.fatal(
403+ "You need to specify --auth for uploading retraced results"
404diff --git a/debian/patches/series b/debian/patches/series
405index 1c3393f..e03ab4b 100644
406--- a/debian/patches/series
407+++ b/debian/patches/series
408@@ -11,3 +11,6 @@ setup-replace-distutils.log-by-logging-library.patch
409 setup-drop-DistUtilsExtra-version-check.patch
410 setup-explicitly-list-packages-to-install.patch
411 setup-port-build_java-to-setuptools.patch
412+apport-retrace-split-out-report-loading-into-its-own-func.patch
413+retrace-directly-test-crashid-relevance.patch
414+fix-use-context-manager-when-manipulating-GzipFiles-LP-20.patch
415diff --git a/debian/rules b/debian/rules
416index 0acdb99..b38b9fd 100755
417--- a/debian/rules
418+++ b/debian/rules
419@@ -3,21 +3,6 @@
420 %:
421 dh "$@" --with python3,translations --buildsystem pybuild
422
423-# needs manual commands for Python 3, see Debian #597105
424-override_dh_auto_clean:
425- dh_auto_clean
426- rm -rf build
427-
428-override_dh_auto_build:
429- dh_auto_build
430- python3 setup.py build
431-
432-override_dh_auto_install:
433- dh_auto_install
434- set -ex; for python in $(shell py3versions -r); do \
435- $$python setup.py install --root=$(CURDIR)/debian/tmp --install-layout=deb; \
436- done
437-
438 override_dh_auto_test:
439 ifeq (, $(findstring nocheck, $(DEB_BUILD_OPTIONS)))
440 tests/run-linters --errors-only
441@@ -25,7 +10,7 @@ ifeq (, $(findstring nocheck, $(DEB_BUILD_OPTIONS)))
442 endif
443
444 override_dh_installinit:
445- dh_installinit --error-handler=true
446+ dh_installinit -papport-core-dump-handler --name=apport --error-handler=true --only-scripts
447
448 override_dh_install:
449 dh_install -X.pyc -X.egg-info

Subscribers

People subscribed via source and target branches