Merge lp:~xnox/ubuntu/raring/apport/ubuntu into lp:ubuntu/raring/apport

Proposed by Dimitri John Ledkov
Status: Superseded
Proposed branch: lp:~xnox/ubuntu/raring/apport/ubuntu
Merge into: lp:ubuntu/raring/apport
Diff against target: 125999 lines (+124810/-0) (has conflicts)
224 files modified
.bzr-builddeb/default.conf (+2/-0)
.bzrignore (+9/-0)
AUTHORS (+34/-0)
COPYING (+339/-0)
NEWS (+1801/-0)
README (+87/-0)
TODO (+21/-0)
apport/REThread.py (+65/-0)
apport/__init__.py (+73/-0)
apport/com.ubuntu.apport.policy.in (+34/-0)
apport/crashdb.py (+856/-0)
apport/crashdb_impl/debian.py (+113/-0)
apport/crashdb_impl/launchpad.py (+1993/-0)
apport/crashdb_impl/memory.py (+300/-0)
apport/fileutils.py (+376/-0)
apport/hookutils.py (+897/-0)
apport/packaging.py (+237/-0)
apport/report.py (+1528/-0)
apport/sandboxutils.py (+212/-0)
apport/ui.py (+1560/-0)
apport_python_hook.py (+197/-0)
backends/packaging-apt-dpkg.py (+924/-0)
bin/apport-bug (+93/-0)
bin/apport-cli (+368/-0)
bin/apport-retrace (+412/-0)
bin/apport-unpack (+66/-0)
bin/apport-valgrind (+174/-0)
bin/crash-digger (+217/-0)
bin/dupdb-admin (+96/-0)
data/apport (+459/-0)
data/apport-checkreports (+40/-0)
data/apportcheckresume (+91/-0)
data/dump_acpi_tables.py (+54/-0)
data/gcc_ice_hook (+35/-0)
data/general-hooks/automatix.py (+24/-0)
data/general-hooks/generic.py (+94/-0)
data/general-hooks/parse_segv.py (+376/-0)
data/general-hooks/ubuntu.py (+477/-0)
data/icons/scalable/apps/apport.svg (+444/-0)
data/is-enabled (+19/-0)
data/java_uncaught_exception (+92/-0)
data/kernel_crashdump (+45/-0)
data/kernel_oops (+39/-0)
data/package-hooks/source_apport.py (+20/-0)
data/package-hooks/source_debian-installer.py (+59/-0)
data/package-hooks/source_linux-nexus7.py (+25/-0)
data/package-hooks/source_linux.py (+113/-0)
data/package-hooks/source_ubiquity.py (+129/-0)
data/package_hook (+65/-0)
data/recoverable_problem (+47/-0)
data/root_info_wrapper (+3/-0)
data/unkillable_shutdown (+108/-0)
debhelper/apport.pm (+10/-0)
debhelper/dh_apport (+83/-0)
debian/apport-gtk.install (+2/-0)
debian/apport-kde.install (+9/-0)
debian/apport-retrace.install (+5/-0)
debian/apport-valgrind.install (+2/-0)
debian/apport.install (+33/-0)
debian/apport.links (+4/-0)
debian/apport.logrotate (+9/-0)
debian/apport.maintscript (+1/-0)
debian/apport.postinst (+10/-0)
debian/apport.upstart (+50/-0)
debian/changelog (+7406/-0)
debian/clean (+2/-0)
debian/compat (+1/-0)
debian/control (+223/-0)
debian/copyright (+15/-0)
debian/dh-apport.install (+2/-0)
debian/dh-apport.manpages (+1/-0)
debian/python-apport.install (+2/-0)
debian/python-problem-report.install (+1/-0)
debian/python3-apport.install (+2/-0)
debian/python3-problem-report.install (+1/-0)
debian/rules (+39/-0)
debian/source/format (+1/-0)
debian/tests/control (+3/-0)
debian/tests/upstream-system (+16/-0)
debian/watch (+2/-0)
doc/crashdb-conf.txt (+149/-0)
doc/data-format.tex (+301/-0)
doc/package-hooks.txt (+158/-0)
doc/symptoms.txt (+73/-0)
etc/apport/blacklist.d/README.blacklist (+4/-0)
etc/apport/blacklist.d/apport (+5/-0)
etc/apport/crashdb.conf (+37/-0)
etc/bash_completion.d/apport_completion (+268/-0)
etc/cron.daily/apport (+4/-0)
etc/default/apport (+4/-0)
etc/init.d/apport (+110/-0)
gtk/apport-gtk (+590/-0)
gtk/apport-gtk-mime.desktop.in (+12/-0)
gtk/apport-gtk.desktop.in (+12/-0)
gtk/apport-gtk.ui (+597/-0)
java/README (+13/-0)
java/com/ubuntu/apport/ApportUncaughtExceptionHandler.java (+108/-0)
java/crash.java (+8/-0)
kde/apport-kde (+522/-0)
kde/apport-kde-mime.desktop.in (+12/-0)
kde/apport-kde-mimelnk.desktop.in (+9/-0)
kde/apport-kde.desktop.in (+11/-0)
kde/bugreport.ui (+177/-0)
kde/choices.ui (+95/-0)
kde/error.ui (+136/-0)
kde/progress.ui (+115/-0)
kde/userpass.ui (+127/-0)
man/apport-bug.1 (+129/-0)
man/apport-cli.1 (+150/-0)
man/apport-retrace.1 (+190/-0)
man/apport-unpack.1 (+32/-0)
man/apport-valgrind.1 (+125/-0)
man/dupdb-admin.1 (+68/-0)
pm-utils/sleep.d/000record-status (+16/-0)
po/ace.po (+840/-0)
po/af.po (+840/-0)
po/am.po (+840/-0)
po/an.po (+840/-0)
po/apport.pot (+835/-0)
po/ar.po (+1177/-0)
po/ast.po (+1035/-0)
po/be.po (+1071/-0)
po/bg.po (+992/-0)
po/bn.po (+858/-0)
po/br.po (+840/-0)
po/bs.po (+861/-0)
po/ca.po (+1100/-0)
po/ca@valencia.po (+900/-0)
po/cs.po (+1053/-0)
po/cv.po (+840/-0)
po/da.po (+1042/-0)
po/de.po (+1145/-0)
po/el.po (+1116/-0)
po/en_AU.po (+1094/-0)
po/en_CA.po (+1081/-0)
po/en_GB.po (+1094/-0)
po/eo.po (+1053/-0)
po/es.po (+1124/-0)
po/et.po (+1211/-0)
po/eu.po (+1023/-0)
po/fa.po (+840/-0)
po/fi.po (+1086/-0)
po/fr.po (+1267/-0)
po/ga.po (+840/-0)
po/gd.po (+840/-0)
po/gl.po (+1121/-0)
po/gu.po (+884/-0)
po/he.po (+1002/-0)
po/hi.po (+881/-0)
po/hr.po (+997/-0)
po/hu.po (+1110/-0)
po/hy.po (+840/-0)
po/id.po (+1211/-0)
po/is.po (+975/-0)
po/it.po (+1094/-0)
po/ja.po (+1166/-0)
po/kab.po (+840/-0)
po/kk.po (+840/-0)
po/km.po (+958/-0)
po/kn.po (+908/-0)
po/ko.po (+962/-0)
po/ku.po (+928/-0)
po/lo.po (+840/-0)
po/lt.po (+1095/-0)
po/lv.po (+859/-0)
po/mk.po (+840/-0)
po/ml.po (+840/-0)
po/ms.po (+983/-0)
po/my.po (+844/-0)
po/nb.po (+1019/-0)
po/nds.po (+878/-0)
po/ne.po (+852/-0)
po/nl.po (+1045/-0)
po/oc.po (+1120/-0)
po/pl.po (+1281/-0)
po/pt.po (+1030/-0)
po/pt_BR.po (+1117/-0)
po/ro.po (+1055/-0)
po/ru.po (+1257/-0)
po/sc.po (+840/-0)
po/se.po (+840/-0)
po/shn.po (+840/-0)
po/si.po (+844/-0)
po/sk.po (+1008/-0)
po/sl.po (+1069/-0)
po/sq.po (+1045/-0)
po/sr.po (+1306/-0)
po/sv.po (+1104/-0)
po/ta.po (+874/-0)
po/te.po (+850/-0)
po/th.po (+879/-0)
po/tr.po (+1262/-0)
po/ug.po (+991/-0)
po/uk.po (+1203/-0)
po/uz.po (+839/-0)
po/vi.po (+1008/-0)
po/zh_CN.po (+965/-0)
po/zh_HK.po (+844/-0)
po/zh_TW.po (+979/-0)
problem_report.py (+634/-0)
setup.py (+129/-0)
test/run (+128/-0)
test/test_apport_unpack.py (+116/-0)
test/test_apport_valgrind.py (+154/-0)
test/test_backend_apt_dpkg.py (+781/-0)
test/test_crash_digger.py (+237/-0)
test/test_crashdb.py (+694/-0)
test/test_fileutils.py (+342/-0)
test/test_hooks.py (+270/-0)
test/test_hookutils.py (+453/-0)
test/test_java_crashes.py (+92/-0)
test/test_packaging.py (+16/-0)
test/test_parse_segv.py (+544/-0)
test/test_problem_report.py (+988/-0)
test/test_python_crashes.py (+398/-0)
test/test_recoverable_problem.py (+76/-0)
test/test_report.py (+2120/-0)
test/test_rethread.py (+88/-0)
test/test_signal_crashes.py (+672/-0)
test/test_ui.py (+2106/-0)
test/test_ui_gtk.py (+863/-0)
test/test_ui_kde.py (+607/-0)
use-local (+7/-0)
xdg-mime/apport.xml (+11/-0)
Conflict adding file AUTHORS.  Moved existing file to AUTHORS.moved.
Conflict adding file COPYING.  Moved existing file to COPYING.moved.
Conflict adding file NEWS.  Moved existing file to NEWS.moved.
Conflict adding file README.  Moved existing file to README.moved.
Conflict adding file TODO.  Moved existing file to TODO.moved.
Conflict adding file apport.  Moved existing file to apport.moved.
Conflict adding file apport_python_hook.py.  Moved existing file to apport_python_hook.py.moved.
Conflict adding file backends.  Moved existing file to backends.moved.
Conflict adding file bin.  Moved existing file to bin.moved.
Conflict adding file data.  Moved existing file to data.moved.
Conflict adding file debhelper.  Moved existing file to debhelper.moved.
Conflict adding file debian.  Moved existing file to debian.moved.
Conflict adding file doc.  Moved existing file to doc.moved.
Conflict adding file etc.  Moved existing file to etc.moved.
Conflict adding file gtk.  Moved existing file to gtk.moved.
Conflict adding file java.  Moved existing file to java.moved.
Conflict adding file kde.  Moved existing file to kde.moved.
Conflict adding file man.  Moved existing file to man.moved.
Conflict adding file pm-utils.  Moved existing file to pm-utils.moved.
Conflict adding file po.  Moved existing file to po.moved.
Conflict adding file problem_report.py.  Moved existing file to problem_report.py.moved.
Conflict adding file setup.py.  Moved existing file to setup.py.moved.
Conflict adding file test.  Moved existing file to test.moved.
Conflict adding file use-local.  Moved existing file to use-local.moved.
Conflict adding file xdg-mime.  Moved existing file to xdg-mime.moved.
To merge this branch: bzr merge lp:~xnox/ubuntu/raring/apport/ubuntu
Reviewer Review Type Date Requested Status
Ubuntu branches Pending
Review via email: mp+153223@code.launchpad.net
To post a comment you must log in.

Unmerged revisions

2165. By Dimitri John Ledkov

Include ubiquity.log generated by upstart for the ubiquity.conf
upstart job.

2164. By Martin Pitt

releasing version 2.9.1-0ubuntu1

2163. By Martin Pitt

Bump Standards-Version to 3.9.4 (no changes necessary).

2162. By Martin Pitt

new upstream release 2.9.1

2161. By Martin Pitt

releasing version 2.9-0ubuntu2

2160. By Martin Pitt

* Merge from trunk:
  - test_signal_crashes.py: Fix test_crash_apport() when being run under
    LD_PRELOAD.

2159. By Martin Pitt

releasing version 2.9-0ubuntu1

2158. By Martin Pitt

data/general-hooks/ubuntu.py: Drop unused pwd import

2157. By Martin Pitt

new upstream release 2.9

2156. By Brian Murray

source_ubiquity.py: set a default value for cd_error

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory '.bzr-builddeb'
=== added file '.bzr-builddeb/default.conf'
--- .bzr-builddeb/default.conf 1970-01-01 00:00:00 +0000
+++ .bzr-builddeb/default.conf 2013-03-13 18:57:21 +0000
@@ -0,0 +1,2 @@
1[BUILDDEB]
2merge = True
03
=== added file '.bzrignore'
--- .bzrignore 1970-01-01 00:00:00 +0000
+++ .bzrignore 2013-03-13 18:57:21 +0000
@@ -0,0 +1,9 @@
1apport/packaging_impl.py
2debhelper/dh_apport.1
3doc/*.aux
4doc/*.log
5doc/*.pdf
6doc/*.toc
7build
8dist
9MANIFEST
010
=== added file 'AUTHORS'
--- AUTHORS 1970-01-01 00:00:00 +0000
+++ AUTHORS 2013-03-13 18:57:21 +0000
@@ -0,0 +1,34 @@
1Copyright:
2---------
3General:
4 Copyright (C) 2006 - 2011 Canonical Ltd.
5
6backends/packaging_rpm.py:
7 Copyright (C) 2007 Red Hat Inc.
8
9Authors and Contributors:
10-------------------------
11Martin Pitt <martin.pitt@ubuntu.com>:
12 Lead developer, design, backend, GTK frontend development,
13 maintenance of other frontends
14
15Michael Hofmann <mh21@piware.de>:
16 Creation of Qt4 and CLI frontends
17
18Richard A. Johnson <nixternal@ubuntu.com>:
19 Changed Qt4 frontend to KDE frontend
20
21Robert Collins <robert@ubuntu.com>:
22 Python crash hook
23
24Will Woods <wwoods@redhat.com>:
25 RPM packaging backend
26
27Matt Zimmerman <mdz@canonical.com>:
28 Convenience function library for hooks (apport/hookutils.py)
29
30Troy James Sobotka <troy.sobotka@gmail.com>:
31 Apport icon (apport/apport.svg)
32
33Kees Cook <kees.cook@canonical.com>:
34 Various fixes, additional GDB output, SEGV parser.
035
=== renamed file 'AUTHORS' => 'AUTHORS.moved'
=== added file 'COPYING'
--- COPYING 1970-01-01 00:00:00 +0000
+++ COPYING 2013-03-13 18:57:21 +0000
@@ -0,0 +1,339 @@
1 GNU GENERAL PUBLIC LICENSE
2 Version 2, June 1991
3
4 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 Everyone is permitted to copy and distribute verbatim copies
7 of this license document, but changing it is not allowed.
8
9 Preamble
10
11 The licenses for most software are designed to take away your
12freedom to share and change it. By contrast, the GNU General Public
13License is intended to guarantee your freedom to share and change free
14software--to make sure the software is free for all its users. This
15General Public License applies to most of the Free Software
16Foundation's software and to any other program whose authors commit to
17using it. (Some other Free Software Foundation software is covered by
18the GNU Lesser General Public License instead.) You can apply it to
19your programs, too.
20
21 When we speak of free software, we are referring to freedom, not
22price. Our General Public Licenses are designed to make sure that you
23have the freedom to distribute copies of free software (and charge for
24this service if you wish), that you receive source code or can get it
25if you want it, that you can change the software or use pieces of it
26in new free programs; and that you know you can do these things.
27
28 To protect your rights, we need to make restrictions that forbid
29anyone to deny you these rights or to ask you to surrender the rights.
30These restrictions translate to certain responsibilities for you if you
31distribute copies of the software, or if you modify it.
32
33 For example, if you distribute copies of such a program, whether
34gratis or for a fee, you must give the recipients all the rights that
35you have. You must make sure that they, too, receive or can get the
36source code. And you must show them these terms so they know their
37rights.
38
39 We protect your rights with two steps: (1) copyright the software, and
40(2) offer you this license which gives you legal permission to copy,
41distribute and/or modify the software.
42
43 Also, for each author's protection and ours, we want to make certain
44that everyone understands that there is no warranty for this free
45software. If the software is modified by someone else and passed on, we
46want its recipients to know that what they have is not the original, so
47that any problems introduced by others will not reflect on the original
48authors' reputations.
49
50 Finally, any free program is threatened constantly by software
51patents. We wish to avoid the danger that redistributors of a free
52program will individually obtain patent licenses, in effect making the
53program proprietary. To prevent this, we have made it clear that any
54patent must be licensed for everyone's free use or not licensed at all.
55
56 The precise terms and conditions for copying, distribution and
57modification follow.
58
59 GNU GENERAL PUBLIC LICENSE
60 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
62 0. This License applies to any program or other work which contains
63a notice placed by the copyright holder saying it may be distributed
64under the terms of this General Public License. The "Program", below,
65refers to any such program or work, and a "work based on the Program"
66means either the Program or any derivative work under copyright law:
67that is to say, a work containing the Program or a portion of it,
68either verbatim or with modifications and/or translated into another
69language. (Hereinafter, translation is included without limitation in
70the term "modification".) Each licensee is addressed as "you".
71
72Activities other than copying, distribution and modification are not
73covered by this License; they are outside its scope. The act of
74running the Program is not restricted, and the output from the Program
75is covered only if its contents constitute a work based on the
76Program (independent of having been made by running the Program).
77Whether that is true depends on what the Program does.
78
79 1. You may copy and distribute verbatim copies of the Program's
80source code as you receive it, in any medium, provided that you
81conspicuously and appropriately publish on each copy an appropriate
82copyright notice and disclaimer of warranty; keep intact all the
83notices that refer to this License and to the absence of any warranty;
84and give any other recipients of the Program a copy of this License
85along with the Program.
86
87You may charge a fee for the physical act of transferring a copy, and
88you may at your option offer warranty protection in exchange for a fee.
89
90 2. You may modify your copy or copies of the Program or any portion
91of it, thus forming a work based on the Program, and copy and
92distribute such modifications or work under the terms of Section 1
93above, provided that you also meet all of these conditions:
94
95 a) You must cause the modified files to carry prominent notices
96 stating that you changed the files and the date of any change.
97
98 b) You must cause any work that you distribute or publish, that in
99 whole or in part contains or is derived from the Program or any
100 part thereof, to be licensed as a whole at no charge to all third
101 parties under the terms of this License.
102
103 c) If the modified program normally reads commands interactively
104 when run, you must cause it, when started running for such
105 interactive use in the most ordinary way, to print or display an
106 announcement including an appropriate copyright notice and a
107 notice that there is no warranty (or else, saying that you provide
108 a warranty) and that users may redistribute the program under
109 these conditions, and telling the user how to view a copy of this
110 License. (Exception: if the Program itself is interactive but
111 does not normally print such an announcement, your work based on
112 the Program is not required to print an announcement.)
113
114These requirements apply to the modified work as a whole. If
115identifiable sections of that work are not derived from the Program,
116and can be reasonably considered independent and separate works in
117themselves, then this License, and its terms, do not apply to those
118sections when you distribute them as separate works. But when you
119distribute the same sections as part of a whole which is a work based
120on the Program, the distribution of the whole must be on the terms of
121this License, whose permissions for other licensees extend to the
122entire whole, and thus to each and every part regardless of who wrote it.
123
124Thus, it is not the intent of this section to claim rights or contest
125your rights to work written entirely by you; rather, the intent is to
126exercise the right to control the distribution of derivative or
127collective works based on the Program.
128
129In addition, mere aggregation of another work not based on the Program
130with the Program (or with a work based on the Program) on a volume of
131a storage or distribution medium does not bring the other work under
132the scope of this License.
133
134 3. You may copy and distribute the Program (or a work based on it,
135under Section 2) in object code or executable form under the terms of
136Sections 1 and 2 above provided that you also do one of the following:
137
138 a) Accompany it with the complete corresponding machine-readable
139 source code, which must be distributed under the terms of Sections
140 1 and 2 above on a medium customarily used for software interchange; or,
141
142 b) Accompany it with a written offer, valid for at least three
143 years, to give any third party, for a charge no more than your
144 cost of physically performing source distribution, a complete
145 machine-readable copy of the corresponding source code, to be
146 distributed under the terms of Sections 1 and 2 above on a medium
147 customarily used for software interchange; or,
148
149 c) Accompany it with the information you received as to the offer
150 to distribute corresponding source code. (This alternative is
151 allowed only for noncommercial distribution and only if you
152 received the program in object code or executable form with such
153 an offer, in accord with Subsection b above.)
154
155The source code for a work means the preferred form of the work for
156making modifications to it. For an executable work, complete source
157code means all the source code for all modules it contains, plus any
158associated interface definition files, plus the scripts used to
159control compilation and installation of the executable. However, as a
160special exception, the source code distributed need not include
161anything that is normally distributed (in either source or binary
162form) with the major components (compiler, kernel, and so on) of the
163operating system on which the executable runs, unless that component
164itself accompanies the executable.
165
166If distribution of executable or object code is made by offering
167access to copy from a designated place, then offering equivalent
168access to copy the source code from the same place counts as
169distribution of the source code, even though third parties are not
170compelled to copy the source along with the object code.
171
172 4. You may not copy, modify, sublicense, or distribute the Program
173except as expressly provided under this License. Any attempt
174otherwise to copy, modify, sublicense or distribute the Program is
175void, and will automatically terminate your rights under this License.
176However, parties who have received copies, or rights, from you under
177this License will not have their licenses terminated so long as such
178parties remain in full compliance.
179
180 5. You are not required to accept this License, since you have not
181signed it. However, nothing else grants you permission to modify or
182distribute the Program or its derivative works. These actions are
183prohibited by law if you do not accept this License. Therefore, by
184modifying or distributing the Program (or any work based on the
185Program), you indicate your acceptance of this License to do so, and
186all its terms and conditions for copying, distributing or modifying
187the Program or works based on it.
188
189 6. Each time you redistribute the Program (or any work based on the
190Program), the recipient automatically receives a license from the
191original licensor to copy, distribute or modify the Program subject to
192these terms and conditions. You may not impose any further
193restrictions on the recipients' exercise of the rights granted herein.
194You are not responsible for enforcing compliance by third parties to
195this License.
196
197 7. If, as a consequence of a court judgment or allegation of patent
198infringement or for any other reason (not limited to patent issues),
199conditions are imposed on you (whether by court order, agreement or
200otherwise) that contradict the conditions of this License, they do not
201excuse you from the conditions of this License. If you cannot
202distribute so as to satisfy simultaneously your obligations under this
203License and any other pertinent obligations, then as a consequence you
204may not distribute the Program at all. For example, if a patent
205license would not permit royalty-free redistribution of the Program by
206all those who receive copies directly or indirectly through you, then
207the only way you could satisfy both it and this License would be to
208refrain entirely from distribution of the Program.
209
210If any portion of this section is held invalid or unenforceable under
211any particular circumstance, the balance of the section is intended to
212apply and the section as a whole is intended to apply in other
213circumstances.
214
215It is not the purpose of this section to induce you to infringe any
216patents or other property right claims or to contest validity of any
217such claims; this section has the sole purpose of protecting the
218integrity of the free software distribution system, which is
219implemented by public license practices. Many people have made
220generous contributions to the wide range of software distributed
221through that system in reliance on consistent application of that
222system; it is up to the author/donor to decide if he or she is willing
223to distribute software through any other system and a licensee cannot
224impose that choice.
225
226This section is intended to make thoroughly clear what is believed to
227be a consequence of the rest of this License.
228
229 8. If the distribution and/or use of the Program is restricted in
230certain countries either by patents or by copyrighted interfaces, the
231original copyright holder who places the Program under this License
232may add an explicit geographical distribution limitation excluding
233those countries, so that distribution is permitted only in or among
234countries not thus excluded. In such case, this License incorporates
235the limitation as if written in the body of this License.
236
237 9. The Free Software Foundation may publish revised and/or new versions
238of the General Public License from time to time. Such new versions will
239be similar in spirit to the present version, but may differ in detail to
240address new problems or concerns.
241
242Each version is given a distinguishing version number. If the Program
243specifies a version number of this License which applies to it and "any
244later version", you have the option of following the terms and conditions
245either of that version or of any later version published by the Free
246Software Foundation. If the Program does not specify a version number of
247this License, you may choose any version ever published by the Free Software
248Foundation.
249
250 10. If you wish to incorporate parts of the Program into other free
251programs whose distribution conditions are different, write to the author
252to ask for permission. For software which is copyrighted by the Free
253Software Foundation, write to the Free Software Foundation; we sometimes
254make exceptions for this. Our decision will be guided by the two goals
255of preserving the free status of all derivatives of our free software and
256of promoting the sharing and reuse of software generally.
257
258 NO WARRANTY
259
260 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268REPAIR OR CORRECTION.
269
270 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278POSSIBILITY OF SUCH DAMAGES.
279
280 END OF TERMS AND CONDITIONS
281
282 How to Apply These Terms to Your New Programs
283
284 If you develop a new program, and you want it to be of the greatest
285possible use to the public, the best way to achieve this is to make it
286free software which everyone can redistribute and change under these terms.
287
288 To do so, attach the following notices to the program. It is safest
289to attach them to the start of each source file to most effectively
290convey the exclusion of warranty; and each file should have at least
291the "copyright" line and a pointer to where the full notice is found.
292
293 <one line to give the program's name and a brief idea of what it does.>
294 Copyright (C) <year> <name of author>
295
296 This program is free software; you can redistribute it and/or modify
297 it under the terms of the GNU General Public License as published by
298 the Free Software Foundation; either version 2 of the License, or
299 (at your option) any later version.
300
301 This program is distributed in the hope that it will be useful,
302 but WITHOUT ANY WARRANTY; without even the implied warranty of
303 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 GNU General Public License for more details.
305
306 You should have received a copy of the GNU General Public License along
307 with this program; if not, write to the Free Software Foundation, Inc.,
308 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309
310Also add information on how to contact you by electronic and paper mail.
311
312If the program is interactive, make it output a short notice like this
313when it starts in an interactive mode:
314
315 Gnomovision version 69, Copyright (C) year name of author
316 Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 This is free software, and you are welcome to redistribute it
318 under certain conditions; type `show c' for details.
319
320The hypothetical commands `show w' and `show c' should show the appropriate
321parts of the General Public License. Of course, the commands you use may
322be called something other than `show w' and `show c'; they could even be
323mouse-clicks or menu items--whatever suits your program.
324
325You should also get your employer (if you work as a programmer) or your
326school, if any, to sign a "copyright disclaimer" for the program, if
327necessary. Here is a sample; alter the names:
328
329 Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 `Gnomovision' (which makes passes at compilers) written by James Hacker.
331
332 <signature of Ty Coon>, 1 April 1989
333 Ty Coon, President of Vice
334
335This General Public License does not permit incorporating your program into
336proprietary programs. If your program is a subroutine library, you may
337consider it more useful to permit linking proprietary applications with the
338library. If this is what you want to do, use the GNU Lesser General
339Public License instead of this License.
0340
=== renamed file 'COPYING' => 'COPYING.moved'
=== added file 'NEWS'
--- NEWS 1970-01-01 00:00:00 +0000
+++ NEWS 2013-03-13 18:57:21 +0000
@@ -0,0 +1,1801 @@
1This file summarizes the major and interesting changes for each release. For a
2detailled list of changes, please see ChangeLog.
3
42.9.1 (2013-03-07):
5-------------------
6Improvements:
7 * launchpad.py: Add support for filing bugs as private. Document this in
8 doc/crashdb-conf.txt. (LP: #1144647)
9
10Bug fixes:
11 * test_signal_crashes.py: Fix test_crash_apport() when being run under
12 LD_PRELOAD.
13 * Fix crash in error() and warning() if there is no sys.stderr. (LP: #1012445)
14 * Fix Turkish translation to add missing keyboard accelerator. (LP: #648750)
15 * fileutils.py, find_package_desktopfile(): Restrict to autostart and
16 application .desktop files. (LP: #1147528)
17 * apt/dpkg get_modified_files(): Fix crash when encountering non-ASCII file
18 names in an ASCII locale. (LP: #1044014)
19
202.9 (2013-03-01):
21-----------------
22Improvements:
23 * fileutils.py, shared_libraries(): Return a "name → path" dict instead of
24 just a set of names. Thanks Kyle Nitzsche.
25 * sandboxutils.py: Support unpackaged executables, i. e. reports which do not
26 have "Package" and "Dependencies" fields. For those, get required libraries
27 from "ProcMaps". Thanks Kyle Nitzsche.
28
29Bug fixes:
30 * Add "com.ubuntu.apport.apport-gtk-root" polkit action for running apport-gtk
31 through pkexec to access system crash reports. Thanks Brian Murray.
32 * ui.py: Check $PKEXEC_UID in addition to $SUDO_UID for opening a browser.
33 * apport/report.py: report if LD_PRELOAD and LD_LIBRARY_PATH are set. Thanks
34 James Hunt.
35 * apport-valgrind: Cleanly exit on keyboard interrupts. Thanks Kyle Nitzsche.
36 * debian.py: Fix "string payload expected" crash when building the report.
37 Thanks Dmitry Shachnev. (Debian #698010)
38 * Move shared_libraries() and links_with_shared_library() from hookutils into
39 fileutils, so that we can use it from apport-valgrind. Thanks to Kyle
40 Nitzsche for the initial patch.
41 * fileutils.shared_libraries(): Filter out virtual "linux-vdso" from result.
42 Thanks Kyle Nitzsche.
43 * apport-valgrind: Fix path to debug symbols in the sandbox.
44 * ui.py, get_desktop_entry(): Fix for Python 2.
45
462.8 (2013-01-08):
47-----------------
48Improvements:
49 * Factor out sandbox management functions from apport-retrace into
50 apport/sandboxutils.py, so that other programs can re-use the API easily.
51 Thanks to Kyle Nitzsche for the initial work on this.
52 * Generate a crash signature for kernel OOPSes.
53 * Add "apport-valgrind" tool to produce valgrind reports in a temporary
54 sandbox with debug symbols (similar to apport-retrace). Thanks Alex Chiang
55 and Kyle Nitzsche!
56
57Bug fixes:
58 * Fix StacktraceAddressSignature generation on ARM. (LP: #1089778)
59 * debian.py: Fix TypeError crash in payload generation. Thanks Ritesh Raj
60 Sarraf.
61 * apport_python_hook.py: Update "ExecutableTimestamp" field when mangling
62 "ExecutablePath". (LP: #1077253)
63
642.7 (2012-12-10):
65-----------------
66Improvements:
67 * packaging.py, get_file_package(): Add optional "release" and "arch"
68 arguments for getting a file's package for a foreign release or
69 architecture. Implement this for apt/dpkg backend.
70 * packaging.py, install_packages(): Add optional "architecture" argument for
71 creating a sandbox for a foreign architecture. Implement this for apt/dpkg
72 backend.
73 * When a report's architecture does not match the system architecture, try to
74 use gdb-multiarch (if available, as packaged on Debian/Ubuntu), and set
75 architecture and gnutarget accordingly in gdb. This supports x86, x86_64,
76 and ARM for now, so that reports from all these architectures can be
77 retraced on an x86_84 machine. (LP: #1044437)
78 * launchpad.py: Add "architecture" option to process reports for a foreign
79 architecture.
80 * Add exceptions from package hooks to new HookError_<filename> report field,
81 to make them more visible. Until now they were only written to stderr.
82 (LP: #1086309)
83
84Bug fixes:
85 * Fix test_find_package_desktopfile test to not consider packages with only
86 one "NoDisplay=true" .desktop file for the "has one desktop file" test.
87 * report.py, mark_ignore(): Use home directory of actual effective user, not
88 of $HOME. Fixes ignore file when using through sudo.
89 * apport-cli: Fix showing of prompt. Thanks Dmitry Shachnev!
90 * fileutils.py, mark_report_upload(): Do not try to remove the .uploaded file,
91 as this is not owned by the user.
92 * backends/packaging-apt-dpkg.py, install_packages(): Set mirror to the one in
93 the sandbox config.
94 * apportcheckresume: Fix crash if state file does not exist.
95
962.6.3 (2012-11-30):
97-------------------
98 * test_signal_crashes.py: Fix incompatibility with Python 3.3.
99 * test_signal_crashes.py: Allow XDG_RUNTIME_DIR environment variable, as it
100 only shows whether or not it is set. (Test regression from 2.6)
101 * debian.py: Only reject reports with useless stack traces if the report
102 actually has a stack trace at all.
103 * debian.py: Fix UTF-8 string handling. Thanks Ritesh Raj Sarraf.
104 * debian.py: Fix crash on broken "Package" fields, as generated by current
105 Debian/Ubuntu dkms package.
106 * data/apport: Call fsync when writing upstart crash reports.
107 * report.py, add_gdb_info(): Handle libnih's assertion messages.
108 (LP: #997359)
109 * apport-gtk, apport-kde: Don't provide an option to restart a crashed
110 application when the crash occurred in a thread (LP: #1033902).
111 * apport-retrace: Disallow option -C without option -S. Thanks Kyle Nitzsche.
112 * fileutils.py, mark_report_upload(): Refresh the .upload stamps if a previous
113 version of the report was already uploaded, but another instance of the
114 problem happened since then. Thanks Brian Murray. (LP: #1084296)
115 * Ignore implausibly low addresses when computing StacktraceAddressSignature.
116 These are usually artifacts from gdb when not having debug symbols, and
117 having too many of them prevents proper client-side duplicate detection and
118 proper bucketing in daisy. (LP: #1084996)
119 * fileutils.py: Ignore .desktop files with NoDisplay=true. (LP: #1048524)
120
1212.6.2 (2012-11-06):
122-------------------
123 * problem_report.py: Fix UnicodeDecodeError crash under Python 2 when the
124 report has an unicode field with an unprintable ASCII character < 20.
125 * debian.py: Fix calling of parent accepts() method and return value. Thanks
126 Ritesh Raj Sarraf.
127 * bin/apport-retrace: Fix crash when not using --sandbox mode.
128 * report.py, add_proc_info(): Throw correct exception if the executable path
129 does not exist, to provide a more appropriate error message. (LP: #1065129)
130 * report.py, add_gdb_info(): Check __glib_assert_msg for assertion messages,
131 too.
132 * REThread.py: Fix for Python 3.3.
133
1342.6.1 (2012-10-01):
135-------------------
136 * setup.py: Specify "-source 1.5" javac option as well, to avoid build failure
137 with OpenJDK 7.
138
1392.6 (2012-10-01):
140-----------------
141 * setup.py: Build java module with "-target 1.5" option, so that you can run
142 it with OpenJDK 6 even if you build with OpenJDK 7.
143 * report.py, add_proc_info(): Show if $XDG_RUNTIME_DIR is set.
144 * Add apport/crashdb_impl/debian.py: Initial crash database implementation for
145 the Debian BTS. Add configuration for it to etc/apport/crashdb.conf. Thanks
146 Ritesh Raj Sarraf!
147 * test_python_crashes.py: Robustify "$PYTHONPATH in ProcEnviron" check.
148
1492.5.3 (2012-09-28):
150-------------------
151 * data/apportcheckresume: Open report file in binary mode. (LP: #1040353)
152 * packaging-apt-dpkg.py: When throwing ValueErrors, show the non-existing
153 package name. This makes it easier to debug such crashes.
154 * launchpad.py: Replace characters from tags which are not allowed by
155 Launchpad with '.' (LP: #1029479)
156 * launchpad.py: Temporarily disable filing private bugs in the test suite, to
157 work around the SSLHandshakeError error when downloading private attachments
158 from staging.
159 * hookutils.py, attach_root_command_outputs(): Ignore IOError crash about
160 nonexisting files, which can happen if the user dismisses authorization.
161 (LP: #1051222)
162 * report.py, search_bug_patterns(): Fix bug patterns containing non-ASCII
163 characters. Thanks Brian Murray.
164 * apport_python_hook.py: Capture $PYTHONPATH and $PYTHONHOME environment
165 variables for Python crashes. Thanks Brian Murray.
166
1672.5.2 (2012-09-17):
168-------------------
169 * test/run: Ignore root_info_wrapper with pyflakes.
170 * packaging-apt-dpkg.py: Add recommended packages to "Dependencies:" field.
171 (LP: #1014428)
172 * test_hookutils.py, test_in_session_of_problem(): Use year 2038 for a future
173 date instead of 2211, as current Python 3.2 now crashes with an
174 OverflowError on 32 bit machines with later years.
175 * Fix crash on broken .desktop files. (LP: #1039889)
176 * apport-gtk: For console program crashes, say "stopped" instead of "closed".
177 Add a subtitle label with a hint about hanging programs. Thanks Matt Price
178 and Matthew Paul Thomas!
179 * report.py: Fix crash on determination of Python module path when examining a
180 crash of "python -m ...".
181 * apport-kde: Fix crash with undefined QString under Python 3. Thanks Jonathan
182 Riddell! (LP: #1028984)
183 * launchpad.py: Add missing "Pre-release Freeze" status. Thanks Brian Murray!
184 * report.py, _check_bug_pattern(): Fix bug pattern matching against binary
185 values. Thanks Brian Murray for the original patch. (LP: #1016380)
186
1872.5.1 (2012-08-22):
188-------------------
189 * data/root_info_wrapper: Turn into a real file, a symlink can cause some
190 packaging problems.
191
1922.5 (2012-08-22):
193-------------------
194Bug fixes:
195 * test_recoverable_problem.py: Fix test for calling test runner with absolute
196 path.
197 * packaging-apt-dpkg.py: Fix crash on writing virtual_mapping.db when running
198 with --sandbox-dir and -S system or giving no --cache.
199 * REThread.py: Fix re-raising of exceptions in Python 3. Thanks Martin
200 Packman! (LP: #1024836)
201 * apport-retrace: Keep compressed CoreDump from .crash files instead of
202 uncompressing them into memory. This dramatically reduces memory usage.
203 (LP: #981155)
204
205Improvements:
206 * Add an apport.memdbg() function which prints out current memory usage if
207 APPORT_MEMDEBUG is set. Annotate apport-retrace with it.
208 * hookutils.py: Allow specifying a list of profile names when using
209 attach_mac_events(). Thanks Marc Deslauriers.
210 * hookutils.py, attach_root_command_outputs() and root_command_output(): Drop
211 usage of sudo, kdesudo, and gksu, and replace with pkexec. For
212 attach_root_command_outputs(), use a wrapper and proper .policy file which
213 explains the action and works under every environment; thus
214 attach_root_command_outputs() is preferred over root_command_output() now,
215 as it provides a better user experience.
216 * Package hooks which want to send the report to a different crash database
217 than "default" can now also give the database specification itself in the
218 "CrashDB" field, not just the DB name. With this, packages do not need to
219 ship a separate /etc/apport/crashdb.conf.d/ file. Please see
220 doc/package-hooks.txt for details. (LP: #551330)
221 * report.py, add_hooks_info(): If reporting against a package/program in /opt,
222 also search for package hooks in the corresponding /opt directory. This
223 allows such hooks to define a custom crash database and thus report bugs
224 against their own project instead of against the distribution.
225 (LP: #1020503)
226
2272.4 (2012-07-18):
228-----------------
229Improvements:
230 * apport_python_hook.py: For org.freedesktop.DBus.Error.ServiceUnknown
231 exceptions, add a 'DbusErrorAnalysis' field to the report which points out
232 whether any .service file provides the service it tried to talk to, and
233 whether the processes for those are running. This helps to determine the
234 root cause for such errors (missing dependencies, broken .service files,
235 talking to the wrong bus, etc.) (LP: #1020572)
236 * hookutils.py, attach_alsa(): Use alsa-info.sh when available. Thanks David
237 Henningson.
238 * Add new "RecoverableProblem" report type for problems which the application
239 can handle, but still wishes to notify the user and send a problem report
240 about. As an example, the application may wish to notify the user because
241 handling the error resulted in degraded functionality. The user interface
242 may fail to load items, or the action just performed may not return any
243 data. Applications call /usr/share/apport/recoverable_problem with a
244 string of arbitrary NUL-separated key/value pairs that are added to the
245 report. Thanks Evan Dandrea!
246
247Bug fixes:
248 * ui tests, test_wait_for_pid(): Fix eternal hang when running as root.
249 * testsuite: Fix ResourceWarnings when running with Python 3.
250 * test_python_crashes.py: Fix race condition in timeout test.
251 * launchpad.py: Fix setting of 'Medium' importance on duplicate checking.
252 * apport-retrace: Fix StacktraceSource generation for relative --cache paths.
253 * crashdb.py, check_duplicate(): Do not try to mark a bug as duplicate of
254 itself. This can happen when re-processing a previously retraced bug.
255 * apport-retrace: Fix UnicodeDecodeError when encountering a non-ASCII source
256 code file and running under a non-UTF-8 locale.
257
2582.3 (2012-07-09):
259-----------------
260Improvements:
261 * launchpad.py: Rework test suite to not use Launchpad's +storeblob facility
262 at all any more. It almost never works on staging and is horribly slow. Fake
263 the bug creation from a blob by manually creating the comment and
264 attachments ourselves, and just assume that storeblob works on production.
265 Also change the structure to allow running every test individually.
266 * crash-digger: Add --crash-db option to specify a non-default crash databae
267 name. (LP: #1003506)
268 * apport-gtk: Add --hanging option to specify the process ID of a hanging
269 application. If the user chooses to report this error, apport will terminate
270 the pid with SIGABRT, otherwise it will send SIGKILL. The normal core pipe
271 handler will be used to process the resulting report file, with a .hanging
272 file in /var/crash to separate these from regular crashes.
273
274Bug fixes:
275 * apport: Also treat a binary as modified if the /proc/pid/exe symlink does
276 not point to an existing file any more. (LP: #984944)
277 * Fix PEP-8 violations picked up by latest pep8 checker.
278 * ui.py: Do not ignore certain exceptions during upload which are not likely
279 to be a network error.
280 * launchpad.py: Recongize Launchpad projects for bug query and marking
281 operations. (LP: #1003506)
282 * packaging-apt-dpkg.py: Fix get_source_tree() to work with apt sandboxes.
283 * apport-retrace: Turn StacktraceSource generation back on, now that it works
284 with the current sandboxing.
285 * launchpad.py: Ensure that upload chunk size does not underrun. (LP: #1013334)
286 * apport_python_hook: Fix UnicodeEncodeError crash with Python 2 for
287 exceptions with non-ASCII characters. (LP: #972436)
288 * test_ui_kde.py: Fix occasional test failure in test_1_crash_details if the
289 application ends before the "is progress bar visible" check is done.
290
2912.2.5 (2012-06-21):
292-------------------
293 * launchpad.py: Fix str vs. bytes crash for already known bugs, take 2. (LP: #1015788)
294 * apport/ui.py, get_desktop_entry(): Disable interpolation, to correctly read
295 desktop files with % signs. (LP: #1014341)
296 * apport/ui.py: Fix rare crash if a report is already being updated in the
297 background when the UI tries to update a previous version. (LP: #949196)
298 * GTK and KDE UI tests: Avoid eternal hangs due to "this is not a distro
299 package" error messages.
300
3012.2.4 (2012-06-21):
302--------------------
303Bug fixes:
304 * test_apport_unpack.py: Fix test_unpack_python() test when running the
305 system-installed tests.
306 * data/java_uncaught_exception: Fix for Python 3.
307 * test_signal_crashes.py: Show crash reports in /var/crash/.
308 * test_crash_digger.py: Do not write crash reports of crash-digger into system
309 /var/crash, use a temporary directory.
310 * test/run: Wait for a previous xvfb server to finish before trying to start
311 one. This fixes a race condition in the KDE UI tests which often failed to
312 start up xvfb.
313 * apport-cli: Unbreak "keep" option. (LP: #1007826)
314 * launchpad.py: Fix str vs. bytes crash for already known bugs. (LP: #1015788)
315
3162.2.3 (2012-06-15):
317-------------------
318Bug fixes:
319 * test/run: Do not run pep8 and pyflakes when running against the sytem
320 installed Apport.
321 * test_backend_apt_dpkg.py: For the "are we online" check, verify that we can
322 download from http://ddebs.ubuntu.com/, not just whether we have a default
323 route. The latter is not sufficient for e. g. buildd environments which are
324 online, but are restricted by proxies or firewalls.
325 * test_report.py: Call "sync" after test script write core dumps, to ensure
326 that subsequent operations have a complete one.
327 * test_signal_crashes.py: Drop the broken and obsolete test_local_python()
328 test. Instead, add two tests which check proper logging.
329 * launchpad.py: Fix urlopen() for Python3. Thanks Steve Langasek.
330 * test/run: Run the tests under LC_MESSAGES=C, to avoid failing tests on
331 translated strings.
332
3332.2.2 (2012-06-13):
334-------------------
335Improvements:
336 * testsuite: Run with Python 3 by default. To test with Python 2, run
337 "PYTHON=python2 test/run".
338
339Bug fixes:
340 * apport: Redefine sys.std{out,err} when redirecting output, as they are None
341 in Python 3 when being called from the kernel.
342 * test/test_signal_crashes.py: Clean up unexpected core dumps on failed test
343 cases.
344 * apport-gtk: Fix crash when closing the crash dialog while the information is
345 being collected.
346 * hookutils.py, xsession_errors(): Fix crash when running under a non-UTF8 locale.
347 * data/apport: Do not use sys.stdin.fileno(), it is invalid when being called
348 from the kernel with Python 3.
349 * data/apport: When core dumps are enabled, read them from the written report
350 instead of directly from stdin (and then reading the written core file into
351 the .crash report). If the core file size is limited, we otherwise stop
352 reading the core dump from the kernel in the middle and have no (or a
353 broken) core dump to be put into the report.
354 * data/apport: Properly close the written crash report before changing its
355 permissions to be readable. This prevents having crash reporting UI from
356 looking at incomplete .crash files.
357
3582.2.1 (2012-06-11)
359------------------
360Bug fixes:
361 * apport-cli: Port to work with Python 3.
362 * setup.py: When fixing hashbang lines of installed scripts, only include the
363 major Python version.
364 * hookutils.py, read_file, attach_file(), attach_file_if_exists(): Convert
365 file contents to unicode if the contents is UTF-8, or the newly added
366 force_unicode argument is True.
367 * hooktuils, command_output(): Convert output to unicode by default, and add
368 a "decode_utf8" parameter to disable this.
369 * hookutils.py, recent_logfile(): Fix fd leak.
370 * data/apport: Do not assume that sys.stdout and sys.stderr always have a
371 name; they can be None in Python 3.
372 * data/dump_acpi_tables.py: Fix for Python 3.
373
3742.2 (2012-06-11)
375----------------
376Improvements:
377 * Clean up module imports.
378 * test/run: Run pyflakes, if available.
379 * package_hook: Add --tags option. Thanks to Brian Murray.
380 * launchpad.py: Drop the external multipartpost_handler.py (which is not
381 portable to Python 3) and replace it with using the standard email module.
382 * launchpad.py: Also work with Python 3. Deal gracefully with a missing
383 "launchpadlib" module; this is not yet available for Python 3, but not
384 required for client-side reporting.
385 * apport-kde: Port to work with Python 3.
386
387Bug fixes:
388 * apport-retrace: Fix crash when using the --procmaps option.
389 * setup.py: Update hashbang lines of installed scripts in data directory to
390 the python executable setup.py was run with, similar to what already happens
391 to scripts installed to ../bin/.
392
3932.1.1 (2012-05-30)
394------------------
395Improvements:
396 * launchpad.py: When closing a bug as a duplicate, copy some well-known tags
397 to the master bug. Thanks Brian Murray.
398 * launchpad.py: Set importance of Python crash reports to "Medium" by default,
399 similar to signal crashes. Thanks Brian Murray.
400 * hookutils.py: Add attach_default_grub() convenience function from the grub2
401 package hook so it can be used by other packages. Thanks Brian Murray.
402 * launchpad.py: Make Launchpad bug subscription user/team configurable: The
403 initial subscriber after filing a bug can be set with the
404 "initial_subscriber" crashdb option, and the team which gets subscribed
405 after retracing with "triaging_team". (LP: #980726)
406
407Bug fixes:
408 * report.py: Do not change the SourcePackage: field if the binary package is
409 not installed and does not exist. This fixes source package hooks to
410 actually work in some cases where source and binary package names overlap.
411 (part of LP: #993810)
412 * apport-gtk, apport-kde: Avoid collecting information twice in "bug update"
413 mode. This caused a crash in cases where the source package in a bug report
414 does not correspond to an installed binary package. (LP: #993810)
415
4162.1 (2012-05-18)
417----------------
418Improvements:
419 * packaging.py, install_packages(): Add permanent_rootdir flag and if set,
420 only unpack newly downloaded packages. Implement it for the apt/dpkg
421 backend. Thanks Evan Dandrea.
422 * apport-retrace: Add --sandbox-dir option for keeping a permanent sandbox
423 (unpacked packages). This provides a considerable speedup. Thanks Evan
424 Dandrea.
425 * crash-digger: Add --sandbox-dir option and pass it to apport-retrace.
426 * Fix the whole code to be PEP-8 compatible, and enforce this in test/run by
427 running the "pep8" tool.
428 * GTK UI tests: Ensure that there are no GLib/GTK warnings or criticals.
429 * Support Python 3. Everything except the launchpad crashdb backend now works
430 with both Python 2 and 3. An important change is that the load(),
431 write(), and write_mime() methods of a ProblemReport and apport.Report
432 object now require the file stream to be opened in binary mode.
433 * data/apport: Ignore a crash if the executable was modified after the process
434 started. This often happens if the package is upgraded and a long-running
435 process is not stopped before. (LP: #984944)
436 * Add test cases for apport-unpack.
437 * apport-retrace: Add information about outdated packages to the
438 "RetraceOutdatedPackages" field.
439 * ui.py: Drop python-xdg dependency, use ConfigParser to read the .desktop
440 files.
441
442Bug fixes:
443 * apport-gtk: Work around GTK crash when trying to set pixmap on an already
444 destroyed parent window. (LP: #938090)
445 * data/dump_acpi_tables.py: Fix crash on undefined variable with non-standard
446 tables. (LP: #982267)
447 * backends/packaging-apt-dpkg.py: Fix crash if a package is installed, but has
448 no candidates in apt. (LP: #980094)
449 * data/general-hooks/generic.py: Bump minimum free space requirement from 10
450 to 50 MB. 10 is not nearly enough particularly for /tmp. (LP: #979928)
451 * hookutils.py, recent_logfile(): Use a default limit of 10000 lines and call
452 "tail" instead of reading the whole file. This protects against using up all
453 memory when there are massive repeated log messages. (LP: #984256)
454 * apport-gtk: Do not assume that an icon requested for size 42 actually
455 delivers size 42; some themes do not have this available and deliver a
456 smaller one instead, causing overflows. Also, copy the image as
457 gtk_icon_theme_load_icon() returns a readonly result which we must not
458 modify. (LP: #937249)
459 * ui.py: Don't show the duplicate warning when the crash database does not
460 accept the problem type, and they are just being sent to whoopsie. Thanks
461 Evan Dandrea. (LP: #989779)
462 * report.py: Correctly escape the file path passed to gdb.
463 * apport-gtk, apport-kde: Do not show the information collection progress
464 dialog if the crash database does not accept this kind of report. In that
465 case whoopsie will upload it in the background and the dialog is not
466 necessary. (LP: #989698)
467
4682.0.1 (2012-04-10)
469------------------
470Bug fixes:
471 * test_ui_gtk.py: Disable package hooks for the tests, as they might ask for
472 sudo passwords and other interactive bits, and thus make the tests hang.
473 * test_backend_apt_dpkg.py: Fix checks for the installation of -dbgsym
474 packages. This should always happen, as the sandboxes have a ddeb apt
475 source. Only make it conditional on the system apt sources in the "use
476 system config" test.
477 * test_report.py: Sleep a bit after calling our test crash script, to ensure
478 the kernel has time to finish writing the core file.
479 * generic package hook: Also check /tmp for enough space. Thanks Brian Murray.
480 (LP: #972933)
481 * problem_report.py, write_mime(): Fix regression from version 1.95: Add a
482 value as attachment if it is bigger than 1000 bytes, not if it is bigger
483 than 100. (LP: #977882)
484
485Improvements:
486 * packaging-apt-dpkg.py: Avoid constructing and updating the apt.Cache()
487 objects multiple times, to speed up retracing. Thanks Evan Dandrea.
488 (LP: #973494)
489
4902.0 (2012-03-30)
491----------------
492This is the final 2.0 release, featuring the overhauled and simplified GUI,
493support for whoopsie-daemon, and client-side duplicate checking.
494
495Bug fixes:
496 - report.py, anonymize(): Only replace whole words, not substrings.
497 (LP: #966562)
498 - apport_python_hook.py: Fix filtering of org.freedesktop.DBus.Error.NoReply
499 exceptions. (LP: #958575)
500 - crashdb.py: When publishing the crash database, cut hash file names after
501 quoting, to avoid that the quoting causes them to become too long.
502 (LP: #968070) This also uncovered that known() did not actually find any
503 signature which contained an URL-quoted character, therefore breaking
504 client-side duplicate checking in a lot of cases. Double-quote the file name
505 now, as urlopen() unquotes it.
506 - Add a new crash database option "problem_types" and a CrashDatabase method
507 "accepts(report)". This can be used to stop uploading particular problem
508 report types to that database. E. g. a distribution might decide to not get
509 "Crash" reports any more after release. Document the new option in
510 doc/crashdb-conf.txt.
511 - ui.py: Do not upload a report if the crash database does not accept the
512 report's type. This behaviour is not really correct, but necessary as long
513 as we only support a single crashdb and have whoopsie hardcoded. Once we
514 have multiple crash dbs, we need to not even present the data if none of the
515 DBs wants the report. See LP #957177 for details. (LP: #968121)
516 - ui.py: Do not short-circuit information collection if report already has a
517 "DistroRelease" field, as the GUIs add that in some cases. Check for
518 "Dependencies" instead. This fixes information collection for kernel
519 problems (which now has a full GTK GUI test case). (LP: #968488)
520
5211.95 (2012-03-22)
522-----------------
523Bug fixes:
524 - ui.py: Ensure that the report file is readable by the crash reporting daemon
525 after running through collect_info(). Thanks Evan Dandrea.
526 - apport-gtk, apport-kde: Set the window title to the distribution name, as
527 per http://wiki.ubuntu.com/ErrorTracker#error . Thanks Evan Dandrea.
528 (LP: #948015)
529 - test/run: Ignore obsolete packages on the system, to avoid breaking the GUI
530 tests due to them.
531 - apport-gtk, apport-kde: When reporting a "system crash", don't say "... of
532 this program version", but "...of this type", as we don't show a program
533 version in the initial dialog (https://wiki.ubuntu.com/ErrorTracker#error)
534 (LP: #961065)
535 - problem_report.py, write_mime(): Do not put a key inline if it is bigger
536 than 1 kB, to guard against very long lines. (LP: #957326)
537 - etc/cron.daily/apport: Do not remove whoopsie's *.upload* stamps every day,
538 only if they are older than a week. whoopsie comes with its own cron job
539 which deals with them. Thanks Steve Langasek. (LP: #957102)
540 - report.py, mark_ignore(): Fix crash if executable went away underneath us.
541 (LP: #961410)
542 - apport-gtk: Do not compare current continue button label against a
543 translated string. Instead just remember whether or not we can restart the
544 application. (LP: #960439)
545 - hookutils.py, command_output(): Add option to keep the locale instead of
546 disabling it.
547 - hookutils.py, command_output(): Actually make the "input" parameter work,
548 instead of causing an eternal hang. Add tests for all possible modes of
549 operation.
550 - hooktuils.py: Change root_command_output() and attach_root_command_outputs()
551 to disable translated messages (LC_MESSAGES=C) only as part of the command
552 to be run, not already for the root prefix command. This will keep the
553 latter (gksu, kdesudo, etc.) translated. (LP: #961659)
554 - apport-gtk: Cut off text values after 4000 characters, as Gtk's TreeView
555 does not get along well with huge values. KDE's copes fine, so continue to
556 display the complete value there. (LP: #957062)
557 - apport-gtk: Make details window resizable in bug reporting mode.
558 - crashdb.py, known(): Check the address signature duplicate database if the
559 symbolic signature exists, but did not find any result. (LP: #103083)
560 - ui.py: Run anonymization after checking for duplicates, to prevent host or
561 user names which look like hex numbers to corrupt the stack trace.
562 (LP: #953104)
563 - apport-gtk: Require an application to both have TERM and SHELL in its
564 environment to consider it a command line application that was started by
565 the user. (LP: #962130)
566 - backends/packaging-apt-dpkg.py, _check_files_md5(): Fix double encoding,
567 which caused UnicodeDecodeErrors on non-ASCII characters in an md5sum file.
568 (LP: #953682)
569 - apport-kde, apport-gtk: Only show "Relaunch" if the report has a
570 ProcCmdline, otherwise we cannot restart it. (LP: #956173)
571
572Improvements:
573 - hookutils.py, attach_alsa(): Add the full "pacmd list" output instead of
574 just sinks and sources. Thanks David Henningsson.
575 - apport-gtk, apport-kde: Show the ExecutablePath while we're collecting data
576 for the crash report. Thanks Evan Dandrea. (LP: #938707).
577
5781.94.1 (2012-03-07)
579-------------------
580Bug fixes:
581 - test_ui_kde.py: Re-enable inadvertently disabled "bug report for uninstalled
582 package" test.
583 - ui.py, collect_info(): Do not assume that reports have a "ProblemType"
584 field. This is not the case when updating a bug. (LP: #947519)
585 - apport-cli: Consistently handle unicode vs. byte arrays. (LP: #946207)
586 - report.py, anonymize(): Fix crash when the hostname or user name contain
587 non-ASCII characters. (LP: #945230)
588 - packaging-apt-dpkg.py: Fix UnicodeDecodeError on unexpected md5sum output.
589 (LP: #921037)
590 - apport-gtk: Fix handling of non-ASCII strings in message dialogs.
591 (LP: #865394)
592
5931.94 (2012-03-02)
594-----------------
595Bug fixes:
596 - apport: Set the group of written reports to "whoopsie" if that group exists.
597 - Fix tests to run properly against the system-installed modules and binaries.
598 - test/run: Run under LC_MESSAGES=C to avoid test failures due to translated
599 strings.
600 - general-hooks/generic.py: Also attach xsession-errors for programs that link
601 to libgtk-3.
602 - launchpad.py: Properly handle "Expired" status, to avoid marking new bugs as
603 duplicates of expired ones. (LP: #941854)
604 - apport: Fix crash if the "whoopsie" group does not exist. (LP: #942326)
605 - report.py, crash_signature(): Do not put "<module>" frames into Python crash
606 signatures that happen outside of function/method calls. Fall back to the
607 file/line number as a frame description instead. This will do a much better
608 job at disambiguating e. g. different ImportError crashes. (LP: #920403)
609 - Make "binary changed since the time of the crash" error message more
610 comprehensible, thanks Paolo Rotolo. (LP: #942830)
611 - crashdb.py, check_duplicate(): It can happen that a bug gets identified as
612 being a duplicate of bug S by symbolic signatures and a duplicate of bug A
613 by address signatures. Empirical evidence shows that this is due to the
614 unavoidable jitter in stack traces (A and S not being identified as
615 duplicates as their signatures differ slightly) and not a logic error. So
616 instead of erroring out, duplicate all three bugs and keep the lowest number
617 as the master ID. (LP: #943117)
618 - Revert the usage of multiple nested threads during data collection, and
619 switch back to only using one UI thread. The UI implementations can, and now
620 do, decide between showing a spinner and showing a progress dialog in the
621 ui_*_info_collection_progress() methods. This fixes libX11 crashes when
622 multiple UI threads do changes concurrently (LP: #901675), and also avoids
623 multi-thread induced crashes in Pango (LP: #943661). The removal of the
624 collect() method also fixes the new crashes in it. (LP: #942098, #939803)
625 - ui.py, get_desktop_entry(): Fix crash on uninstalled package. (LP: #940984)
626 - data/unkillable_shutdown: Fix crash on race condition when PID goes away
627 while the report is created. (LP: #546369)
628 - apport/hookutils.py, pci_devices(): Fix crash on unexpected lines from
629 lspci. (LP: #904489)
630 - Drop hardcoded "Ubuntu" words again which crept in with the whoopsie support
631 merge. Use the DistroRelease: field.
632 - apport-kde: Fix Home page URL in KApplication metadata.
633 - apport-gtk: Fix resizability and size after hiding details. (LP: #405418)
634
635Improvements:
636 - test/run: Drop "local" argument. This now tests against the source tree when
637 run in the source tree root, and against the system libraries/programs when
638 run from anywhere else.
639 - test/run: Consider command line arguments as test names and only run those
640 when given. Also support just running a single test.
641 - testsuite: Force the skipping of online tests when $SKIP_ONLINE_TESTS is
642 set.
643 - hookutils.py, xsession_errors(): Add a reasonable default pattern which
644 matches glib-style warnings, errors, criticals etc. and X window errors.
645 In data/general-hooks/generic.py, call it with that default instead of the
646 rather incomplete custom pattern. (LP: #932660)
647 - packaging.py: Add get_package_origin() method, and implement it for
648 apt-dpkg.
649 - report.py, add_package_info(): Add "[origin: ...]" tag to "Package" and
650 "Dependencies" fields for any package which is not native to the
651 distribution. If any such package is present, tag the report with
652 "third-party-packages" in data/general-hooks/generic.py. (LP: #927912)
653 - apport/packaging.py: Add get_uninstalled_package() method as a helper method
654 for the test suite. Use it instead of a hardcoded Debian/Ubuntu specific
655 name in test/test_hooks.py.
656 - test/test_ui_{gtk,kde}.py: Add test cases for complete UI workflow runs for
657 reporting a bug against an installed/uninstalled package, and reporting a
658 crash with and without showing details. This reproduces the recent crashes
659 like LP #901675 or LP #943661.
660 - test_ui.py: Add a test case for reporting a complete report on uninstalled
661 package. This happens when reporting a problem from a different machine
662 through copying a .crash file.
663 - test/run: Add a test that there are no hardcoded "Ubuntu" words in the
664 source. The code should use the DistroRelease: field or lsb_release.
665
6661.93 (2012-02-23):
667------------------
668Bug fixes:
669 - apport-gtk: Fix crash on nonexisting icon. Thanks Evan Dandrea.
670 (LP: #937354)
671 - ui.py, open_url(): Revert back to calling sudo instead of dropping
672 privileges ourselves; with the latter, calling firefox as the sudo'ing user
673 fails. (LP: #916810, #938128)
674 - ui.py: Fix aborting with "AssertionError" if the report is already known,
675 but without an URL. (LP: #938778)
676 - launchpad.py: If a bug is already known, but the report is private, do not
677 send the report. There is little sense piling up lots of duplicates.
678 (LP: #938700)
679 - test/crash: Fix regression of test_crash_apport(), consider $TERM a
680 non-sensitive variable.
681 - ui.py: Fix test failures for data collection progress, they are not expected
682 to happen for "ProblemType: Crash" any more (happens in the background
683 during sending, or if user clicks on "Show Details").
684 - test/hooks: Use a package from Debian/Ubuntu main, so that this works better
685 during package builds on build servers.
686 - test/python: Do not assume that /var/crash/ exists. Use /var/tmp/ for the
687 fake binaries instead.
688 - data/general-hooks/parse_segv.py: Fix test case name.
689 - ui.py: Fix crash on invalid core dumps. (LP: #937215)
690 - launchpad.py: Fix crash on unicode report titles. (LP: #896626)
691
692Improvements:
693 - apport-gtk: Show the most interesting fields first in the details view.
694 - do-release: Call pyflakes and abort on errors other than unused imports.
695 - Move all test suites out of the code modules into test/test_<module>.py.
696 This avoids having to load it every time the program runs, and also allows
697 running the tests against the installed version of Apport.
698 - Clean up the other executable test script in test/* and change them to the
699 same structure as the module tests.
700
7011.92 (2012-02-20):
702------------------
703Bug fixes:
704 - ui.py: Fix wrong creation of "~" folder instead of expanding it to home
705 directory when using "Examine locally". Thanks Jason Conti! (LP: #909149)
706 - Replace file() calls with open() for Python 3 compatibility. Thanks Colin
707 Watson!
708 - launchpad.py: Avoid sending tag names with upper case. (LP: #924181)
709 - report.py, crash_signature_addresses(): Fix crash if report does not have
710 "Signal".
711 - apport-gtk: Fix resize handling of expander in details window. Thanks Thomas
712 Bechtold! (LP: #930562)
713 - Clean up unnecessary imports. Thanks Evan Dandrea!
714
715Improvements:
716 - man/apport-bug.1: Mention where crash files are stored. Thanks David
717 Kastrup.
718 - hookutils.py, attach_hardware(): Sort ProcModules, thanks Brian Murray.
719 - launchpad.py: Keep "Dependencies" attachment in duplicates. Thanks Brian
720 Murray.
721 - Reorganize the GNOME and KDE user interface to do the crash notifications
722 and detail browser in a single dialog. Add test/gtk and test/kde tests to
723 check expected dialog layout for different cases. Thanks Evan Dandrea!
724 - Add support for the whoopsie-daisy crash reporting daemon by creating
725 zero-byte .upload file stamps for crash reports. Thanks Evan Dandrea!
726
7271.91 (2012-01-18):
728------------------
729Bug fixes:
730 - crashdb.py, check_duplicate(): If a crash has a signature but no existing
731 duplicate in the DB, also check for an existing address signature duplicate
732 in the DB.
733 - apport-retrace: Use DistroRelease specific subdirectory of the cache dir for
734 mapping a file to a package, as these maps are release specific.
735 - packaging-apt-dpkg.py: Refresh Contents.gz cache if it is older than one
736 day.
737 - crashdb.py: Ensure that address_signature duplicate db table does not have
738 multiple identical signatures by making it a primary key. Bump the db format
739 to "3". Existing databases need to be migrated manually as SQLite does not
740 allow adding a "PRIMARY KEY" constraint to existing tables.
741 - crashdb.py: Do not add a new address signature entry if one already exists.
742 - apport-cli: Fix UnicodeDecodeError on unicode report values. (LP: #275972)
743 - launchpad.py: Only set bug task importance if it is undecided.
744 - apport-retrace: Fix "an useful" typo. (LP: #911437)
745 - report.py: Filter out frames which are internal kernel/glibc implementation
746 details and not stable across duplicates. In particular, filter out
747 __kernel-syscall() and the SSE stubs.
748 - crashdb.py: Remove debugging leftover which completely disabled bug pattern
749 checking.
750 - report.py: Update reading AssertionMessage. Current (e)glibc turned
751 __abort_msg from a simple static string into a struct.
752
753Improvements:
754 - Change permissions of .crash files from 0600 to 0640, so that /var/crash can
755 be made g+s and crash handling daemons can access those.
756 - Python exceptions: Blacklist DBus.Error.NoReply. It does not help to get
757 these traces from the client-side application, you need the actual exception
758 in the D-Bus server backend instead. (LP: #914220)
759 - Support /etc/apport/whitelist.d/ similarly to /etc/apport/blacklist.d/, for
760 cases like installer environments where only crashes of a few selected
761 programs should be reported.
762
7631.90 (2011-11-24):
764------------------
765First beta release of 2.0 which introduces client-side duplicate checking.
766
767Bug fixes:
768 - backends/packaging-apt-dpkg.py: Fix another test case failure when ddeb
769 repository is not enabled.
770 - backends/packaging-apt-dpkg.py: Fix handling of explicit cache directory
771 name when it is a relative path.
772 - launchpad.py: Only query for bugs after 2011-08-01, to avoid timeouts.
773 - ui.py: Also anonymize standard bug title. (LP: #893863)
774 - launchpad.py: Current Launchpad cannot have private bugs which affect
775 multiple projects. Fix test suite accordingly.
776
777Improvements:
778 - report.py: Break out new method stacktrace_top_function() from
779 standard_title(), so that other parts of the code can use this as well.
780 - launchpad.net: When sending retraced results back to the bug report, update
781 the topmost function in the bug title. (LP: #869970)
782 - report.py, add_gdb_info(): Add a new field "StacktraceAddressSignature"
783 which is a heuristic signature for signal crashes. This should be used if
784 crash_signature() fails, i. e. the Stacktrace field does not have enough
785 symbols. This can be used to check for duplicates on the client side,
786 provided that the crash database server supports querying for these.
787 Do not expose this field when uploading to crash databases though, as it can
788 be recomputed from the already existing information (ProcMaps and
789 Stacktrace) and thus would just clutter the reports.
790 - crashdb.py: Add a table "version" with the database format version. Add
791 automatic upgrading to the most current format.
792 - crashdb.py: Put address signatures from reports checked with
793 check_duplicate() into the duplicate database, so that implementations of
794 known() can check for these.
795 - dupdb-admin: Add "publish" dupdb-admin command which exports the
796 duplicate database into a set of text files suitable for WWW publishing.
797 - crashdb.py: Add new method "known(report)" which can be implemented to check
798 if the crash db already knows about the crash signature. If so, the report
799 will not be uploaded, and instead the user will be directed to the existing
800 report URL (if available), similar to bug patterns. The default
801 implementation checks this format, if the crash database is initialized with
802 a "dupdb_url" option pointing to the exported database.
803 - launchpad.py: Override known() to check if the master bug is actually
804 accessible by the reporter, and is not tagged with "apport-failed-retrace"
805 or "apport-request-retrace"; otherwise file it anyway.
806 - crash-digger: Add --publish-db option to conveniently integrate duplicate DB
807 publication (similar to dupdb-admin publish) into retracer setups.
808 - launchpad.py: Attach updated stack traces from a duplicate to the master bug
809 if it failed retracing previously or has an "apport-request-retrace" tag.
810 (LP: #869982)
811 - apport-kde, apport-gtk: Support the "Annotation" field for custom dialog
812 titles for "Crash" and "Package" problem types as well, not just for
813 "Kernel". (LP: #664378)
814
8151.26 (2011-11-11):
816------------------
817Bug fixes:
818 - backends/packaging-apt-dpkg.py: Port to current python-apt API.
819 - hookutils.py: Fix path_to_key() to also work with unicode arguments.
820 - test/crash: Exit successfully if apport is not enabled in the system. This
821 allows packages to run the test suite during build.
822 - report.py, add_proc_info(): Correctly handle "python -m <modulename>"
823 programs as being interpreted and determine the appropriate module path.
824 - Fix some import statements to also work for the system-installed test suite.
825 - test/run: Fix testing data/general-hooks/parse_segv.py when called in
826 system-installed mode.
827 - apport/ui.py: Clean up test .crash file after test cases.
828 - Fix tests when running as root.
829 - setup.py: Fix crash when "javac -version" fails.
830 - README: Update command for one-time enablement.
831 - backends/packaging-apt-dpkg.py: Fix interleaving usage of install_packages()
832 with other operations such as get_version(), by resetting the apt status
833 after building and using the sandbox.
834 - report.py test suite: Remove requirement that $USER is set, which makes it
835 easier to run this from package build environments.
836 - apport/ui.py, test/crash: Use "yes" as test process instead of "cat". The
837 former is less likely to run already, and does not depend on having a stdin,
838 so it runs better in test environments like autopkgtest.
839 - backends/packaging-apt-dpkg.py: Fix tests if system does not have a dbgsym
840 apt source.
841
842Improvements:
843 - Ignore a crash if gnome-session is running and says that the session is
844 being shut down. These often die because X.org or other services are going
845 away, are usually harmless, and just cause a lot of clutter in bug trackers.
846 (LP: #460932)
847 - test/crash: Rewrite using Python's unittest, to be in line with other tests,
848 and be easier to maintain and extend.
849
8501.25 (2011-11-02):
851------------------
852Improvements:
853 - Add new response "Examine locally" to presenting the report details, which
854 runs apport-retrace in the chosen mode in a terminal. This should be made
855 available for crash reports if apport-retrace and a Terminal application are
856 installed; add an abstrace UI method for this. (LP: #75901)
857 - apport-gtk: Add "Examine locally..." button, and implement
858 ui_run_terminal().
859 - apport-cli: Add "Examine locally..." responses, and implement
860 ui_run_terminal().
861 - apport-cli: Greatly speed up displaying large reports. This also changes the
862 format to avoid indenting each line with a space, and visually set apart the
863 keys in a better way.
864 - apport_python_hook.py: Move tests out of this file into test/python, to
865 avoid having to parse the unit tests at each Python startup.
866 - test/python: Also make tests work if Python hook is not installed in
867 system's sitecustomize.py.
868 - packaging.py: Add get_modified_conffiles() API, and implement it in
869 packaging-apt-dpkg.py.
870 - hookutils.py: Add attach_conffiles().
871 - hookutils.py: Add attach_upstart_overrides().
872
873Bug fixes:
874 - launchpad.py: Remove "Ubuntu" in bug response, replace with "this software".
875 (LP: #883234)
876 - apport-kde: Rearrange order of imports to get intended error message if
877 PyKDE is not installed.
878 - packaging-apt-dpkg.py: Ignore hardening-wrapper diversions, to make
879 gcc_ice_hook work if hardening-wrapper is installed.
880 - apport_python_hook: Respect $APPORT_REPORT_DIR.
881 - apport_python_hook: Limit successive crashes per program and user to 3 per
882 day, just like signal crashes. (LP: #603503)
883 - packaging-apt-dpkg.py: Skip online tests when there is no default route.
884 - ui.py: Fix test suite to not fail if system has some obsolete or non-distro
885 packages.
886
8871.24 (2011-10-19):
888------------------
889Bug fixes:
890 - backends/packaging-apt-dpkg.py, install_packages(): Also copy
891 apt/sources.list.d/ into sandbox.
892 - backends/packaging-apt-dpkg.py, install_packages(): Install apt keyrings
893 from config dir or from system into sandbox. (LP: #856216)
894 - packaging.py, backends/packaging-apt-dpkg.py: Define that install_packages()
895 should return a SystemError for broken configs/unreachable servers etc., and
896 fix the apt/dpkg implementation accordingly.
897 - apport-retrace: Don't crash, just give a proper error message if servers are
898 unreachable, or configuration files are broken. (LP: #859248)
899 - backends/packaging-apt-dpkg.py: Fix crash when /etc/apport/native-origins.d
900 contains any files. (LP: #865199)
901 - hookutils, recent_logfile(): Fix invalid return value if log file is not
902 readable. (LP: #819357)
903 - test/crash: Fix race condition in the "second instance terminates
904 immediately" check.
905 - hookutils.py: Replace attach_gconf() with a no-op stub. It used static
906 python modules like "gconf" which broke the PyGI GTK user interface, and
907 gconf is rather obsolete these days.
908 - ui.py, open_url(): Greatly simply and robustify by just using xdg-open. This
909 already does the right thing wrt. reading the default browser from GNOME,
910 KDE, XCE, and other desktops. (LP: #198449)
911 - data/general-hooks/generic.py: Only attach ~/.xsession_errors if the bug is
912 reported in the same XDG session as the crash happened. (LP: #869974)
913 - Ignore crashes for programs which got updated in between the crash and
914 reporting. (LP: #132904)
915 - Special-case crashes of 'twistd': Try to determine the client program and
916 assign the report to that, or fail with an UnreportableReason. (LP: #755025)
917 - apport-gtk: In bug update mode, make details dialog resizable and fix
918 default size. (LP: #865754)
919 - apport-gtk: Fix crash if report does not have ProcCmdline. (LP: #854452)
920 - hookutils.py, attach_wifi(): Anonymize ESSID and AP MAC from "iwconfig"
921 output. (LP: #746900)
922 - test/crash: Fix test failure if user is not in any system groups.
923 - test/crash: Change to /tmp/ for test crash process, to fix failure if the
924 user that runs the test suite cannot write into the current directory.
925 (LP: #868695)
926 - ui.py: Improve error message if package is not a genuine distro package.
927 Thanks to Ronan Jouchet. (LP: #559345)
928
929Improvements:
930 - apport-retrace: Add --timestamp option to prepend a timestamp to log
931 messages. This is useful for batch operations.
932 - crash-digger: Call apport-retrace with --timestamps, to get consistent
933 timestamps in log output.
934 - hookutils.py: Add two new functions attach_gsettings_package() and
935 attach_gsettings_schema() for adding user-modified gsettings keys to a
936 report. (LP: #836489)
937 - hookutils.py: Add new function in_session_of_problem() which returns whether
938 the given report happened in the currently running XDG session. This can be
939 used to determine if e. g. ~/.xsession-errors is relevant and should be
940 attached.
941
9421.23.1 (2011-09-29)
943-------------------
944Bug fixes:
945 - apport/crashdb.py: Ensure that duplicate table only has one entry per report
946 ID.
947 - apport-retrace: Pass correct executable path to gdb in --gdb with --sandbox
948 mode.
949 - apport-retrace: Do not leave behind temporary directories on errors.
950 - apport-retrace: Drop assertion failure for existance of "Stacktrace". This
951 isn't present in the case of gdb crashing, and there is not much we can do
952 about it. This should not break the retracer.
953 - apport/report.py: Unwind XError() from stack traces for the "StacktraceTop"
954 field, as they take a significant part of the trace. This causes bugs to be
955 duplicated which really have different causes.
956
9571.23 (2011-09-14)
958-----------------
959Improvements:
960 - crashdb.py, crash-digger, dupdb-admin: Drop the concept of "duplicate DB
961 consolidation". Such massive queries cause timeouts with e. g. Launchpad.
962 Instead, update the status of potential master bugs in the crash DB whenever
963 check_duplicate() is called.
964
965Bug fixes:
966 - launchpad.py: Fix crash in close_duplicate() if master bug was already
967 marked as a duplicate of the examined bug.
968 - problem_report.py, load(): Fix missing last character if the last line in a
969 multi-line field is not terminated with a newline.
970 - launchpad.py: Fix test_marking_python_task_mangle() check to work with
971 current Launchpad.
972 - apport-retrace: If the user did not specify a --cache directory, create a
973 shared one instead of letting the two install_packages() calls create their
974 own. This ensures that the apt and dpkg status is up to date, and avoids
975 downloading the package indexes multiple times. (LP: #847951)
976 - apport-retrace: Give proper error mesage instead of AssertionError crash if
977 a report does not contain standard Apport format data. (LP: #843221)
978 - fileutils.py, get_new_reports(): Fix crash if report file disappears in the
979 middle of the operation. (LP: #640216)
980 - apport/ui.py, load_report(): Intercept another case of broken report files.
981 (LP: #445142)
982 - apport/report.py, standard_title(): Escape regular expression control
983 characters in custom exception names. (LP: #762998)
984
9851.22.1 (2011-09-06)
986-------------------
987Improvements:
988 - dupdb-admin: Add "removeid" command.
989
990Bug fixes:
991 - dupdb-admin: Use the in-memory CrashDB implementation for simple operations
992 like dump or changeid, which do not require an actual backend. This makes
993 the command work in checkouts without a /etc/apport/crashdb.conf.
994 - dupdb-admin: Fix UnicodeEncodeError crash.
995 - launchpad.py: Fix crash if a crash report does not have a DistroRelease.
996 - Set the default "Apport" title for choice dialogs instead of the default
997 apport-gtk title. Thanks Robert Roth. (LP: #608222)
998 - apport-gtk: Update markup_escape_text() call to current glib. (LP: #829635)
999
10001.22 (2011-08-25)
1001-----------------
1002Improvements:
1003 - Completely rework apport-retrace to use gdb's "debug-file-directory" and
1004 "solib-absolute-prefix" settings and only unpack the necessary packages in a
1005 temporary directory. This makes it possible to use it in a running system
1006 without actually touching installed packages, does not need any root
1007 privileges, and stops the requirement of using chroots with fakechroot and
1008 fakeroot. This is a lot easier to maintain and use, and a lot faster, too.
1009 As a consequence, drop the chroot module, and update crash-digger
1010 accordingly. See "man apport-retrace" for the new usage.
1011 It is now also easier to port to other packaging backends, as a lot of the
1012 common logic moved out of the packaging API;
1013 packaging.install_retracing_packages() got dropped in favor of the simpler
1014 packaging.install_packages().
1015 - crash-digger: Show how many bugs are left in the pool with each new retrace.
1016
1017Bug fixes:
1018 - apport-gtk: Fix crash in GLib.markup_escape_text() call, regression from
1019 1.21.3. (LP: #828010)
1020 - launchpad.py: When searchTasks() times out, exit with 99 as this is a
1021 transient error.
1022 - crash-digger: Intercept OverflowError from downloaded compressed
1023 attachments.
1024
10251.21.3 (2011-08-17)
1026-------------------
1027Bug fixes:
1028 - gtk/apport-gtk.desktop.in: Also show in Unity. (LP: #803519)
1029 - apport-unpack: Fix crash on file errors.
1030 - Add apport.packaging.get_library_paths() interface and implement it for
1031 backends/packaging-apt-dpkg.py using dpkg multiarch directories. Use it in
1032 chroot.py.
1033 - hookutils.py: Don't attach empty values. Thanks Bryce Harrington.
1034 (LP: #813798)
1035 - apport-gtk: Correctly pass message dialog type.
1036 - apport-gtk: Fix GLib and GObject imports to be compatible with the future
1037 pygobject 3.0.
1038
1039Improvements:
1040 - hookutils.py: Add attach_mac_events() for reporting logs of MAC systems.
1041 Looks for AppArmor messages for now. Thanks Marc Deslauriers!
1042 - hookutils.py, attach_alsa(): Get a list of outputs/inputs that PulseAudio
1043 knows about, which also shows the currently selected output/input, as well
1044 as volumes. This should help with "no sound" bug troubleshooting. Thanks
1045 Luke Yelavich.
1046
10471.21.2 (2011-07-01)
1048-------------------
1049Improvements:
1050 - test/run: Check $PYTHON for using a different Python interpreter (such as
1051 "python3") for the tests.
1052 - generic hook: Don't report package installation failures due to segfaulting
1053 maintainer scripts. We want the actual crash report only. Thanks Brian
1054 Murray.
1055 - hookutils.py, attach_wifi(): Also include wpasupplicant logs. Thanks Mathieu
1056 Trudel-Lapierre!
1057
1058Bug fixes:
1059 - backends/packaging-apt-dpkg.py: Fix crash introduced in 1.21.1's multiarch
1060 fixes.
1061 - report.py: Fix bug patterns to correctly match against compressed report
1062 fields.
1063
10641.21.1 (2011-06-20)
1065-------------------
1066Improvements:
1067 - data/general-hooks/generic.py: Also check for low space on /var. Thanks
1068 Brian Murray.
1069 - hookutils.py, attach_file() and attach_file_if_exists(): Add a new
1070 "overwrite" flag option. If not given, now default to overwriting an
1071 existing key, as this is usually what you need when attaching files
1072 (instead of attaching it several times with '_' appended to the keys). You
1073 can get the old behaviour by setting overwrite=False.
1074
1075Bug fixes:
1076 - When showing the size of the full report, take the compressed size of binary
1077 values instead of their uncompressed size, as the crash db upload will use
1078 the compressed values.
1079 - backends/packaging-apt-dpkg.py: Fix for current dpkg with multiarch support.
1080 - test/run: Fix the test suite to run against the system installed libraries
1081 with current Python versions (2.6, 2.7) where __file__ does not work any
1082 more with imports.
1083
10841.21 (2011-06-08)
1085-----------------
1086Improvements:
1087 - Supply --desktop option to kdesudo to improve the description which program
1088 is requesting administrative privileges.
1089 - apport-checkreports: Exit with status 2 if there are new reports, but apport
1090 is disabled. This helps crash notification GUIs to not display new crash
1091 reports in that case. Thanks to Michael Vogt for the original patch.
1092 - Add data/is-enabled: Shell script to check if apport is enabled. Non-Python
1093 programs (which can't use apport.packaging.enabled() ) can call this instead
1094 of having to parse /etc/default/apport themselves, and just check the exit
1095 code. Inspired by original patch from Michael Vogt, thanks!
1096
1097Bug fixes:
1098 - apport-gtk: HTML-escape text for dialogs with URLs. (LP: #750870)
1099 - dump_acpi_tables.py: Check to see if acpi/tables dir is mounted first.
1100 Thanks Brian Murray. (LP: #729622)
1101 - man/apport-cli.1: Document recently added -w/--window option. Thanks Abhinav
1102 Upadhyay! (LP: #765600)
1103 - Use kde-open instead of kfmclient to open URLs under KDE. Thanks Philip
1104 Muškovac. (LP: #765808)
1105
11061.20.1 (2011-03-31)
1107-------------------
1108Bug fixes:
1109 - Add bash completion support for new -w/--window option that was introduced
1110 in 1.20. Thanks Philip Muškovac.
1111 - apport-unpack: Fix crash if target directory already exists.
1112 - Fix crash if UnreportableReason is a non-ASCII string. (LP: #738632)
1113 - Fix crash if application from desktop name is a non-ASCII string.
1114 (LP: #737799)
1115 - unkillable_shutdown: Fix rare crash if ExecutablePath does not exist (any
1116 more). (LP: #537904)
1117 - kernel_crashdump: Fix crash if the vmcore file disappeared underneath us.
1118 (LP: #450295)
1119 - unkillable_shutdown: Fix crash if the checked process terminated underneath
1120 us. (LP: #540436)
1121 - ui.py: Properly raise exceptions from the upload thread that happen at its
1122 very end. (LP: #469943)
1123
11241.20 (2011-03-17)
1125-----------------
1126Improvements:
1127 - Add support for -w/--window option which will enable user to select a
1128 window as a target for filing a problem report. Thanks Abhinav Upadhyay for
1129 the patch! (LP: #357847)
1130 - Disable the filtering on SIGABRT without assertion messages. Turns out that
1131 developers want these crash reports after all. (LP: #729223)
1132 - Add support for a "DuplicateSignature" report fields. This allows package
1133 hooks to implement custom duplicate problem handling which doesn't need to
1134 be hardcoded in Apport itself. Update the launchpad backend to tag such bugs
1135 as "need-duplicate-check".
1136
1137Bug fixes:
1138 - report.py, add_hooks_info(): Properly report TypeErrors from hooks.
1139 - apport-retrace: Intercept SystemErrors from ill-formed gzip attachments as
1140 well.
1141 - Fix crash if crash database configuration does not specify a
1142 bug_pattern_url. Just assume None. (LP: #731526)
1143 - If a custom crash database does not specify a bug_pattern_url, fall back to
1144 using the default database's. (LP: #731526)
1145 - hookutils.py Update WifiSyslog regex to correctly catch application log
1146 messages in syslog. Thanks Mathieu Trudel-Lapierre. (LP: #732917)
1147 - hookutils.py, attach_hardware(): Avoid error message if machine does not
1148 have a PCI bus. Thanks Marcin Juszkiewicz! (LP: #608449)
1149 - backends/packaging-apt-dpkg.py: Replace deprecated getChanges() call with
1150 get_changes().
1151 - apport-gtk: Fix broken dialog heading if the name of the crashed program
1152 contains an & or other markup specific characters.
1153 - apport-gtk: Don't crash if GTK cannot be initialized. This usually happens
1154 without a $DISPLAY or when the session is being shut down. Just print an
1155 error message. If there are pending crashes, they will be shown again the
1156 next time a session starts. (LP: #730569)
1157
11581.19 (2011-02-28)
1159-----------------
1160Bug fixes:
1161 - Update stack unwind patterns for current glib (slightly changed function
1162 names), and also ignore a preceding '*'. (LP: #716251)
1163 - Fix crash_signature() to fail if there is an empty or too short
1164 StacktraceTop.
1165 - apt backend: Do not generate a warning if the opportunistically added -dbg
1166 package does not exist.
1167 - apt backend: Only add -dbg in --no-pkg mode, as there will be conflicts in
1168 normal package mode.
1169 - apt backend: Call tar with target cwd instead of using -C; the latter causes
1170 an extra openat() call which breaks with current fakechroot.
1171 - launchpad.py: Fix retracer crash if DistroRelease field does not exist.
1172 - Convert deprecated failIf()/assert_() TestCase method calls to
1173 assertFalse()/assertTrue().
1174
1175Improvements:
1176 - In apport-bug, if the user specifies a PID referring to a kernel thread,
1177 do the right thing and file the bug against the kernel
1178 - In hookutils.attach_dmesg, skip over an initial truncated message if one
1179 is present (this happens when the ring buffer overflows)
1180 - Change bug patterns to just use one central file instead of per-package
1181 files. This allows bug patterns to be written which are not package
1182 specific, and is easier to maintain as well. IMPORTANT: This changed the
1183 format of crashdb.conf: bug_pattern_base is now obsolete, and the new
1184 attribute bug_pattern_url now points to the full URL/path of the patterns
1185 file. Thanks to Matt Zimmerman!
1186
11871.18 (2011-02-16)
1188-----------------
1189Bug fixes:
1190 - Ensure that symptom scripts define a run() function, and don't show them if
1191 not.
1192 - Do not show symptom scripts which start with an underscore. These can be
1193 used for private libraries for the actual symptom scripts.
1194 - Update bash completion. Thanks Philip Muškovac.
1195 - etc/default/apport: Remove obsolete "maxsize" setting. (LP: #719564)
1196
1197Improvements:
1198 - Remove explicit handling of KDE *.ui files in setup.py, as
1199 python-distutils-extra 2.24 fixes this. Bump version check.
1200 - hookutils.py: Add attach_root_command_outputs() to run several commands
1201 at once. This avoids asking for the password several times. (LP: #716595)
1202
12031.17.2 (2011-02-04)
1204-------------------
1205Improvements:
1206 - Be more Python 3 compatible (not fully working with Python 3 yet, though).
1207 - apt/dpkg backend: Drop support for pre-0.7.9 python-apt API.
1208 - Add --tag option to add extra tags to reports. (LP: #572504)
1209
1210Bug fixes:
1211 - hookutils.py, attach_dmesg(): Do not overwrite already existing dmesg.
1212 - hookutils.py: Be more robust against file permission errors. (LP: #444678)
1213 - ui.py: Do not show all the options in --help when invoked as *-bug.
1214 (LP: #665953)
1215 - launchpad.py: Adapt test cases to current standard_title() behaviour.
1216
12171.17.1 (2011-01-10)
1218-------------------
1219Bug fixes:
1220 - Make the GTK frontend work with GTK 2.0 as well, and drop "3.0" requirement.
1221
12221.17 (2010-12-31)
1223-----------------
1224Improvements:
1225 - Better standard bug titles for Python crashes. Thanks Matt Zimmerman!
1226 (LP: #681574)
1227 - Add handler for uncaught Java exceptions. There is no integration for
1228 automatically intercepting all Java crashes yet, see java/README.
1229 Thanks Matt Zimmerman! (LP: #548877)
1230
1231Bug fixes:
1232 - GTK frontend: Require GTK 3.0.
1233 - launchpad.py: Default to "production" instance, not "edge", since edge is
1234 obsolete now.
1235 - hookutils.py, attach_alsa(): Fix crash if /proc/asound/cards does not exist.
1236 (LP: #626215)
1237 - ui.py, format_filesize(): Fix to work with stricter locale.format() in
1238 Python 2.7. (LP: #688535). While we are at it, also change it to use base-10
1239 units.
1240 - hookutils.py, package_versions(): Always include all requested package names
1241 even if they're unknown to us. Thanks Matt Zimmerman! (LP: #695188)
1242 - launchpad.py: When updating a bug, also add new tags. Thanks Brian Murray!
1243
12441.16 (2010-11-19)
1245-----------------
1246New features:
1247 - Port GTK frontend from pygtk2 to GTK+3.0 and gobject-introspection.
1248
1249Bug fixes:
1250 - Fix symptoms again. Version 1.15 broke the default symptom directory.
1251 - Fix memory test case to work with current Python versions, where the SQLite
1252 integrity check throws a different exception.
1253
12541.15 (2010-11-11)
1255-----------------
1256New features:
1257 - Add dump_acpi_tables.py script. This can be called by package hooks which
1258 need ACPI table information (in particular, kernel bug reports). Thanks to
1259 Brad Figg for the script!
1260 - Order symptom descriptions alphabetically. Thanks to Javier Collado.
1261 - Check $APPORT_SYMPTOMS_DIR environment variable for overriding the system
1262 default path. Thanks to Javier Collado.
1263
1264Bug fixes:
1265 - testsuite: Check that crashdb.conf can have dynamic code to determine DB
1266 names and options.
1267 - ui.py test suite: Rewrite _gen_test_crash() to have the test process core
1268 dump itself, instead of using gdb to do it. The latter fails in ptrace
1269 restricted environments, such as Ubuntu 10.10.
1270 - packaging-apt-dpkg.py: Fix handling of /etc/apport/native-origins.d to
1271 actually work. Thanks Steve Langasek. (LP: #627777)
1272 - apport-kde: Load correct translation catalogue. Thanks Jonathan Riddell.
1273 (LP: #633483)
1274 - launchpad.py: Use launchpadlib to file a bug instead of screen scraping.
1275 The latter was completely broken with current Launchpad, so this makes the
1276 test suite actually work again. Thanks to Diogo Matsubara!
1277 - launchpad.py: Change $APPORT_STAGING to $APPORT_LAUNCHPAD_INSTANCE, so that
1278 you can now specify "staging", "edge", or "dev" (for a local
1279 http://launchpad.dev installation). Thanks to Diogo Matsubara!
1280 - backends/packaging-apt-dpkg.py: Fix crash on empty lines in ProcMaps
1281 attachment.
1282 - doc/symptoms.txt: Fix typo, thanks Philip Muskovac. (LP: #590521)
1283 - apport/hookutils.py: rename ProcCmdLine to ProcKernelCmdLine to not wipe
1284 wipe out /proc/$pid/cmdline information. (LP: #657091)
1285 - apport/hookutils.py: attach_file() will not overwrite existing report
1286 keys, instead appending "_" until the key is unique.
1287 - Fix --save option to recognise ~, thanks Philip Muškovac. (LP: #657278)
1288 - Remove escalation_subscription from Ubuntu bug DB definition, turned out to
1289 not be useful; thanks Brian Murray.
1290 - launchpad.py: Fix APPORT_LAUNCHPAD_INSTANCE values with a https:// prefix.
1291 - apt backend: Opportunistically try to install a -dbg package in addition to
1292 -dbgsym, to increase the chance that at least one of it exists. Thanks
1293 Daniel J Blueman!
1294
12951.14.1 (2010-06-24)
1296-------------------
1297Bug fixes:
1298 - hookutils.py, attach_drm_info(): Sanitize connector names. Thanks Chris
1299 Halse Rogers! (LP: #597558)
1300 - bash completion: Complete all path names, apport-bug can be invoked with a
1301 path to a program. Thanks Philip Muskovac.
1302
13031.14 (2010-06-16)
1304-----------------
1305New features:
1306 - hookutils.py: Add new method attach_drm_info() to read and format
1307 /sys/class/drm/*.
1308
1309Bug fixes:
1310 - packaging-apt-dpkg.py: Fix deprecated python-apt variables, thanks David
1311 Stansby. (LP: #591695)
1312 - launchpad.py: Fix crash on attachments which are named *.gz, but
1313 uncompressed. (LP: #574360)
1314 - hookutils.py, attach_gconf(): Fix defaults parsing for boolean keys.
1315 (LP: #583109)
1316
13171.13.4 (2010-05-04)
1318-------------------
1319 - bash completion: Fix error message if /usr/share/apport/symptoms does not
1320 exist. Thanks Philip Muškovac! (LP: #562118)
1321 - general-hooks/parse_segv.py: Report stack exhaustion more clearly and
1322 correctly handle register dereferencing calls.
1323 - Save/restore environment when calling hooks, in case they change the locale,
1324 etc. (LP: #564422)
1325 - hookutils.py, command_output(): Do not set $LC_MESSAGES for the calling
1326 process/hook, just for the command to be called.
1327 - ui.py: When displaying strings from system exceptions, decode them into an
1328 unicode string, to avoid crashing the KDE UI. (LP: #567253)
1329 - apport-retrace: Fix crash for retracing kernel vmcores, which do not have an
1330 ExecutablePath.
1331 - apport-bug manpage: Clarify when apport-collect may be used. Thanks Brian
1332 Murray! (LP: #537273)
1333 - generic hook: Check ProcMaps for unpackaged libraries, and ask the user if
1334 he really wants to continue. If he does, tag the report as "local-libs" and
1335 add a "LocalLibraries" field to the report with a list of them.
1336 (LP: #545227)
1337
13381.13.3 (2010-04-14)
1339-------------------
1340 - data/general-hooks/parse_segv.py: suggest segv-in-kernel possibility.
1341 - ui.py: When running as root, only show system crash reports, to avoid
1342 restarting user programs as root. (LP: #445017)
1343
13441.13.2 (2010-03-31)
1345-------------------
1346 - problem_report.py, write_mime(): Add new optional argument "priority_fields"
1347 for ordering report keys. Patch by Brian Murray, thanks!
1348 - launchpad.py: Put some interesting fields first in the report, with the new
1349 priority_fields argument. Patch by Brian Murray, thanks!
1350 - packaging-apt-dpkg.py, _install_debug_kernel(): Do not crash on an outdated
1351 kernel, just return that it is outdated. (LP: #532923)
1352 - launchpad.py test suite: Add "Referer" HTTP header, now required by
1353 launchpad.
1354 - launchpad.py: Fix crash if configuration does not have an "escalated_tag"
1355 option.
1356 - launchpad.py: Port to launchpadlib 1.0 API, thanks Michael Bienia for the
1357 initial patch! (LP: #545009)
1358 - gtk/apport-gtk-mime.desktop.in, kde/apport-kde-mime.desktop.in: Change
1359 categories so that these do not ever appear in menu editors. (LP: #449215)
1360 - launchpad.py: Some LP bugs have broken attachments (this is a bug in
1361 Launchpad itself). Ignore those instead of crashing.
1362 - apport-gtk: Turn http:// and https:// links into clickable hyperlinks in
1363 information and error dialogs. (LP: #516323)
1364 - apport-retrace: Fix crash when trying to rebuild package info for reports
1365 without an ExecutablePath. (LP: #436157)
1366 - ui.py: Fix crash when package information cannot be determined due to broken
1367 apt status. (LP: #362743)
1368 - ui.py: Fix crash when /etc/apport/crashdb.conf is damaged; print an
1369 appropriate error message instead. (LP: #528327)
1370 - data/kernel_crashdump: Fix crash if log file disappeared underneath us.
1371 (LP: #510327)
1372 - data/apport: Fix IOError when apport is called with invalid number of
1373 arguments, and stderr is not a valid fd. (LP: #467363)
1374 - hookutils.py: Factor out the DMI collection code from attach_hardware()
1375 into attach_dmi(), and call that in attach_alsa() as well. Thanks to Brad
1376 Figg for the patch! (LP: #552091)
1377 - apport/ui.py: Fix the help output if Apport is invoked under an alternative
1378 name (like apport-collect). (LP: #539427)
1379
13801.13.1 (2010-03-20)
1381-------------------
1382Bug fixes:
1383 - Update parse-segv to handle gdb 7.1 output.
1384 - Enhance test suite to work with gdb 7.1 as well, and catch future outputs.
1385 - UI: Add exception string to the "network error" dialog, to better tell what
1386 the problem is.
1387 - UI: Add back -p option to apport-collect/apport-update-bug (regression from
1388 1.13). (LP: #538944)
1389 - launchpad.py: Add yet another workaround for LP#336866. (LP: #516381)
1390 - launchpad.py, download(): Ignore attachments with invalid key names.
1391 - Fix regression from 1.10 which made it impossible for a package hook to set
1392 a third-party crash database for non-native packages. (LP: #517272)
1393 - apport-cli: Create the 'details' string only if user wants to view details,
1394 and do not show files larger than 1MB. Thanks Scott Moser! (LP: #486122)
1395 - packaging-apt-dpkg.py: Silence apt.Cache() spewage to stdout with newer
1396 python-apt versions. (LP: #531518)
1397
1398Improvements:
1399 - unkillable_shutdown: Add list of running processes and blacklisted pids to
1400 report. (LP: #537262)
1401 - Sort the report by key in the details view. (LP: #519416)
1402
14031.13 (2010-03-10)
1404-----------------
1405New features:
1406 - Add "unkillable_shutdown" script to collect information about processes
1407 which are still running after sending SIGTERM to them. This can be hooked
1408 into e. g. /etc/init.d/sendsigs on Debian/Ubuntu systems.
1409
1410Improvements:
1411 - apport_python_hook.py: Directly check /etc/default/apport instead of
1412 querying packaging.enabled(), to avoid importing lots of modules for
1413 non-packaged scripts. Thanks Stuart Colville! (LP: #528355)
1414
1415Bug fixes:
1416 - Fix SegV parser to notice walking off the stack during "call" or "ret"
1417 (LP: #531672).
1418 - Fix --help output for bug updating mode (invocation as apport-collect or
1419 apport-update-bug). (LP: #504116)
1420 - Fix bug escalation tagging, thanks to Brian Murray.
1421 - Fix option processing when being invoked as apport-bug. Thanks to Daniel
1422 Hahler for the patch! (LP: #532944)
1423
14241.12.1 (2010-02-22)
1425-------------------
1426Bug fixes:
1427 - launchpad.py: Do not keep escalating bugs, just escalate at the 10th
1428 duplicate.
1429 - Improve error message if a symptom script did not determine a package name.
1430 (LP: #503834)
1431 - general-hooks/generic.py: Fix crash on libGL check with empty StacktraceTop.
1432 - Review and clean up usage of chmod(). This fixes a small race condition in the
1433 Python exception hook where a local attacker could read the information from
1434 another user's crash report. (LP: #516029)
1435 - hookutils, package_versions(): Ignore "None" packages, for more robust
1436 package hooks. (LP: #518295)
1437
14381.12 (2010-01-20)
1439-----------------
1440Improvements:
1441 - launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
1442 handling bugs with more than 10 duplicates.
1443 - crashdb.conf: For Ubuntu, escalate bugs with >= 10 duplicates to
1444 "ubuntu-bugcontrol" and tag them with "bugpattern-needed". (LP: #487900)
1445 - general-hooks/generic.py: Filter out crashes on missing GLX (LP: #327673)
1446 - Add bash completion script. Thanks to Philip Muškovac. (LP: #218933)
1447
1448Bug fixes:
1449 - launchpad.py: Drop APPORT_FILES whitelist for download() and instead just
1450 filter out file extensions that we know about (*.txt and *.gz).
1451 (LP: #444975)
1452 - launchpad.py: Do not put the Tags: field into the bug description, since
1453 they are already proper tags. In download(), convert the real tags back to
1454 the Tags: field. (LP: #505671)
1455 - test/crash: Update expected core dump flags for changed rlimit behaviour in
1456 Linux 2.6.32.
1457 - launchpad.py: Fix marking of 'checked for duplicate' for bugs with upstream
1458 tasks.
1459 - launchpad.py, get_fixed_version(): Do not consider a bug as invalid just
1460 because it has any invalid distro package task.
1461
14621.11 (2009-12-23)
1463-----------------
1464Improvements:
1465 - Add "--save" UI option to store the collected information into an .apport
1466 file instead of sending it right away. The file can then later be sent
1467 through apport-bug. Update manpages accordingly.
1468 - Update all copyright and description headers and consistently format them.
1469 - Rename all TestCase classes to "_T", which makes it much easier to run
1470 individual tests from the command line.
1471 - Testsuite: Verify that report details are/are not shown. This uncovered that
1472 details about package installation failures were not shown before sending
1473 them, which is fixed now.
1474
1475Bug fixes:
1476 - test/hooks: Do not try to add hook information to kernel_crashdump test
1477 case, since we do not have an UI here. This test case broke when the system
1478 had an interactive package hook for the kernel.
1479 - When reporting a bug from a saved .apport file, let the user review/confirm
1480 the content before sending.
1481
14821.10.1 (2009-12-23)
1483-------------------
1484Improvements:
1485 - Install apport-collect symlink.
1486 - Update translations from Launchpad.
1487
1488Bug fixes:
1489 - Move all remaining option/argument parsing from apport-bug into ui.py. This
1490 allows the user to add options to apport-bug/apport-collect, and also avoids
1491 unwieldy dissection of options/arguments in shell.
1492
14931.10 (2009-12-19)
1494-----------------
1495New features:
1496 - Add a mode for updating an existing problem report to ui.py (-u/--update).
1497 This is similar to the Ubuntu specific "apport-collect" tool, but
1498 implemented the right way now: In particular, this has access to the UI and
1499 thus can use interactive hooks (LP: #385811) and show you what is being sent
1500 for confirmation/cancelling (LP: #371827)
1501 - apport-bug: If invoked as "apport-collect" or "apport-update-bug" (i. e.
1502 through a symlink), run apport in update mode (-u <number>). This provides a
1503 convenient no-options command line program. Please note that setup.py does
1504 not currently install such a symlink. Update the apport-bug manpage
1505 accordingly.
1506
1507Improvements:
1508 - launchpad.py: Use new login_with() to clean up code, and specify allowed
1509 access levels (WRITE_PRIVATE is the only sensible one anyway). (LP: #410205)
1510 - New hookutils functions:
1511 xsession_errors (match lines from ~/.xsession-errors)
1512 shared_libraries (determine which libraries a binary links with)
1513 links_with_shared_library (test if a binary links with a particular library)
1514 - New CrashDatabase API: get_affected_packages(), can_update(), is_reporter()
1515 - Rename CrashDatabase.update() to update_traces().
1516 - Add CrashDatabase.update() for adding all new fields of a report. This is
1517 primarily useful for collecting local standard and package hook data for an
1518 already existing bug report which was not filed through Apport. This checks
1519 can_update()/is_reporter() if the user is eligible for updating that
1520 particular bug. (LP: #485880)
1521
1522Bug fixes:
1523 - Ignore SIGXCPU and SIGXFSZ; thanks to Kees Cook. (LP: #498074)
1524 - launchpad.py: Do not mark non-Ubuntu bugs as needs-retrace, since there is
1525 no retracer right now. (LP: #489794)
1526 - packaging-apt-dpkg.py, install_retracing_packages(): Do not crash on
1527 malformed Dependencies.txt lines. (LP: #441709)
1528 - use-local: Fix for new source tree location of "apport" binary.
1529
15301.9.6 (2009-12-01)
1531------------------
1532Improvements:
1533 - Add pm-utils hook to record current operation, so that apportcheckresume can
1534 check it. Before this was kept in Ubuntu's pm-utils package.
1535 - general-hooks/generic.py: Check if using ecryptfs, and which directory.
1536 (LP: #444656)
1537
1538Bug fixes:
1539 - launchpad.py: Ensure that text attachments on initial bug filing are valid
1540 UTF-8. (LP: #453203)
1541 - man/apport-retrace.1: Document -R option.
1542
15431.9.5 (2009-11-20)
1544------------------
1545Bug fixes:
1546 - apport-retrace: Fix crash if InterpreterPath/ExecutablePath do not exist.
1547 - hookutils.py, attach_alsa(): Attach /proc/cpuinfo too, for CPU flags.
1548 - Fix crash if InterpreterPath does not exist any more at the time of
1549 reporting. (LP: #428289)
1550 - apport-gtk: Connect signals properly, to repair cancel/window close buttons.
1551 (LP: #427814)
1552 - Update German translations and fix "konnre" typo. (LP: #484119)
1553
15541.9.4 (2009-11-06)
1555------------------
1556Bug fixes:
1557 - Fix crash when ExecutablePath isn't part of a package. (LP: #424965)
1558 - hookutils.py, attach_hardware(): Anonymize disk labels. Thanks to Marco
1559 Rodrigues. (LP: #394411)
1560 - hookutils.py, attach_wifi(): Anonymize encryption key (which appeared in hex
1561 when being called as root). Thanks to Marco Rodrigues. (LP: #446299)
1562 - launchpad.py: If unset, set bug task source package also for interpreter
1563 crashes.
1564 - apport-gtk: Give details window a minimize/maximize button, which were
1565 missing in some window managers. Thanks to Marien Zwart. (LP: #447749)
1566 - apport-kde: Properly terminate program after closing the last dialog.
1567 (LP: #458662)
1568 - hookutils.py, attach_alsa(): Attach /proc/asound/version. (LP: #467233)
1569 - general-hooks/generic.py: Only collect ~/.xsession-errors bits when we have
1570 an ExecutablePath linked to libgtk.
1571
15721.9.3 (2009-10-14)
1573------------------
1574Changes:
1575 - Drop handling of the APPORT_REPORT_THIRDPARTY environment variable and
1576 "thirdparty" configuration file option. This has never been documented, and
1577 conceptually does not work. There is a proper mechanism for this in place
1578 now, e. g. launchpad.py's "project" option.
1579
1580Bug fixes:
1581 - hookutils.py: Fix error codes from "comm", thanks to Brian Murray.
1582 - general-hooks/generic.py: Catch xkbcomp error messages. (LP: #431807)
1583 - launchpad.py: Assert that we have exactly one of "distro" or "project"
1584 option.
1585 - doc/crashdb-conf.txt: Improve documentation of crash database options.
1586 - apport-gtk: Make Cancel/Send buttons focusable. Thanks to Marco Rodrigues.
1587 (LP: #447780)
1588
15891.9.2 (2009-10-02)
1590------------------
1591Improvements:
1592 - apport-cli: Print the URL and ask whether to open a browser. In many
1593 situations (such as usage on a server through ssh), it's preferable to not
1594 open the browser on the reporting computer. Thanks to Matt Zimmerman for the
1595 initial patch! (LP: #286415)
1596 - general-hooks/generic.py: Collect important glib errors/assertions (which
1597 should not have private data) from ~/.xsession-errors (LP: #431807)
1598 - launchpad.py: Link hardware data submission key if it exists. (LP: #424382)
1599
1600Bug fixes:
1601 - apport-cli: Fix crash with non-ASCII characters in prompts.
1602 - Fix "apport-bug symptomname" to actually work.
1603 - launchpad.py: Fix crash on invalid credentials file. Thanks to Marco
1604 Rodrigues for the initial patch! (LP: #414055)
1605
16061.9.1 (2009-09-22)
1607------------------
1608Bug fixes:
1609 - hookutils.py, attach_hardware(): Do not attach empty Pccardctl*.
1610 - apport/report.py, add_gdb_info(): Do not throw away stderr from gdb.
1611 - data/general-hooks/parse_segv.py:
1612 + Handle arithmetic wrapping correctly.
1613 + Handle empty base, scale, or index registers in disassembly.
1614 + Handle in/out ioport faults.
1615 - Various improvements to user-visible strings, thanks to Marco Rodrigues!
1616 (LP: #178507)
1617 - Various apport-retrace robustifications.
1618 - setup.py: Fix DistUtilsExtra version check. (LP: #428337)
1619 - hookutils.py, attach_gconf(): Do not overwrite previous values from other
1620 packages, thanks LoĂŻc Minier!
1621 - hookutils.py, attach_gconf(): Fix crash with nonexisting <applyto> tags.
1622
16231.9 (2009-09-08)
1624----------------
1625New features:
1626 - Add "do what I mean" mode to command line argument parsing (applies to all
1627 interfaces: -cli, -gtk, -kde). When giving a single argument and no options,
1628 determine the most likely mode, like reporting a bug against a symptom,
1629 package, executable name, or PID.
1630 - Add program "apport-bug" which determines the most appropriate user
1631 interface (GTK, KDE, CLI) and files a bug through it, using the single
1632 argument "do what I mean" mode. This is an improved version of Ubuntu's
1633 "ubuntu-bug" script.
1634
1635Bug fixes:
1636 - Update apport-cli manpage to current set of options and behaviour. Also
1637 point out that apport-gtk and apport-kde share the same CLI.
1638 - setup.py now installs apport-{gtk,kde} into $prefix/share/apport/, they are
1639 not supposed to be called directly. This also reflects the path which the
1640 .desktop files expect.
1641 - setup.py now installs the internal helper scripts like "kernel_crashdump",
1642 "apport", or "apportcheckresume" into $prefix/share/apport instead of
1643 $prefix/bin.
1644 - Update usage of gettext to work around Python bug of gettext() not returning
1645 unicodes, but str. Fixes UnicodeDecodeErrors on translated --help output.
1646
16471.8.2 (2009-09-05)
1648------------------
1649Bug fixes.
1650
16511.8.1 (2009-09-03)
1652------------------
1653Lots of bug fixes.
1654
16551.8 (2009-08-26)
1656----------------
1657New features:
1658 - Do not generally ignore SIGABRT any more. Try to extract the assertion
1659 message from the core dump, and add it as "AssertionMessage" field. Mark
1660 reports as unreportable if they do not have an assertion message and crashed
1661 with SIGABRT. This requires your glibc to have this patch:
1662 http://sourceware.org/git/?p=glibc.git;a=commitdiff;h=48dcd0ba
1663 - report.py, add_hooks_info(): Add optional package/srcpackage argument. Hooks
1664 can use that to change the affected package or call hooks from different
1665 packages.
1666 - KDE frontend implementation of ui_question_userpass(), for crash databases
1667 which need to ask for credentials.
1668 - hookutils.py: New funtion attach_wifi() to add wireless network related
1669 information to reports.
1670
1671Important bug fixes:
1672 - Fix the test suite on current kernels; test/crash previously often failed
1673 with python segfaults, since it killed the test processes too early.
1674
16751.7 (2009-08-05):
1676-----------------
1677New features:
1678 - Support for "symptom" scripts, which figure out the package for a bug report
1679 based on interactive questions.
1680
16811.6 (2009-07-15)
1682----------------
1683New features:
1684 - Integrate analysis and retracing of kernel vmcore crashes with the "crash"
1685 tool. Courtesy of Michael Vogt.
1686
1687Various little bug fixes.
1688
16891.5 (2009-06-29)
1690----------------
1691New features:
1692 - Drop all Makefiles, po/POTFILES.in, and most code from setup.py, and use
1693 DistUtilsExtras.auto which "just does the right thing" for most build system
1694 tasks. This requires python-distutils-extra >= 2.2, see
1695 https://launchpad.net/python-distutils-extra
1696
1697Cleanup:
1698 - Move all test scripts into test/, to unclutter source tree.
1699 - setup.py now auto-detects the required packaging backend if
1700 apport/packaging_impl.py is not manually installed.
1701
17021.4 (2009-06-26)
1703----------------
1704New features:
1705 - Replaced Qt4 frontend with a KDE frontend for better KDE integration.
1706
1707Major bug fixes:
1708 - packaging-apt-dpkg.py: Add backwards compatibility code for python-apt <
1709 0.7.9 to not break backportability.
1710
17111.3 (2009-06-10)
1712----------------
1713New features:
1714- Interactive package hooks:
1715 * Add apport.ui.HookUI class which provides GUI functionality such as yes/no
1716 questions or file dialogs to hooks.
1717 * add_info() in package hooks now can (optionally) take a second argument which
1718 is the HookUI instance.
1719 * See doc/package-hooks.txt for details.
1720- New function apport.hookutils.root_command_output() to run a command as root,
1721 through gksu/kdesudo/sudo, depending on the desktop environment.
1722- Add general hook for analyzing reason of a segfault.
1723
1724Bug fixes:
1725- Drop "UnsupportableReason" field, it is too similar to UnreportableReason and
1726 just confusing.
1727- Report key names can now contain dashes ('-') and underscores ('_').
1728 (LP #380811)
1729
17301.2.1 (2009-05-15)
1731------------------
1732Bug fixes:
1733- Fix setup.py and PO file merging for recent .glade -> .ui renaming.
1734
1735Translations:
1736- Update German translations.
1737
17381.2.0 (2009-05-15)
1739------------------
1740Moving away from deprecated APIs:
1741- packaging-apt-dpkg.py: Use python-apt >= 0.7.9 official API and drop usage of
1742 internal symbols.
1743- hookutils.py: Drop hal related functions and queries, replace with udev
1744 database, udev log file, and DMI information from sysfs.
1745- gtk UI: Convert from libglade to gtk.Builder.
1746
1747Bug fixes:
1748- hookutils.py: Drop /proc/version_signature collection, it is Ubuntu specific.
1749- apportcheckresume: Fix log collection from pm-utils.
1750- Fix various crashes and report properties for reporting against uninstalled
1751 packages.
1752
17531.1.1 (2009-04-30)
1754------------------
1755Security fix:
1756- etc/cron.daily/apport: Only attempt to remove files and symlinks, do not
1757 descend into subdirectories of /var/crash/. Doing so might be exploited by a
1758 race condition between find traversing a huge directory tree, changing an
1759 existing subdir into a symlink to e. g. /etc/, and finally getting that piped
1760 to rm. This also changes the find command to not use GNU extensions. Thanks
1761 to Stephane Chazelas for discovering this! (LP #357024, CVE-2009-1295)
1762
1763Bug fixes:
1764- launchpad.py: Send and read Date: field again, reverting r1128; it is useful
1765 after all. (LP #349139)
1766- Only add ProcAttrCurrent to reports if it is not "unconfined", to remove some
1767 noise from reports.
1768- Detect invalid PIDs in the UI (such as for kernel processes) and give a
1769 friendly error message instead of silently doing nothing. (LP #360608)
1770- Always run common hooks, and run source package hooks if we do not have a
1771 binary package name. (LP #350131)
1772- launchpad.py: Consider socket errors when connecting as transient, so
1773 that crash-digger doesn't stop completely on them.
1774
17751.1 (2009-04-20)
1776----------------
1777New features:
1778- Add hookutils methods for attaching relevant packages, greatly improve
1779 attach_alsa() for sound problem debugging.
1780- Move launchpad crash database implementation from ever-breaking
1781 python-launchpad-bugs (screenscraping) to launchpadlib (official and stable
1782 Launchpad API).
1783
1784Bug fixes:
1785- Drop some remaining distro specific pieces of code.
1786- Add new field Report.pid which gets set on add_proc_info() and can be used by
1787 hooks.
1788- setup.py: Properly clean up all generated files, install missing
1789 mimetypes/text-x-apport.svg icon symlink.
1790- Add README file.
1791- Add translations from Launchpad.
1792- Remove preloadlib/*; it's undermaintained, and not really useful any more
1793 these days.
1794- Various bug fixes; most visible being the misnamed etc/default/apport.default
1795 file (which should just be etc/default/apport).
1796
17971.0 (2009-04-06)
1798----------------
1799First upstream release, based on Ubuntu packaging branch; that had been the
1800de-facto trunk for many years, but this becomes unpractical with several
1801distributions using it now.
01802
=== renamed file 'NEWS' => 'NEWS.moved'
=== added file 'README'
--- README 1970-01-01 00:00:00 +0000
+++ README 2013-03-13 18:57:21 +0000
@@ -0,0 +1,87 @@
1Apport crash detection/reporting
2================================
3
4Apport intercepts Program crashes, collects debugging information about the
5crash and the operating system environment, and sends it to bug trackers in a
6standardized form. It also offers the user to report a bug about a package,
7with again collecting as much information about it as possible.
8
9It currently supports
10
11 - Crashes from standard signals (SIGSEGV, SIGILL, etc.) through the kernel
12 coredump handler (in piping mode)
13 - Unhandled Python exceptions
14 - GTK, KDE, and command line user interfaces
15 - Packages can ship hooks for collecting specific data (such as
16 /var/log/Xorg.0.log for X.org, or modified gconf settings for GNOME
17 programs)
18 - apt/dpkg and rpm backend (in production use in Ubuntu and OpenSUSE)
19 - Reprocessing a core dump and debug symbols for post-mortem (and preferably
20 server-side) generation of fully symbolic stack traces (apport-retrace)
21 - Reporting bugs to Launchpad (more backends can be easily added)
22
23Please see https://wiki.ubuntu.com/Apport for more details and further links.
24The files in doc/ document particular details such as package hooks, crash
25database configuration, or the internal data format.
26
27Temporarily enabling apport
28===========================
29
30The automatic crash interception component of apport is disabled by default in
31stable releases for a number of reasons [1]. To enable it just for the current
32session, do
33
34 sudo service apport start force_start=1
35
36Then you can simply trigger the crash again, and Apport's dialog will show up
37with instructions to report a bug with traces. Apport will be automatically
38disabled on next start.
39
40If you are triaging bugs, this is the best way to get traces from bug reporters
41that didn't use Apport in the first place.
42
43To enable it permanently, do:
44
45 sudo nano /etc/default/apport
46
47and change enabled from "0" to "1".
48
49[1] https://wiki.ubuntu.com/Apport#How%20to%20enable%20apport
50
51Crash notification on servers
52=============================
53
54You can add
55
56if [ -x /usr/bin/apport-cli ]; then
57 if groups | grep -qw admin && /usr/share/apport/apport-checkreports -s; then
58 cat <<-EOF
59 You have new problem reports waiting in /var/crash.
60 To take a look at them, run "sudo apport-cli".
61
62 EOF
63 elif /usr/share/apport/apport-checkreports; then
64 cat <<-EOF
65 You have new problem reports waiting in /var/crash.
66 To take a look at them, run "apport-cli".
67
68 EOF
69 fi
70fi
71
72to your ~/.bashrc to get automatic notification of problem reports.
73
74Contributing
75============
76
77Please visit Apport's Launchpad homepage for links to the source code revision
78control, the bug tracker, translations, downloads, etc.:
79
80 https://launchpad.net/apport
81
82The preferred mode of operation for Linux distribution packagers is to create
83their own branch from 'trunk' and add the distro specific packaging and patches
84to it. Please send patches which are applicable to trunk as merge requests or
85bug reports, so that (1) other distributions can benefit from them as well, and
86(2) you reduce the code delta to upstream.
87
088
=== renamed file 'README' => 'README.moved'
=== added file 'TODO'
--- TODO 1970-01-01 00:00:00 +0000
+++ TODO 2013-03-13 18:57:21 +0000
@@ -0,0 +1,21 @@
1apport:
2 - check crashes of root processes with dropped privs in test suite
3
4dup detection:
5 - add merging of two databases -> needs time stamp of last change
6
7GUI:
8 - point out bug privacy and to leave it private by default
9
10hooks:
11 - add hooks which run during program crash, to collect more runtime data
12
13hookutils:
14- run hooks for related packages in attach_related_packages
15
16apt-dpkg backend:
17- use python-apt's Version.get_source() instead of apt-get source
18
19apport-retrace:
20- add "gdb" mode: apport-gdb /usr/bin/myprog -> imply -S system, throw you into
21 gdb session with debug symbols
022
=== renamed file 'TODO' => 'TODO.moved'
=== added directory 'apport'
=== renamed directory 'apport' => 'apport.moved'
=== added file 'apport/REThread.py'
--- apport/REThread.py 1970-01-01 00:00:00 +0000
+++ apport/REThread.py 2013-03-13 18:57:21 +0000
@@ -0,0 +1,65 @@
1'''Enhanced Thread with support for return values and exception propagation.'''
2
3# Copyright (C) 2007 Canonical Ltd.
4# Author: Martin Pitt <martin.pitt@ubuntu.com>
5#
6# This program is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by the
8# Free Software Foundation; either version 2 of the License, or (at your
9# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
10# the full text of the license.
11
12import threading, sys
13
14
15class REThread(threading.Thread):
16 '''Thread with return values and exception propagation.'''
17
18 def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
19 '''Initialize Thread, identical to threading.Thread.__init__().'''
20
21 threading.Thread.__init__(self, group, target, name, args, kwargs)
22 self.__target = target
23 self.__args = args
24 self.__kwargs = kwargs
25 self._retval = None
26 self._exception = None
27
28 def run(self):
29 '''Run target function, identical to threading.Thread.run().'''
30
31 if self.__target:
32 try:
33 self._retval = self.__target(*self.__args, **self.__kwargs)
34 except:
35 if sys:
36 self._exception = sys.exc_info()
37
38 def return_value(self):
39 '''Return value from target function.
40
41 This can only be called after the thread has finished, i. e. when
42 isAlive() is False and did not terminate with an exception.
43 '''
44 assert not self.isAlive()
45 assert not self._exception
46 return self._retval
47
48 def exc_info(self):
49 '''Return (type, value, traceback) of the exception caught in run().'''
50
51 return self._exception
52
53 def exc_raise(self):
54 '''Raise the exception caught in the thread.
55
56 Do nothing if no exception was caught.
57 '''
58 if self._exception:
59 # there is no syntax which both Python 2 and 3 parse, so we need a
60 # hack using exec() here
61 # Python 3:
62 if sys.version > '3':
63 raise self._exception[1].with_traceback(self._exception[2])
64 else:
65 exec('raise self._exception[0], self._exception[1], self._exception[2]')
066
=== added file 'apport/__init__.py'
--- apport/__init__.py 1970-01-01 00:00:00 +0000
+++ apport/__init__.py 2013-03-13 18:57:21 +0000
@@ -0,0 +1,73 @@
1import sys
2import os
3import time
4
5from apport.report import Report
6
7from apport.packaging_impl import impl as packaging
8
9Report # pyflakes
10packaging # pyflakes
11
12# fix gettext to output proper unicode strings
13import gettext
14
15
16def unicode_gettext(str):
17 trans = gettext.gettext(str)
18 if type(trans) == type(b''):
19 return trans.decode('UTF-8')
20 else:
21 return trans
22
23
24def log(message, timestamp=False):
25 '''Log the given string to stdout. Prepend timestamp if requested'''
26
27 if timestamp:
28 sys.stdout.write('%s: ' % time.strftime('%x %X'))
29 print(message)
30
31
32def fatal(msg, *args):
33 '''Print out an error message and exit the program.'''
34
35 error(msg, *args)
36 sys.exit(1)
37
38
39def error(msg, *args):
40 '''Print out an error message.'''
41
42 if sys.stderr:
43 sys.stderr.write('ERROR: ')
44 sys.stderr.write(msg % args)
45 sys.stderr.write('\n')
46
47
48def warning(msg, *args):
49 '''Print out an warning message.'''
50
51 if sys.stderr:
52 sys.stderr.write('WARNING: ')
53 sys.stderr.write(msg % args)
54 sys.stderr.write('\n')
55
56
57def memdbg(checkpoint):
58 '''Print current memory usage.
59
60 This is only done if $APPORT_MEMDEBUG is set.
61 '''
62 if not 'APPORT_MEMDEBUG' in os.environ or not sys.stderr:
63 return
64
65 memstat = {}
66 with open('/proc/self/status') as f:
67 for l in f:
68 if l.startswith('Vm'):
69 (field, size, unit) = l.split()
70 memstat[field[:-1]] = int(size) / 1024.
71
72 sys.stderr.write('Size: %.1f MB, RSS: %.1f MB, Stk: %.1f MB @ %s\n' %
73 (memstat['VmSize'], memstat['VmRSS'], memstat['VmStk'], checkpoint))
074
=== added file 'apport/com.ubuntu.apport.policy.in'
--- apport/com.ubuntu.apport.policy.in 1970-01-01 00:00:00 +0000
+++ apport/com.ubuntu.apport.policy.in 2013-03-13 18:57:21 +0000
@@ -0,0 +1,34 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE policyconfig PUBLIC
3 "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
4 "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
5<policyconfig>
6 <vendor>Apport</vendor>
7 <vendor_url>https://wiki.ubuntu.com/Apport</vendor_url>
8 <icon_name>apport</icon_name>
9
10 <action id="com.ubuntu.apport.root-info">
11 <_description>Collect system information</_description>
12 <_message>Authentication is required to collect system information for this problem report</_message>
13 <annotate key="org.freedesktop.policykit.exec.path">/usr/share/apport/root_info_wrapper</annotate>
14 <!-- <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate> -->
15 <defaults>
16 <allow_any>auth_admin</allow_any>
17 <allow_inactive>auth_admin</allow_inactive>
18 <allow_active>auth_admin</allow_active>
19 </defaults>
20 </action>
21
22 <action id="com.ubuntu.apport.apport-gtk-root">
23 <_description>System problem reports</_description>
24 <_message>Please enter your password to access problem reports of system programs</_message>
25 <annotate key="org.freedesktop.policykit.exec.path">/usr/share/apport/apport-gtk</annotate>
26 <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
27 <defaults>
28 <allow_any>auth_admin</allow_any>
29 <allow_inactive>auth_admin</allow_inactive>
30 <allow_active>auth_admin</allow_active>
31 </defaults>
32 </action>
33
34</policyconfig>
035
=== added file 'apport/crashdb.py'
--- apport/crashdb.py 1970-01-01 00:00:00 +0000
+++ apport/crashdb.py 2013-03-13 18:57:21 +0000
@@ -0,0 +1,856 @@
1'''Abstract crash database interface.'''
2
3# Copyright (C) 2007 - 2009 Canonical Ltd.
4# Author: Martin Pitt <martin.pitt@ubuntu.com>
5#
6# This program is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by the
8# Free Software Foundation; either version 2 of the License, or (at your
9# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
10# the full text of the license.
11
12import os, os.path, sys, shutil
13
14try:
15 from exceptions import Exception
16 from urllib import quote_plus, urlopen
17 URLError = IOError
18 (quote_plus, urlopen) # pyflakes
19except ImportError:
20 # python 3
21 from functools import cmp_to_key
22 from urllib.parse import quote_plus
23 from urllib.request import urlopen
24 from urllib.error import URLError
25
26import apport
27
28
29def _u(str):
30 '''Convert str to an unicode if it isn't already.'''
31
32 if type(str) == type(b''):
33 return str.decode('UTF-8', 'ignore')
34 return str
35
36
37class CrashDatabase:
38 def __init__(self, auth_file, options):
39 '''Initialize crash database connection.
40
41 You need to specify an implementation specific file with the
42 authentication credentials for retracing access for download() and
43 update(). For upload() and get_comment_url() you can use None.
44
45 options is a dictionary with additional settings from crashdb.conf; see
46 get_crashdb() for details.
47 '''
48 self.auth_file = auth_file
49 self.options = options
50 self.duplicate_db = None
51
52 def get_bugpattern_baseurl(self):
53 '''Return the base URL for bug patterns.
54
55 See apport.report.Report.search_bug_patterns() for details. If this
56 function returns None, bug patterns are disabled.
57 '''
58 return self.options.get('bug_pattern_url')
59
60 def accepts(self, report):
61 '''Check if this report can be uploaded to this database.
62
63 Crash databases might limit the types of reports they get with e. g.
64 the "problem_types" option.
65 '''
66 if 'problem_types' in self.options:
67 return report.get('ProblemType') in self.options['problem_types']
68
69 return True
70
71 #
72 # API for duplicate detection
73 #
74 # Tests are in apport/crashdb_impl/memory.py.
75
76 def init_duplicate_db(self, path):
77 '''Initialize duplicate database.
78
79 path specifies an SQLite database. It will be created if it does not
80 exist yet.
81 '''
82 import sqlite3 as dbapi2
83
84 assert dbapi2.paramstyle == 'qmark', \
85 'this module assumes qmark dbapi parameter style'
86
87 self.format_version = 3
88
89 init = not os.path.exists(path) or path == ':memory:' or \
90 os.path.getsize(path) == 0
91 self.duplicate_db = dbapi2.connect(path, timeout=7200)
92
93 if init:
94 cur = self.duplicate_db.cursor()
95 cur.execute('CREATE TABLE version (format INTEGER NOT NULL)')
96 cur.execute('INSERT INTO version VALUES (?)', [self.format_version])
97
98 cur.execute('''CREATE TABLE crashes (
99 signature VARCHAR(255) NOT NULL,
100 crash_id INTEGER NOT NULL,
101 fixed_version VARCHAR(50),
102 last_change TIMESTAMP,
103 CONSTRAINT crashes_pk PRIMARY KEY (crash_id))''')
104
105 cur.execute('''CREATE TABLE address_signatures (
106 signature VARCHAR(1000) NOT NULL,
107 crash_id INTEGER NOT NULL,
108 CONSTRAINT address_signatures_pk PRIMARY KEY (signature))''')
109
110 self.duplicate_db.commit()
111
112 # verify integrity
113 cur = self.duplicate_db.cursor()
114 cur.execute('PRAGMA integrity_check')
115 result = cur.fetchall()
116 if result != [('ok',)]:
117 raise SystemError('Corrupt duplicate db:' + str(result))
118
119 try:
120 cur.execute('SELECT format FROM version')
121 result = cur.fetchone()
122 except self.duplicate_db.OperationalError as e:
123 if 'no such table' in str(e):
124 # first db format did not have version table yet
125 result = [0]
126 if result[0] > self.format_version:
127 raise SystemError('duplicate DB has unknown format %i' % result[0])
128 if result[0] < self.format_version:
129 print('duplicate db has format %i, upgrading to %i' %
130 (result[0], self.format_version))
131 self._duplicate_db_upgrade(result[0])
132
133 def check_duplicate(self, id, report=None):
134 '''Check whether a crash is already known.
135
136 If the crash is new, it will be added to the duplicate database and the
137 function returns None. If the crash is already known, the function
138 returns a pair (crash_id, fixed_version), where fixed_version might be
139 None if the crash is not fixed in the latest version yet. Depending on
140 whether the version in report is smaller than/equal to the fixed
141 version or larger, this calls close_duplicate() or mark_regression().
142
143 If the report does not have a valid crash signature, this function does
144 nothing and just returns None.
145
146 By default, the report gets download()ed, but for performance reasons
147 it can be explicitly passed to this function if it is already available.
148 '''
149 assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
150
151 if not report:
152 report = self.download(id)
153
154 self._mark_dup_checked(id, report)
155
156 if 'DuplicateSignature' in report:
157 sig = report['DuplicateSignature']
158 else:
159 sig = report.crash_signature()
160 existing = []
161 if sig:
162 # use real duplicate signature
163 existing = self._duplicate_search_signature(sig, id)
164
165 if existing:
166 # update status of existing master bugs
167 for (ex_id, _) in existing:
168 self._duplicate_db_sync_status(ex_id)
169 existing = self._duplicate_search_signature(sig, id)
170
171 try:
172 report_package_version = report['Package'].split()[1]
173 except (KeyError, IndexError):
174 report_package_version = None
175
176 # check the existing IDs whether there is one that is unfixed or not
177 # older than the report's package version; if so, we have a duplicate.
178 master_id = None
179 master_ver = None
180 for (ex_id, ex_ver) in existing:
181 if not ex_ver or not report_package_version or apport.packaging.compare_versions(report_package_version, ex_ver) < 0:
182 master_id = ex_id
183 master_ver = ex_ver
184 break
185 else:
186 # if we did not find a new enough open master report,
187 # we have a regression of the latest fix. Mark it so, and create a
188 # new unfixed ID for it later on
189 if existing:
190 self.mark_regression(id, existing[-1][0])
191
192 # now query address signatures, they might turn up another duplicate
193 # (not necessarily the same, due to Stacktraces sometimes being
194 # slightly different)
195 addr_sig = report.crash_signature_addresses()
196 if addr_sig:
197 addr_match = self._duplicate_search_address_signature(addr_sig)
198 if addr_match and addr_match != master_id:
199 if master_id is None:
200 # we have a duplicate only identified by address sig, close it
201 master_id = addr_match
202 else:
203 # our bug is a dupe of two different masters, one from
204 # symbolic, the other from addr matching (see LP#943117);
205 # make them all duplicates of each other, using the lower
206 # number as master
207 if master_id < addr_match:
208 self.close_duplicate(report, addr_match, master_id)
209 self._duplicate_db_merge_id(addr_match, master_id)
210 else:
211 self.close_duplicate(report, master_id, addr_match)
212 self._duplicate_db_merge_id(master_id, addr_match)
213 master_id = addr_match
214 master_ver = None # no version tracking for address signatures yet
215
216 if master_id is not None and master_id != id:
217 if addr_sig:
218 self._duplicate_db_add_address_signature(addr_sig, master_id)
219 self.close_duplicate(report, id, master_id)
220 return (master_id, master_ver)
221
222 # no duplicate detected; create a new record for the ID if we don't have one already
223 if sig:
224 cur = self.duplicate_db.cursor()
225 cur.execute('SELECT count(*) FROM crashes WHERE crash_id == ?', [id])
226 count_id = cur.fetchone()[0]
227 if count_id == 0:
228 cur.execute('INSERT INTO crashes VALUES (?, ?, ?, CURRENT_TIMESTAMP)', (_u(sig), id, None))
229 self.duplicate_db.commit()
230 if addr_sig:
231 self._duplicate_db_add_address_signature(addr_sig, id)
232
233 return None
234
235 def known(self, report):
236 '''Check if the crash db already knows about the crash signature.
237
238 Check if the report has a DuplicateSignature, crash_signature(), or
239 StacktraceAddressSignature, and ask the database whether the problem is
240 already known. If so, return an URL where the user can check the status
241 or subscribe (if available), or just return True if the report is known
242 but there is no public URL. In that case the report will not be
243 uploaded (i. e. upload() will not be called).
244
245 Return None if the report does not have any signature or the crash
246 database does not support checking for duplicates on the client side.
247
248 The default implementation uses a text file format generated by
249 duplicate_db_publish() at an URL specified by the "dupdb_url" option.
250 Subclasses are free to override this with a custom implementation, such
251 as a real database lookup.
252 '''
253 if not self.options.get('dupdb_url'):
254 return None
255
256 for kind in ('sig', 'address'):
257 # get signature
258 if kind == 'sig':
259 if 'DuplicateSignature' in report:
260 sig = report['DuplicateSignature']
261 else:
262 sig = report.crash_signature()
263 else:
264 sig = report.crash_signature_addresses()
265
266 if not sig:
267 continue
268
269 # build URL where the data should be
270 h = self.duplicate_sig_hash(sig)
271 if not h:
272 return None
273
274 # the hash is already quoted, but we really want to open the quoted
275 # file names; as urlopen() unquotes, we need to double-quote here
276 # again so that urlopen() sees the single-quoted file names
277 url = os.path.join(self.options['dupdb_url'], kind, quote_plus(h))
278
279 # read data file
280 try:
281 f = urlopen(url)
282 contents = f.read().decode('UTF-8')
283 f.close()
284 if '<title>404 Not Found' in contents:
285 continue
286 except (IOError, URLError):
287 # does not exist, failed to load, etc.
288 continue
289
290 # now check if we find our signature
291 for line in contents.splitlines():
292 try:
293 id, s = line.split(None, 1)
294 id = int(id)
295 except ValueError:
296 continue
297 if s == sig:
298 result = self.get_id_url(report, id)
299 if not result:
300 # if we can't have an URL, just report as "known"
301 result = '1'
302 return result
303
304 return None
305
306 def duplicate_db_fixed(self, id, version):
307 '''Mark given crash ID as fixed in the duplicate database.
308
309 version specifies the package version the crash was fixed in (None for
310 'still unfixed').
311 '''
312 assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
313
314 cur = self.duplicate_db.cursor()
315 n = cur.execute('UPDATE crashes SET fixed_version = ?, last_change = CURRENT_TIMESTAMP WHERE crash_id = ?',
316 (version, id))
317 assert n.rowcount == 1
318 self.duplicate_db.commit()
319
320 def duplicate_db_remove(self, id):
321 '''Remove crash from the duplicate database.
322
323 This happens when a report got rejected or manually duplicated.
324 '''
325 assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
326
327 cur = self.duplicate_db.cursor()
328 cur.execute('DELETE FROM crashes WHERE crash_id = ?', [id])
329 cur.execute('DELETE FROM address_signatures WHERE crash_id = ?', [id])
330 self.duplicate_db.commit()
331
332 def duplicate_db_change_master_id(self, old_id, new_id):
333 '''Change a crash ID.'''
334
335 assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
336
337 cur = self.duplicate_db.cursor()
338 cur.execute('UPDATE crashes SET crash_id = ?, last_change = CURRENT_TIMESTAMP WHERE crash_id = ?',
339 [new_id, old_id])
340 cur.execute('UPDATE address_signatures SET crash_id = ? WHERE crash_id = ?',
341 [new_id, old_id])
342 self.duplicate_db.commit()
343
344 def duplicate_db_publish(self, dir):
345 '''Create text files suitable for www publishing.
346
347 Create a number of text files in the given directory which Apport
348 clients can use to determine whether a problem is already reported to
349 the database, through the known() method. This directory is suitable
350 for publishing to the web.
351
352 The database is indexed by the first two fields of the duplicate or
353 crash signature, to avoid having to download the entire database every
354 time.
355
356 If the directory already exists, it will be updated. The new content is
357 built in a new directory which is the given one with ".new" appended,
358 then moved to the given name in an almost atomic way.
359 '''
360 assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
361
362 # first create the temporary new dir; if that fails, nothing has been
363 # changed and we fail early
364 out = dir + '.new'
365 os.mkdir(out)
366
367 # crash addresses
368 addr_base = os.path.join(out, 'address')
369 os.mkdir(addr_base)
370 cur_hash = None
371 cur_file = None
372
373 cur = self.duplicate_db.cursor()
374
375 cur.execute('SELECT * from address_signatures ORDER BY signature')
376 for (sig, id) in cur.fetchall():
377 h = self.duplicate_sig_hash(sig)
378 if h is None:
379 # some entries can't be represented in a single line
380 continue
381 if h != cur_hash:
382 cur_hash = h
383 if cur_file:
384 cur_file.close()
385 cur_file = open(os.path.join(addr_base, cur_hash), 'w')
386
387 cur_file.write('%i %s\n' % (id, sig))
388
389 if cur_file:
390 cur_file.close()
391
392 # duplicate signatures
393 sig_base = os.path.join(out, 'sig')
394 os.mkdir(sig_base)
395 cur_hash = None
396 cur_file = None
397
398 cur.execute('SELECT signature, crash_id from crashes ORDER BY signature')
399 for (sig, id) in cur.fetchall():
400 h = self.duplicate_sig_hash(sig)
401 if h is None:
402 # some entries can't be represented in a single line
403 continue
404 if h != cur_hash:
405 cur_hash = h
406 if cur_file:
407 cur_file.close()
408 cur_file = open(os.path.join(sig_base, cur_hash), 'wb')
409
410 cur_file.write(('%i %s\n' % (id, sig)).encode('UTF-8'))
411
412 if cur_file:
413 cur_file.close()
414
415 # switch over tree; this is as atomic as we can be with directories
416 if os.path.exists(dir):
417 os.rename(dir, dir + '.old')
418 os.rename(out, dir)
419 if os.path.exists(dir + '.old'):
420 shutil.rmtree(dir + '.old')
421
422 def _duplicate_db_upgrade(self, cur_format):
423 '''Upgrade database to current format'''
424
425 # Format 3 added a primary key which can't be done as an upgrade in
426 # SQLite
427 if cur_format < 3:
428 raise SystemError('Cannot upgrade database from format earlier than 3')
429
430 cur = self.duplicate_db.cursor()
431
432 cur.execute('UPDATE version SET format = ?', (cur_format,))
433 self.duplicate_db.commit()
434
435 assert cur_format == self.format_version
436
437 def _duplicate_search_signature(self, sig, id):
438 '''Look up signature in the duplicate db.
439
440 Return [(id, fixed_version)] tuple list.
441
442 There might be several matches if a crash has been reintroduced in a
443 later version. The results are sorted so that the highest fixed version
444 comes first, and "unfixed" being the last result.
445
446 id is the bug we are looking to find a duplicate for. The result will
447 never contain id, to avoid marking a bug as a duplicate of itself if a
448 bug is reprocessed more than once.
449 '''
450 cur = self.duplicate_db.cursor()
451 cur.execute('SELECT crash_id, fixed_version FROM crashes WHERE signature = ? AND crash_id <> ?', [_u(sig), id])
452 existing = cur.fetchall()
453
454 def cmp(x, y):
455 x = x[1]
456 y = y[1]
457 if x == y:
458 return 0
459 if x == '':
460 if y is None:
461 return -1
462 else:
463 return 1
464 if y == '':
465 if x is None:
466 return 1
467 else:
468 return -1
469 if x is None:
470 return 1
471 if y is None:
472 return -1
473 return apport.packaging.compare_versions(x, y)
474
475 if sys.version[0] >= '3':
476 existing.sort(key=cmp_to_key(cmp))
477 else:
478 existing.sort(cmp=cmp)
479
480 return existing
481
482 def _duplicate_search_address_signature(self, sig):
483 '''Return ID for crash address signature.
484
485 Return None if signature is unknown.
486 '''
487 if not sig:
488 return None
489
490 cur = self.duplicate_db.cursor()
491
492 cur.execute('SELECT crash_id FROM address_signatures WHERE signature == ?', [sig])
493 existing_ids = cur.fetchall()
494 assert len(existing_ids) <= 1
495 if existing_ids:
496 return existing_ids[0][0]
497 else:
498 return None
499
500 def _duplicate_db_dump(self, with_timestamps=False):
501 '''Return the entire duplicate database as a dictionary.
502
503 The returned dictionary maps "signature" to (crash_id, fixed_version)
504 pairs.
505
506 If with_timestamps is True, then the map will contain triples
507 (crash_id, fixed_version, last_change) instead.
508
509 This is mainly useful for debugging and test suites.
510 '''
511 assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
512
513 dump = {}
514 cur = self.duplicate_db.cursor()
515 cur.execute('SELECT * FROM crashes')
516 for (sig, id, ver, last_change) in cur:
517 if with_timestamps:
518 dump[sig] = (id, ver, last_change)
519 else:
520 dump[sig] = (id, ver)
521 return dump
522
523 def _duplicate_db_sync_status(self, id):
524 '''Update the duplicate db to the reality of the report in the crash db.
525
526 This uses get_fixed_version() to get the status of the given crash.
527 An invalid ID gets removed from the duplicate db, and a crash which got
528 fixed is marked as such in the database.
529 '''
530 assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
531
532 cur = self.duplicate_db.cursor()
533 cur.execute('SELECT fixed_version FROM crashes WHERE crash_id = ?', [id])
534 db_fixed_version = cur.fetchone()
535 if not db_fixed_version:
536 return
537 db_fixed_version = db_fixed_version[0]
538
539 real_fixed_version = self.get_fixed_version(id)
540
541 # crash got rejected
542 if real_fixed_version == 'invalid':
543 print('DEBUG: bug %i was invalidated, removing from database' % id)
544 self.duplicate_db_remove(id)
545 return
546
547 # crash got fixed
548 if not db_fixed_version and real_fixed_version:
549 print('DEBUG: bug %i got fixed in version %s, updating database' % (id, real_fixed_version))
550 self.duplicate_db_fixed(id, real_fixed_version)
551 return
552
553 # crash got reopened
554 if db_fixed_version and not real_fixed_version:
555 print('DEBUG: bug %i got reopened, dropping fixed version %s from database' % (id, db_fixed_version))
556 self.duplicate_db_fixed(id, real_fixed_version)
557 return
558
559 def _duplicate_db_add_address_signature(self, sig, id):
560 # sanity check
561 existing = self._duplicate_search_address_signature(sig)
562 if existing:
563 if existing != id:
564 raise SystemError('ID %i has signature %s, but database already has that signature for ID %i' % (
565 id, sig, existing))
566 else:
567 cur = self.duplicate_db.cursor()
568 cur.execute('INSERT INTO address_signatures VALUES (?, ?)', (_u(sig), id))
569 self.duplicate_db.commit()
570
571 def _duplicate_db_merge_id(self, dup, master):
572 '''Merge two crash IDs.
573
574 This is necessary when having to mark a bug as a duplicate if it
575 already is in the duplicate DB.
576 '''
577 assert self.duplicate_db, 'init_duplicate_db() needs to be called before'
578
579 cur = self.duplicate_db.cursor()
580 cur.execute('DELETE FROM crashes WHERE crash_id = ?', [dup])
581 cur.execute('UPDATE address_signatures SET crash_id = ? WHERE crash_id = ?',
582 [master, dup])
583 self.duplicate_db.commit()
584
585 @classmethod
586 def duplicate_sig_hash(klass, sig):
587 '''Create a www/URL proof hash for a duplicate signature'''
588
589 # cannot hash multi-line custom duplicate signatures
590 if '\n' in sig:
591 return None
592
593 # custom DuplicateSignatures have a free format, split off first word
594 i = sig.split(' ', 1)[0]
595 # standard crash/address signatures use ':' as field separator, usually
596 # for ExecutableName:Signal
597 i = '_'.join(i.split(':', 2)[:2])
598 # we manually quote '/' to make them nicer to read
599 i = i.replace('/', '_')
600 i = quote_plus(i.encode('UTF-8'))
601 # avoid too long file names
602 i = i[:200]
603 return i
604
605 #
606 # Abstract functions that need to be implemented by subclasses
607 #
608
609 def upload(self, report, progress_callback=None):
610 '''Upload given problem report return a handle for it.
611
612 This should happen noninteractively.
613
614 If the implementation supports it, and a function progress_callback is
615 passed, that is called repeatedly with two arguments: the number of
616 bytes already sent, and the total number of bytes to send. This can be
617 used to provide a proper upload progress indication on frontends.
618
619 Implementations ought to "assert self.accepts(report)". The UI logic
620 already prevents uploading a report to a database which does not accept
621 it, but for third-party users of the API this should still be checked.
622
623 This method can raise a NeedsCredentials exception in case of failure.
624 '''
625 raise NotImplementedError('this method must be implemented by a concrete subclass')
626
627 def get_comment_url(self, report, handle):
628 '''Return an URL that should be opened after report has been uploaded
629 and upload() returned handle.
630
631 Should return None if no URL should be opened (anonymous filing without
632 user comments); in that case this function should do whichever
633 interactive steps it wants to perform.
634 '''
635 raise NotImplementedError('this method must be implemented by a concrete subclass')
636
637 def get_id_url(self, report, id):
638 '''Return URL for a given report ID.
639
640 The report is passed in case building the URL needs additional
641 information from it, such as the SourcePackage name.
642
643 Return None if URL is not available or cannot be determined.
644 '''
645 raise NotImplementedError('this method must be implemented by a concrete subclass')
646
647 def download(self, id):
648 '''Download the problem report from given ID and return a Report.'''
649
650 raise NotImplementedError('this method must be implemented by a concrete subclass')
651
652 def update(self, id, report, comment, change_description=False,
653 attachment_comment=None, key_filter=None):
654 '''Update the given report ID with all data from report.
655
656 This creates a text comment with the "short" data (see
657 ProblemReport.write_mime()), and creates attachments for all the
658 bulk/binary data.
659
660 If change_description is True, and the crash db implementation supports
661 it, the short data will be put into the description instead (like in a
662 new bug).
663
664 comment will be added to the "short" data. If attachment_comment is
665 given, it will be added to the attachment uploads.
666
667 If key_filter is a list or set, then only those keys will be added.
668 '''
669 raise NotImplementedError('this method must be implemented by a concrete subclass')
670
671 def update_traces(self, id, report, comment=''):
672 '''Update the given report ID for retracing results.
673
674 This updates Stacktrace, ThreadStacktrace, StacktraceTop,
675 and StacktraceSource. You can also supply an additional comment.
676 '''
677 self.update(id, report, comment, key_filter=[
678 'Stacktrace', 'ThreadStacktrace', 'StacktraceSource', 'StacktraceTop'])
679
680 def set_credentials(self, username, password):
681 '''Set username and password.'''
682
683 raise NotImplementedError('this method must be implemented by a concrete subclass')
684
685 def get_distro_release(self, id):
686 '''Get 'DistroRelease: <release>' from the report ID.'''
687
688 raise NotImplementedError('this method must be implemented by a concrete subclass')
689
690 def get_unretraced(self):
691 '''Return set of crash IDs which have not been retraced yet.
692
693 This should only include crashes which match the current host
694 architecture.
695 '''
696 raise NotImplementedError('this method must be implemented by a concrete subclass')
697
698 def get_dup_unchecked(self):
699 '''Return set of crash IDs which need duplicate checking.
700
701 This is mainly useful for crashes of scripting languages such as
702 Python, since they do not need to be retraced. It should not return
703 bugs that are covered by get_unretraced().
704 '''
705 raise NotImplementedError('this method must be implemented by a concrete subclass')
706
707 def get_unfixed(self):
708 '''Return an ID set of all crashes which are not yet fixed.
709
710 The list must not contain bugs which were rejected or duplicate.
711
712 This function should make sure that the returned list is correct. If
713 there are any errors with connecting to the crash database, it should
714 raise an exception (preferably IOError).
715 '''
716 raise NotImplementedError('this method must be implemented by a concrete subclass')
717
718 def get_fixed_version(self, id):
719 '''Return the package version that fixes a given crash.
720
721 Return None if the crash is not yet fixed, or an empty string if the
722 crash is fixed, but it cannot be determined by which version. Return
723 'invalid' if the crash report got invalidated, such as closed a
724 duplicate or rejected.
725
726 This function should make sure that the returned result is correct. If
727 there are any errors with connecting to the crash database, it should
728 raise an exception (preferably IOError).
729 '''
730 raise NotImplementedError('this method must be implemented by a concrete subclass')
731
732 def get_affected_packages(self, id):
733 '''Return list of affected source packages for given ID.'''
734
735 raise NotImplementedError('this method must be implemented by a concrete subclass')
736
737 def is_reporter(self, id):
738 '''Check whether the user is the reporter of given ID.'''
739
740 raise NotImplementedError('this method must be implemented by a concrete subclass')
741
742 def can_update(self, id):
743 '''Check whether the user is eligible to update a report.
744
745 A user should add additional information to an existing ID if (s)he is
746 the reporter or subscribed, the bug is open, not a duplicate, etc. The
747 exact policy and checks should be done according to the particular
748 implementation.
749 '''
750 raise NotImplementedError('this method must be implemented by a concrete subclass')
751
752 def duplicate_of(self, id):
753 '''Return master ID for a duplicate bug.
754
755 If the bug is not a duplicate, return None.
756 '''
757 raise NotImplementedError('this method must be implemented by a concrete subclass')
758
759 def close_duplicate(self, report, id, master):
760 '''Mark a crash id as duplicate of given master ID.
761
762 If master is None, id gets un-duplicated.
763 '''
764 raise NotImplementedError('this method must be implemented by a concrete subclass')
765
766 def mark_regression(self, id, master):
767 '''Mark a crash id as reintroducing an earlier crash which is
768 already marked as fixed (having ID 'master').'''
769
770 raise NotImplementedError('this method must be implemented by a concrete subclass')
771
772 def mark_retraced(self, id):
773 '''Mark crash id as retraced.'''
774
775 raise NotImplementedError('this method must be implemented by a concrete subclass')
776
777 def mark_retrace_failed(self, id, invalid_msg=None):
778 '''Mark crash id as 'failed to retrace'.
779
780 If invalid_msg is given, the bug should be closed as invalid with given
781 message, otherwise just marked as a failed retrace.
782
783 This can be a no-op if you are not interested in this.
784 '''
785 raise NotImplementedError('this method must be implemented by a concrete subclass')
786
787 def _mark_dup_checked(self, id, report):
788 '''Mark crash id as checked for being a duplicate
789
790 This is an internal method that should not be called from outside.
791 '''
792 raise NotImplementedError('this method must be implemented by a concrete subclass')
793
794#
795# factory
796#
797
798
799def get_crashdb(auth_file, name=None, conf=None):
800 '''Return a CrashDatabase object for the given crash db name.
801
802 This reads the configuration file 'conf'.
803
804 If name is None, it defaults to the 'default' value in conf.
805
806 If conf is None, it defaults to the environment variable
807 APPORT_CRASHDB_CONF; if that does not exist, the hardcoded default is
808 /etc/apport/crashdb.conf. This Python syntax file needs to specify:
809
810 - A string variable 'default', giving a default value for 'name' if that is
811 None.
812
813 - A dictionary 'databases' which maps names to crash db configuration
814 dictionaries. These need to have at least the key 'impl' (Python module
815 in apport.crashdb_impl which contains a concrete 'CrashDatabase' class
816 implementation for that crash db type). Other generally known options are
817 'bug_pattern_url', 'dupdb_url', and 'problem_types'.
818 '''
819 if not conf:
820 conf = os.environ.get('APPORT_CRASHDB_CONF', '/etc/apport/crashdb.conf')
821 settings = {}
822 with open(conf) as f:
823 exec(compile(f.read(), conf, 'exec'), settings)
824
825 # Load third parties crashdb.conf
826 confdDir = conf + '.d'
827 if os.path.isdir(confdDir):
828 for cf in os.listdir(confdDir):
829 cfpath = os.path.join(confdDir, cf)
830 if os.path.isfile(cfpath) and cf.endswith('.conf'):
831 try:
832 with open(cfpath) as f:
833 exec(compile(f.read(), cfpath, 'exec'), settings['databases'])
834 except Exception as e:
835 # ignore broken files
836 sys.stderr.write('Invalid file %s: %s\n' % (cfpath, str(e)))
837 pass
838
839 if not name:
840 name = settings['default']
841
842 return load_crashdb(auth_file, settings['databases'][name])
843
844
845def load_crashdb(auth_file, spec):
846 '''Return a CrashDatabase object for a given DB specification.
847
848 spec is a crash db configuration dictionary as described in get_crashdb().
849 '''
850 m = __import__('apport.crashdb_impl.' + spec['impl'], globals(), locals(), ['CrashDatabase'])
851 return m.CrashDatabase(auth_file, spec)
852
853
854class NeedsCredentials(Exception):
855 '''This may be raised when unable to log in to the crashdb.'''
856 pass
0857
=== added directory 'apport/crashdb_impl'
=== added file 'apport/crashdb_impl/__init__.py'
=== added file 'apport/crashdb_impl/debian.py'
--- apport/crashdb_impl/debian.py 1970-01-01 00:00:00 +0000
+++ apport/crashdb_impl/debian.py 2013-03-13 18:57:21 +0000
@@ -0,0 +1,113 @@
1'''Debian crash database interface.'''
2
3# Debian adaptation Copyright (C) 2012 Ritesh Raj Sarraf <rrs@debian.org>
4#
5# This program is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by the
7# Free Software Foundation; either version 2 of the License, or (at your
8# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
9# the full text of the license.
10
11
12import smtplib, tempfile
13from email.mime.text import MIMEText
14
15import apport
16import apport.crashdb
17
18
19class CrashDatabase(apport.crashdb.CrashDatabase):
20 '''
21 Debian crash database
22 This is a Apport CrashDB implementation for interacting with Debian BTS
23 '''
24 def __init__(self, auth_file, options):
25 '''
26 Initialize crash database connection.
27
28 Debian implementation is pretty basic as most of its bug management
29 processes revolve around the email interface
30 '''
31 apport.crashdb.CrashDatabase.__init__(self, auth_file, options)
32 self.options = options
33
34 if not self.options.get('smtphost'):
35 self.options['smtphost'] = 'reportbug.debian.org'
36
37 if not self.options.get('recipient'):
38 self.options['recipient'] = 'submit@bugs.debian.org'
39
40 def accepts(self, report):
41 '''
42 Check if this report can be uploaded to this database.
43 Checks for the proper settings of apport.
44 '''
45 if not self.options.get('sender') and 'UnreportableReason' not in report:
46 report['UnreportableReason'] = 'Please configure sender settings in /etc/apport/crashdb.conf'
47
48 # At this time, we are not ready to take CrashDumps
49 if 'Stacktrace' in report and not report.has_useful_stacktrace():
50 report['UnreportableReason'] = 'Incomplete backtrace. Please install the debug symbol packages'
51
52 return apport.crashdb.CrashDatabase.accepts(self, report)
53
54 def upload(self, report, progress_callback=None):
55 '''Upload given problem report return a handle for it.
56
57 In Debian, we use BTS, which is heavily email oriented
58 This method crafts the bug into an email report understood by Debian BTS
59 '''
60 # first and foremost, let's check if the apport bug filing settings are set correct
61 assert self.accepts(report)
62
63 # Frame the report in the format the BTS understands
64 try:
65 (buggyPackage, buggyVersion) = report['Package'].split(' ')
66 except (KeyError, ValueError):
67 return False
68
69 temp = tempfile.NamedTemporaryFile()
70
71 temp.file.write(('Package: ' + buggyPackage + '\n').encode('UTF-8'))
72 temp.file.write(('Version: ' + buggyVersion + '\n\n\n').encode('UTF-8'))
73 temp.file.write(('=============================\n\n').encode('UTF-8'))
74
75 # Let's remove the CoreDump first
76
77 # Even if we have a valid backtrace, we already are reporting it as text
78 # We don't want to send very large emails to the BTS.
79 # OTOH, if the backtrace is invalid, has_useful_backtrace() will already
80 # deny reporting of the bug report.
81 try:
82 del report['CoreDump']
83 except KeyError:
84 pass
85
86 # Now write the apport bug report
87 report.write(temp)
88
89 temp.file.seek(0)
90
91 msg = MIMEText(temp.file.read().decode('UTF-8'))
92 msg['Subject'] = report['Title']
93 msg['From'] = self.options['sender']
94 msg['To'] = self.options['recipient']
95
96 # Subscribe the submitted to the bug report
97 msg.add_header('X-Debbugs-CC', self.options['sender'])
98 msg.add_header('Usertag', 'apport-%s' % report['ProblemType'].lower())
99
100 s = smtplib.SMTP(self.options['smtphost'])
101 s.sendmail(self.options['sender'], self.options['recipient'], msg.as_string().encode('UTF-8'))
102 s.quit()
103
104 def get_comment_url(self, report, handle):
105 '''
106 Return an URL that should be opened after report has been uploaded
107 and upload() returned handle.
108
109 Should return None if no URL should be opened (anonymous filing without
110 user comments); in that case this function should do whichever
111 interactive steps it wants to perform.
112 '''
113 return None
0114
=== added file 'apport/crashdb_impl/launchpad.py'
--- apport/crashdb_impl/launchpad.py 1970-01-01 00:00:00 +0000
+++ apport/crashdb_impl/launchpad.py 2013-03-13 18:57:21 +0000
@@ -0,0 +1,1993 @@
1# vim: set fileencoding=UTF-8 :
2'''Crash database implementation for Launchpad.'''
3
4# Copyright (C) 2007 - 2009 Canonical Ltd.
5# Authors: Martin Pitt <martin.pitt@ubuntu.com> and Markus Korn <thekorn@gmx.de>
6#
7# This program is free software; you can redistribute it and/or modify it
8# under the terms of the GNU General Public License as published by the
9# Free Software Foundation; either version 2 of the License, or (at your
10# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
11# the full text of the license.
12
13import tempfile, os.path, re, gzip, sys, email, time
14
15from io import BytesIO
16
17if sys.version_info.major == 2:
18 from urllib2 import HTTPSHandler, Request, build_opener
19 from httplib import HTTPSConnection
20 from urllib import urlencode, urlopen
21 (HTTPSHandler, Request, build_opener, HTTPSConnection, urlencode, urlopen) # pyflakes
22 _python2 = True
23else:
24 from urllib.request import HTTPSHandler, Request, build_opener, urlopen
25 from urllib.parse import urlencode
26 from http.client import HTTPSConnection
27 _python2 = False
28
29try:
30 from launchpadlib.errors import HTTPError
31 from launchpadlib.launchpad import Launchpad
32 Launchpad # pyflakes
33except ImportError:
34 # if launchpadlib is not available, only client-side reporting will work
35 Launchpad = None
36
37import apport.crashdb
38import apport
39
40default_credentials_path = os.path.expanduser('~/.cache/apport/launchpad.credentials')
41
42
43def filter_filename(attachments):
44 for attachment in attachments:
45 try:
46 f = attachment.data.open()
47 except HTTPError:
48 apport.error('Broken attachment on bug, ignoring')
49 continue
50 name = f.filename
51 if name.endswith('.txt') or name.endswith('.gz'):
52 yield f
53
54
55def id_set(tasks):
56 # same as set(int(i.bug.id) for i in tasks) but faster
57 return set(int(i.self_link.split('/').pop()) for i in tasks)
58
59
60class CrashDatabase(apport.crashdb.CrashDatabase):
61 '''Launchpad implementation of crash database interface.'''
62
63 def __init__(self, auth, options):
64 '''Initialize Launchpad crash database.
65
66 You need to specify a launchpadlib-style credentials file to
67 access launchpad. If you supply None, it will use
68 default_credentials_path (~/.cache/apport/launchpad.credentials).
69
70 Recognized options are:
71 - distro: Name of the distribution in Launchpad
72 - project: Name of the project in Launchpad
73 (Note that exactly one of "distro" or "project" must be given.)
74 - launchpad_instance: If set, this uses the given launchpad instance
75 instead of production (optional). This can be overriden or set by
76 $APPORT_LAUNCHPAD_INSTANCE environment.
77 - cache_dir: Path to a permanent cache directory; by default it uses a
78 temporary one. (optional). This can be overridden or set by
79 $APPORT_LAUNCHPAD_CACHE environment.
80 - escalation_subscription: This subscribes the given person or team to
81 a bug once it gets the 10th duplicate.
82 - escalation_tag: This adds the given tag to a bug once it gets more
83 than 10 duplicates.
84 - initial_subscriber: The Launchpad user which gets subscribed to newly
85 filed bugs (default: "apport"). It should be a bot user which the
86 crash-digger instance runs as, as this will get to see all bug
87 details immediately.
88 - triaging_team: The Launchpad user/team which gets subscribed after
89 updating a crash report bug by the retracer (default:
90 "ubuntu-crashes-universe")
91 - architecture: If set, this sets and watches out for needs-*-retrace
92 tags of this architecture. This is useful when being used with
93 apport-retrace and crash-digger to process crash reports of foreign
94 architectures. Defaults to system architecture.
95 '''
96 if os.getenv('APPORT_LAUNCHPAD_INSTANCE'):
97 options['launchpad_instance'] = os.getenv(
98 'APPORT_LAUNCHPAD_INSTANCE')
99 if not auth:
100 lp_instance = options.get('launchpad_instance')
101 if lp_instance:
102 auth = default_credentials_path + '.' + lp_instance.split('://', 1)[-1]
103 else:
104 auth = default_credentials_path
105 apport.crashdb.CrashDatabase.__init__(self, auth, options)
106
107 self.distro = options.get('distro')
108 if self.distro:
109 assert 'project' not in options, 'Must not set both "project" and "distro" option'
110 else:
111 assert 'project' in options, 'Need to have either "project" or "distro" option'
112
113 if 'architecture' in options:
114 self.arch_tag = 'need-%s-retrace' % options['architecture']
115 else:
116 self.arch_tag = 'need-%s-retrace' % apport.packaging.get_system_architecture()
117 self.options = options
118 self.auth = auth
119 assert self.auth
120
121 self.__launchpad = None
122 self.__lp_distro = None
123 self.__lpcache = os.getenv('APPORT_LAUNCHPAD_CACHE', options.get('cache_dir'))
124
125 @property
126 def launchpad(self):
127 '''Return Launchpad instance.'''
128
129 if self.__launchpad:
130 return self.__launchpad
131
132 if Launchpad is None:
133 sys.stderr.write('ERROR: The launchpadlib Python module is not installed. This functionality is not available.\n')
134 sys.exit(1)
135
136 if self.options.get('launchpad_instance'):
137 launchpad_instance = self.options.get('launchpad_instance')
138 else:
139 launchpad_instance = 'production'
140
141 auth_dir = os.path.dirname(self.auth)
142 if auth_dir and not os.path.isdir(auth_dir):
143 os.makedirs(auth_dir)
144
145 try:
146 self.__launchpad = Launchpad.login_with('apport-collect',
147 launchpad_instance,
148 launchpadlib_dir=self.__lpcache,
149 allow_access_levels=['WRITE_PRIVATE'],
150 credentials_file=self.auth,
151 version='1.0')
152 except Exception as e:
153 if hasattr(e, 'content'):
154 msg = e.content
155 else:
156 msg = str(e)
157 apport.error('connecting to Launchpad failed: %s\nYou can reset the credentials by removing the file "%s"', msg, self.auth)
158 sys.exit(99) # transient error
159
160 return self.__launchpad
161
162 def _get_distro_tasks(self, tasks):
163 if not self.distro:
164 raise StopIteration
165
166 for t in tasks:
167 if t.bug_target_name.lower() == self.distro or \
168 re.match('^.+\(%s.*\)$' % self.distro, t.bug_target_name.lower()):
169 yield t
170
171 @property
172 def lp_distro(self):
173 if self.__lp_distro is None:
174 if self.distro:
175 self.__lp_distro = self.launchpad.distributions[self.distro]
176 elif 'project' in self.options:
177 self.__lp_distro = self.launchpad.projects[self.options['project']]
178 else:
179 raise SystemError('distro or project needs to be specified in crashdb options')
180
181 return self.__lp_distro
182
183 def upload(self, report, progress_callback=None):
184 '''Upload given problem report return a handle for it.
185
186 This should happen noninteractively.
187
188 If the implementation supports it, and a function progress_callback is
189 passed, that is called repeatedly with two arguments: the number of
190 bytes already sent, and the total number of bytes to send. This can be
191 used to provide a proper upload progress indication on frontends.
192 '''
193 assert self.accepts(report)
194
195 blob_file = self._generate_upload_blob(report)
196 ticket = upload_blob(blob_file, progress_callback, hostname=self.get_hostname())
197 blob_file.close()
198 assert ticket
199 return ticket
200
201 def get_hostname(self):
202 '''Return the hostname for the Launchpad instance.'''
203
204 launchpad_instance = self.options.get('launchpad_instance')
205 if launchpad_instance:
206 if launchpad_instance == 'staging':
207 hostname = 'staging.launchpad.net'
208 else:
209 hostname = 'launchpad.dev'
210 else:
211 hostname = 'launchpad.net'
212 return hostname
213
214 def get_comment_url(self, report, handle):
215 '''Return an URL that should be opened after report has been uploaded
216 and upload() returned handle.
217
218 Should return None if no URL should be opened (anonymous filing without
219 user comments); in that case this function should do whichever
220 interactive steps it wants to perform.'''
221
222 args = {}
223 title = report.get('Title', report.standard_title())
224 if title:
225 # always use UTF-8 encoding, urlencode() blows up otherwise in
226 # python 2.7
227 if type(title) != type(b''):
228 title = title.encode('UTF-8')
229 args['field.title'] = title
230
231 hostname = self.get_hostname()
232
233 project = self.options.get('project')
234
235 if not project:
236 if 'SourcePackage' in report:
237 return 'https://bugs.%s/%s/+source/%s/+filebug/%s?%s' % (
238 hostname, self.distro, report['SourcePackage'], handle, urlencode(args))
239 else:
240 return 'https://bugs.%s/%s/+filebug/%s?%s' % (
241 hostname, self.distro, handle, urlencode(args))
242 else:
243 return 'https://bugs.%s/%s/+filebug/%s?%s' % (
244 hostname, project, handle, urlencode(args))
245
246 def get_id_url(self, report, id):
247 '''Return URL for a given report ID.
248
249 The report is passed in case building the URL needs additional
250 information from it, such as the SourcePackage name.
251
252 Return None if URL is not available or cannot be determined.
253 '''
254 return 'https://bugs.launchpad.net/bugs/' + str(id)
255
256 def download(self, id):
257 '''Download the problem report from given ID and return a Report.'''
258
259 report = apport.Report()
260 b = self.launchpad.bugs[id]
261
262 # parse out fields from summary
263 m = re.search(r'(ProblemType:.*)$', b.description, re.S)
264 if not m:
265 m = re.search(r'^--- \r?$[\r\n]*(.*)', b.description, re.M | re.S)
266 assert m, 'bug description must contain standard apport format data'
267
268 description = m.group(1).encode('UTF-8').replace('\xc2\xa0', ' ').replace('\r\n', '\n')
269
270 if '\n\n' in description:
271 # this often happens, remove all empty lines between top and
272 # 'Uname'
273 if 'Uname:' in description:
274 # this will take care of bugs like LP #315728 where stuff
275 # is added after the apport data
276 (part1, part2) = description.split('Uname:', 1)
277 description = part1.replace('\n\n', '\n') + 'Uname:' \
278 + part2.split('\n\n', 1)[0]
279 else:
280 # just parse out the Apport block; e. g. LP #269539
281 description = description.split('\n\n', 1)[0]
282
283 report.load(BytesIO(description))
284
285 if 'Date' not in report:
286 # We had not submitted this field for a while, claiming it
287 # redundant. But it is indeed required for up-to-the-minute
288 # comparison with log files, etc. For backwards compatibility with
289 # those reported bugs, read the creation date
290 try:
291 report['Date'] = b.date_created.ctime()
292 except AttributeError:
293 # support older wadllib API which returned strings
294 report['Date'] = b.date_created
295 if 'ProblemType' not in report:
296 if 'apport-bug' in b.tags:
297 report['ProblemType'] = 'Bug'
298 elif 'apport-crash' in b.tags:
299 report['ProblemType'] = 'Crash'
300 elif 'apport-kernelcrash' in b.tags:
301 report['ProblemType'] = 'KernelCrash'
302 elif 'apport-package' in b.tags:
303 report['ProblemType'] = 'Package'
304 else:
305 raise ValueError('cannot determine ProblemType from tags: ' + str(b.tags))
306
307 report['Tags'] = ' '.join(b.tags)
308
309 if 'Title' in report:
310 report['OriginalTitle'] = report['Title']
311
312 report['Title'] = b.title
313
314 for attachment in filter_filename(b.attachments):
315 key, ext = os.path.splitext(attachment.filename)
316 # ignore attachments with invalid keys
317 try:
318 report[key] = ''
319 except:
320 continue
321 if ext == '.txt':
322 report[key] = attachment.read()
323 elif ext == '.gz':
324 try:
325 report[key] = gzip.GzipFile(fileobj=attachment).read()
326 except IOError as e:
327 # some attachments are only called .gz, but are
328 # uncompressed (LP #574360)
329 if 'Not a gzip' not in str(e):
330 raise
331 attachment.seek(0)
332 report[key] = attachment.read()
333 else:
334 raise Exception('Unknown attachment type: ' + attachment.filename)
335 return report
336
337 def update(self, id, report, comment, change_description=False,
338 attachment_comment=None, key_filter=None):
339 '''Update the given report ID with all data from report.
340
341 This creates a text comment with the "short" data (see
342 ProblemReport.write_mime()), and creates attachments for all the
343 bulk/binary data.
344
345 If change_description is True, and the crash db implementation supports
346 it, the short data will be put into the description instead (like in a
347 new bug).
348
349 comment will be added to the "short" data. If attachment_comment is
350 given, it will be added to the attachment uploads.
351
352 If key_filter is a list or set, then only those keys will be added.
353 '''
354 bug = self.launchpad.bugs[id]
355
356 if key_filter:
357 skip_keys = set(report.keys()) - set(key_filter)
358 else:
359 skip_keys = None
360
361 # we want to reuse the knowledge of write_mime() with all its different input
362 # types and output formatting; however, we have to dissect the mime ourselves,
363 # since we can't just upload it as a blob
364 mime = tempfile.TemporaryFile()
365 report.write_mime(mime, skip_keys=skip_keys)
366 mime.flush()
367 mime.seek(0)
368 msg = email.message_from_file(mime)
369 msg_iter = msg.walk()
370
371 # first part is the multipart container
372 part = msg_iter.next()
373 assert part.is_multipart()
374
375 # second part should be an inline text/plain attachments with all short
376 # fields
377 part = msg_iter.next()
378 assert not part.is_multipart()
379 assert part.get_content_type() == 'text/plain'
380
381 if not key_filter:
382 # when we update a complete report, we are updating an existing bug
383 # with apport-collect
384 x = bug.tags[:] # LP#254901 workaround
385 x.append('apport-collected')
386 # add any tags (like the release) to the bug
387 if 'Tags' in report:
388 x += self._filter_tag_names(report['Tags']).split()
389 bug.tags = x
390 bug.lp_save()
391 bug = self.launchpad.bugs[id] # fresh bug object, LP#336866 workaround
392
393 # short text data
394 if change_description:
395 bug.description = bug.description + '\n--- \n' + part.get_payload(decode=True).decode('UTF-8', 'replace')
396 bug.lp_save()
397 else:
398 bug.newMessage(content=part.get_payload(decode=True), subject=comment)
399
400 # other parts are the attachments:
401 for part in msg_iter:
402 # print ' attachment: %s...' % part.get_filename()
403 bug.addAttachment(comment=attachment_comment or '',
404 description=part.get_filename(),
405 content_type=None,
406 data=part.get_payload(decode=True),
407 filename=part.get_filename(), is_patch=False)
408
409 def update_traces(self, id, report, comment=''):
410 '''Update the given report ID for retracing results.
411
412 This updates Stacktrace, ThreadStacktrace, StacktraceTop,
413 and StacktraceSource. You can also supply an additional comment.
414 '''
415 apport.crashdb.CrashDatabase.update_traces(self, id, report, comment)
416
417 bug = self.launchpad.bugs[id]
418 # ensure it's assigned to a package
419 if 'SourcePackage' in report:
420 for task in bug.bug_tasks:
421 if task.target.resource_type_link.endswith('#distribution'):
422 task.target = self.lp_distro.getSourcePackage(name=report['SourcePackage'])
423 task.lp_save()
424 bug = self.launchpad.bugs[id]
425 break
426
427 # remove core dump if stack trace is usable
428 if report.has_useful_stacktrace():
429 for a in bug.attachments:
430 if a.title == 'CoreDump.gz':
431 try:
432 a.removeFromBug()
433 except HTTPError:
434 pass # LP#249950 workaround
435 try:
436 task = self._get_distro_tasks(bug.bug_tasks).next()
437 if task.importance == 'Undecided':
438 task.importance = 'Medium'
439 task.lp_save()
440 except StopIteration:
441 pass # no distro tasks
442
443 # update bug title with retraced function name
444 fn = report.stacktrace_top_function()
445 if fn:
446 m = re.match('^(.*crashed with SIG.* in )([^( ]+)(\(\).*$)', bug.title)
447 if m and m.group(2) != fn:
448 bug.title = m.group(1) + fn + m.group(3)
449 try:
450 bug.lp_save()
451 except HTTPError:
452 pass # LP#336866 workaround
453 bug = self.launchpad.bugs[id]
454
455 self._subscribe_triaging_team(bug, report)
456
457 def get_distro_release(self, id):
458 '''Get 'DistroRelease: <release>' from the given report ID and return
459 it.'''
460 bug = self.launchpad.bugs[id]
461 m = re.search('DistroRelease: ([-a-zA-Z0-9.+/ ]+)', bug.description)
462 if m:
463 return m.group(1)
464 raise ValueError('URL does not contain DistroRelease: field')
465
466 def get_affected_packages(self, id):
467 '''Return list of affected source packages for given ID.'''
468
469 bug_target_re = re.compile(
470 r'/%s/(?:(?P<suite>[^/]+)/)?\+source/(?P<source>[^/]+)$' % self.distro)
471
472 bug = self.launchpad.bugs[id]
473 result = []
474
475 for task in bug.bug_tasks:
476 match = bug_target_re.search(task.target.self_link)
477 if not match:
478 continue
479 if task.status in ('Invalid', "Won't Fix", 'Fix Released'):
480 continue
481 result.append(match.group('source'))
482 return result
483
484 def is_reporter(self, id):
485 '''Check whether the user is the reporter of given ID.'''
486
487 bug = self.launchpad.bugs[id]
488 return bug.owner.name == self.launchpad.me.name
489
490 def can_update(self, id):
491 '''Check whether the user is eligible to update a report.
492
493 A user should add additional information to an existing ID if (s)he is
494 the reporter or subscribed, the bug is open, not a duplicate, etc. The
495 exact policy and checks should be done according to the particular
496 implementation.
497 '''
498 bug = self.launchpad.bugs[id]
499 if bug.duplicate_of:
500 return False
501
502 if bug.owner.name == self.launchpad.me.name:
503 return True
504
505 # check subscription
506 me = self.launchpad.me.self_link
507 for sub in bug.subscriptions.entries:
508 if sub['person_link'] == me:
509 return True
510
511 return False
512
513 def get_unretraced(self):
514 '''Return an ID set of all crashes which have not been retraced yet and
515 which happened on the current host architecture.'''
516 try:
517 bugs = self.lp_distro.searchTasks(tags=self.arch_tag, created_since='2011-08-01')
518 return id_set(bugs)
519 except Exception as e:
520 apport.error('connecting to Launchpad failed: %s', str(e))
521 sys.exit(99) # transient error
522
523 def get_dup_unchecked(self):
524 '''Return an ID set of all crashes which have not been checked for
525 being a duplicate.
526
527 This is mainly useful for crashes of scripting languages such as
528 Python, since they do not need to be retraced. It should not return
529 bugs that are covered by get_unretraced().'''
530
531 try:
532 bugs = self.lp_distro.searchTasks(tags='need-duplicate-check', created_since='2011-08-01')
533 return id_set(bugs)
534 except Exception as e:
535 apport.error('connecting to Launchpad failed: %s', str(e))
536 sys.exit(99) # transient error
537
538 def get_unfixed(self):
539 '''Return an ID set of all crashes which are not yet fixed.
540
541 The list must not contain bugs which were rejected or duplicate.
542
543 This function should make sure that the returned list is correct. If
544 there are any errors with connecting to the crash database, it should
545 raise an exception (preferably IOError).'''
546
547 bugs = self.lp_distro.searchTasks(tags='apport-crash')
548 return id_set(bugs)
549
550 def _get_source_version(self, package):
551 '''Return the version of given source package in the latest release of
552 given distribution.
553
554 If 'distro' is None, we will look for a launchpad project .
555 '''
556 sources = self.lp_distro.main_archive.getPublishedSources(
557 exact_match=True,
558 source_name=package,
559 distro_series=self.lp_distro.current_series
560 )
561 # first element is the latest one
562 return sources[0].source_package_version
563
564 def get_fixed_version(self, id):
565 '''Return the package version that fixes a given crash.
566
567 Return None if the crash is not yet fixed, or an empty string if the
568 crash is fixed, but it cannot be determined by which version. Return
569 'invalid' if the crash report got invalidated, such as closed a
570 duplicate or rejected.
571
572 This function should make sure that the returned result is correct. If
573 there are any errors with connecting to the crash database, it should
574 raise an exception (preferably IOError).
575 '''
576 # do not do version tracking yet; for that, we need to get the current
577 # distrorelease and the current package version in that distrorelease
578 # (or, of course, proper version tracking in Launchpad itself)
579
580 try:
581 b = self.launchpad.bugs[id]
582 except KeyError:
583 return 'invalid'
584
585 if b.duplicate_of:
586 return 'invalid'
587
588 tasks = list(b.bug_tasks) # just fetch it once
589
590 if self.distro:
591 distro_identifier = '(%s)' % self.distro.lower()
592 fixed_tasks = filter(lambda task: task.status == 'Fix Released' and
593 distro_identifier in task.bug_target_display_name.lower(), tasks)
594
595 if not fixed_tasks:
596 fixed_distro = filter(lambda task: task.status == 'Fix Released' and
597 task.bug_target_name.lower() == self.distro.lower(), tasks)
598 if fixed_distro:
599 # fixed in distro inself (without source package)
600 return ''
601
602 if len(fixed_tasks) > 1:
603 apport.warning('There is more than one task fixed in %s %s, using first one to determine fixed version', self.distro, id)
604 return ''
605
606 if fixed_tasks:
607 task = fixed_tasks.pop()
608 try:
609 return self._get_source_version(task.bug_target_display_name.split()[0])
610 except IndexError:
611 # source does not exist any more
612 return 'invalid'
613 else:
614 # check if there only invalid ones
615 invalid_tasks = filter(lambda task: task.status in ('Invalid', "Won't Fix", 'Expired') and
616 distro_identifier in task.bug_target_display_name.lower(), tasks)
617 if invalid_tasks:
618 non_invalid_tasks = filter(
619 lambda task: task.status not in ('Invalid', "Won't Fix", 'Expired') and
620 distro_identifier in task.bug_target_display_name.lower(), tasks)
621 if not non_invalid_tasks:
622 return 'invalid'
623 else:
624 fixed_tasks = filter(lambda task: task.status == 'Fix Released', tasks)
625 if fixed_tasks:
626 # TODO: look for current series
627 return ''
628 # check if there any invalid ones
629 if filter(lambda task: task.status == 'Invalid', tasks):
630 return 'invalid'
631
632 return None
633
634 def duplicate_of(self, id):
635 '''Return master ID for a duplicate bug.
636
637 If the bug is not a duplicate, return None.
638 '''
639 b = self.launchpad.bugs[id].duplicate_of
640 if b:
641 return b.id
642 else:
643 return None
644
645 def close_duplicate(self, report, id, master_id):
646 '''Mark a crash id as duplicate of given master ID.
647
648 If master is None, id gets un-duplicated.
649 '''
650 bug = self.launchpad.bugs[id]
651
652 if master_id:
653 assert id != master_id, 'cannot mark bug %s as a duplicate of itself' % str(id)
654
655 # check whether the master itself is a dup
656 master = self.launchpad.bugs[master_id]
657 if master.duplicate_of:
658 master = master.duplicate_of
659 master_id = master.id
660 if master.id == id:
661 # this happens if the bug was manually duped to a newer one
662 apport.warning('Bug %i was manually marked as a dupe of newer bug %i, not closing as duplicate',
663 id, master_id)
664 return
665
666 for a in bug.attachments:
667 if a.title in ('CoreDump.gz', 'Stacktrace.txt',
668 'ThreadStacktrace.txt', 'ProcMaps.txt',
669 'ProcStatus.txt', 'Registers.txt',
670 'Disassembly.txt'):
671 try:
672 a.removeFromBug()
673 except HTTPError:
674 pass # LP#249950 workaround
675
676 bug = self.launchpad.bugs[id] # fresh bug object, LP#336866 workaround
677 bug.newMessage(content='Thank you for taking the time to report this crash and helping \
678to make this software better. This particular crash has already been reported and \
679is a duplicate of bug #%i, so is being marked as such. Please look at the \
680other bug report to see if there is any missing information that you can \
681provide, or to see if there is a workaround for the bug. Additionally, any \
682further discussion regarding the bug should occur in the other report. \
683Please continue to report any other bugs you may find.' % master_id,
684 subject='This bug is a duplicate')
685
686 bug = self.launchpad.bugs[id] # refresh, LP#336866 workaround
687 if bug.private:
688 bug.private = False
689
690 # set duplicate last, since we cannot modify already dup'ed bugs
691 if not bug.duplicate_of:
692 bug.duplicate_of = master
693
694 # cache tags of master bug report instead of performing multiple
695 # queries
696 master_tags = master.tags
697
698 if len(master.duplicates) == 10:
699 if 'escalation_tag' in self.options and self.options['escalation_tag'] not in master_tags and self.options.get('escalated_tag', ' invalid ') not in master_tags:
700 master.tags = master_tags + [self.options['escalation_tag']] # LP#254901 workaround
701 master.lp_save()
702
703 if 'escalation_subscription' in self.options and self.options.get('escalated_tag', ' invalid ') not in master_tags:
704 p = self.launchpad.people[self.options['escalation_subscription']]
705 master.subscribe(person=p)
706
707 # requesting updated stack trace?
708 if report.has_useful_stacktrace() and ('apport-request-retrace' in master_tags
709 or 'apport-failed-retrace' in master_tags):
710 self.update(master_id, report, 'Updated stack trace from duplicate bug %i' % id,
711 key_filter=['Stacktrace', 'ThreadStacktrace',
712 'Package', 'Dependencies', 'ProcMaps', 'ProcCmdline'])
713
714 master = self.launchpad.bugs[master_id]
715 x = master.tags[:] # LP#254901 workaround
716 try:
717 x.remove('apport-failed-retrace')
718 except ValueError:
719 pass
720 try:
721 x.remove('apport-request-retrace')
722 except ValueError:
723 pass
724 master.tags = x
725 try:
726 master.lp_save()
727 except HTTPError:
728 pass # LP#336866 workaround
729
730 # white list of tags to copy from duplicates bugs to the master
731 tags_to_copy = ['bugpattern-needed']
732 for series in self.lp_distro.series:
733 if series.status not in ['Active Development',
734 'Current Stable Release',
735 'Supported', 'Pre-release Freeze']:
736 continue
737 tags_to_copy.append(series.name)
738 # copy tags over from the duplicate bug to the master bug
739 dupe_tags = set(bug.tags)
740 # reload master tags as they may have changed
741 master_tags = master.tags
742 missing_tags = dupe_tags.difference(master_tags)
743
744 for tag in missing_tags:
745 if tag in tags_to_copy:
746 master_tags.append(tag)
747
748 master.tags = master_tags
749 master.lp_save()
750
751 else:
752 if bug.duplicate_of:
753 bug.duplicate_of = None
754
755 if bug._dirty_attributes: # LP#336866 workaround
756 bug.lp_save()
757
758 def mark_regression(self, id, master):
759 '''Mark a crash id as reintroducing an earlier crash which is
760 already marked as fixed (having ID 'master').'''
761
762 bug = self.launchpad.bugs[id]
763 bug.newMessage(content='This crash has the same stack trace characteristics as bug #%i. \
764However, the latter was already fixed in an earlier package version than the \
765one in this report. This might be a regression or because the problem is \
766in a dependent package.' % master,
767 subject='Possible regression detected')
768 bug = self.launchpad.bugs[id] # fresh bug object, LP#336866 workaround
769 bug.tags = bug.tags + ['regression-retracer'] # LP#254901 workaround
770 bug.lp_save()
771
772 def mark_retraced(self, id):
773 '''Mark crash id as retraced.'''
774
775 bug = self.launchpad.bugs[id]
776 if self.arch_tag in bug.tags:
777 x = bug.tags[:] # LP#254901 workaround
778 x.remove(self.arch_tag)
779 bug.tags = x
780 try:
781 bug.lp_save()
782 except HTTPError:
783 pass # LP#336866 workaround
784
785 def mark_retrace_failed(self, id, invalid_msg=None):
786 '''Mark crash id as 'failed to retrace'.'''
787
788 bug = self.launchpad.bugs[id]
789 if invalid_msg:
790 try:
791 task = self._get_distro_tasks(bug.bug_tasks).next()
792 except StopIteration:
793 # no distro task, just use the first one
794 task = bug.bug_tasks[0]
795 task.status = 'Invalid'
796 task.lp_save()
797 bug.newMessage(content=invalid_msg,
798 subject='Crash report cannot be processed')
799
800 for a in bug.attachments:
801 if a.title == 'CoreDump.gz':
802 try:
803 a.removeFromBug()
804 except HTTPError:
805 pass # LP#249950 workaround
806 else:
807 if 'apport-failed-retrace' not in bug.tags:
808 bug.tags = bug.tags + ['apport-failed-retrace'] # LP#254901 workaround
809 bug.lp_save()
810
811 def _mark_dup_checked(self, id, report):
812 '''Mark crash id as checked for being a duplicate.'''
813
814 bug = self.launchpad.bugs[id]
815
816 # if we have a distro task without a package, fix it
817 if 'SourcePackage' in report:
818 for task in bug.bug_tasks:
819 if task.target.resource_type_link.endswith('#distribution'):
820 task.target = self.lp_distro.getSourcePackage(
821 name=report['SourcePackage'])
822 task.lp_save()
823 bug = self.launchpad.bugs[id]
824 break
825
826 if 'need-duplicate-check' in bug.tags:
827 x = bug.tags[:] # LP#254901 workaround
828 x.remove('need-duplicate-check')
829 bug.tags = x
830 bug.lp_save()
831 if 'Traceback' in report:
832 for task in bug.bug_tasks:
833 if '#distribution' in task.target.resource_type_link:
834 if task.importance == 'Undecided':
835 task.importance = 'Medium'
836 task.lp_save()
837 self._subscribe_triaging_team(bug, report)
838
839 def known(self, report):
840 '''Check if the crash db already knows about the crash signature.
841
842 Check if the report has a DuplicateSignature, crash_signature(), or
843 StacktraceAddressSignature, and ask the database whether the problem is
844 already known. If so, return an URL where the user can check the status
845 or subscribe (if available), or just return True if the report is known
846 but there is no public URL. In that case the report will not be
847 uploaded (i. e. upload() will not be called).
848
849 Return None if the report does not have any signature or the crash
850 database does not support checking for duplicates on the client side.
851
852 The default implementation uses a text file format generated by
853 duplicate_db_publish() at an URL specified by the "dupdb_url" option.
854 Subclasses are free to override this with a custom implementation, such
855 as a real database lookup.
856 '''
857 # we override the method here to check if the user actually has access
858 # to the bug, and if the bug requests more retraces; in either case we
859 # should file it.
860 url = apport.crashdb.CrashDatabase.known(self, report)
861
862 if not url:
863 return url
864
865 # record the fact that it is a duplicate, for triagers
866 report['DuplicateOf'] = url
867
868 try:
869 f = urlopen(url + '/+text')
870 except IOError:
871 # if we are offline, or LP is down, upload will fail anyway, so we
872 # can just as well avoid the upload
873 return url
874
875 line = f.readline()
876 if not line.startswith(b'bug:'):
877 # presumably a 404 etc. page, which happens for private bugs
878 return True
879
880 # check tags
881 for line in f:
882 if line.startswith(b'tags:'):
883 if b'apport-failed-retrace' in line or b'apport-request-retrace' in line:
884 return None
885 else:
886 break
887
888 # stop at the first task, tags are in the first block
889 if not line.strip():
890 break
891
892 return url
893
894 def _subscribe_triaging_team(self, bug, report):
895 '''Subscribe the right triaging team to the bug.'''
896
897 #FIXME: this entire function is an ugly Ubuntu specific hack until LP
898 #gets a real crash db; see https://wiki.ubuntu.com/CrashReporting
899
900 if 'DistroRelease' in report and report['DistroRelease'].split()[0] != 'Ubuntu':
901 return # only Ubuntu bugs are filed private
902
903 #use a url hack here, it is faster
904 person = '%s~%s' % (self.launchpad._root_uri,
905 self.options.get('triaging_team', 'ubuntu-crashes-universe'))
906 bug.subscribe(person=person)
907
908 def _generate_upload_blob(self, report):
909 '''Generate a multipart/MIME temporary file for uploading.
910
911 You have to close the returned file object after you are done with it.
912 '''
913 # set reprocessing tags
914 hdr = {}
915 hdr['Tags'] = 'apport-%s' % report['ProblemType'].lower()
916 a = report.get('PackageArchitecture')
917 if not a or a == 'all':
918 a = report.get('Architecture')
919 if a:
920 hdr['Tags'] += ' ' + a
921 if 'Tags' in report:
922 hdr['Tags'] += ' ' + self._filter_tag_names(report['Tags'])
923
924 # privacy/retracing for distro reports
925 # FIXME: ugly hack until LP has a real crash db
926 if 'DistroRelease' in report:
927 if a and ('VmCore' in report or 'CoreDump' in report or 'LaunchpadPrivate' in report):
928 hdr['Private'] = 'yes'
929 hdr['Subscribers'] = report.get('LaunchpadSubscribe',
930 self.options.get('initial_subscriber', 'apport'))
931 hdr['Tags'] += ' need-%s-retrace' % a
932 elif 'Traceback' in report:
933 hdr['Private'] = 'yes'
934 hdr['Subscribers'] = 'apport'
935 hdr['Tags'] += ' need-duplicate-check'
936 if 'DuplicateSignature' in report and 'need-duplicate-check' not in hdr['Tags']:
937 hdr['Tags'] += ' need-duplicate-check'
938
939 # if we have checkbox submission key, link it to the bug; keep text
940 # reference until the link is shown in Launchpad's UI
941 if 'CheckboxSubmission' in report:
942 hdr['HWDB-Submission'] = report['CheckboxSubmission']
943
944 # order in which keys should appear in the temporary file
945 order = ['ProblemType', 'DistroRelease', 'Package', 'Regression', 'Reproducible',
946 'TestedUpstream', 'ProcVersionSignature', 'Uname', 'NonfreeKernelModules']
947
948 # write MIME/Multipart version into temporary file
949 mime = tempfile.TemporaryFile()
950 report.write_mime(mime, extra_headers=hdr,
951 skip_keys=['Tags', 'LaunchpadPrivate', 'LaunchpadSubscribe'],
952 priority_fields=order)
953 mime.flush()
954 mime.seek(0)
955
956 return mime
957
958 @classmethod
959 def _filter_tag_names(klass, tags):
960 '''Replace characters from tags which are not palatable to Launchpad'''
961
962 res = ''
963 for ch in tags.lower().encode('ASCII', errors='ignore'):
964 if ch in b'abcdefghijklmnopqrstuvwxyz0123456789 ' or (len(res) > 0 and ch in b'+-.'):
965 if _python2:
966 res += ch
967 else:
968 res += chr(ch)
969 else:
970 res += '.'
971
972 return res
973
974#
975# Launchpad storeblob API (should go into launchpadlib, see LP #315358)
976#
977
978_https_upload_callback = None
979
980
981#
982# This progress code is based on KodakLoader by Jason Hildebrand
983# <jason@opensky.ca>. See http://www.opensky.ca/~jdhildeb/software/kodakloader/
984# for details.
985class HTTPSProgressConnection(HTTPSConnection):
986 '''Implement a HTTPSConnection with an optional callback function for
987 upload progress.'''
988
989 def send(self, data):
990 global _https_upload_callback
991
992 # if callback has not been set, call the old method
993 if not _https_upload_callback:
994 HTTPSConnection.send(self, data)
995 return
996
997 sent = 0
998 total = len(data)
999 chunksize = 1024
1000 while sent < total:
1001 _https_upload_callback(sent, total)
1002 t1 = time.time()
1003 HTTPSConnection.send(self, data[sent:(sent + chunksize)])
1004 sent += chunksize
1005 t2 = time.time()
1006
1007 # adjust chunksize so that it takes between .5 and 2
1008 # seconds to send a chunk
1009 if chunksize > 1024:
1010 if t2 - t1 < .5:
1011 chunksize <<= 1
1012 elif t2 - t1 > 2:
1013 chunksize >>= 1
1014
1015
1016class HTTPSProgressHandler(HTTPSHandler):
1017
1018 def https_open(self, req):
1019 return self.do_open(HTTPSProgressConnection, req)
1020
1021
1022def upload_blob(blob, progress_callback=None, hostname='launchpad.net'):
1023 '''Upload blob (file-like object) to Launchpad.
1024
1025 progress_callback can be set to a function(sent, total) which is regularly
1026 called with the number of bytes already sent and total number of bytes to
1027 send. It is called every 0.5 to 2 seconds (dynamically adapted to upload
1028 bandwidth).
1029
1030 Return None on error, or the ticket number on success.
1031
1032 By default this uses the production Launchpad hostname. Set
1033 hostname to 'launchpad.dev' or 'staging.launchpad.net' to use another
1034 instance for testing.
1035 '''
1036 ticket = None
1037 url = 'https://%s/+storeblob' % hostname
1038
1039 global _https_upload_callback
1040 _https_upload_callback = progress_callback
1041
1042 # build the form-data multipart/MIME request
1043 data = email.mime.multipart.MIMEMultipart()
1044
1045 submit = email.mime.text.MIMEText('1')
1046 submit.add_header('Content-Disposition', 'form-data; name="FORM_SUBMIT"')
1047 data.attach(submit)
1048
1049 form_blob = email.mime.base.MIMEBase('application', 'octet-stream')
1050 form_blob.add_header('Content-Disposition', 'form-data; name="field.blob"; filename="x"')
1051 form_blob.set_payload(blob.read().decode('ascii'))
1052 data.attach(form_blob)
1053
1054 data_flat = BytesIO()
1055 if sys.version_info.major == 2:
1056 gen = email.generator.Generator(data_flat, mangle_from_=False)
1057 else:
1058 gen = email.generator.BytesGenerator(data_flat, mangle_from_=False)
1059 gen.flatten(data)
1060
1061 # do the request; we need to explicitly set the content type here, as it
1062 # defaults to x-www-form-urlencoded
1063 req = Request(url, data_flat.getvalue())
1064 req.add_header('Content-Type', 'multipart/form-data; boundary=' + data.get_boundary())
1065 opener = build_opener(HTTPSProgressHandler)
1066 result = opener.open(req)
1067 ticket = result.info().get('X-Launchpad-Blob-Token')
1068
1069 assert ticket
1070 return ticket
1071
1072#
1073# Unit tests
1074#
1075
1076if __name__ == '__main__':
1077 import unittest, atexit, shutil, subprocess
1078 import mock
1079
1080 crashdb = None
1081 _segv_report = None
1082 _python_report = None
1083 _uncommon_description_report = None
1084
1085 class _T(unittest.TestCase):
1086 # this assumes that a source package 'coreutils' exists and builds a
1087 # binary package 'coreutils'
1088 test_package = 'coreutils'
1089 test_srcpackage = 'coreutils'
1090
1091 #
1092 # Generic tests, should work for all CrashDB implementations
1093 #
1094
1095 def setUp(self):
1096 global crashdb
1097 if not crashdb:
1098 crashdb = self._get_instance()
1099 self.crashdb = crashdb
1100
1101 # create a local reference report so that we can compare
1102 # DistroRelease, Architecture, etc.
1103 self.ref_report = apport.Report()
1104 self.ref_report.add_os_info()
1105 self.ref_report.add_user_info()
1106 self.ref_report['SourcePackage'] = 'coreutils'
1107
1108 # Objects tests rely on.
1109 self._create_project('langpack-o-matic')
1110
1111 def _create_project(self, name):
1112 '''Create a project using launchpadlib to be used by tests.'''
1113
1114 project = self.crashdb.launchpad.projects[name]
1115 if not project:
1116 self.crashdb.launchpad.projects.new_project(
1117 description=name + 'description',
1118 display_name=name,
1119 name=name,
1120 summary=name + 'summary',
1121 title=name + 'title')
1122
1123 @property
1124 def hostname(self):
1125 '''Get the Launchpad hostname for the given crashdb.'''
1126
1127 return self.crashdb.get_hostname()
1128
1129 def get_segv_report(self, force_fresh=False):
1130 '''Generate SEGV crash report.
1131
1132 This is only done once, subsequent calls will return the already
1133 existing ID, unless force_fresh is True.
1134
1135 Return the ID.
1136 '''
1137 global _segv_report
1138 if not force_fresh and _segv_report is not None:
1139 return _segv_report
1140
1141 r = self._generate_sigsegv_report()
1142 r.add_package_info(self.test_package)
1143 r.add_os_info()
1144 r.add_gdb_info()
1145 r.add_user_info()
1146 self.assertEqual(r.standard_title(), 'crash crashed with SIGSEGV in f()')
1147
1148 # add some binary gibberish which isn't UTF-8
1149 r['ShortGibberish'] = ' "]\xb6"\n'
1150 r['LongGibberish'] = 'a\nb\nc\nd\ne\n\xff\xff\xff\n\f'
1151
1152 # create a bug for the report
1153 bug_target = self._get_bug_target(self.crashdb, r)
1154 self.assertTrue(bug_target)
1155
1156 id = self._file_bug(bug_target, r)
1157 self.assertTrue(id > 0)
1158
1159 sys.stderr.write('(Created SEGV report: https://%s/bugs/%i) ' % (self.hostname, id))
1160 if not force_fresh:
1161 _segv_report = id
1162 return id
1163
1164 def get_python_report(self):
1165 '''Generate Python crash report.
1166
1167 Return the ID.
1168 '''
1169 global _python_report
1170 if _python_report is not None:
1171 return _python_report
1172
1173 r = apport.Report('Crash')
1174 r['ExecutablePath'] = '/bin/foo'
1175 r['Traceback'] = '''Traceback (most recent call last):
1176 File "/bin/foo", line 67, in fuzz
1177 print(weird)
1178NameError: global name 'weird' is not defined'''
1179 r['Tags'] = 'boogus pybogus'
1180 r.add_package_info(self.test_package)
1181 r.add_os_info()
1182 r.add_user_info()
1183 self.assertEqual(r.standard_title(),
1184 "foo crashed with NameError in fuzz(): global name 'weird' is not defined")
1185
1186 bug_target = self._get_bug_target(self.crashdb, r)
1187 self.assertTrue(bug_target)
1188
1189 id = self._file_bug(bug_target, r)
1190 self.assertTrue(id > 0)
1191 sys.stderr.write('(Created Python report: https://%s/bugs/%i) ' % (self.hostname, id))
1192 _python_report = id
1193 return id
1194
1195 def get_uncommon_description_report(self, force_fresh=False):
1196 '''File a bug report with an uncommon description.
1197
1198 This is only done once, subsequent calls will return the already
1199 existing ID, unless force_fresh is True.
1200
1201 Example taken from real LP bug 269539. It contains only
1202 ProblemType/Architecture/DistroRelease in the description, and has
1203 free-form description text after the Apport data.
1204
1205 Return the ID.
1206 '''
1207 global _uncommon_description_report
1208 if not force_fresh and _uncommon_description_report is not None:
1209 return _uncommon_description_report
1210
1211 desc = '''problem
1212
1213ProblemType: Package
1214Architecture: amd64
1215DistroRelease: Ubuntu 8.10
1216
1217more text
1218
1219and more
1220'''
1221 bug = self.crashdb.launchpad.bugs.createBug(
1222 title=b'mixed description bug'.encode(),
1223 description=desc,
1224 target=self.crashdb.lp_distro)
1225 sys.stderr.write('(Created uncommon description: https://%s/bugs/%i) ' % (self.hostname, bug.id))
1226
1227 if not force_fresh:
1228 _uncommon_description_report = bug.id
1229 return bug.id
1230
1231 def test_1_download(self):
1232 '''download()'''
1233
1234 r = self.crashdb.download(self.get_segv_report())
1235 self.assertEqual(r['ProblemType'], 'Crash')
1236 self.assertEqual(r['Title'], 'crash crashed with SIGSEGV in f()')
1237 self.assertEqual(r['DistroRelease'], self.ref_report['DistroRelease'])
1238 self.assertEqual(r['Architecture'], self.ref_report['Architecture'])
1239 self.assertEqual(r['Uname'], self.ref_report['Uname'])
1240 self.assertEqual(r.get('NonfreeKernelModules'),
1241 self.ref_report.get('NonfreeKernelModules'))
1242 self.assertEqual(r.get('UserGroups'), self.ref_report.get('UserGroups'))
1243 tags = set(r['Tags'].split())
1244 self.assertEqual(tags, set([self.crashdb.arch_tag, 'apport-crash',
1245 apport.packaging.get_system_architecture()]))
1246
1247 self.assertEqual(r['Signal'], '11')
1248 self.assertTrue(r['ExecutablePath'].endswith('/crash'))
1249 self.assertEqual(r['SourcePackage'], self.test_srcpackage)
1250 self.assertTrue(r['Package'].startswith(self.test_package + ' '))
1251 self.assertTrue('f (x=42)' in r['Stacktrace'])
1252 self.assertTrue('f (x=42)' in r['StacktraceTop'])
1253 self.assertTrue('f (x=42)' in r['ThreadStacktrace'])
1254 self.assertTrue(len(r['CoreDump']) > 1000)
1255 self.assertTrue('Dependencies' in r)
1256 self.assertTrue('Disassembly' in r)
1257 self.assertTrue('Registers' in r)
1258
1259 # check tags
1260 r = self.crashdb.download(self.get_python_report())
1261 tags = set(r['Tags'].split())
1262 self.assertEqual(tags, set(['apport-crash', 'boogus', 'pybogus',
1263 'need-duplicate-check', apport.packaging.get_system_architecture()]))
1264
1265 def test_2_update_traces(self):
1266 '''update_traces()'''
1267
1268 r = self.crashdb.download(self.get_segv_report())
1269 self.assertTrue('CoreDump' in r)
1270 self.assertTrue('Dependencies' in r)
1271 self.assertTrue('Disassembly' in r)
1272 self.assertTrue('Registers' in r)
1273 self.assertTrue('Stacktrace' in r)
1274 self.assertTrue('ThreadStacktrace' in r)
1275 self.assertEqual(r['Title'], 'crash crashed with SIGSEGV in f()')
1276
1277 # updating with a useless stack trace retains core dump
1278 r['StacktraceTop'] = '?? ()'
1279 r['Stacktrace'] = 'long\ntrace'
1280 r['ThreadStacktrace'] = 'thread\neven longer\ntrace'
1281 r['FooBar'] = 'bogus'
1282 self.crashdb.update_traces(self.get_segv_report(), r, 'I can has a better retrace?')
1283 r = self.crashdb.download(self.get_segv_report())
1284 self.assertTrue('CoreDump' in r)
1285 self.assertTrue('Dependencies' in r)
1286 self.assertTrue('Disassembly' in r)
1287 self.assertTrue('Registers' in r)
1288 self.assertTrue('Stacktrace' in r) # TODO: ascertain that it's the updated one
1289 self.assertTrue('ThreadStacktrace' in r)
1290 self.assertFalse('FooBar' in r)
1291 self.assertEqual(r['Title'], 'crash crashed with SIGSEGV in f()')
1292
1293 tags = self.crashdb.launchpad.bugs[self.get_segv_report()].tags
1294 self.assertTrue('apport-crash' in tags)
1295 self.assertFalse('apport-collected' in tags)
1296
1297 # updating with a useful stack trace removes core dump
1298 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
1299 r['Stacktrace'] = 'long\ntrace'
1300 r['ThreadStacktrace'] = 'thread\neven longer\ntrace'
1301 self.crashdb.update_traces(self.get_segv_report(), r, 'good retrace!')
1302 r = self.crashdb.download(self.get_segv_report())
1303 self.assertFalse('CoreDump' in r)
1304 self.assertTrue('Dependencies' in r)
1305 self.assertTrue('Disassembly' in r)
1306 self.assertTrue('Registers' in r)
1307 self.assertTrue('Stacktrace' in r)
1308 self.assertTrue('ThreadStacktrace' in r)
1309 self.assertFalse('FooBar' in r)
1310
1311 # as previous title had standard form, the top function gets
1312 # updated
1313 self.assertEqual(r['Title'], 'crash crashed with SIGSEGV in read()')
1314
1315 # respects title amendments
1316 bug = self.crashdb.launchpad.bugs[self.get_segv_report()]
1317 bug.title = 'crash crashed with SIGSEGV in f() on exit'
1318 try:
1319 bug.lp_save()
1320 except HTTPError:
1321 pass # LP#336866 workaround
1322 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
1323 self.crashdb.update_traces(self.get_segv_report(), r, 'good retrace with title amendment')
1324 r = self.crashdb.download(self.get_segv_report())
1325 self.assertEqual(r['Title'], 'crash crashed with SIGSEGV in read() on exit')
1326
1327 # does not destroy custom titles
1328 bug = self.crashdb.launchpad.bugs[self.get_segv_report()]
1329 bug.title = 'crash is crashy'
1330 try:
1331 bug.lp_save()
1332 except HTTPError:
1333 pass # LP#336866 workaround
1334
1335 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
1336 self.crashdb.update_traces(self.get_segv_report(), r, 'good retrace with custom title')
1337 r = self.crashdb.download(self.get_segv_report())
1338 self.assertEqual(r['Title'], 'crash is crashy')
1339
1340 # test various situations which caused crashes
1341 r['Stacktrace'] = '' # empty file
1342 r['ThreadStacktrace'] = '"]\xb6"\n' # not interpretable as UTF-8, LP #353805
1343 r['StacktraceSource'] = 'a\nb\nc\nd\ne\n\xff\xff\xff\n\f'
1344 self.crashdb.update_traces(self.get_segv_report(), r, 'tests')
1345
1346 def test_get_comment_url(self):
1347 '''get_comment_url() for non-ASCII titles'''
1348
1349 # UTF-8 bytestring, works in both python 2.7 and 3
1350 title = b'1\xc3\xa4\xe2\x99\xa52'
1351
1352 # distro, UTF-8 bytestring
1353 r = apport.Report('Bug')
1354 r['Title'] = title
1355 url = self.crashdb.get_comment_url(r, 42)
1356 self.assertTrue(url.endswith('/ubuntu/+filebug/42?field.title=1%C3%A4%E2%99%A52'))
1357
1358 # distro, unicode
1359 r['Title'] = title.decode('UTF-8')
1360 url = self.crashdb.get_comment_url(r, 42)
1361 self.assertTrue(url.endswith('/ubuntu/+filebug/42?field.title=1%C3%A4%E2%99%A52'))
1362
1363 # package, unicode
1364 r['SourcePackage'] = 'coreutils'
1365 url = self.crashdb.get_comment_url(r, 42)
1366 self.assertTrue(url.endswith('/ubuntu/+source/coreutils/+filebug/42?field.title=1%C3%A4%E2%99%A52'))
1367
1368 def test_update_description(self):
1369 '''update() with changing description'''
1370
1371 bug_target = self.crashdb.lp_distro.getSourcePackage(name='bash')
1372 bug = self.crashdb.launchpad.bugs.createBug(
1373 description='test description for test bug.',
1374 target=bug_target,
1375 title='testbug')
1376 id = bug.id
1377 self.assertTrue(id > 0)
1378 sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
1379
1380 r = apport.Report('Bug')
1381
1382 r['OneLiner'] = b'bogus\xe2\x86\x92'.decode('UTF-8')
1383 r['StacktraceTop'] = 'f()\ng()\nh(1)'
1384 r['ShortGoo'] = 'lineone\nlinetwo'
1385 r['DpkgTerminalLog'] = 'one\ntwo\nthree\nfour\nfive\nsix'
1386 r['VarLogDistupgradeBinGoo'] = b'\x01' * 1024
1387
1388 self.crashdb.update(id, r, 'NotMe', change_description=True)
1389
1390 r = self.crashdb.download(id)
1391
1392 self.assertEqual(r['OneLiner'], b'bogus\xe2\x86\x92'.decode('UTF-8'))
1393 self.assertEqual(r['ShortGoo'], 'lineone\nlinetwo')
1394 self.assertEqual(r['DpkgTerminalLog'], 'one\ntwo\nthree\nfour\nfive\nsix')
1395 self.assertEqual(r['VarLogDistupgradeBinGoo'], b'\x01' * 1024)
1396
1397 self.assertEqual(self.crashdb.launchpad.bugs[id].tags,
1398 ['apport-collected'])
1399
1400 def test_update_comment(self):
1401 '''update() with appending comment'''
1402
1403 bug_target = self.crashdb.lp_distro.getSourcePackage(name='bash')
1404 # we need to fake an apport description separator here, since we
1405 # want to be lazy and use download() for checking the result
1406 bug = self.crashdb.launchpad.bugs.createBug(
1407 description='Pr0blem\n\n--- \nProblemType: Bug',
1408 target=bug_target,
1409 title='testbug')
1410 id = bug.id
1411 self.assertTrue(id > 0)
1412 sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
1413
1414 r = apport.Report('Bug')
1415
1416 r['OneLiner'] = 'bogus→'
1417 r['StacktraceTop'] = 'f()\ng()\nh(1)'
1418 r['ShortGoo'] = 'lineone\nlinetwo'
1419 r['DpkgTerminalLog'] = 'one\ntwo\nthree\nfour\nfive\nsix'
1420 r['VarLogDistupgradeBinGoo'] = '\x01' * 1024
1421
1422 self.crashdb.update(id, r, 'meow', change_description=False)
1423
1424 r = self.crashdb.download(id)
1425
1426 self.assertFalse('OneLiner' in r)
1427 self.assertFalse('ShortGoo' in r)
1428 self.assertEqual(r['ProblemType'], 'Bug')
1429 self.assertEqual(r['DpkgTerminalLog'], 'one\ntwo\nthree\nfour\nfive\nsix')
1430 self.assertEqual(r['VarLogDistupgradeBinGoo'], '\x01' * 1024)
1431
1432 self.assertEqual(self.crashdb.launchpad.bugs[id].tags,
1433 ['apport-collected'])
1434
1435 def test_update_filter(self):
1436 '''update() with a key filter'''
1437
1438 bug_target = self.crashdb.lp_distro.getSourcePackage(name='bash')
1439 bug = self.crashdb.launchpad.bugs.createBug(
1440 description='test description for test bug',
1441 target=bug_target,
1442 title='testbug')
1443 id = bug.id
1444 self.assertTrue(id > 0)
1445 sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
1446
1447 r = apport.Report('Bug')
1448
1449 r['OneLiner'] = 'bogus→'
1450 r['StacktraceTop'] = 'f()\ng()\nh(1)'
1451 r['ShortGoo'] = 'lineone\nlinetwo'
1452 r['DpkgTerminalLog'] = 'one\ntwo\nthree\nfour\nfive\nsix'
1453 r['VarLogDistupgradeBinGoo'] = '\x01' * 1024
1454
1455 self.crashdb.update(id, r, 'NotMe', change_description=True,
1456 key_filter=['ProblemType', 'ShortGoo', 'DpkgTerminalLog'])
1457
1458 r = self.crashdb.download(id)
1459
1460 self.assertFalse('OneLiner' in r)
1461 self.assertEqual(r['ShortGoo'], 'lineone\nlinetwo')
1462 self.assertEqual(r['ProblemType'], 'Bug')
1463 self.assertEqual(r['DpkgTerminalLog'], 'one\ntwo\nthree\nfour\nfive\nsix')
1464 self.assertFalse('VarLogDistupgradeBinGoo' in r)
1465
1466 self.assertEqual(self.crashdb.launchpad.bugs[id].tags, [])
1467
1468 def test_get_distro_release(self):
1469 '''get_distro_release()'''
1470
1471 self.assertEqual(self.crashdb.get_distro_release(self.get_segv_report()),
1472 self.ref_report['DistroRelease'])
1473
1474 def test_get_affected_packages(self):
1475 '''get_affected_packages()'''
1476
1477 self.assertEqual(self.crashdb.get_affected_packages(self.get_segv_report()),
1478 [self.ref_report['SourcePackage']])
1479
1480 def test_is_reporter(self):
1481 '''is_reporter()'''
1482
1483 self.assertTrue(self.crashdb.is_reporter(self.get_segv_report()))
1484 self.assertFalse(self.crashdb.is_reporter(1))
1485
1486 def test_can_update(self):
1487 '''can_update()'''
1488
1489 self.assertTrue(self.crashdb.can_update(self.get_segv_report()))
1490 self.assertFalse(self.crashdb.can_update(1))
1491
1492 def test_duplicates(self):
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches