Merge lp:~brian-murray/ubuntu/quantal/apport/string-bytes into lp:ubuntu/quantal/apport

Proposed by Brian Murray
Status: Merged
Merge reported by: Martin Pitt
Merged at revision: not available
Proposed branch: lp:~brian-murray/ubuntu/quantal/apport/string-bytes
Merge into: lp:ubuntu/quantal/apport
Diff against target: 126538 lines (+125415/-0) (has conflicts)
211 files modified
.bzr-builddeb/default.conf (+2/-0)
.bzrignore (+9/-0)
AUTHORS (+34/-0)
COPYING (+339/-0)
NEWS (+1564/-0)
README (+87/-0)
TODO (+21/-0)
apport/REThread.py (+66/-0)
apport/__init__.py (+42/-0)
apport/crashdb.py (+850/-0)
apport/crashdb_impl/launchpad.py (+1931/-0)
apport/crashdb_impl/memory.py (+300/-0)
apport/fileutils.py (+310/-0)
apport/hookutils.py (+910/-0)
apport/packaging.py (+229/-0)
apport/report.py (+1387/-0)
apport/ui.py (+1489/-0)
apport_python_hook.py (+195/-0)
backends/packaging-apt-dpkg.py (+884/-0)
bin/apport-bug (+86/-0)
bin/apport-cli (+367/-0)
bin/apport-retrace (+521/-0)
bin/apport-unpack (+66/-0)
bin/crash-digger (+224/-0)
bin/dupdb-admin (+96/-0)
data/apport (+448/-0)
data/apport-checkreports (+40/-0)
data/apportcheckresume (+93/-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 (+402/-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.py (+114/-0)
data/package-hooks/source_ubiquity.py (+123/-0)
data/package_hook (+65/-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 (+8/-0)
debian/apport-retrace.install (+3/-0)
debian/apport-retrace.manpages (+2/-0)
debian/apport.install (+25/-0)
debian/apport.links (+4/-0)
debian/apport.logrotate (+9/-0)
debian/apport.manpages (+3/-0)
debian/apport.postinst (+10/-0)
debian/apport.upstart (+50/-0)
debian/changelog (+6836/-0)
debian/clean (+2/-0)
debian/compat (+1/-0)
debian/control (+197/-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 (+2/-0)
debian/tests/upstream-system (+16/-0)
debian/watch (+2/-0)
doc/crashdb-conf.txt (+134/-0)
doc/data-format.tex (+301/-0)
doc/package-hooks.txt (+149/-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 (+570/-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 (+506/-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/dupdb-admin.1 (+68/-0)
pm-utils/sleep.d/000record-status (+16/-0)
po/ace.po (+949/-0)
po/af.po (+949/-0)
po/am.po (+949/-0)
po/an.po (+949/-0)
po/apport.pot (+775/-0)
po/ar.po (+1098/-0)
po/ast.po (+1135/-0)
po/be.po (+1180/-0)
po/bg.po (+1101/-0)
po/bn.po (+963/-0)
po/br.po (+949/-0)
po/bs.po (+970/-0)
po/ca.po (+1200/-0)
po/ca@valencia.po (+995/-0)
po/cs.po (+1154/-0)
po/cv.po (+949/-0)
po/da.po (+1133/-0)
po/de.po (+1245/-0)
po/el.po (+1213/-0)
po/en_AU.po (+1195/-0)
po/en_CA.po (+1190/-0)
po/en_GB.po (+1195/-0)
po/eo.po (+1154/-0)
po/es.po (+1225/-0)
po/et.po (+1136/-0)
po/eu.po (+1132/-0)
po/fa.po (+949/-0)
po/fi.po (+1189/-0)
po/fr.po (+1153/-0)
po/ga.po (+949/-0)
po/gd.po (+949/-0)
po/gl.po (+1222/-0)
po/gu.po (+987/-0)
po/he.po (+1105/-0)
po/hi.po (+960/-0)
po/hr.po (+1089/-0)
po/hu.po (+1211/-0)
po/hy.po (+949/-0)
po/id.po (+1129/-0)
po/is.po (+1084/-0)
po/it.po (+1195/-0)
po/ja.po (+1086/-0)
po/kab.po (+949/-0)
po/kk.po (+949/-0)
po/km.po (+1063/-0)
po/kn.po (+1017/-0)
po/ko.po (+1071/-0)
po/ku.po (+1037/-0)
po/lo.po (+949/-0)
po/lt.po (+1196/-0)
po/lv.po (+960/-0)
po/mk.po (+949/-0)
po/ml.po (+949/-0)
po/ms.po (+991/-0)
po/nb.po (+1128/-0)
po/nds.po (+987/-0)
po/ne.po (+961/-0)
po/nl.po (+1145/-0)
po/oc.po (+1221/-0)
po/pl.po (+1194/-0)
po/pt.po (+1139/-0)
po/pt_BR.po (+1220/-0)
po/ro.po (+1160/-0)
po/ru.po (+1169/-0)
po/sc.po (+949/-0)
po/shn.po (+949/-0)
po/si.po (+953/-0)
po/sk.po (+1112/-0)
po/sl.po (+1171/-0)
po/sq.po (+1145/-0)
po/sr.po (+1212/-0)
po/sv.po (+1209/-0)
po/ta.po (+983/-0)
po/te.po (+959/-0)
po/th.po (+988/-0)
po/tr.po (+1174/-0)
po/ug.po (+1037/-0)
po/uk.po (+1128/-0)
po/vi.po (+1117/-0)
po/zh_CN.po (+1070/-0)
po/zh_HK.po (+949/-0)
po/zh_TW.po (+1084/-0)
problem_report.py (+631/-0)
setup.py (+128/-0)
test/run (+128/-0)
test/test_apport_unpack.py (+116/-0)
test/test_backend_apt_dpkg.py (+599/-0)
test/test_crash_digger.py (+237/-0)
test/test_crashdb.py (+694/-0)
test/test_fileutils.py (+285/-0)
test/test_hooks.py (+270/-0)
test/test_hookutils.py (+322/-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 (+985/-0)
test/test_python_crashes.py (+361/-0)
test/test_report.py (+1813/-0)
test/test_rethread.py (+74/-0)
test/test_signal_crashes.py (+666/-0)
test/test_ui.py (+1968/-0)
test/test_ui_gtk.py (+812/-0)
test/test_ui_kde.py (+560/-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:~brian-murray/ubuntu/quantal/apport/string-bytes
Reviewer Review Type Date Requested Status
Martin Pitt Approve
Review via email: mp+114291@code.launchpad.net

Description of the change

I discovered that regular expressions in the ubuntu general hook were failing when checking DpkgTerminalLog files. I've dealt with this by treating DpkgTerminalLog as a bytes object in trim_log and converting it to a string object so the regular expressions don't have to be modified and continue to be readable. However, I think its worth a review by someone else.

To post a comment you must log in.
Revision history for this message
Martin Pitt (pitti) wrote :

Looks good to me, if you tested this. Thanks!

Merged into packaging branch, but closing manually since this MP specifies the wrong merge target.

review: Approve

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 2012-07-10 22:38:19 +0000
@@ -0,0 +1,2 @@
1[BUILDDEB]
2merge = True
03
=== added file '.bzrignore'
--- .bzrignore 1970-01-01 00:00:00 +0000
+++ .bzrignore 2012-07-10 22:38:19 +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 2012-07-10 22:38:19 +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 2012-07-10 22:38:19 +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 2012-07-10 22:38:19 +0000
@@ -0,0 +1,1564 @@
1This file summarizes the major and interesting changes for each release. For a
2detailled list of changes, please see ChangeLog.
3
42.4 (UNRELEASED):
5-----------------
6Improvements:
7 * apport_python_hook.py: For org.freedesktop.DBus.Error.ServiceUnknown
8 exceptions, add a 'DbusErrorAnalysis' field to the report which points out
9 whether any .service file provides the service it tried to talk to, and
10 whether the processes for those are running. This helps to determine the
11 root cause for such errors (missing dependencies, broken .service files,
12 talking to the wrong bus, etc.) (LP: #1020572)
13 * hookutils.py, attach_alsa(): Use alsa-info.sh when available. Thanks David
14 Henningson.
15
16Bug fixes:
17 * ui tests, test_wait_for_pid(): Fix eternal hang when running as root.
18 * testsuite: Fix ResourceWarnings when running with Python 3.
19 * test_python_crashes.py: Fix race condition in timeout test.
20
212.3 (2012-07-09):
22-----------------
23Improvements:
24 * launchpad.py: Rework test suite to not use Launchpad's +storeblob facility
25 at all any more. It almost never works on staging and is horribly slow. Fake
26 the bug creation from a blob by manually creating the comment and
27 attachments ourselves, and just assume that storeblob works on production.
28 Also change the structure to allow running every test individually.
29 * crash-digger: Add --crash-db option to specify a non-default crash databae
30 name. (LP: #1003506)
31 * apport-gtk: Add --hanging option to specify the process ID of a hanging
32 application. If the user chooses to report this error, apport will terminate
33 the pid with SIGABRT, otherwise it will send SIGKILL. The normal core pipe
34 handler will be used to process the resulting report file, with a .hanging
35 file in /var/crash to separate these from regular crashes.
36
37Bug fixes:
38 * apport: Also treat a binary as modified if the /proc/pid/exe symlink does
39 not point to an existing file any more. (LP: #984944)
40 * Fix PEP-8 violations picked up by latest pep8 checker.
41 * ui.py: Do not ignore certain exceptions during upload which are not likely
42 to be a network error.
43 * launchpad.py: Recongize Launchpad projects for bug query and marking
44 operations. (LP: #1003506)
45 * packaging-apt-dpkg.py: Fix get_source_tree() to work with apt sandboxes.
46 * apport-retrace: Turn StacktraceSource generation back on, now that it works
47 with the current sandboxing.
48 * launchpad.py: Ensure that upload chunk size does not underrun. (LP: #1013334)
49 * apport_python_hook: Fix UnicodeEncodeError crash with Python 2 for
50 exceptions with non-ASCII characters. (LP: #972436)
51 * test_ui_kde.py: Fix occasional test failure in test_1_crash_details if the
52 application ends before the "is progress bar visible" check is done.
53
542.2.5 (2012-06-21):
55-------------------
56 * launchpad.py: Fix str vs. bytes crash for already known bugs, take 2. (LP: #1015788)
57 * apport/ui.py, get_desktop_entry(): Disable interpolation, to correctly read
58 desktop files with % signs. (LP: #1014341)
59 * apport/ui.py: Fix rare crash if a report is already being updated in the
60 background when the UI tries to update a previous version. (LP: #949196)
61 * GTK and KDE UI tests: Avoid eternal hangs due to "this is not a distro
62 package" error messages.
63
642.2.4 (2012-06-21):
65--------------------
66Bug fixes:
67 * test_apport_unpack.py: Fix test_unpack_python() test when running the
68 system-installed tests.
69 * data/java_uncaught_exception: Fix for Python 3.
70 * test_signal_crashes.py: Show crash reports in /var/crash/.
71 * test_crash_digger.py: Do not write crash reports of crash-digger into system
72 /var/crash, use a temporary directory.
73 * test/run: Wait for a previous xvfb server to finish before trying to start
74 one. This fixes a race condition in the KDE UI tests which often failed to
75 start up xvfb.
76 * apport-cli: Unbreak "keep" option. (LP: #1007826)
77 * launchpad.py: Fix str vs. bytes crash for already known bugs. (LP: #1015788)
78
792.2.3 (2012-06-15):
80-------------------
81Bug fixes:
82 * test/run: Do not run pep8 and pyflakes when running against the sytem
83 installed Apport.
84 * test_backend_apt_dpkg.py: For the "are we online" check, verify that we can
85 download from http://ddebs.ubuntu.com/, not just whether we have a default
86 route. The latter is not sufficient for e. g. buildd environments which are
87 online, but are restricted by proxies or firewalls.
88 * test_report.py: Call "sync" after test script write core dumps, to ensure
89 that subsequent operations have a complete one.
90 * test_signal_crashes.py: Drop the broken and obsolete test_local_python()
91 test. Instead, add two tests which check proper logging.
92 * launchpad.py: Fix urlopen() for Python3. Thanks Steve Langasek.
93 * test/run: Run the tests under LC_MESSAGES=C, to avoid failing tests on
94 translated strings.
95
962.2.2 (2012-06-13):
97-------------------
98Improvements:
99 * testsuite: Run with Python 3 by default. To test with Python 2, run
100 "PYTHON=python2 test/run".
101
102Bug fixes:
103 * apport: Redefine sys.std{out,err} when redirecting output, as they are None
104 in Python 3 when being called from the kernel.
105 * test/test_signal_crashes.py: Clean up unexpected core dumps on failed test
106 cases.
107 * apport-gtk: Fix crash when closing the crash dialog while the information is
108 being collected.
109 * hookutils.py, xsession_errors(): Fix crash when running under a non-UTF8 locale.
110 * data/apport: Do not use sys.stdin.fileno(), it is invalid when being called
111 from the kernel with Python 3.
112 * data/apport: When core dumps are enabled, read them from the written report
113 instead of directly from stdin (and then reading the written core file into
114 the .crash report). If the core file size is limited, we otherwise stop
115 reading the core dump from the kernel in the middle and have no (or a
116 broken) core dump to be put into the report.
117 * data/apport: Properly close the written crash report before changing its
118 permissions to be readable. This prevents having crash reporting UI from
119 looking at incomplete .crash files.
120
1212.2.1 (2012-06-11)
122------------------
123Bug fixes:
124 * apport-cli: Port to work with Python 3.
125 * setup.py: When fixing hashbang lines of installed scripts, only include the
126 major Python version.
127 * hookutils.py, read_file, attach_file(), attach_file_if_exists(): Convert
128 file contents to unicode if the contents is UTF-8, or the newly added
129 force_unicode argument is True.
130 * hooktuils, command_output(): Convert output to unicode by default, and add
131 a "decode_utf8" parameter to disable this.
132 * hookutils.py, recent_logfile(): Fix fd leak.
133 * data/apport: Do not assume that sys.stdout and sys.stderr always have a
134 name; they can be None in Python 3.
135 * data/dump_acpi_tables.py: Fix for Python 3.
136
1372.2 (2012-06-11)
138----------------
139Improvements:
140 * Clean up module imports.
141 * test/run: Run pyflakes, if available.
142 * package_hook: Add --tags option. Thanks to Brian Murray.
143 * launchpad.py: Drop the external multipartpost_handler.py (which is not
144 portable to Python 3) and replace it with using the standard email module.
145 * launchpad.py: Also work with Python 3. Deal gracefully with a missing
146 "launchpadlib" module; this is not yet available for Python 3, but not
147 required for client-side reporting.
148 * apport-kde: Port to work with Python 3.
149
150Bug fixes:
151 * apport-retrace: Fix crash when using the --procmaps option.
152 * setup.py: Update hashbang lines of installed scripts in data directory to
153 the python executable setup.py was run with, similar to what already happens
154 to scripts installed to ../bin/.
155
1562.1.1 (2012-05-30)
157------------------
158Improvements:
159 * launchpad.py: When closing a bug as a duplicate, copy some well-known tags
160 to the master bug. Thanks Brian Murray.
161 * launchpad.py: Set importance of Python crash reports to "Medium" by default,
162 similar to signal crashes. Thanks Brian Murray.
163 * hookutils.py: Add attach_default_grub() convenience function from the grub2
164 package hook so it can be used by other packages. Thanks Brian Murray.
165 * launchpad.py: Make Launchpad bug subscription user/team configurable: The
166 initial subscriber after filing a bug can be set with the
167 "initial_subscriber" crashdb option, and the team which gets subscribed
168 after retracing with "triaging_team". (LP: #980726)
169
170Bug fixes:
171 * report.py: Do not change the SourcePackage: field if the binary package is
172 not installed and does not exist. This fixes source package hooks to
173 actually work in some cases where source and binary package names overlap.
174 (part of LP: #993810)
175 * apport-gtk, apport-kde: Avoid collecting information twice in "bug update"
176 mode. This caused a crash in cases where the source package in a bug report
177 does not correspond to an installed binary package. (LP: #993810)
178
1792.1 (2012-05-18)
180----------------
181Improvements:
182 * packaging.py, install_packages(): Add permanent_rootdir flag and if set,
183 only unpack newly downloaded packages. Implement it for the apt/dpkg
184 backend. Thanks Evan Dandrea.
185 * apport-retrace: Add --sandbox-dir option for keeping a permanent sandbox
186 (unpacked packages). This provides a considerable speedup. Thanks Evan
187 Dandrea.
188 * crash-digger: Add --sandbox-dir option and pass it to apport-retrace.
189 * Fix the whole code to be PEP-8 compatible, and enforce this in test/run by
190 running the "pep8" tool.
191 * GTK UI tests: Ensure that there are no GLib/GTK warnings or criticals.
192 * Support Python 3. Everything except the launchpad crashdb backend now works
193 with both Python 2 and 3. An important change is that the load(),
194 write(), and write_mime() methods of a ProblemReport and apport.Report
195 object now require the file stream to be opened in binary mode.
196 * data/apport: Ignore a crash if the executable was modified after the process
197 started. This often happens if the package is upgraded and a long-running
198 process is not stopped before. (LP: #984944)
199 * Add test cases for apport-unpack.
200 * apport-retrace: Add information about outdated packages to the
201 "RetraceOutdatedPackages" field.
202 * ui.py: Drop python-xdg dependency, use ConfigParser to read the .desktop
203 files.
204
205Bug fixes:
206 * apport-gtk: Work around GTK crash when trying to set pixmap on an already
207 destroyed parent window. (LP: #938090)
208 * data/dump_acpi_tables.py: Fix crash on undefined variable with non-standard
209 tables. (LP: #982267)
210 * backends/packaging-apt-dpkg.py: Fix crash if a package is installed, but has
211 no candidates in apt. (LP: #980094)
212 * data/general-hooks/generic.py: Bump minimum free space requirement from 10
213 to 50 MB. 10 is not nearly enough particularly for /tmp. (LP: #979928)
214 * hookutils.py, recent_logfile(): Use a default limit of 10000 lines and call
215 "tail" instead of reading the whole file. This protects against using up all
216 memory when there are massive repeated log messages. (LP: #984256)
217 * apport-gtk: Do not assume that an icon requested for size 42 actually
218 delivers size 42; some themes do not have this available and deliver a
219 smaller one instead, causing overflows. Also, copy the image as
220 gtk_icon_theme_load_icon() returns a readonly result which we must not
221 modify. (LP: #937249)
222 * ui.py: Don't show the duplicate warning when the crash database does not
223 accept the problem type, and they are just being sent to whoopsie. Thanks
224 Evan Dandrea. (LP: #989779)
225 * report.py: Correctly escape the file path passed to gdb.
226 * apport-gtk, apport-kde: Do not show the information collection progress
227 dialog if the crash database does not accept this kind of report. In that
228 case whoopsie will upload it in the background and the dialog is not
229 necessary. (LP: #989698)
230
2312.0.1 (2012-04-10)
232------------------
233Bug fixes:
234 * test_ui_gtk.py: Disable package hooks for the tests, as they might ask for
235 sudo passwords and other interactive bits, and thus make the tests hang.
236 * test_backend_apt_dpkg.py: Fix checks for the installation of -dbgsym
237 packages. This should always happen, as the sandboxes have a ddeb apt
238 source. Only make it conditional on the system apt sources in the "use
239 system config" test.
240 * test_report.py: Sleep a bit after calling our test crash script, to ensure
241 the kernel has time to finish writing the core file.
242 * generic package hook: Also check /tmp for enough space. Thanks Brian Murray.
243 (LP: #972933)
244 * problem_report.py, write_mime(): Fix regression from version 1.95: Add a
245 value as attachment if it is bigger than 1000 bytes, not if it is bigger
246 than 100. (LP: #977882)
247
248Improvements:
249 * packaging-apt-dpkg.py: Avoid constructing and updating the apt.Cache()
250 objects multiple times, to speed up retracing. Thanks Evan Dandrea.
251 (LP: #973494)
252
2532.0 (2012-03-30)
254----------------
255This is the final 2.0 release, featuring the overhauled and simplified GUI,
256support for whoopsie-daemon, and client-side duplicate checking.
257
258Bug fixes:
259 - report.py, anonymize(): Only replace whole words, not substrings.
260 (LP: #966562)
261 - apport_python_hook.py: Fix filtering of org.freedesktop.DBus.Error.NoReply
262 exceptions. (LP: #958575)
263 - crashdb.py: When publishing the crash database, cut hash file names after
264 quoting, to avoid that the quoting causes them to become too long.
265 (LP: #968070) This also uncovered that known() did not actually find any
266 signature which contained an URL-quoted character, therefore breaking
267 client-side duplicate checking in a lot of cases. Double-quote the file name
268 now, as urlopen() unquotes it.
269 - Add a new crash database option "problem_types" and a CrashDatabase method
270 "accepts(report)". This can be used to stop uploading particular problem
271 report types to that database. E. g. a distribution might decide to not get
272 "Crash" reports any more after release. Document the new option in
273 doc/crashdb-conf.txt.
274 - ui.py: Do not upload a report if the crash database does not accept the
275 report's type. This behaviour is not really correct, but necessary as long
276 as we only support a single crashdb and have whoopsie hardcoded. Once we
277 have multiple crash dbs, we need to not even present the data if none of the
278 DBs wants the report. See LP #957177 for details. (LP: #968121)
279 - ui.py: Do not short-circuit information collection if report already has a
280 "DistroRelease" field, as the GUIs add that in some cases. Check for
281 "Dependencies" instead. This fixes information collection for kernel
282 problems (which now has a full GTK GUI test case). (LP: #968488)
283
2841.95 (2012-03-22)
285-----------------
286Bug fixes:
287 - ui.py: Ensure that the report file is readable by the crash reporting daemon
288 after running through collect_info(). Thanks Evan Dandrea.
289 - apport-gtk, apport-kde: Set the window title to the distribution name, as
290 per http://wiki.ubuntu.com/ErrorTracker#error . Thanks Evan Dandrea.
291 (LP: #948015)
292 - test/run: Ignore obsolete packages on the system, to avoid breaking the GUI
293 tests due to them.
294 - apport-gtk, apport-kde: When reporting a "system crash", don't say "... of
295 this program version", but "...of this type", as we don't show a program
296 version in the initial dialog (https://wiki.ubuntu.com/ErrorTracker#error)
297 (LP: #961065)
298 - problem_report.py, write_mime(): Do not put a key inline if it is bigger
299 than 1 kB, to guard against very long lines. (LP: #957326)
300 - etc/cron.daily/apport: Do not remove whoopsie's *.upload* stamps every day,
301 only if they are older than a week. whoopsie comes with its own cron job
302 which deals with them. Thanks Steve Langasek. (LP: #957102)
303 - report.py, mark_ignore(): Fix crash if executable went away underneath us.
304 (LP: #961410)
305 - apport-gtk: Do not compare current continue button label against a
306 translated string. Instead just remember whether or not we can restart the
307 application. (LP: #960439)
308 - hookutils.py, command_output(): Add option to keep the locale instead of
309 disabling it.
310 - hookutils.py, command_output(): Actually make the "input" parameter work,
311 instead of causing an eternal hang. Add tests for all possible modes of
312 operation.
313 - hooktuils.py: Change root_command_output() and attach_root_command_outputs()
314 to disable translated messages (LC_MESSAGES=C) only as part of the command
315 to be run, not already for the root prefix command. This will keep the
316 latter (gksu, kdesudo, etc.) translated. (LP: #961659)
317 - apport-gtk: Cut off text values after 4000 characters, as Gtk's TreeView
318 does not get along well with huge values. KDE's copes fine, so continue to
319 display the complete value there. (LP: #957062)
320 - apport-gtk: Make details window resizable in bug reporting mode.
321 - crashdb.py, known(): Check the address signature duplicate database if the
322 symbolic signature exists, but did not find any result. (LP: #103083)
323 - ui.py: Run anonymization after checking for duplicates, to prevent host or
324 user names which look like hex numbers to corrupt the stack trace.
325 (LP: #953104)
326 - apport-gtk: Require an application to both have TERM and SHELL in its
327 environment to consider it a command line application that was started by
328 the user. (LP: #962130)
329 - backends/packaging-apt-dpkg.py, _check_files_md5(): Fix double encoding,
330 which caused UnicodeDecodeErrors on non-ASCII characters in an md5sum file.
331 (LP: #953682)
332 - apport-kde, apport-gtk: Only show "Relaunch" if the report has a
333 ProcCmdline, otherwise we cannot restart it. (LP: #956173)
334
335Improvements:
336 - hookutils.py, attach_alsa(): Add the full "pacmd list" output instead of
337 just sinks and sources. Thanks David Henningsson.
338 - apport-gtk, apport-kde: Show the ExecutablePath while we're collecting data
339 for the crash report. Thanks Evan Dandrea. (LP: #938707).
340
3411.94.1 (2012-03-07)
342-------------------
343Bug fixes:
344 - test_ui_kde.py: Re-enable inadvertently disabled "bug report for uninstalled
345 package" test.
346 - ui.py, collect_info(): Do not assume that reports have a "ProblemType"
347 field. This is not the case when updating a bug. (LP: #947519)
348 - apport-cli: Consistently handle unicode vs. byte arrays. (LP: #946207)
349 - report.py, anonymize(): Fix crash when the hostname or user name contain
350 non-ASCII characters. (LP: #945230)
351 - packaging-apt-dpkg.py: Fix UnicodeDecodeError on unexpected md5sum output.
352 (LP: #921037)
353 - apport-gtk: Fix handling of non-ASCII strings in message dialogs.
354 (LP: #865394)
355
3561.94 (2012-03-02)
357-----------------
358Bug fixes:
359 - apport: Set the group of written reports to "whoopsie" if that group exists.
360 - Fix tests to run properly against the system-installed modules and binaries.
361 - test/run: Run under LC_MESSAGES=C to avoid test failures due to translated
362 strings.
363 - general-hooks/generic.py: Also attach xsession-errors for programs that link
364 to libgtk-3.
365 - launchpad.py: Properly handle "Expired" status, to avoid marking new bugs as
366 duplicates of expired ones. (LP: #941854)
367 - apport: Fix crash if the "whoopsie" group does not exist. (LP: #942326)
368 - report.py, crash_signature(): Do not put "<module>" frames into Python crash
369 signatures that happen outside of function/method calls. Fall back to the
370 file/line number as a frame description instead. This will do a much better
371 job at disambiguating e. g. different ImportError crashes. (LP: #920403)
372 - Make "binary changed since the time of the crash" error message more
373 comprehensible, thanks Paolo Rotolo. (LP: #942830)
374 - crashdb.py, check_duplicate(): It can happen that a bug gets identified as
375 being a duplicate of bug S by symbolic signatures and a duplicate of bug A
376 by address signatures. Empirical evidence shows that this is due to the
377 unavoidable jitter in stack traces (A and S not being identified as
378 duplicates as their signatures differ slightly) and not a logic error. So
379 instead of erroring out, duplicate all three bugs and keep the lowest number
380 as the master ID. (LP: #943117)
381 - Revert the usage of multiple nested threads during data collection, and
382 switch back to only using one UI thread. The UI implementations can, and now
383 do, decide between showing a spinner and showing a progress dialog in the
384 ui_*_info_collection_progress() methods. This fixes libX11 crashes when
385 multiple UI threads do changes concurrently (LP: #901675), and also avoids
386 multi-thread induced crashes in Pango (LP: #943661). The removal of the
387 collect() method also fixes the new crashes in it. (LP: #942098, #939803)
388 - ui.py, get_desktop_entry(): Fix crash on uninstalled package. (LP: #940984)
389 - data/unkillable_shutdown: Fix crash on race condition when PID goes away
390 while the report is created. (LP: #546369)
391 - apport/hookutils.py, pci_devices(): Fix crash on unexpected lines from
392 lspci. (LP: #904489)
393 - Drop hardcoded "Ubuntu" words again which crept in with the whoopsie support
394 merge. Use the DistroRelease: field.
395 - apport-kde: Fix Home page URL in KApplication metadata.
396 - apport-gtk: Fix resizability and size after hiding details. (LP: #405418)
397
398Improvements:
399 - test/run: Drop "local" argument. This now tests against the source tree when
400 run in the source tree root, and against the system libraries/programs when
401 run from anywhere else.
402 - test/run: Consider command line arguments as test names and only run those
403 when given. Also support just running a single test.
404 - testsuite: Force the skipping of online tests when $SKIP_ONLINE_TESTS is
405 set.
406 - hookutils.py, xsession_errors(): Add a reasonable default pattern which
407 matches glib-style warnings, errors, criticals etc. and X window errors.
408 In data/general-hooks/generic.py, call it with that default instead of the
409 rather incomplete custom pattern. (LP: #932660)
410 - packaging.py: Add get_package_origin() method, and implement it for
411 apt-dpkg.
412 - report.py, add_package_info(): Add "[origin: ...]" tag to "Package" and
413 "Dependencies" fields for any package which is not native to the
414 distribution. If any such package is present, tag the report with
415 "third-party-packages" in data/general-hooks/generic.py. (LP: #927912)
416 - apport/packaging.py: Add get_uninstalled_package() method as a helper method
417 for the test suite. Use it instead of a hardcoded Debian/Ubuntu specific
418 name in test/test_hooks.py.
419 - test/test_ui_{gtk,kde}.py: Add test cases for complete UI workflow runs for
420 reporting a bug against an installed/uninstalled package, and reporting a
421 crash with and without showing details. This reproduces the recent crashes
422 like LP #901675 or LP #943661.
423 - test_ui.py: Add a test case for reporting a complete report on uninstalled
424 package. This happens when reporting a problem from a different machine
425 through copying a .crash file.
426 - test/run: Add a test that there are no hardcoded "Ubuntu" words in the
427 source. The code should use the DistroRelease: field or lsb_release.
428
4291.93 (2012-02-23):
430------------------
431Bug fixes:
432 - apport-gtk: Fix crash on nonexisting icon. Thanks Evan Dandrea.
433 (LP: #937354)
434 - ui.py, open_url(): Revert back to calling sudo instead of dropping
435 privileges ourselves; with the latter, calling firefox as the sudo'ing user
436 fails. (LP: #916810, #938128)
437 - ui.py: Fix aborting with "AssertionError" if the report is already known,
438 but without an URL. (LP: #938778)
439 - launchpad.py: If a bug is already known, but the report is private, do not
440 send the report. There is little sense piling up lots of duplicates.
441 (LP: #938700)
442 - test/crash: Fix regression of test_crash_apport(), consider $TERM a
443 non-sensitive variable.
444 - ui.py: Fix test failures for data collection progress, they are not expected
445 to happen for "ProblemType: Crash" any more (happens in the background
446 during sending, or if user clicks on "Show Details").
447 - test/hooks: Use a package from Debian/Ubuntu main, so that this works better
448 during package builds on build servers.
449 - test/python: Do not assume that /var/crash/ exists. Use /var/tmp/ for the
450 fake binaries instead.
451 - data/general-hooks/parse_segv.py: Fix test case name.
452 - ui.py: Fix crash on invalid core dumps. (LP: #937215)
453 - launchpad.py: Fix crash on unicode report titles. (LP: #896626)
454
455Improvements:
456 - apport-gtk: Show the most interesting fields first in the details view.
457 - do-release: Call pyflakes and abort on errors other than unused imports.
458 - Move all test suites out of the code modules into test/test_<module>.py.
459 This avoids having to load it every time the program runs, and also allows
460 running the tests against the installed version of Apport.
461 - Clean up the other executable test script in test/* and change them to the
462 same structure as the module tests.
463
4641.92 (2012-02-20):
465------------------
466Bug fixes:
467 - ui.py: Fix wrong creation of "~" folder instead of expanding it to home
468 directory when using "Examine locally". Thanks Jason Conti! (LP: #909149)
469 - Replace file() calls with open() for Python 3 compatibility. Thanks Colin
470 Watson!
471 - launchpad.py: Avoid sending tag names with upper case. (LP: #924181)
472 - report.py, crash_signature_addresses(): Fix crash if report does not have
473 "Signal".
474 - apport-gtk: Fix resize handling of expander in details window. Thanks Thomas
475 Bechtold! (LP: #930562)
476 - Clean up unnecessary imports. Thanks Evan Dandrea!
477
478Improvements:
479 - man/apport-bug.1: Mention where crash files are stored. Thanks David
480 Kastrup.
481 - hookutils.py, attach_hardware(): Sort ProcModules, thanks Brian Murray.
482 - launchpad.py: Keep "Dependencies" attachment in duplicates. Thanks Brian
483 Murray.
484 - Reorganize the GNOME and KDE user interface to do the crash notifications
485 and detail browser in a single dialog. Add test/gtk and test/kde tests to
486 check expected dialog layout for different cases. Thanks Evan Dandrea!
487 - Add support for the whoopsie-daisy crash reporting daemon by creating
488 zero-byte .upload file stamps for crash reports. Thanks Evan Dandrea!
489
4901.91 (2012-01-18):
491------------------
492Bug fixes:
493 - crashdb.py, check_duplicate(): If a crash has a signature but no existing
494 duplicate in the DB, also check for an existing address signature duplicate
495 in the DB.
496 - apport-retrace: Use DistroRelease specific subdirectory of the cache dir for
497 mapping a file to a package, as these maps are release specific.
498 - packaging-apt-dpkg.py: Refresh Contents.gz cache if it is older than one
499 day.
500 - crashdb.py: Ensure that address_signature duplicate db table does not have
501 multiple identical signatures by making it a primary key. Bump the db format
502 to "3". Existing databases need to be migrated manually as SQLite does not
503 allow adding a "PRIMARY KEY" constraint to existing tables.
504 - crashdb.py: Do not add a new address signature entry if one already exists.
505 - apport-cli: Fix UnicodeDecodeError on unicode report values. (LP: #275972)
506 - launchpad.py: Only set bug task importance if it is undecided.
507 - apport-retrace: Fix "an useful" typo. (LP: #911437)
508 - report.py: Filter out frames which are internal kernel/glibc implementation
509 details and not stable across duplicates. In particular, filter out
510 __kernel-syscall() and the SSE stubs.
511 - crashdb.py: Remove debugging leftover which completely disabled bug pattern
512 checking.
513 - report.py: Update reading AssertionMessage. Current (e)glibc turned
514 __abort_msg from a simple static string into a struct.
515
516Improvements:
517 - Change permissions of .crash files from 0600 to 0640, so that /var/crash can
518 be made g+s and crash handling daemons can access those.
519 - Python exceptions: Blacklist DBus.Error.NoReply. It does not help to get
520 these traces from the client-side application, you need the actual exception
521 in the D-Bus server backend instead. (LP: #914220)
522 - Support /etc/apport/whitelist.d/ similarly to /etc/apport/blacklist.d/, for
523 cases like installer environments where only crashes of a few selected
524 programs should be reported.
525
5261.90 (2011-11-24):
527------------------
528First beta release of 2.0 which introduces client-side duplicate checking.
529
530Bug fixes:
531 - backends/packaging-apt-dpkg.py: Fix another test case failure when ddeb
532 repository is not enabled.
533 - backends/packaging-apt-dpkg.py: Fix handling of explicit cache directory
534 name when it is a relative path.
535 - launchpad.py: Only query for bugs after 2011-08-01, to avoid timeouts.
536 - ui.py: Also anonymize standard bug title. (LP: #893863)
537 - launchpad.py: Current Launchpad cannot have private bugs which affect
538 multiple projects. Fix test suite accordingly.
539
540Improvements:
541 - report.py: Break out new method stacktrace_top_function() from
542 standard_title(), so that other parts of the code can use this as well.
543 - launchpad.net: When sending retraced results back to the bug report, update
544 the topmost function in the bug title. (LP: #869970)
545 - report.py, add_gdb_info(): Add a new field "StacktraceAddressSignature"
546 which is a heuristic signature for signal crashes. This should be used if
547 crash_signature() fails, i. e. the Stacktrace field does not have enough
548 symbols. This can be used to check for duplicates on the client side,
549 provided that the crash database server supports querying for these.
550 Do not expose this field when uploading to crash databases though, as it can
551 be recomputed from the already existing information (ProcMaps and
552 Stacktrace) and thus would just clutter the reports.
553 - crashdb.py: Add a table "version" with the database format version. Add
554 automatic upgrading to the most current format.
555 - crashdb.py: Put address signatures from reports checked with
556 check_duplicate() into the duplicate database, so that implementations of
557 known() can check for these.
558 - dupdb-admin: Add "publish" dupdb-admin command which exports the
559 duplicate database into a set of text files suitable for WWW publishing.
560 - crashdb.py: Add new method "known(report)" which can be implemented to check
561 if the crash db already knows about the crash signature. If so, the report
562 will not be uploaded, and instead the user will be directed to the existing
563 report URL (if available), similar to bug patterns. The default
564 implementation checks this format, if the crash database is initialized with
565 a "dupdb_url" option pointing to the exported database.
566 - launchpad.py: Override known() to check if the master bug is actually
567 accessible by the reporter, and is not tagged with "apport-failed-retrace"
568 or "apport-request-retrace"; otherwise file it anyway.
569 - crash-digger: Add --publish-db option to conveniently integrate duplicate DB
570 publication (similar to dupdb-admin publish) into retracer setups.
571 - launchpad.py: Attach updated stack traces from a duplicate to the master bug
572 if it failed retracing previously or has an "apport-request-retrace" tag.
573 (LP: #869982)
574 - apport-kde, apport-gtk: Support the "Annotation" field for custom dialog
575 titles for "Crash" and "Package" problem types as well, not just for
576 "Kernel". (LP: #664378)
577
5781.26 (2011-11-11):
579------------------
580Bug fixes:
581 - backends/packaging-apt-dpkg.py: Port to current python-apt API.
582 - hookutils.py: Fix path_to_key() to also work with unicode arguments.
583 - test/crash: Exit successfully if apport is not enabled in the system. This
584 allows packages to run the test suite during build.
585 - report.py, add_proc_info(): Correctly handle "python -m <modulename>"
586 programs as being interpreted and determine the appropriate module path.
587 - Fix some import statements to also work for the system-installed test suite.
588 - test/run: Fix testing data/general-hooks/parse_segv.py when called in
589 system-installed mode.
590 - apport/ui.py: Clean up test .crash file after test cases.
591 - Fix tests when running as root.
592 - setup.py: Fix crash when "javac -version" fails.
593 - README: Update command for one-time enablement.
594 - backends/packaging-apt-dpkg.py: Fix interleaving usage of install_packages()
595 with other operations such as get_version(), by resetting the apt status
596 after building and using the sandbox.
597 - report.py test suite: Remove requirement that $USER is set, which makes it
598 easier to run this from package build environments.
599 - apport/ui.py, test/crash: Use "yes" as test process instead of "cat". The
600 former is less likely to run already, and does not depend on having a stdin,
601 so it runs better in test environments like autopkgtest.
602 - backends/packaging-apt-dpkg.py: Fix tests if system does not have a dbgsym
603 apt source.
604
605Improvements:
606 - Ignore a crash if gnome-session is running and says that the session is
607 being shut down. These often die because X.org or other services are going
608 away, are usually harmless, and just cause a lot of clutter in bug trackers.
609 (LP: #460932)
610 - test/crash: Rewrite using Python's unittest, to be in line with other tests,
611 and be easier to maintain and extend.
612
6131.25 (2011-11-02):
614------------------
615Improvements:
616 - Add new response "Examine locally" to presenting the report details, which
617 runs apport-retrace in the chosen mode in a terminal. This should be made
618 available for crash reports if apport-retrace and a Terminal application are
619 installed; add an abstrace UI method for this. (LP: #75901)
620 - apport-gtk: Add "Examine locally..." button, and implement
621 ui_run_terminal().
622 - apport-cli: Add "Examine locally..." responses, and implement
623 ui_run_terminal().
624 - apport-cli: Greatly speed up displaying large reports. This also changes the
625 format to avoid indenting each line with a space, and visually set apart the
626 keys in a better way.
627 - apport_python_hook.py: Move tests out of this file into test/python, to
628 avoid having to parse the unit tests at each Python startup.
629 - test/python: Also make tests work if Python hook is not installed in
630 system's sitecustomize.py.
631 - packaging.py: Add get_modified_conffiles() API, and implement it in
632 packaging-apt-dpkg.py.
633 - hookutils.py: Add attach_conffiles().
634 - hookutils.py: Add attach_upstart_overrides().
635
636Bug fixes:
637 - launchpad.py: Remove "Ubuntu" in bug response, replace with "this software".
638 (LP: #883234)
639 - apport-kde: Rearrange order of imports to get intended error message if
640 PyKDE is not installed.
641 - packaging-apt-dpkg.py: Ignore hardening-wrapper diversions, to make
642 gcc_ice_hook work if hardening-wrapper is installed.
643 - apport_python_hook: Respect $APPORT_REPORT_DIR.
644 - apport_python_hook: Limit successive crashes per program and user to 3 per
645 day, just like signal crashes. (LP: #603503)
646 - packaging-apt-dpkg.py: Skip online tests when there is no default route.
647 - ui.py: Fix test suite to not fail if system has some obsolete or non-distro
648 packages.
649
6501.24 (2011-10-19):
651------------------
652Bug fixes:
653 - backends/packaging-apt-dpkg.py, install_packages(): Also copy
654 apt/sources.list.d/ into sandbox.
655 - backends/packaging-apt-dpkg.py, install_packages(): Install apt keyrings
656 from config dir or from system into sandbox. (LP: #856216)
657 - packaging.py, backends/packaging-apt-dpkg.py: Define that install_packages()
658 should return a SystemError for broken configs/unreachable servers etc., and
659 fix the apt/dpkg implementation accordingly.
660 - apport-retrace: Don't crash, just give a proper error message if servers are
661 unreachable, or configuration files are broken. (LP: #859248)
662 - backends/packaging-apt-dpkg.py: Fix crash when /etc/apport/native-origins.d
663 contains any files. (LP: #865199)
664 - hookutils, recent_logfile(): Fix invalid return value if log file is not
665 readable. (LP: #819357)
666 - test/crash: Fix race condition in the "second instance terminates
667 immediately" check.
668 - hookutils.py: Replace attach_gconf() with a no-op stub. It used static
669 python modules like "gconf" which broke the PyGI GTK user interface, and
670 gconf is rather obsolete these days.
671 - ui.py, open_url(): Greatly simply and robustify by just using xdg-open. This
672 already does the right thing wrt. reading the default browser from GNOME,
673 KDE, XCE, and other desktops. (LP: #198449)
674 - data/general-hooks/generic.py: Only attach ~/.xsession_errors if the bug is
675 reported in the same XDG session as the crash happened. (LP: #869974)
676 - Ignore crashes for programs which got updated in between the crash and
677 reporting. (LP: #132904)
678 - Special-case crashes of 'twistd': Try to determine the client program and
679 assign the report to that, or fail with an UnreportableReason. (LP: #755025)
680 - apport-gtk: In bug update mode, make details dialog resizable and fix
681 default size. (LP: #865754)
682 - apport-gtk: Fix crash if report does not have ProcCmdline. (LP: #854452)
683 - hookutils.py, attach_wifi(): Anonymize ESSID and AP MAC from "iwconfig"
684 output. (LP: #746900)
685 - test/crash: Fix test failure if user is not in any system groups.
686 - test/crash: Change to /tmp/ for test crash process, to fix failure if the
687 user that runs the test suite cannot write into the current directory.
688 (LP: #868695)
689 - ui.py: Improve error message if package is not a genuine distro package.
690 Thanks to Ronan Jouchet. (LP: #559345)
691
692Improvements:
693 - apport-retrace: Add --timestamp option to prepend a timestamp to log
694 messages. This is useful for batch operations.
695 - crash-digger: Call apport-retrace with --timestamps, to get consistent
696 timestamps in log output.
697 - hookutils.py: Add two new functions attach_gsettings_package() and
698 attach_gsettings_schema() for adding user-modified gsettings keys to a
699 report. (LP: #836489)
700 - hookutils.py: Add new function in_session_of_problem() which returns whether
701 the given report happened in the currently running XDG session. This can be
702 used to determine if e. g. ~/.xsession-errors is relevant and should be
703 attached.
704
7051.23.1 (2011-09-29)
706-------------------
707Bug fixes:
708 - apport/crashdb.py: Ensure that duplicate table only has one entry per report
709 ID.
710 - apport-retrace: Pass correct executable path to gdb in --gdb with --sandbox
711 mode.
712 - apport-retrace: Do not leave behind temporary directories on errors.
713 - apport-retrace: Drop assertion failure for existance of "Stacktrace". This
714 isn't present in the case of gdb crashing, and there is not much we can do
715 about it. This should not break the retracer.
716 - apport/report.py: Unwind XError() from stack traces for the "StacktraceTop"
717 field, as they take a significant part of the trace. This causes bugs to be
718 duplicated which really have different causes.
719
7201.23 (2011-09-14)
721-----------------
722Improvements:
723 - crashdb.py, crash-digger, dupdb-admin: Drop the concept of "duplicate DB
724 consolidation". Such massive queries cause timeouts with e. g. Launchpad.
725 Instead, update the status of potential master bugs in the crash DB whenever
726 check_duplicate() is called.
727
728Bug fixes:
729 - launchpad.py: Fix crash in close_duplicate() if master bug was already
730 marked as a duplicate of the examined bug.
731 - problem_report.py, load(): Fix missing last character if the last line in a
732 multi-line field is not terminated with a newline.
733 - launchpad.py: Fix test_marking_python_task_mangle() check to work with
734 current Launchpad.
735 - apport-retrace: If the user did not specify a --cache directory, create a
736 shared one instead of letting the two install_packages() calls create their
737 own. This ensures that the apt and dpkg status is up to date, and avoids
738 downloading the package indexes multiple times. (LP: #847951)
739 - apport-retrace: Give proper error mesage instead of AssertionError crash if
740 a report does not contain standard Apport format data. (LP: #843221)
741 - fileutils.py, get_new_reports(): Fix crash if report file disappears in the
742 middle of the operation. (LP: #640216)
743 - apport/ui.py, load_report(): Intercept another case of broken report files.
744 (LP: #445142)
745 - apport/report.py, standard_title(): Escape regular expression control
746 characters in custom exception names. (LP: #762998)
747
7481.22.1 (2011-09-06)
749-------------------
750Improvements:
751 - dupdb-admin: Add "removeid" command.
752
753Bug fixes:
754 - dupdb-admin: Use the in-memory CrashDB implementation for simple operations
755 like dump or changeid, which do not require an actual backend. This makes
756 the command work in checkouts without a /etc/apport/crashdb.conf.
757 - dupdb-admin: Fix UnicodeEncodeError crash.
758 - launchpad.py: Fix crash if a crash report does not have a DistroRelease.
759 - Set the default "Apport" title for choice dialogs instead of the default
760 apport-gtk title. Thanks Robert Roth. (LP: #608222)
761 - apport-gtk: Update markup_escape_text() call to current glib. (LP: #829635)
762
7631.22 (2011-08-25)
764-----------------
765Improvements:
766 - Completely rework apport-retrace to use gdb's "debug-file-directory" and
767 "solib-absolute-prefix" settings and only unpack the necessary packages in a
768 temporary directory. This makes it possible to use it in a running system
769 without actually touching installed packages, does not need any root
770 privileges, and stops the requirement of using chroots with fakechroot and
771 fakeroot. This is a lot easier to maintain and use, and a lot faster, too.
772 As a consequence, drop the chroot module, and update crash-digger
773 accordingly. See "man apport-retrace" for the new usage.
774 It is now also easier to port to other packaging backends, as a lot of the
775 common logic moved out of the packaging API;
776 packaging.install_retracing_packages() got dropped in favor of the simpler
777 packaging.install_packages().
778 - crash-digger: Show how many bugs are left in the pool with each new retrace.
779
780Bug fixes:
781 - apport-gtk: Fix crash in GLib.markup_escape_text() call, regression from
782 1.21.3. (LP: #828010)
783 - launchpad.py: When searchTasks() times out, exit with 99 as this is a
784 transient error.
785 - crash-digger: Intercept OverflowError from downloaded compressed
786 attachments.
787
7881.21.3 (2011-08-17)
789-------------------
790Bug fixes:
791 - gtk/apport-gtk.desktop.in: Also show in Unity. (LP: #803519)
792 - apport-unpack: Fix crash on file errors.
793 - Add apport.packaging.get_library_paths() interface and implement it for
794 backends/packaging-apt-dpkg.py using dpkg multiarch directories. Use it in
795 chroot.py.
796 - hookutils.py: Don't attach empty values. Thanks Bryce Harrington.
797 (LP: #813798)
798 - apport-gtk: Correctly pass message dialog type.
799 - apport-gtk: Fix GLib and GObject imports to be compatible with the future
800 pygobject 3.0.
801
802Improvements:
803 - hookutils.py: Add attach_mac_events() for reporting logs of MAC systems.
804 Looks for AppArmor messages for now. Thanks Marc Deslauriers!
805 - hookutils.py, attach_alsa(): Get a list of outputs/inputs that PulseAudio
806 knows about, which also shows the currently selected output/input, as well
807 as volumes. This should help with "no sound" bug troubleshooting. Thanks
808 Luke Yelavich.
809
8101.21.2 (2011-07-01)
811-------------------
812Improvements:
813 - test/run: Check $PYTHON for using a different Python interpreter (such as
814 "python3") for the tests.
815 - generic hook: Don't report package installation failures due to segfaulting
816 maintainer scripts. We want the actual crash report only. Thanks Brian
817 Murray.
818 - hookutils.py, attach_wifi(): Also include wpasupplicant logs. Thanks Mathieu
819 Trudel-Lapierre!
820
821Bug fixes:
822 - backends/packaging-apt-dpkg.py: Fix crash introduced in 1.21.1's multiarch
823 fixes.
824 - report.py: Fix bug patterns to correctly match against compressed report
825 fields.
826
8271.21.1 (2011-06-20)
828-------------------
829Improvements:
830 - data/general-hooks/generic.py: Also check for low space on /var. Thanks
831 Brian Murray.
832 - hookutils.py, attach_file() and attach_file_if_exists(): Add a new
833 "overwrite" flag option. If not given, now default to overwriting an
834 existing key, as this is usually what you need when attaching files
835 (instead of attaching it several times with '_' appended to the keys). You
836 can get the old behaviour by setting overwrite=False.
837
838Bug fixes:
839 - When showing the size of the full report, take the compressed size of binary
840 values instead of their uncompressed size, as the crash db upload will use
841 the compressed values.
842 - backends/packaging-apt-dpkg.py: Fix for current dpkg with multiarch support.
843 - test/run: Fix the test suite to run against the system installed libraries
844 with current Python versions (2.6, 2.7) where __file__ does not work any
845 more with imports.
846
8471.21 (2011-06-08)
848-----------------
849Improvements:
850 - Supply --desktop option to kdesudo to improve the description which program
851 is requesting administrative privileges.
852 - apport-checkreports: Exit with status 2 if there are new reports, but apport
853 is disabled. This helps crash notification GUIs to not display new crash
854 reports in that case. Thanks to Michael Vogt for the original patch.
855 - Add data/is-enabled: Shell script to check if apport is enabled. Non-Python
856 programs (which can't use apport.packaging.enabled() ) can call this instead
857 of having to parse /etc/default/apport themselves, and just check the exit
858 code. Inspired by original patch from Michael Vogt, thanks!
859
860Bug fixes:
861 - apport-gtk: HTML-escape text for dialogs with URLs. (LP: #750870)
862 - dump_acpi_tables.py: Check to see if acpi/tables dir is mounted first.
863 Thanks Brian Murray. (LP: #729622)
864 - man/apport-cli.1: Document recently added -w/--window option. Thanks Abhinav
865 Upadhyay! (LP: #765600)
866 - Use kde-open instead of kfmclient to open URLs under KDE. Thanks Philip
867 Muškovac. (LP: #765808)
868
8691.20.1 (2011-03-31)
870-------------------
871Bug fixes:
872 - Add bash completion support for new -w/--window option that was introduced
873 in 1.20. Thanks Philip Muškovac.
874 - apport-unpack: Fix crash if target directory already exists.
875 - Fix crash if UnreportableReason is a non-ASCII string. (LP: #738632)
876 - Fix crash if application from desktop name is a non-ASCII string.
877 (LP: #737799)
878 - unkillable_shutdown: Fix rare crash if ExecutablePath does not exist (any
879 more). (LP: #537904)
880 - kernel_crashdump: Fix crash if the vmcore file disappeared underneath us.
881 (LP: #450295)
882 - unkillable_shutdown: Fix crash if the checked process terminated underneath
883 us. (LP: #540436)
884 - ui.py: Properly raise exceptions from the upload thread that happen at its
885 very end. (LP: #469943)
886
8871.20 (2011-03-17)
888-----------------
889Improvements:
890 - Add support for -w/--window option which will enable user to select a
891 window as a target for filing a problem report. Thanks Abhinav Upadhyay for
892 the patch! (LP: #357847)
893 - Disable the filtering on SIGABRT without assertion messages. Turns out that
894 developers want these crash reports after all. (LP: #729223)
895 - Add support for a "DuplicateSignature" report fields. This allows package
896 hooks to implement custom duplicate problem handling which doesn't need to
897 be hardcoded in Apport itself. Update the launchpad backend to tag such bugs
898 as "need-duplicate-check".
899
900Bug fixes:
901 - report.py, add_hooks_info(): Properly report TypeErrors from hooks.
902 - apport-retrace: Intercept SystemErrors from ill-formed gzip attachments as
903 well.
904 - Fix crash if crash database configuration does not specify a
905 bug_pattern_url. Just assume None. (LP: #731526)
906 - If a custom crash database does not specify a bug_pattern_url, fall back to
907 using the default database's. (LP: #731526)
908 - hookutils.py Update WifiSyslog regex to correctly catch application log
909 messages in syslog. Thanks Mathieu Trudel-Lapierre. (LP: #732917)
910 - hookutils.py, attach_hardware(): Avoid error message if machine does not
911 have a PCI bus. Thanks Marcin Juszkiewicz! (LP: #608449)
912 - backends/packaging-apt-dpkg.py: Replace deprecated getChanges() call with
913 get_changes().
914 - apport-gtk: Fix broken dialog heading if the name of the crashed program
915 contains an & or other markup specific characters.
916 - apport-gtk: Don't crash if GTK cannot be initialized. This usually happens
917 without a $DISPLAY or when the session is being shut down. Just print an
918 error message. If there are pending crashes, they will be shown again the
919 next time a session starts. (LP: #730569)
920
9211.19 (2011-02-28)
922-----------------
923Bug fixes:
924 - Update stack unwind patterns for current glib (slightly changed function
925 names), and also ignore a preceding '*'. (LP: #716251)
926 - Fix crash_signature() to fail if there is an empty or too short
927 StacktraceTop.
928 - apt backend: Do not generate a warning if the opportunistically added -dbg
929 package does not exist.
930 - apt backend: Only add -dbg in --no-pkg mode, as there will be conflicts in
931 normal package mode.
932 - apt backend: Call tar with target cwd instead of using -C; the latter causes
933 an extra openat() call which breaks with current fakechroot.
934 - launchpad.py: Fix retracer crash if DistroRelease field does not exist.
935 - Convert deprecated failIf()/assert_() TestCase method calls to
936 assertFalse()/assertTrue().
937
938Improvements:
939 - In apport-bug, if the user specifies a PID referring to a kernel thread,
940 do the right thing and file the bug against the kernel
941 - In hookutils.attach_dmesg, skip over an initial truncated message if one
942 is present (this happens when the ring buffer overflows)
943 - Change bug patterns to just use one central file instead of per-package
944 files. This allows bug patterns to be written which are not package
945 specific, and is easier to maintain as well. IMPORTANT: This changed the
946 format of crashdb.conf: bug_pattern_base is now obsolete, and the new
947 attribute bug_pattern_url now points to the full URL/path of the patterns
948 file. Thanks to Matt Zimmerman!
949
9501.18 (2011-02-16)
951-----------------
952Bug fixes:
953 - Ensure that symptom scripts define a run() function, and don't show them if
954 not.
955 - Do not show symptom scripts which start with an underscore. These can be
956 used for private libraries for the actual symptom scripts.
957 - Update bash completion. Thanks Philip Muškovac.
958 - etc/default/apport: Remove obsolete "maxsize" setting. (LP: #719564)
959
960Improvements:
961 - Remove explicit handling of KDE *.ui files in setup.py, as
962 python-distutils-extra 2.24 fixes this. Bump version check.
963 - hookutils.py: Add attach_root_command_outputs() to run several commands
964 at once. This avoids asking for the password several times. (LP: #716595)
965
9661.17.2 (2011-02-04)
967-------------------
968Improvements:
969 - Be more Python 3 compatible (not fully working with Python 3 yet, though).
970 - apt/dpkg backend: Drop support for pre-0.7.9 python-apt API.
971 - Add --tag option to add extra tags to reports. (LP: #572504)
972
973Bug fixes:
974 - hookutils.py, attach_dmesg(): Do not overwrite already existing dmesg.
975 - hookutils.py: Be more robust against file permission errors. (LP: #444678)
976 - ui.py: Do not show all the options in --help when invoked as *-bug.
977 (LP: #665953)
978 - launchpad.py: Adapt test cases to current standard_title() behaviour.
979
9801.17.1 (2011-01-10)
981-------------------
982Bug fixes:
983 - Make the GTK frontend work with GTK 2.0 as well, and drop "3.0" requirement.
984
9851.17 (2010-12-31)
986-----------------
987Improvements:
988 - Better standard bug titles for Python crashes. Thanks Matt Zimmerman!
989 (LP: #681574)
990 - Add handler for uncaught Java exceptions. There is no integration for
991 automatically intercepting all Java crashes yet, see java/README.
992 Thanks Matt Zimmerman! (LP: #548877)
993
994Bug fixes:
995 - GTK frontend: Require GTK 3.0.
996 - launchpad.py: Default to "production" instance, not "edge", since edge is
997 obsolete now.
998 - hookutils.py, attach_alsa(): Fix crash if /proc/asound/cards does not exist.
999 (LP: #626215)
1000 - ui.py, format_filesize(): Fix to work with stricter locale.format() in
1001 Python 2.7. (LP: #688535). While we are at it, also change it to use base-10
1002 units.
1003 - hookutils.py, package_versions(): Always include all requested package names
1004 even if they're unknown to us. Thanks Matt Zimmerman! (LP: #695188)
1005 - launchpad.py: When updating a bug, also add new tags. Thanks Brian Murray!
1006
10071.16 (2010-11-19)
1008-----------------
1009New features:
1010 - Port GTK frontend from pygtk2 to GTK+3.0 and gobject-introspection.
1011
1012Bug fixes:
1013 - Fix symptoms again. Version 1.15 broke the default symptom directory.
1014 - Fix memory test case to work with current Python versions, where the SQLite
1015 integrity check throws a different exception.
1016
10171.15 (2010-11-11)
1018-----------------
1019New features:
1020 - Add dump_acpi_tables.py script. This can be called by package hooks which
1021 need ACPI table information (in particular, kernel bug reports). Thanks to
1022 Brad Figg for the script!
1023 - Order symptom descriptions alphabetically. Thanks to Javier Collado.
1024 - Check $APPORT_SYMPTOMS_DIR environment variable for overriding the system
1025 default path. Thanks to Javier Collado.
1026
1027Bug fixes:
1028 - testsuite: Check that crashdb.conf can have dynamic code to determine DB
1029 names and options.
1030 - ui.py test suite: Rewrite _gen_test_crash() to have the test process core
1031 dump itself, instead of using gdb to do it. The latter fails in ptrace
1032 restricted environments, such as Ubuntu 10.10.
1033 - packaging-apt-dpkg.py: Fix handling of /etc/apport/native-origins.d to
1034 actually work. Thanks Steve Langasek. (LP: #627777)
1035 - apport-kde: Load correct translation catalogue. Thanks Jonathan Riddell.
1036 (LP: #633483)
1037 - launchpad.py: Use launchpadlib to file a bug instead of screen scraping.
1038 The latter was completely broken with current Launchpad, so this makes the
1039 test suite actually work again. Thanks to Diogo Matsubara!
1040 - launchpad.py: Change $APPORT_STAGING to $APPORT_LAUNCHPAD_INSTANCE, so that
1041 you can now specify "staging", "edge", or "dev" (for a local
1042 http://launchpad.dev installation). Thanks to Diogo Matsubara!
1043 - backends/packaging-apt-dpkg.py: Fix crash on empty lines in ProcMaps
1044 attachment.
1045 - doc/symptoms.txt: Fix typo, thanks Philip Muskovac. (LP: #590521)
1046 - apport/hookutils.py: rename ProcCmdLine to ProcKernelCmdLine to not wipe
1047 wipe out /proc/$pid/cmdline information. (LP: #657091)
1048 - apport/hookutils.py: attach_file() will not overwrite existing report
1049 keys, instead appending "_" until the key is unique.
1050 - Fix --save option to recognise ~, thanks Philip Muškovac. (LP: #657278)
1051 - Remove escalation_subscription from Ubuntu bug DB definition, turned out to
1052 not be useful; thanks Brian Murray.
1053 - launchpad.py: Fix APPORT_LAUNCHPAD_INSTANCE values with a https:// prefix.
1054 - apt backend: Opportunistically try to install a -dbg package in addition to
1055 -dbgsym, to increase the chance that at least one of it exists. Thanks
1056 Daniel J Blueman!
1057
10581.14.1 (2010-06-24)
1059-------------------
1060Bug fixes:
1061 - hookutils.py, attach_drm_info(): Sanitize connector names. Thanks Chris
1062 Halse Rogers! (LP: #597558)
1063 - bash completion: Complete all path names, apport-bug can be invoked with a
1064 path to a program. Thanks Philip Muskovac.
1065
10661.14 (2010-06-16)
1067-----------------
1068New features:
1069 - hookutils.py: Add new method attach_drm_info() to read and format
1070 /sys/class/drm/*.
1071
1072Bug fixes:
1073 - packaging-apt-dpkg.py: Fix deprecated python-apt variables, thanks David
1074 Stansby. (LP: #591695)
1075 - launchpad.py: Fix crash on attachments which are named *.gz, but
1076 uncompressed. (LP: #574360)
1077 - hookutils.py, attach_gconf(): Fix defaults parsing for boolean keys.
1078 (LP: #583109)
1079
10801.13.4 (2010-05-04)
1081-------------------
1082 - bash completion: Fix error message if /usr/share/apport/symptoms does not
1083 exist. Thanks Philip Muškovac! (LP: #562118)
1084 - general-hooks/parse_segv.py: Report stack exhaustion more clearly and
1085 correctly handle register dereferencing calls.
1086 - Save/restore environment when calling hooks, in case they change the locale,
1087 etc. (LP: #564422)
1088 - hookutils.py, command_output(): Do not set $LC_MESSAGES for the calling
1089 process/hook, just for the command to be called.
1090 - ui.py: When displaying strings from system exceptions, decode them into an
1091 unicode string, to avoid crashing the KDE UI. (LP: #567253)
1092 - apport-retrace: Fix crash for retracing kernel vmcores, which do not have an
1093 ExecutablePath.
1094 - apport-bug manpage: Clarify when apport-collect may be used. Thanks Brian
1095 Murray! (LP: #537273)
1096 - generic hook: Check ProcMaps for unpackaged libraries, and ask the user if
1097 he really wants to continue. If he does, tag the report as "local-libs" and
1098 add a "LocalLibraries" field to the report with a list of them.
1099 (LP: #545227)
1100
11011.13.3 (2010-04-14)
1102-------------------
1103 - data/general-hooks/parse_segv.py: suggest segv-in-kernel possibility.
1104 - ui.py: When running as root, only show system crash reports, to avoid
1105 restarting user programs as root. (LP: #445017)
1106
11071.13.2 (2010-03-31)
1108-------------------
1109 - problem_report.py, write_mime(): Add new optional argument "priority_fields"
1110 for ordering report keys. Patch by Brian Murray, thanks!
1111 - launchpad.py: Put some interesting fields first in the report, with the new
1112 priority_fields argument. Patch by Brian Murray, thanks!
1113 - packaging-apt-dpkg.py, _install_debug_kernel(): Do not crash on an outdated
1114 kernel, just return that it is outdated. (LP: #532923)
1115 - launchpad.py test suite: Add "Referer" HTTP header, now required by
1116 launchpad.
1117 - launchpad.py: Fix crash if configuration does not have an "escalated_tag"
1118 option.
1119 - launchpad.py: Port to launchpadlib 1.0 API, thanks Michael Bienia for the
1120 initial patch! (LP: #545009)
1121 - gtk/apport-gtk-mime.desktop.in, kde/apport-kde-mime.desktop.in: Change
1122 categories so that these do not ever appear in menu editors. (LP: #449215)
1123 - launchpad.py: Some LP bugs have broken attachments (this is a bug in
1124 Launchpad itself). Ignore those instead of crashing.
1125 - apport-gtk: Turn http:// and https:// links into clickable hyperlinks in
1126 information and error dialogs. (LP: #516323)
1127 - apport-retrace: Fix crash when trying to rebuild package info for reports
1128 without an ExecutablePath. (LP: #436157)
1129 - ui.py: Fix crash when package information cannot be determined due to broken
1130 apt status. (LP: #362743)
1131 - ui.py: Fix crash when /etc/apport/crashdb.conf is damaged; print an
1132 appropriate error message instead. (LP: #528327)
1133 - data/kernel_crashdump: Fix crash if log file disappeared underneath us.
1134 (LP: #510327)
1135 - data/apport: Fix IOError when apport is called with invalid number of
1136 arguments, and stderr is not a valid fd. (LP: #467363)
1137 - hookutils.py: Factor out the DMI collection code from attach_hardware()
1138 into attach_dmi(), and call that in attach_alsa() as well. Thanks to Brad
1139 Figg for the patch! (LP: #552091)
1140 - apport/ui.py: Fix the help output if Apport is invoked under an alternative
1141 name (like apport-collect). (LP: #539427)
1142
11431.13.1 (2010-03-20)
1144-------------------
1145Bug fixes:
1146 - Update parse-segv to handle gdb 7.1 output.
1147 - Enhance test suite to work with gdb 7.1 as well, and catch future outputs.
1148 - UI: Add exception string to the "network error" dialog, to better tell what
1149 the problem is.
1150 - UI: Add back -p option to apport-collect/apport-update-bug (regression from
1151 1.13). (LP: #538944)
1152 - launchpad.py: Add yet another workaround for LP#336866. (LP: #516381)
1153 - launchpad.py, download(): Ignore attachments with invalid key names.
1154 - Fix regression from 1.10 which made it impossible for a package hook to set
1155 a third-party crash database for non-native packages. (LP: #517272)
1156 - apport-cli: Create the 'details' string only if user wants to view details,
1157 and do not show files larger than 1MB. Thanks Scott Moser! (LP: #486122)
1158 - packaging-apt-dpkg.py: Silence apt.Cache() spewage to stdout with newer
1159 python-apt versions. (LP: #531518)
1160
1161Improvements:
1162 - unkillable_shutdown: Add list of running processes and blacklisted pids to
1163 report. (LP: #537262)
1164 - Sort the report by key in the details view. (LP: #519416)
1165
11661.13 (2010-03-10)
1167-----------------
1168New features:
1169 - Add "unkillable_shutdown" script to collect information about processes
1170 which are still running after sending SIGTERM to them. This can be hooked
1171 into e. g. /etc/init.d/sendsigs on Debian/Ubuntu systems.
1172
1173Improvements:
1174 - apport_python_hook.py: Directly check /etc/default/apport instead of
1175 querying packaging.enabled(), to avoid importing lots of modules for
1176 non-packaged scripts. Thanks Stuart Colville! (LP: #528355)
1177
1178Bug fixes:
1179 - Fix SegV parser to notice walking off the stack during "call" or "ret"
1180 (LP: #531672).
1181 - Fix --help output for bug updating mode (invocation as apport-collect or
1182 apport-update-bug). (LP: #504116)
1183 - Fix bug escalation tagging, thanks to Brian Murray.
1184 - Fix option processing when being invoked as apport-bug. Thanks to Daniel
1185 Hahler for the patch! (LP: #532944)
1186
11871.12.1 (2010-02-22)
1188-------------------
1189Bug fixes:
1190 - launchpad.py: Do not keep escalating bugs, just escalate at the 10th
1191 duplicate.
1192 - Improve error message if a symptom script did not determine a package name.
1193 (LP: #503834)
1194 - general-hooks/generic.py: Fix crash on libGL check with empty StacktraceTop.
1195 - Review and clean up usage of chmod(). This fixes a small race condition in the
1196 Python exception hook where a local attacker could read the information from
1197 another user's crash report. (LP: #516029)
1198 - hookutils, package_versions(): Ignore "None" packages, for more robust
1199 package hooks. (LP: #518295)
1200
12011.12 (2010-01-20)
1202-----------------
1203Improvements:
1204 - launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for
1205 handling bugs with more than 10 duplicates.
1206 - crashdb.conf: For Ubuntu, escalate bugs with >= 10 duplicates to
1207 "ubuntu-bugcontrol" and tag them with "bugpattern-needed". (LP: #487900)
1208 - general-hooks/generic.py: Filter out crashes on missing GLX (LP: #327673)
1209 - Add bash completion script. Thanks to Philip Muškovac. (LP: #218933)
1210
1211Bug fixes:
1212 - launchpad.py: Drop APPORT_FILES whitelist for download() and instead just
1213 filter out file extensions that we know about (*.txt and *.gz).
1214 (LP: #444975)
1215 - launchpad.py: Do not put the Tags: field into the bug description, since
1216 they are already proper tags. In download(), convert the real tags back to
1217 the Tags: field. (LP: #505671)
1218 - test/crash: Update expected core dump flags for changed rlimit behaviour in
1219 Linux 2.6.32.
1220 - launchpad.py: Fix marking of 'checked for duplicate' for bugs with upstream
1221 tasks.
1222 - launchpad.py, get_fixed_version(): Do not consider a bug as invalid just
1223 because it has any invalid distro package task.
1224
12251.11 (2009-12-23)
1226-----------------
1227Improvements:
1228 - Add "--save" UI option to store the collected information into an .apport
1229 file instead of sending it right away. The file can then later be sent
1230 through apport-bug. Update manpages accordingly.
1231 - Update all copyright and description headers and consistently format them.
1232 - Rename all TestCase classes to "_T", which makes it much easier to run
1233 individual tests from the command line.
1234 - Testsuite: Verify that report details are/are not shown. This uncovered that
1235 details about package installation failures were not shown before sending
1236 them, which is fixed now.
1237
1238Bug fixes:
1239 - test/hooks: Do not try to add hook information to kernel_crashdump test
1240 case, since we do not have an UI here. This test case broke when the system
1241 had an interactive package hook for the kernel.
1242 - When reporting a bug from a saved .apport file, let the user review/confirm
1243 the content before sending.
1244
12451.10.1 (2009-12-23)
1246-------------------
1247Improvements:
1248 - Install apport-collect symlink.
1249 - Update translations from Launchpad.
1250
1251Bug fixes:
1252 - Move all remaining option/argument parsing from apport-bug into ui.py. This
1253 allows the user to add options to apport-bug/apport-collect, and also avoids
1254 unwieldy dissection of options/arguments in shell.
1255
12561.10 (2009-12-19)
1257-----------------
1258New features:
1259 - Add a mode for updating an existing problem report to ui.py (-u/--update).
1260 This is similar to the Ubuntu specific "apport-collect" tool, but
1261 implemented the right way now: In particular, this has access to the UI and
1262 thus can use interactive hooks (LP: #385811) and show you what is being sent
1263 for confirmation/cancelling (LP: #371827)
1264 - apport-bug: If invoked as "apport-collect" or "apport-update-bug" (i. e.
1265 through a symlink), run apport in update mode (-u <number>). This provides a
1266 convenient no-options command line program. Please note that setup.py does
1267 not currently install such a symlink. Update the apport-bug manpage
1268 accordingly.
1269
1270Improvements:
1271 - launchpad.py: Use new login_with() to clean up code, and specify allowed
1272 access levels (WRITE_PRIVATE is the only sensible one anyway). (LP: #410205)
1273 - New hookutils functions:
1274 xsession_errors (match lines from ~/.xsession-errors)
1275 shared_libraries (determine which libraries a binary links with)
1276 links_with_shared_library (test if a binary links with a particular library)
1277 - New CrashDatabase API: get_affected_packages(), can_update(), is_reporter()
1278 - Rename CrashDatabase.update() to update_traces().
1279 - Add CrashDatabase.update() for adding all new fields of a report. This is
1280 primarily useful for collecting local standard and package hook data for an
1281 already existing bug report which was not filed through Apport. This checks
1282 can_update()/is_reporter() if the user is eligible for updating that
1283 particular bug. (LP: #485880)
1284
1285Bug fixes:
1286 - Ignore SIGXCPU and SIGXFSZ; thanks to Kees Cook. (LP: #498074)
1287 - launchpad.py: Do not mark non-Ubuntu bugs as needs-retrace, since there is
1288 no retracer right now. (LP: #489794)
1289 - packaging-apt-dpkg.py, install_retracing_packages(): Do not crash on
1290 malformed Dependencies.txt lines. (LP: #441709)
1291 - use-local: Fix for new source tree location of "apport" binary.
1292
12931.9.6 (2009-12-01)
1294------------------
1295Improvements:
1296 - Add pm-utils hook to record current operation, so that apportcheckresume can
1297 check it. Before this was kept in Ubuntu's pm-utils package.
1298 - general-hooks/generic.py: Check if using ecryptfs, and which directory.
1299 (LP: #444656)
1300
1301Bug fixes:
1302 - launchpad.py: Ensure that text attachments on initial bug filing are valid
1303 UTF-8. (LP: #453203)
1304 - man/apport-retrace.1: Document -R option.
1305
13061.9.5 (2009-11-20)
1307------------------
1308Bug fixes:
1309 - apport-retrace: Fix crash if InterpreterPath/ExecutablePath do not exist.
1310 - hookutils.py, attach_alsa(): Attach /proc/cpuinfo too, for CPU flags.
1311 - Fix crash if InterpreterPath does not exist any more at the time of
1312 reporting. (LP: #428289)
1313 - apport-gtk: Connect signals properly, to repair cancel/window close buttons.
1314 (LP: #427814)
1315 - Update German translations and fix "konnre" typo. (LP: #484119)
1316
13171.9.4 (2009-11-06)
1318------------------
1319Bug fixes:
1320 - Fix crash when ExecutablePath isn't part of a package. (LP: #424965)
1321 - hookutils.py, attach_hardware(): Anonymize disk labels. Thanks to Marco
1322 Rodrigues. (LP: #394411)
1323 - hookutils.py, attach_wifi(): Anonymize encryption key (which appeared in hex
1324 when being called as root). Thanks to Marco Rodrigues. (LP: #446299)
1325 - launchpad.py: If unset, set bug task source package also for interpreter
1326 crashes.
1327 - apport-gtk: Give details window a minimize/maximize button, which were
1328 missing in some window managers. Thanks to Marien Zwart. (LP: #447749)
1329 - apport-kde: Properly terminate program after closing the last dialog.
1330 (LP: #458662)
1331 - hookutils.py, attach_alsa(): Attach /proc/asound/version. (LP: #467233)
1332 - general-hooks/generic.py: Only collect ~/.xsession-errors bits when we have
1333 an ExecutablePath linked to libgtk.
1334
13351.9.3 (2009-10-14)
1336------------------
1337Changes:
1338 - Drop handling of the APPORT_REPORT_THIRDPARTY environment variable and
1339 "thirdparty" configuration file option. This has never been documented, and
1340 conceptually does not work. There is a proper mechanism for this in place
1341 now, e. g. launchpad.py's "project" option.
1342
1343Bug fixes:
1344 - hookutils.py: Fix error codes from "comm", thanks to Brian Murray.
1345 - general-hooks/generic.py: Catch xkbcomp error messages. (LP: #431807)
1346 - launchpad.py: Assert that we have exactly one of "distro" or "project"
1347 option.
1348 - doc/crashdb-conf.txt: Improve documentation of crash database options.
1349 - apport-gtk: Make Cancel/Send buttons focusable. Thanks to Marco Rodrigues.
1350 (LP: #447780)
1351
13521.9.2 (2009-10-02)
1353------------------
1354Improvements:
1355 - apport-cli: Print the URL and ask whether to open a browser. In many
1356 situations (such as usage on a server through ssh), it's preferable to not
1357 open the browser on the reporting computer. Thanks to Matt Zimmerman for the
1358 initial patch! (LP: #286415)
1359 - general-hooks/generic.py: Collect important glib errors/assertions (which
1360 should not have private data) from ~/.xsession-errors (LP: #431807)
1361 - launchpad.py: Link hardware data submission key if it exists. (LP: #424382)
1362
1363Bug fixes:
1364 - apport-cli: Fix crash with non-ASCII characters in prompts.
1365 - Fix "apport-bug symptomname" to actually work.
1366 - launchpad.py: Fix crash on invalid credentials file. Thanks to Marco
1367 Rodrigues for the initial patch! (LP: #414055)
1368
13691.9.1 (2009-09-22)
1370------------------
1371Bug fixes:
1372 - hookutils.py, attach_hardware(): Do not attach empty Pccardctl*.
1373 - apport/report.py, add_gdb_info(): Do not throw away stderr from gdb.
1374 - data/general-hooks/parse_segv.py:
1375 + Handle arithmetic wrapping correctly.
1376 + Handle empty base, scale, or index registers in disassembly.
1377 + Handle in/out ioport faults.
1378 - Various improvements to user-visible strings, thanks to Marco Rodrigues!
1379 (LP: #178507)
1380 - Various apport-retrace robustifications.
1381 - setup.py: Fix DistUtilsExtra version check. (LP: #428337)
1382 - hookutils.py, attach_gconf(): Do not overwrite previous values from other
1383 packages, thanks Loïc Minier!
1384 - hookutils.py, attach_gconf(): Fix crash with nonexisting <applyto> tags.
1385
13861.9 (2009-09-08)
1387----------------
1388New features:
1389 - Add "do what I mean" mode to command line argument parsing (applies to all
1390 interfaces: -cli, -gtk, -kde). When giving a single argument and no options,
1391 determine the most likely mode, like reporting a bug against a symptom,
1392 package, executable name, or PID.
1393 - Add program "apport-bug" which determines the most appropriate user
1394 interface (GTK, KDE, CLI) and files a bug through it, using the single
1395 argument "do what I mean" mode. This is an improved version of Ubuntu's
1396 "ubuntu-bug" script.
1397
1398Bug fixes:
1399 - Update apport-cli manpage to current set of options and behaviour. Also
1400 point out that apport-gtk and apport-kde share the same CLI.
1401 - setup.py now installs apport-{gtk,kde} into $prefix/share/apport/, they are
1402 not supposed to be called directly. This also reflects the path which the
1403 .desktop files expect.
1404 - setup.py now installs the internal helper scripts like "kernel_crashdump",
1405 "apport", or "apportcheckresume" into $prefix/share/apport instead of
1406 $prefix/bin.
1407 - Update usage of gettext to work around Python bug of gettext() not returning
1408 unicodes, but str. Fixes UnicodeDecodeErrors on translated --help output.
1409
14101.8.2 (2009-09-05)
1411------------------
1412Bug fixes.
1413
14141.8.1 (2009-09-03)
1415------------------
1416Lots of bug fixes.
1417
14181.8 (2009-08-26)
1419----------------
1420New features:
1421 - Do not generally ignore SIGABRT any more. Try to extract the assertion
1422 message from the core dump, and add it as "AssertionMessage" field. Mark
1423 reports as unreportable if they do not have an assertion message and crashed
1424 with SIGABRT. This requires your glibc to have this patch:
1425 http://sourceware.org/git/?p=glibc.git;a=commitdiff;h=48dcd0ba
1426 - report.py, add_hooks_info(): Add optional package/srcpackage argument. Hooks
1427 can use that to change the affected package or call hooks from different
1428 packages.
1429 - KDE frontend implementation of ui_question_userpass(), for crash databases
1430 which need to ask for credentials.
1431 - hookutils.py: New funtion attach_wifi() to add wireless network related
1432 information to reports.
1433
1434Important bug fixes:
1435 - Fix the test suite on current kernels; test/crash previously often failed
1436 with python segfaults, since it killed the test processes too early.
1437
14381.7 (2009-08-05):
1439-----------------
1440New features:
1441 - Support for "symptom" scripts, which figure out the package for a bug report
1442 based on interactive questions.
1443
14441.6 (2009-07-15)
1445----------------
1446New features:
1447 - Integrate analysis and retracing of kernel vmcore crashes with the "crash"
1448 tool. Courtesy of Michael Vogt.
1449
1450Various little bug fixes.
1451
14521.5 (2009-06-29)
1453----------------
1454New features:
1455 - Drop all Makefiles, po/POTFILES.in, and most code from setup.py, and use
1456 DistUtilsExtras.auto which "just does the right thing" for most build system
1457 tasks. This requires python-distutils-extra >= 2.2, see
1458 https://launchpad.net/python-distutils-extra
1459
1460Cleanup:
1461 - Move all test scripts into test/, to unclutter source tree.
1462 - setup.py now auto-detects the required packaging backend if
1463 apport/packaging_impl.py is not manually installed.
1464
14651.4 (2009-06-26)
1466----------------
1467New features:
1468 - Replaced Qt4 frontend with a KDE frontend for better KDE integration.
1469
1470Major bug fixes:
1471 - packaging-apt-dpkg.py: Add backwards compatibility code for python-apt <
1472 0.7.9 to not break backportability.
1473
14741.3 (2009-06-10)
1475----------------
1476New features:
1477- Interactive package hooks:
1478 * Add apport.ui.HookUI class which provides GUI functionality such as yes/no
1479 questions or file dialogs to hooks.
1480 * add_info() in package hooks now can (optionally) take a second argument which
1481 is the HookUI instance.
1482 * See doc/package-hooks.txt for details.
1483- New function apport.hookutils.root_command_output() to run a command as root,
1484 through gksu/kdesudo/sudo, depending on the desktop environment.
1485- Add general hook for analyzing reason of a segfault.
1486
1487Bug fixes:
1488- Drop "UnsupportableReason" field, it is too similar to UnreportableReason and
1489 just confusing.
1490- Report key names can now contain dashes ('-') and underscores ('_').
1491 (LP #380811)
1492
14931.2.1 (2009-05-15)
1494------------------
1495Bug fixes:
1496- Fix setup.py and PO file merging for recent .glade -> .ui renaming.
1497
1498Translations:
1499- Update German translations.
1500
15011.2.0 (2009-05-15)
1502------------------
1503Moving away from deprecated APIs:
1504- packaging-apt-dpkg.py: Use python-apt >= 0.7.9 official API and drop usage of
1505 internal symbols.
1506- hookutils.py: Drop hal related functions and queries, replace with udev
1507 database, udev log file, and DMI information from sysfs.
1508- gtk UI: Convert from libglade to gtk.Builder.
1509
1510Bug fixes:
1511- hookutils.py: Drop /proc/version_signature collection, it is Ubuntu specific.
1512- apportcheckresume: Fix log collection from pm-utils.
1513- Fix various crashes and report properties for reporting against uninstalled
1514 packages.
1515
15161.1.1 (2009-04-30)
1517------------------
1518Security fix:
1519- etc/cron.daily/apport: Only attempt to remove files and symlinks, do not
1520 descend into subdirectories of /var/crash/. Doing so might be exploited by a
1521 race condition between find traversing a huge directory tree, changing an
1522 existing subdir into a symlink to e. g. /etc/, and finally getting that piped
1523 to rm. This also changes the find command to not use GNU extensions. Thanks
1524 to Stephane Chazelas for discovering this! (LP #357024, CVE-2009-1295)
1525
1526Bug fixes:
1527- launchpad.py: Send and read Date: field again, reverting r1128; it is useful
1528 after all. (LP #349139)
1529- Only add ProcAttrCurrent to reports if it is not "unconfined", to remove some
1530 noise from reports.
1531- Detect invalid PIDs in the UI (such as for kernel processes) and give a
1532 friendly error message instead of silently doing nothing. (LP #360608)
1533- Always run common hooks, and run source package hooks if we do not have a
1534 binary package name. (LP #350131)
1535- launchpad.py: Consider socket errors when connecting as transient, so
1536 that crash-digger doesn't stop completely on them.
1537
15381.1 (2009-04-20)
1539----------------
1540New features:
1541- Add hookutils methods for attaching relevant packages, greatly improve
1542 attach_alsa() for sound problem debugging.
1543- Move launchpad crash database implementation from ever-breaking
1544 python-launchpad-bugs (screenscraping) to launchpadlib (official and stable
1545 Launchpad API).
1546
1547Bug fixes:
1548- Drop some remaining distro specific pieces of code.
1549- Add new field Report.pid which gets set on add_proc_info() and can be used by
1550 hooks.
1551- setup.py: Properly clean up all generated files, install missing
1552 mimetypes/text-x-apport.svg icon symlink.
1553- Add README file.
1554- Add translations from Launchpad.
1555- Remove preloadlib/*; it's undermaintained, and not really useful any more
1556 these days.
1557- Various bug fixes; most visible being the misnamed etc/default/apport.default
1558 file (which should just be etc/default/apport).
1559
15601.0 (2009-04-06)
1561----------------
1562First upstream release, based on Ubuntu packaging branch; that had been the
1563de-facto trunk for many years, but this becomes unpractical with several
1564distributions using it now.
01565
=== renamed file 'NEWS' => 'NEWS.moved'
=== added file 'README'
--- README 1970-01-01 00:00:00 +0000
+++ README 2012-07-10 22:38:19 +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 2012-07-10 22:38:19 +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 2012-07-10 22:38:19 +0000
@@ -0,0 +1,66 @@
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 verbose=None):
20 '''Initialize Thread, identical to threading.Thread.__init__().'''
21
22 threading.Thread.__init__(self, group, target, name, args, kwargs, verbose)
23 self.__target = target
24 self.__args = args
25 self.__kwargs = kwargs
26 self._retval = None
27 self._exception = None
28
29 def run(self):
30 '''Run target function, identical to threading.Thread.run().'''
31
32 if self.__target:
33 try:
34 self._retval = self.__target(*self.__args, **self.__kwargs)
35 except:
36 if sys:
37 self._exception = sys.exc_info()
38
39 def return_value(self):
40 '''Return value from target function.
41
42 This can only be called after the thread has finished, i. e. when
43 isAlive() is False and did not terminate with an exception.
44 '''
45 assert not self.isAlive()
46 assert not self._exception
47 return self._retval
48
49 def exc_info(self):
50 '''Return (type, value, traceback) of the exception caught in run().'''
51
52 return self._exception
53
54 def exc_raise(self):
55 '''Raise the exception caught in the thread.
56
57 Do nothing if no exception was caught.
58 '''
59 if self._exception:
60 # there is no syntax which both Python 2 and 3 parse, so we need a
61 # hack using exec() here
62 # Python 3:
63 if sys.version > '3':
64 raise self._exception[0](self._exception[1]).with_traceback(self._exception[2])
65 else:
66 exec('raise self._exception[0], self._exception[1], self._exception[2]')
067
=== added file 'apport/__init__.py'
--- apport/__init__.py 1970-01-01 00:00:00 +0000
+++ apport/__init__.py 2012-07-10 22:38:19 +0000
@@ -0,0 +1,42 @@
1from apport.report import Report
2
3from apport.packaging_impl import impl as packaging
4
5Report # pyflakes
6packaging # pyflakes
7
8import sys
9
10# fix gettext to output proper unicode strings
11import gettext
12
13
14def unicode_gettext(str):
15 trans = gettext.gettext(str)
16 if type(trans) == type(b''):
17 return trans.decode('UTF-8')
18 else:
19 return trans
20
21
22def fatal(msg, *args):
23 '''Print out an error message and exit the program.'''
24
25 error(msg, *args)
26 sys.exit(1)
27
28
29def error(msg, *args):
30 '''Print out an error message.'''
31
32 sys.stderr.write('ERROR: ')
33 sys.stderr.write(msg % args)
34 sys.stderr.write('\n')
35
36
37def warning(msg, *args):
38 '''Print out an warning message.'''
39
40 sys.stderr.write('WARNING: ')
41 sys.stderr.write(msg % args)
42 sys.stderr.write('\n')
043
=== added file 'apport/crashdb.py'
--- apport/crashdb.py 1970-01-01 00:00:00 +0000
+++ apport/crashdb.py 2012-07-10 22:38:19 +0000
@@ -0,0 +1,850 @@
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:
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 db = settings['databases'][name]
843
844 m = __import__('apport.crashdb_impl.' + db['impl'], globals(), locals(), ['CrashDatabase'])
845 return m.CrashDatabase(auth_file, db)
846
847
848class NeedsCredentials(Exception):
849 '''This may be raised when unable to log in to the crashdb.'''
850 pass
0851
=== added directory 'apport/crashdb_impl'
=== added file 'apport/crashdb_impl/__init__.py'
=== added file 'apport/crashdb_impl/launchpad.py'
--- apport/crashdb_impl/launchpad.py 1970-01-01 00:00:00 +0000
+++ apport/crashdb_impl/launchpad.py 2012-07-10 22:38:19 +0000
@@ -0,0 +1,1931 @@
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
22else:
23 from urllib.request import HTTPSHandler, Request, build_opener, urlopen
24 from urllib.parse import urlencode
25 from http.client import HTTPSConnection
26
27try:
28 from launchpadlib.errors import HTTPError
29 from launchpadlib.launchpad import Launchpad
30 Launchpad # pyflakes
31except ImportError:
32 # if launchpadlib is not available, only client-side reporting will work
33 Launchpad = None
34
35import apport.crashdb
36import apport
37
38default_credentials_path = os.path.expanduser('~/.cache/apport/launchpad.credentials')
39
40
41def filter_filename(attachments):
42 for attachment in attachments:
43 try:
44 f = attachment.data.open()
45 except HTTPError:
46 apport.error('Broken attachment on bug, ignoring')
47 continue
48 name = f.filename
49 if name.endswith('.txt') or name.endswith('.gz'):
50 yield f
51
52
53def id_set(tasks):
54 # same as set(int(i.bug.id) for i in tasks) but faster
55 return set(int(i.self_link.split('/').pop()) for i in tasks)
56
57
58class CrashDatabase(apport.crashdb.CrashDatabase):
59 '''Launchpad implementation of crash database interface.'''
60
61 def __init__(self, auth, options):
62 '''Initialize Launchpad crash database.
63
64 You need to specify a launchpadlib-style credentials file to
65 access launchpad. If you supply None, it will use
66 default_credentials_path (~/.cache/apport/launchpad.credentials).
67
68 Recognized options are:
69 - distro: Name of the distribution in Launchpad
70 - project: Name of the project in Launchpad
71 (Note that exactly one of "distro" or "project" must be given.)
72 - launchpad_instance: If set, this uses the given launchpad instance
73 instead of production (optional). This can be overriden or set by
74 $APPORT_LAUNCHPAD_INSTANCE environment.
75 - cache_dir: Path to a permanent cache directory; by default it uses a
76 temporary one. (optional). This can be overridden or set by
77 $APPORT_LAUNCHPAD_CACHE environment.
78 - escalation_subscription: This subscribes the given person or team to
79 a bug once it gets the 10th duplicate.
80 - escalation_tag: This adds the given tag to a bug once it gets more
81 than 10 duplicates.
82 - initial_subscriber: The Launchpad user which gets subscribed to newly
83 filed bugs (default: "apport"). It should be a bot user which the
84 crash-digger instance runs as, as this will get to see all bug
85 details immediately.
86 - triaging_team: The Launchpad user/team which gets subscribed after
87 updating a crash report bug by the retracer (default:
88 "ubuntu-crashes-universe")
89 '''
90 if os.getenv('APPORT_LAUNCHPAD_INSTANCE'):
91 options['launchpad_instance'] = os.getenv(
92 'APPORT_LAUNCHPAD_INSTANCE')
93 if not auth:
94 lp_instance = options.get('launchpad_instance')
95 if lp_instance:
96 auth = default_credentials_path + '.' + lp_instance.split('://', 1)[-1]
97 else:
98 auth = default_credentials_path
99 apport.crashdb.CrashDatabase.__init__(self, auth, options)
100
101 self.distro = options.get('distro')
102 if self.distro:
103 assert 'project' not in options, 'Must not set both "project" and "distro" option'
104 else:
105 assert 'project' in options, 'Need to have either "project" or "distro" option'
106
107 self.arch_tag = 'need-%s-retrace' % apport.packaging.get_system_architecture()
108 self.options = options
109 self.auth = auth
110 assert self.auth
111
112 self.__launchpad = None
113 self.__lp_distro = None
114 self.__lpcache = os.getenv('APPORT_LAUNCHPAD_CACHE', options.get('cache_dir'))
115
116 @property
117 def launchpad(self):
118 '''Return Launchpad instance.'''
119
120 if self.__launchpad:
121 return self.__launchpad
122
123 if Launchpad is None:
124 sys.stderr.write('ERROR: The launchpadlib Python module is not installed. This functionality is not available.\n')
125 sys.exit(1)
126
127 if self.options.get('launchpad_instance'):
128 launchpad_instance = self.options.get('launchpad_instance')
129 else:
130 launchpad_instance = 'production'
131
132 auth_dir = os.path.dirname(self.auth)
133 if auth_dir and not os.path.isdir(auth_dir):
134 os.makedirs(auth_dir)
135
136 try:
137 self.__launchpad = Launchpad.login_with('apport-collect',
138 launchpad_instance,
139 launchpadlib_dir=self.__lpcache,
140 allow_access_levels=['WRITE_PRIVATE'],
141 credentials_file=self.auth,
142 version='1.0')
143 except Exception as e:
144 if hasattr(e, 'content'):
145 msg = e.content
146 else:
147 msg = str(e)
148 apport.error('connecting to Launchpad failed: %s\nYou can reset the credentials by removing the file "%s"', msg, self.auth)
149 sys.exit(99) # transient error
150
151 return self.__launchpad
152
153 def _get_distro_tasks(self, tasks):
154 if not self.distro:
155 raise StopIteration
156
157 for t in tasks:
158 if t.bug_target_name.lower() == self.distro or \
159 re.match('^.+\(%s.*\)$' % self.distro, t.bug_target_name.lower()):
160 yield t
161
162 @property
163 def lp_distro(self):
164 if self.__lp_distro is None:
165 if self.distro:
166 self.__lp_distro = self.launchpad.distributions[self.distro]
167 elif 'project' in self.options:
168 self.__lp_distro = self.launchpad.projects[self.options['project']]
169 else:
170 raise SystemError('distro or project needs to be specified in crashdb options')
171
172 return self.__lp_distro
173
174 def upload(self, report, progress_callback=None):
175 '''Upload given problem report return a handle for it.
176
177 This should happen noninteractively.
178
179 If the implementation supports it, and a function progress_callback is
180 passed, that is called repeatedly with two arguments: the number of
181 bytes already sent, and the total number of bytes to send. This can be
182 used to provide a proper upload progress indication on frontends.
183 '''
184 assert self.accepts(report)
185
186 blob_file = self._generate_upload_blob(report)
187 ticket = upload_blob(blob_file, progress_callback, hostname=self.get_hostname())
188 blob_file.close()
189 assert ticket
190 return ticket
191
192 def get_hostname(self):
193 '''Return the hostname for the Launchpad instance.'''
194
195 launchpad_instance = self.options.get('launchpad_instance')
196 if launchpad_instance:
197 if launchpad_instance == 'staging':
198 hostname = 'staging.launchpad.net'
199 else:
200 hostname = 'launchpad.dev'
201 else:
202 hostname = 'launchpad.net'
203 return hostname
204
205 def get_comment_url(self, report, handle):
206 '''Return an URL that should be opened after report has been uploaded
207 and upload() returned handle.
208
209 Should return None if no URL should be opened (anonymous filing without
210 user comments); in that case this function should do whichever
211 interactive steps it wants to perform.'''
212
213 args = {}
214 title = report.get('Title', report.standard_title())
215 if title:
216 # always use UTF-8 encoding, urlencode() blows up otherwise in
217 # python 2.7
218 if type(title) != type(b''):
219 title = title.encode('UTF-8')
220 args['field.title'] = title
221
222 hostname = self.get_hostname()
223
224 project = self.options.get('project')
225
226 if not project:
227 if 'SourcePackage' in report:
228 return 'https://bugs.%s/%s/+source/%s/+filebug/%s?%s' % (
229 hostname, self.distro, report['SourcePackage'], handle, urlencode(args))
230 else:
231 return 'https://bugs.%s/%s/+filebug/%s?%s' % (
232 hostname, self.distro, handle, urlencode(args))
233 else:
234 return 'https://bugs.%s/%s/+filebug/%s?%s' % (
235 hostname, project, handle, urlencode(args))
236
237 def get_id_url(self, report, id):
238 '''Return URL for a given report ID.
239
240 The report is passed in case building the URL needs additional
241 information from it, such as the SourcePackage name.
242
243 Return None if URL is not available or cannot be determined.
244 '''
245 return 'https://bugs.launchpad.net/bugs/' + str(id)
246
247 def download(self, id):
248 '''Download the problem report from given ID and return a Report.'''
249
250 report = apport.Report()
251 b = self.launchpad.bugs[id]
252
253 # parse out fields from summary
254 m = re.search(r'(ProblemType:.*)$', b.description, re.S)
255 if not m:
256 m = re.search(r'^--- \r?$[\r\n]*(.*)', b.description, re.M | re.S)
257 assert m, 'bug description must contain standard apport format data'
258
259 description = m.group(1).encode('UTF-8').replace('\xc2\xa0', ' ').replace('\r\n', '\n')
260
261 if '\n\n' in description:
262 # this often happens, remove all empty lines between top and
263 # 'Uname'
264 if 'Uname:' in description:
265 # this will take care of bugs like LP #315728 where stuff
266 # is added after the apport data
267 (part1, part2) = description.split('Uname:', 1)
268 description = part1.replace('\n\n', '\n') + 'Uname:' \
269 + part2.split('\n\n', 1)[0]
270 else:
271 # just parse out the Apport block; e. g. LP #269539
272 description = description.split('\n\n', 1)[0]
273
274 report.load(BytesIO(description))
275
276 if 'Date' not in report:
277 # We had not submitted this field for a while, claiming it
278 # redundant. But it is indeed required for up-to-the-minute
279 # comparison with log files, etc. For backwards compatibility with
280 # those reported bugs, read the creation date
281 try:
282 report['Date'] = b.date_created.ctime()
283 except AttributeError:
284 # support older wadllib API which returned strings
285 report['Date'] = b.date_created
286 if 'ProblemType' not in report:
287 if 'apport-bug' in b.tags:
288 report['ProblemType'] = 'Bug'
289 elif 'apport-crash' in b.tags:
290 report['ProblemType'] = 'Crash'
291 elif 'apport-kernelcrash' in b.tags:
292 report['ProblemType'] = 'KernelCrash'
293 elif 'apport-package' in b.tags:
294 report['ProblemType'] = 'Package'
295 else:
296 raise ValueError('cannot determine ProblemType from tags: ' + str(b.tags))
297
298 report['Tags'] = ' '.join(b.tags)
299
300 if 'Title' in report:
301 report['OriginalTitle'] = report['Title']
302
303 report['Title'] = b.title
304
305 for attachment in filter_filename(b.attachments):
306 key, ext = os.path.splitext(attachment.filename)
307 # ignore attachments with invalid keys
308 try:
309 report[key] = ''
310 except:
311 continue
312 if ext == '.txt':
313 report[key] = attachment.read()
314 elif ext == '.gz':
315 try:
316 report[key] = gzip.GzipFile(fileobj=attachment).read()
317 except IOError as e:
318 # some attachments are only called .gz, but are
319 # uncompressed (LP #574360)
320 if 'Not a gzip' not in str(e):
321 raise
322 attachment.seek(0)
323 report[key] = attachment.read()
324 else:
325 raise Exception('Unknown attachment type: ' + attachment.filename)
326 return report
327
328 def update(self, id, report, comment, change_description=False,
329 attachment_comment=None, key_filter=None):
330 '''Update the given report ID with all data from report.
331
332 This creates a text comment with the "short" data (see
333 ProblemReport.write_mime()), and creates attachments for all the
334 bulk/binary data.
335
336 If change_description is True, and the crash db implementation supports
337 it, the short data will be put into the description instead (like in a
338 new bug).
339
340 comment will be added to the "short" data. If attachment_comment is
341 given, it will be added to the attachment uploads.
342
343 If key_filter is a list or set, then only those keys will be added.
344 '''
345 bug = self.launchpad.bugs[id]
346
347 if key_filter:
348 skip_keys = set(report.keys()) - set(key_filter)
349 else:
350 skip_keys = None
351
352 # we want to reuse the knowledge of write_mime() with all its different input
353 # types and output formatting; however, we have to dissect the mime ourselves,
354 # since we can't just upload it as a blob
355 mime = tempfile.TemporaryFile()
356 report.write_mime(mime, skip_keys=skip_keys)
357 mime.flush()
358 mime.seek(0)
359 msg = email.message_from_file(mime)
360 msg_iter = msg.walk()
361
362 # first part is the multipart container
363 part = msg_iter.next()
364 assert part.is_multipart()
365
366 # second part should be an inline text/plain attachments with all short
367 # fields
368 part = msg_iter.next()
369 assert not part.is_multipart()
370 assert part.get_content_type() == 'text/plain'
371
372 if not key_filter:
373 # when we update a complete report, we are updating an existing bug
374 # with apport-collect
375 x = bug.tags[:] # LP#254901 workaround
376 x.append('apport-collected')
377 # add any tags (like the release) to the bug
378 if 'Tags' in report:
379 x += report['Tags'].lower().split()
380 bug.tags = x
381 bug.lp_save()
382 bug = self.launchpad.bugs[id] # fresh bug object, LP#336866 workaround
383
384 # short text data
385 if change_description:
386 bug.description = bug.description + '\n--- \n' + part.get_payload(decode=True).decode('UTF-8', 'replace')
387 bug.lp_save()
388 else:
389 bug.newMessage(content=part.get_payload(decode=True), subject=comment)
390
391 # other parts are the attachments:
392 for part in msg_iter:
393 # print ' attachment: %s...' % part.get_filename()
394 bug.addAttachment(comment=attachment_comment or '',
395 description=part.get_filename(),
396 content_type=None,
397 data=part.get_payload(decode=True),
398 filename=part.get_filename(), is_patch=False)
399
400 def update_traces(self, id, report, comment=''):
401 '''Update the given report ID for retracing results.
402
403 This updates Stacktrace, ThreadStacktrace, StacktraceTop,
404 and StacktraceSource. You can also supply an additional comment.
405 '''
406 apport.crashdb.CrashDatabase.update_traces(self, id, report, comment)
407
408 bug = self.launchpad.bugs[id]
409 # ensure it's assigned to a package
410 if 'SourcePackage' in report:
411 for task in bug.bug_tasks:
412 if task.target.resource_type_link.endswith('#distribution'):
413 task.target = self.lp_distro.getSourcePackage(name=report['SourcePackage'])
414 task.lp_save()
415 bug = self.launchpad.bugs[id]
416 break
417
418 # remove core dump if stack trace is usable
419 if report.has_useful_stacktrace():
420 for a in bug.attachments:
421 if a.title == 'CoreDump.gz':
422 try:
423 a.removeFromBug()
424 except HTTPError:
425 pass # LP#249950 workaround
426 try:
427 task = self._get_distro_tasks(bug.bug_tasks).next()
428 if task.importance == 'Undecided':
429 task.importance = 'Medium'
430 task.lp_save()
431 except StopIteration:
432 pass # no distro tasks
433
434 # update bug title with retraced function name
435 fn = report.stacktrace_top_function()
436 if fn:
437 m = re.match('^(.*crashed with SIG.* in )([^( ]+)(\(\).*$)', bug.title)
438 if m and m.group(2) != fn:
439 bug.title = m.group(1) + fn + m.group(3)
440 try:
441 bug.lp_save()
442 except HTTPError:
443 pass # LP#336866 workaround
444 bug = self.launchpad.bugs[id]
445
446 self._subscribe_triaging_team(bug, report)
447
448 def get_distro_release(self, id):
449 '''Get 'DistroRelease: <release>' from the given report ID and return
450 it.'''
451 bug = self.launchpad.bugs[id]
452 m = re.search('DistroRelease: ([-a-zA-Z0-9.+/ ]+)', bug.description)
453 if m:
454 return m.group(1)
455 raise ValueError('URL does not contain DistroRelease: field')
456
457 def get_affected_packages(self, id):
458 '''Return list of affected source packages for given ID.'''
459
460 bug_target_re = re.compile(
461 r'/%s/(?:(?P<suite>[^/]+)/)?\+source/(?P<source>[^/]+)$' % self.distro)
462
463 bug = self.launchpad.bugs[id]
464 result = []
465
466 for task in bug.bug_tasks:
467 match = bug_target_re.search(task.target.self_link)
468 if not match:
469 continue
470 if task.status in ('Invalid', "Won't Fix", 'Fix Released'):
471 continue
472 result.append(match.group('source'))
473 return result
474
475 def is_reporter(self, id):
476 '''Check whether the user is the reporter of given ID.'''
477
478 bug = self.launchpad.bugs[id]
479 return bug.owner.name == self.launchpad.me.name
480
481 def can_update(self, id):
482 '''Check whether the user is eligible to update a report.
483
484 A user should add additional information to an existing ID if (s)he is
485 the reporter or subscribed, the bug is open, not a duplicate, etc. The
486 exact policy and checks should be done according to the particular
487 implementation.
488 '''
489 bug = self.launchpad.bugs[id]
490 if bug.duplicate_of:
491 return False
492
493 if bug.owner.name == self.launchpad.me.name:
494 return True
495
496 # check subscription
497 me = self.launchpad.me.self_link
498 for sub in bug.subscriptions.entries:
499 if sub['person_link'] == me:
500 return True
501
502 return False
503
504 def get_unretraced(self):
505 '''Return an ID set of all crashes which have not been retraced yet and
506 which happened on the current host architecture.'''
507 try:
508 bugs = self.lp_distro.searchTasks(tags=self.arch_tag, created_since='2011-08-01')
509 return id_set(bugs)
510 except Exception as e:
511 apport.error('connecting to Launchpad failed: %s', str(e))
512 sys.exit(99) # transient error
513
514 def get_dup_unchecked(self):
515 '''Return an ID set of all crashes which have not been checked for
516 being a duplicate.
517
518 This is mainly useful for crashes of scripting languages such as
519 Python, since they do not need to be retraced. It should not return
520 bugs that are covered by get_unretraced().'''
521
522 try:
523 bugs = self.lp_distro.searchTasks(tags='need-duplicate-check', created_since='2011-08-01')
524 return id_set(bugs)
525 except Exception as e:
526 apport.error('connecting to Launchpad failed: %s', str(e))
527 sys.exit(99) # transient error
528
529 def get_unfixed(self):
530 '''Return an ID set of all crashes which are not yet fixed.
531
532 The list must not contain bugs which were rejected or duplicate.
533
534 This function should make sure that the returned list is correct. If
535 there are any errors with connecting to the crash database, it should
536 raise an exception (preferably IOError).'''
537
538 bugs = self.lp_distro.searchTasks(tags='apport-crash')
539 return id_set(bugs)
540
541 def _get_source_version(self, package):
542 '''Return the version of given source package in the latest release of
543 given distribution.
544
545 If 'distro' is None, we will look for a launchpad project .
546 '''
547 sources = self.lp_distro.main_archive.getPublishedSources(
548 exact_match=True,
549 source_name=package,
550 distro_series=self.lp_distro.current_series
551 )
552 # first element is the latest one
553 return sources[0].source_package_version
554
555 def get_fixed_version(self, id):
556 '''Return the package version that fixes a given crash.
557
558 Return None if the crash is not yet fixed, or an empty string if the
559 crash is fixed, but it cannot be determined by which version. Return
560 'invalid' if the crash report got invalidated, such as closed a
561 duplicate or rejected.
562
563 This function should make sure that the returned result is correct. If
564 there are any errors with connecting to the crash database, it should
565 raise an exception (preferably IOError).
566 '''
567 # do not do version tracking yet; for that, we need to get the current
568 # distrorelease and the current package version in that distrorelease
569 # (or, of course, proper version tracking in Launchpad itself)
570
571 try:
572 b = self.launchpad.bugs[id]
573 except KeyError:
574 return 'invalid'
575
576 if b.duplicate_of:
577 return 'invalid'
578
579 tasks = list(b.bug_tasks) # just fetch it once
580
581 if self.distro:
582 distro_identifier = '(%s)' % self.distro.lower()
583 fixed_tasks = filter(lambda task: task.status == 'Fix Released' and
584 distro_identifier in task.bug_target_display_name.lower(), tasks)
585
586 if not fixed_tasks:
587 fixed_distro = filter(lambda task: task.status == 'Fix Released' and
588 task.bug_target_name.lower() == self.distro.lower(), tasks)
589 if fixed_distro:
590 # fixed in distro inself (without source package)
591 return ''
592
593 if len(fixed_tasks) > 1:
594 apport.warning('There is more than one task fixed in %s %s, using first one to determine fixed version', self.distro, id)
595 return ''
596
597 if fixed_tasks:
598 task = fixed_tasks.pop()
599 try:
600 return self._get_source_version(task.bug_target_display_name.split()[0])
601 except IndexError:
602 # source does not exist any more
603 return 'invalid'
604 else:
605 # check if there only invalid ones
606 invalid_tasks = filter(lambda task: task.status in ('Invalid', "Won't Fix", 'Expired') and
607 distro_identifier in task.bug_target_display_name.lower(), tasks)
608 if invalid_tasks:
609 non_invalid_tasks = filter(
610 lambda task: task.status not in ('Invalid', "Won't Fix", 'Expired') and
611 distro_identifier in task.bug_target_display_name.lower(), tasks)
612 if not non_invalid_tasks:
613 return 'invalid'
614 else:
615 fixed_tasks = filter(lambda task: task.status == 'Fix Released', tasks)
616 if fixed_tasks:
617 # TODO: look for current series
618 return ''
619 # check if there any invalid ones
620 if filter(lambda task: task.status == 'Invalid', tasks):
621 return 'invalid'
622
623 return None
624
625 def duplicate_of(self, id):
626 '''Return master ID for a duplicate bug.
627
628 If the bug is not a duplicate, return None.
629 '''
630 b = self.launchpad.bugs[id].duplicate_of
631 if b:
632 return b.id
633 else:
634 return None
635
636 def close_duplicate(self, report, id, master_id):
637 '''Mark a crash id as duplicate of given master ID.
638
639 If master is None, id gets un-duplicated.
640 '''
641 bug = self.launchpad.bugs[id]
642
643 if master_id:
644 assert id != master_id, 'cannot mark bug %s as a duplicate of itself' % str(id)
645
646 # check whether the master itself is a dup
647 master = self.launchpad.bugs[master_id]
648 if master.duplicate_of:
649 master = master.duplicate_of
650 master_id = master.id
651 if master.id == id:
652 # this happens if the bug was manually duped to a newer one
653 apport.warning('Bug %i was manually marked as a dupe of newer bug %i, not closing as duplicate',
654 id, master_id)
655 return
656
657 for a in bug.attachments:
658 if a.title in ('CoreDump.gz', 'Stacktrace.txt',
659 'ThreadStacktrace.txt', 'ProcMaps.txt',
660 'ProcStatus.txt', 'Registers.txt',
661 'Disassembly.txt'):
662 try:
663 a.removeFromBug()
664 except HTTPError:
665 pass # LP#249950 workaround
666
667 bug = self.launchpad.bugs[id] # fresh bug object, LP#336866 workaround
668 bug.newMessage(content='Thank you for taking the time to report this crash and helping \
669to make this software better. This particular crash has already been reported and \
670is a duplicate of bug #%i, so is being marked as such. Please look at the \
671other bug report to see if there is any missing information that you can \
672provide, or to see if there is a workaround for the bug. Additionally, any \
673further discussion regarding the bug should occur in the other report. \
674Please continue to report any other bugs you may find.' % master_id,
675 subject='This bug is a duplicate')
676
677 bug = self.launchpad.bugs[id] # refresh, LP#336866 workaround
678 if bug.private:
679 bug.private = False
680
681 # set duplicate last, since we cannot modify already dup'ed bugs
682 if not bug.duplicate_of:
683 bug.duplicate_of = master
684
685 # cache tags of master bug report instead of performing multiple
686 # queries
687 master_tags = master.tags
688
689 if len(master.duplicates) == 10:
690 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:
691 master.tags = master_tags + [self.options['escalation_tag']] # LP#254901 workaround
692 master.lp_save()
693
694 if 'escalation_subscription' in self.options and self.options.get('escalated_tag', ' invalid ') not in master_tags:
695 p = self.launchpad.people[self.options['escalation_subscription']]
696 master.subscribe(person=p)
697
698 # requesting updated stack trace?
699 if report.has_useful_stacktrace() and ('apport-request-retrace' in master_tags
700 or 'apport-failed-retrace' in master_tags):
701 self.update(master_id, report, 'Updated stack trace from duplicate bug %i' % id,
702 key_filter=['Stacktrace', 'ThreadStacktrace',
703 'Package', 'Dependencies', 'ProcMaps', 'ProcCmdline'])
704
705 master = self.launchpad.bugs[master_id]
706 x = master.tags[:] # LP#254901 workaround
707 try:
708 x.remove('apport-failed-retrace')
709 except ValueError:
710 pass
711 try:
712 x.remove('apport-request-retrace')
713 except ValueError:
714 pass
715 master.tags = x
716 try:
717 master.lp_save()
718 except HTTPError:
719 pass # LP#336866 workaround
720
721 # white list of tags to copy from duplicates bugs to the master
722 tags_to_copy = ['bugpattern-needed', 'running-unity']
723 for series in self.lp_distro.series:
724 if series.status not in ['Active Development',
725 'Current Stable Release', 'Supported']:
726 continue
727 tags_to_copy.append(series.name)
728 # copy tags over from the duplicate bug to the master bug
729 dupe_tags = set(bug.tags)
730 # reload master tags as they may have changed
731 master_tags = master.tags
732 missing_tags = dupe_tags.difference(master_tags)
733
734 for tag in missing_tags:
735 if tag in tags_to_copy:
736 master_tags.append(tag)
737
738 master.tags = master_tags
739 master.lp_save()
740
741 else:
742 if bug.duplicate_of:
743 bug.duplicate_of = None
744
745 if bug._dirty_attributes: # LP#336866 workaround
746 bug.lp_save()
747
748 def mark_regression(self, id, master):
749 '''Mark a crash id as reintroducing an earlier crash which is
750 already marked as fixed (having ID 'master').'''
751
752 bug = self.launchpad.bugs[id]
753 bug.newMessage(content='This crash has the same stack trace characteristics as bug #%i. \
754However, the latter was already fixed in an earlier package version than the \
755one in this report. This might be a regression or because the problem is \
756in a dependent package.' % master,
757 subject='Possible regression detected')
758 bug = self.launchpad.bugs[id] # fresh bug object, LP#336866 workaround
759 bug.tags = bug.tags + ['regression-retracer'] # LP#254901 workaround
760 bug.lp_save()
761
762 def mark_retraced(self, id):
763 '''Mark crash id as retraced.'''
764
765 bug = self.launchpad.bugs[id]
766 if self.arch_tag in bug.tags:
767 x = bug.tags[:] # LP#254901 workaround
768 x.remove(self.arch_tag)
769 bug.tags = x
770 try:
771 bug.lp_save()
772 except HTTPError:
773 pass # LP#336866 workaround
774
775 def mark_retrace_failed(self, id, invalid_msg=None):
776 '''Mark crash id as 'failed to retrace'.'''
777
778 bug = self.launchpad.bugs[id]
779 if invalid_msg:
780 try:
781 task = self._get_distro_tasks(bug.bug_tasks).next()
782 except StopIteration:
783 # no distro task, just use the first one
784 task = bug.bug_tasks[0]
785 task.status = 'Invalid'
786 task.lp_save()
787 bug.newMessage(content=invalid_msg,
788 subject='Crash report cannot be processed')
789
790 for a in bug.attachments:
791 if a.title == 'CoreDump.gz':
792 try:
793 a.removeFromBug()
794 except HTTPError:
795 pass # LP#249950 workaround
796 else:
797 if 'apport-failed-retrace' not in bug.tags:
798 bug.tags = bug.tags + ['apport-failed-retrace'] # LP#254901 workaround
799 bug.lp_save()
800
801 def _mark_dup_checked(self, id, report):
802 '''Mark crash id as checked for being a duplicate.'''
803
804 bug = self.launchpad.bugs[id]
805
806 # if we have a distro task without a package, fix it
807 if 'SourcePackage' in report:
808 for task in bug.bug_tasks:
809 if task.target.resource_type_link.endswith('#distribution'):
810 task.target = self.lp_distro.getSourcePackage(
811 name=report['SourcePackage'])
812 task.lp_save()
813 bug = self.launchpad.bugs[id]
814 break
815
816 if 'need-duplicate-check' in bug.tags:
817 x = bug.tags[:] # LP#254901 workaround
818 x.remove('need-duplicate-check')
819 bug.tags = x
820 bug.lp_save()
821 if 'Traceback' in report:
822 for task in bug.bug_tasks:
823 if task.target.resource_type_link.endswith('#distribution'):
824 if task.importance == 'Undecided':
825 task.importance = 'Medium'
826 task.lp_save()
827 self._subscribe_triaging_team(bug, report)
828
829 def known(self, report):
830 '''Check if the crash db already knows about the crash signature.
831
832 Check if the report has a DuplicateSignature, crash_signature(), or
833 StacktraceAddressSignature, and ask the database whether the problem is
834 already known. If so, return an URL where the user can check the status
835 or subscribe (if available), or just return True if the report is known
836 but there is no public URL. In that case the report will not be
837 uploaded (i. e. upload() will not be called).
838
839 Return None if the report does not have any signature or the crash
840 database does not support checking for duplicates on the client side.
841
842 The default implementation uses a text file format generated by
843 duplicate_db_publish() at an URL specified by the "dupdb_url" option.
844 Subclasses are free to override this with a custom implementation, such
845 as a real database lookup.
846 '''
847 # we override the method here to check if the user actually has access
848 # to the bug, and if the bug requests more retraces; in either case we
849 # should file it.
850 url = apport.crashdb.CrashDatabase.known(self, report)
851
852 if not url:
853 return url
854
855 # record the fact that it is a duplicate, for triagers
856 report['DuplicateOf'] = url
857
858 try:
859 f = urlopen(url + '/+text')
860 except IOError:
861 # if we are offline, or LP is down, upload will fail anyway, so we
862 # can just as well avoid the upload
863 return url
864
865 line = f.readline()
866 if not line.startswith(b'bug:'):
867 # presumably a 404 etc. page, which happens for private bugs
868 return True
869
870 # check tags
871 for line in f:
872 if line.startswith(b'tags:'):
873 if b'apport-failed-retrace' in line or b'apport-request-retrace' in line:
874 return None
875 else:
876 break
877
878 # stop at the first task, tags are in the first block
879 if not line.strip():
880 break
881
882 return url
883
884 def _subscribe_triaging_team(self, bug, report):
885 '''Subscribe the right triaging team to the bug.'''
886
887 #FIXME: this entire function is an ugly Ubuntu specific hack until LP
888 #gets a real crash db; see https://wiki.ubuntu.com/CrashReporting
889
890 if 'DistroRelease' in report and report['DistroRelease'].split()[0] != 'Ubuntu':
891 return # only Ubuntu bugs are filed private
892
893 #use a url hack here, it is faster
894 person = '%s~%s' % (self.launchpad._root_uri,
895 self.options.get('triaging_team', 'ubuntu-crashes-universe'))
896 bug.subscribe(person=person)
897
898 def _generate_upload_blob(self, report):
899 '''Generate a multipart/MIME temporary file for uploading.
900
901 You have to close the returned file object after you are done with it.
902 '''
903 # set reprocessing tags
904 hdr = {}
905 hdr['Tags'] = 'apport-%s' % report['ProblemType'].lower()
906 a = report.get('PackageArchitecture')
907 if not a or a == 'all':
908 a = report.get('Architecture')
909 if a:
910 hdr['Tags'] += ' ' + a
911 if 'Tags' in report:
912 hdr['Tags'] += ' ' + report['Tags'].lower()
913
914 # privacy/retracing for distro reports
915 # FIXME: ugly hack until LP has a real crash db
916 if 'DistroRelease' in report:
917 if a and ('VmCore' in report or 'CoreDump' in report):
918 hdr['Private'] = 'yes'
919 hdr['Subscribers'] = self.options.get('initial_subscriber', 'apport')
920 hdr['Tags'] += ' need-%s-retrace' % a
921 elif 'Traceback' in report:
922 hdr['Private'] = 'yes'
923 hdr['Subscribers'] = 'apport'
924 hdr['Tags'] += ' need-duplicate-check'
925 if 'DuplicateSignature' in report and 'need-duplicate-check' not in hdr['Tags']:
926 hdr['Tags'] += ' need-duplicate-check'
927
928 # if we have checkbox submission key, link it to the bug; keep text
929 # reference until the link is shown in Launchpad's UI
930 if 'CheckboxSubmission' in report:
931 hdr['HWDB-Submission'] = report['CheckboxSubmission']
932
933 # order in which keys should appear in the temporary file
934 order = ['ProblemType', 'DistroRelease', 'Package', 'Regression', 'Reproducible',
935 'TestedUpstream', 'ProcVersionSignature', 'Uname', 'NonfreeKernelModules']
936
937 # write MIME/Multipart version into temporary file
938 mime = tempfile.TemporaryFile()
939 report.write_mime(mime, extra_headers=hdr, skip_keys=['Tags'], priority_fields=order)
940 mime.flush()
941 mime.seek(0)
942
943 return mime
944
945#
946# Launchpad storeblob API (should go into launchpadlib, see LP #315358)
947#
948
949_https_upload_callback = None
950
951
952#
953# This progress code is based on KodakLoader by Jason Hildebrand
954# <jason@opensky.ca>. See http://www.opensky.ca/~jdhildeb/software/kodakloader/
955# for details.
956class HTTPSProgressConnection(HTTPSConnection):
957 '''Implement a HTTPSConnection with an optional callback function for
958 upload progress.'''
959
960 def send(self, data):
961 global _https_upload_callback
962
963 # if callback has not been set, call the old method
964 if not _https_upload_callback:
965 HTTPSConnection.send(self, data)
966 return
967
968 sent = 0
969 total = len(data)
970 chunksize = 1024
971 while sent < total:
972 _https_upload_callback(sent, total)
973 t1 = time.time()
974 HTTPSConnection.send(self, data[sent:(sent + chunksize)])
975 sent += chunksize
976 t2 = time.time()
977
978 # adjust chunksize so that it takes between .5 and 2
979 # seconds to send a chunk
980 if chunksize > 1024:
981 if t2 - t1 < .5:
982 chunksize <<= 1
983 elif t2 - t1 > 2:
984 chunksize >>= 1
985
986
987class HTTPSProgressHandler(HTTPSHandler):
988
989 def https_open(self, req):
990 return self.do_open(HTTPSProgressConnection, req)
991
992
993def upload_blob(blob, progress_callback=None, hostname='launchpad.net'):
994 '''Upload blob (file-like object) to Launchpad.
995
996 progress_callback can be set to a function(sent, total) which is regularly
997 called with the number of bytes already sent and total number of bytes to
998 send. It is called every 0.5 to 2 seconds (dynamically adapted to upload
999 bandwidth).
1000
1001 Return None on error, or the ticket number on success.
1002
1003 By default this uses the production Launchpad hostname. Set
1004 hostname to 'launchpad.dev' or 'staging.launchpad.net' to use another
1005 instance for testing.
1006 '''
1007 ticket = None
1008 url = 'https://%s/+storeblob' % hostname
1009
1010 global _https_upload_callback
1011 _https_upload_callback = progress_callback
1012
1013 # build the form-data multipart/MIME request
1014 data = email.mime.multipart.MIMEMultipart()
1015
1016 submit = email.mime.text.MIMEText('1')
1017 submit.add_header('Content-Disposition', 'form-data; name="FORM_SUBMIT"')
1018 data.attach(submit)
1019
1020 form_blob = email.mime.base.MIMEBase('application', 'octet-stream')
1021 form_blob.add_header('Content-Disposition', 'form-data; name="field.blob"; filename="x"')
1022 form_blob.set_payload(blob.read().decode('ascii'))
1023 data.attach(form_blob)
1024
1025 data_flat = BytesIO()
1026 if sys.version_info.major == 2:
1027 gen = email.generator.Generator(data_flat, mangle_from_=False)
1028 else:
1029 gen = email.generator.BytesGenerator(data_flat, mangle_from_=False)
1030 gen.flatten(data)
1031
1032 # do the request; we need to explicitly set the content type here, as it
1033 # defaults to x-www-form-urlencoded
1034 req = Request(url, data_flat.getvalue())
1035 req.add_header('Content-Type', 'multipart/form-data; boundary=' + data.get_boundary())
1036 opener = build_opener(HTTPSProgressHandler)
1037 result = opener.open(req)
1038 ticket = result.info().get('X-Launchpad-Blob-Token')
1039
1040 assert ticket
1041 return ticket
1042
1043#
1044# Unit tests
1045#
1046
1047if __name__ == '__main__':
1048 import unittest, atexit, shutil, subprocess
1049 import mock
1050
1051 crashdb = None
1052 _segv_report = None
1053 _python_report = None
1054 _uncommon_description_report = None
1055
1056 class _T(unittest.TestCase):
1057 # this assumes that a source package 'coreutils' exists and builds a
1058 # binary package 'coreutils'
1059 test_package = 'coreutils'
1060 test_srcpackage = 'coreutils'
1061
1062 #
1063 # Generic tests, should work for all CrashDB implementations
1064 #
1065
1066 def setUp(self):
1067 global crashdb
1068 if not crashdb:
1069 crashdb = self._get_instance()
1070 self.crashdb = crashdb
1071
1072 # create a local reference report so that we can compare
1073 # DistroRelease, Architecture, etc.
1074 self.ref_report = apport.Report()
1075 self.ref_report.add_os_info()
1076 self.ref_report.add_user_info()
1077 self.ref_report['SourcePackage'] = 'coreutils'
1078
1079 # Objects tests rely on.
1080 self._create_project('langpack-o-matic')
1081
1082 def _create_project(self, name):
1083 '''Create a project using launchpadlib to be used by tests.'''
1084
1085 project = self.crashdb.launchpad.projects[name]
1086 if not project:
1087 self.crashdb.launchpad.projects.new_project(
1088 description=name + 'description',
1089 display_name=name,
1090 name=name,
1091 summary=name + 'summary',
1092 title=name + 'title')
1093
1094 @property
1095 def hostname(self):
1096 '''Get the Launchpad hostname for the given crashdb.'''
1097
1098 return self.crashdb.get_hostname()
1099
1100 def get_segv_report(self, force_fresh=False):
1101 '''Generate SEGV crash report.
1102
1103 This is only done once, subsequent calls will return the already
1104 existing ID, unless force_fresh is True.
1105
1106 Return the ID.
1107 '''
1108 global _segv_report
1109 if not force_fresh and _segv_report is not None:
1110 return _segv_report
1111
1112 r = self._generate_sigsegv_report()
1113 r.add_package_info(self.test_package)
1114 r.add_os_info()
1115 r.add_gdb_info()
1116 r.add_user_info()
1117 self.assertEqual(r.standard_title(), 'crash crashed with SIGSEGV in f()')
1118
1119 # add some binary gibberish which isn't UTF-8
1120 r['ShortGibberish'] = ' "]\xb6"\n'
1121 r['LongGibberish'] = 'a\nb\nc\nd\ne\n\xff\xff\xff\n\f'
1122
1123 # create a bug for the report
1124 bug_target = self._get_bug_target(self.crashdb, r)
1125 self.assertTrue(bug_target)
1126
1127 id = self._file_bug(bug_target, r)
1128 self.assertTrue(id > 0)
1129
1130 sys.stderr.write('(Created SEGV report: https://%s/bugs/%i) ' % (self.hostname, id))
1131 if not force_fresh:
1132 _segv_report = id
1133 return id
1134
1135 def get_python_report(self):
1136 '''Generate Python crash report.
1137
1138 Return the ID.
1139 '''
1140 global _python_report
1141 if _python_report is not None:
1142 return _python_report
1143
1144 r = apport.Report('Crash')
1145 r['ExecutablePath'] = '/bin/foo'
1146 r['Traceback'] = '''Traceback (most recent call last):
1147 File "/bin/foo", line 67, in fuzz
1148 print(weird)
1149NameError: global name 'weird' is not defined'''
1150 r['Tags'] = 'boogus pybogus'
1151 r.add_package_info(self.test_package)
1152 r.add_os_info()
1153 r.add_user_info()
1154 self.assertEqual(r.standard_title(),
1155 "foo crashed with NameError in fuzz(): global name 'weird' is not defined")
1156
1157 bug_target = self._get_bug_target(self.crashdb, r)
1158 self.assertTrue(bug_target)
1159
1160 id = self._file_bug(bug_target, r)
1161 self.assertTrue(id > 0)
1162 sys.stderr.write('(Created Python report: https://%s/bugs/%i) ' % (self.hostname, id))
1163 _python_report = id
1164 return id
1165
1166 def get_uncommon_description_report(self, force_fresh=False):
1167 '''File a bug report with an uncommon description.
1168
1169 This is only done once, subsequent calls will return the already
1170 existing ID, unless force_fresh is True.
1171
1172 Example taken from real LP bug 269539. It contains only
1173 ProblemType/Architecture/DistroRelease in the description, and has
1174 free-form description text after the Apport data.
1175
1176 Return the ID.
1177 '''
1178 global _uncommon_description_report
1179 if not force_fresh and _uncommon_description_report is not None:
1180 return _uncommon_description_report
1181
1182 desc = '''problem
1183
1184ProblemType: Package
1185Architecture: amd64
1186DistroRelease: Ubuntu 8.10
1187
1188more text
1189
1190and more
1191'''
1192 bug = self.crashdb.launchpad.bugs.createBug(
1193 title=b'mixed description bug'.encode(),
1194 description=desc,
1195 target=self.crashdb.lp_distro)
1196 sys.stderr.write('(Created uncommon description: https://%s/bugs/%i) ' % (self.hostname, bug.id))
1197
1198 if not force_fresh:
1199 _uncommon_description_report = bug.id
1200 return bug.id
1201
1202 def test_1_download(self):
1203 '''download()'''
1204
1205 r = self.crashdb.download(self.get_segv_report())
1206 self.assertEqual(r['ProblemType'], 'Crash')
1207 self.assertEqual(r['Title'], 'crash crashed with SIGSEGV in f()')
1208 self.assertEqual(r['DistroRelease'], self.ref_report['DistroRelease'])
1209 self.assertEqual(r['Architecture'], self.ref_report['Architecture'])
1210 self.assertEqual(r['Uname'], self.ref_report['Uname'])
1211 self.assertEqual(r.get('NonfreeKernelModules'),
1212 self.ref_report.get('NonfreeKernelModules'))
1213 self.assertEqual(r.get('UserGroups'), self.ref_report.get('UserGroups'))
1214 tags = set(r['Tags'].split())
1215 self.assertEqual(tags, set([self.crashdb.arch_tag, 'apport-crash',
1216 apport.packaging.get_system_architecture()]))
1217
1218 self.assertEqual(r['Signal'], '11')
1219 self.assertTrue(r['ExecutablePath'].endswith('/crash'))
1220 self.assertEqual(r['SourcePackage'], self.test_srcpackage)
1221 self.assertTrue(r['Package'].startswith(self.test_package + ' '))
1222 self.assertTrue('f (x=42)' in r['Stacktrace'])
1223 self.assertTrue('f (x=42)' in r['StacktraceTop'])
1224 self.assertTrue('f (x=42)' in r['ThreadStacktrace'])
1225 self.assertTrue(len(r['CoreDump']) > 1000)
1226 self.assertTrue('Dependencies' in r)
1227 self.assertTrue('Disassembly' in r)
1228 self.assertTrue('Registers' in r)
1229
1230 # check tags
1231 r = self.crashdb.download(self.get_python_report())
1232 tags = set(r['Tags'].split())
1233 self.assertEqual(tags, set(['apport-crash', 'boogus', 'pybogus',
1234 'need-duplicate-check', apport.packaging.get_system_architecture()]))
1235
1236 def test_2_update_traces(self):
1237 '''update_traces()'''
1238
1239 r = self.crashdb.download(self.get_segv_report())
1240 self.assertTrue('CoreDump' in r)
1241 self.assertTrue('Dependencies' in r)
1242 self.assertTrue('Disassembly' in r)
1243 self.assertTrue('Registers' in r)
1244 self.assertTrue('Stacktrace' in r)
1245 self.assertTrue('ThreadStacktrace' in r)
1246 self.assertEqual(r['Title'], 'crash crashed with SIGSEGV in f()')
1247
1248 # updating with a useless stack trace retains core dump
1249 r['StacktraceTop'] = '?? ()'
1250 r['Stacktrace'] = 'long\ntrace'
1251 r['ThreadStacktrace'] = 'thread\neven longer\ntrace'
1252 r['FooBar'] = 'bogus'
1253 self.crashdb.update_traces(self.get_segv_report(), r, 'I can has a better retrace?')
1254 r = self.crashdb.download(self.get_segv_report())
1255 self.assertTrue('CoreDump' in r)
1256 self.assertTrue('Dependencies' in r)
1257 self.assertTrue('Disassembly' in r)
1258 self.assertTrue('Registers' in r)
1259 self.assertTrue('Stacktrace' in r) # TODO: ascertain that it's the updated one
1260 self.assertTrue('ThreadStacktrace' in r)
1261 self.assertFalse('FooBar' in r)
1262 self.assertEqual(r['Title'], 'crash crashed with SIGSEGV in f()')
1263
1264 tags = self.crashdb.launchpad.bugs[self.get_segv_report()].tags
1265 self.assertTrue('apport-crash' in tags)
1266 self.assertFalse('apport-collected' in tags)
1267
1268 # updating with a useful stack trace removes core dump
1269 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
1270 r['Stacktrace'] = 'long\ntrace'
1271 r['ThreadStacktrace'] = 'thread\neven longer\ntrace'
1272 self.crashdb.update_traces(self.get_segv_report(), r, 'good retrace!')
1273 r = self.crashdb.download(self.get_segv_report())
1274 self.assertFalse('CoreDump' in r)
1275 self.assertTrue('Dependencies' in r)
1276 self.assertTrue('Disassembly' in r)
1277 self.assertTrue('Registers' in r)
1278 self.assertTrue('Stacktrace' in r)
1279 self.assertTrue('ThreadStacktrace' in r)
1280 self.assertFalse('FooBar' in r)
1281
1282 # as previous title had standard form, the top function gets
1283 # updated
1284 self.assertEqual(r['Title'], 'crash crashed with SIGSEGV in read()')
1285
1286 # respects title amendments
1287 bug = self.crashdb.launchpad.bugs[self.get_segv_report()]
1288 bug.title = 'crash crashed with SIGSEGV in f() on exit'
1289 try:
1290 bug.lp_save()
1291 except HTTPError:
1292 pass # LP#336866 workaround
1293 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
1294 self.crashdb.update_traces(self.get_segv_report(), r, 'good retrace with title amendment')
1295 r = self.crashdb.download(self.get_segv_report())
1296 self.assertEqual(r['Title'], 'crash crashed with SIGSEGV in read() on exit')
1297
1298 # does not destroy custom titles
1299 bug = self.crashdb.launchpad.bugs[self.get_segv_report()]
1300 bug.title = 'crash is crashy'
1301 try:
1302 bug.lp_save()
1303 except HTTPError:
1304 pass # LP#336866 workaround
1305
1306 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
1307 self.crashdb.update_traces(self.get_segv_report(), r, 'good retrace with custom title')
1308 r = self.crashdb.download(self.get_segv_report())
1309 self.assertEqual(r['Title'], 'crash is crashy')
1310
1311 # test various situations which caused crashes
1312 r['Stacktrace'] = '' # empty file
1313 r['ThreadStacktrace'] = '"]\xb6"\n' # not interpretable as UTF-8, LP #353805
1314 r['StacktraceSource'] = 'a\nb\nc\nd\ne\n\xff\xff\xff\n\f'
1315 self.crashdb.update_traces(self.get_segv_report(), r, 'tests')
1316
1317 def test_get_comment_url(self):
1318 '''get_comment_url() for non-ASCII titles'''
1319
1320 # UTF-8 bytestring, works in both python 2.7 and 3
1321 title = b'1\xc3\xa4\xe2\x99\xa52'
1322
1323 # distro, UTF-8 bytestring
1324 r = apport.Report('Bug')
1325 r['Title'] = title
1326 url = self.crashdb.get_comment_url(r, 42)
1327 self.assertTrue(url.endswith('/ubuntu/+filebug/42?field.title=1%C3%A4%E2%99%A52'))
1328
1329 # distro, unicode
1330 r['Title'] = title.decode('UTF-8')
1331 url = self.crashdb.get_comment_url(r, 42)
1332 self.assertTrue(url.endswith('/ubuntu/+filebug/42?field.title=1%C3%A4%E2%99%A52'))
1333
1334 # package, unicode
1335 r['SourcePackage'] = 'coreutils'
1336 url = self.crashdb.get_comment_url(r, 42)
1337 self.assertTrue(url.endswith('/ubuntu/+source/coreutils/+filebug/42?field.title=1%C3%A4%E2%99%A52'))
1338
1339 def test_update_description(self):
1340 '''update() with changing description'''
1341
1342 bug_target = self.crashdb.lp_distro.getSourcePackage(name='bash')
1343 bug = self.crashdb.launchpad.bugs.createBug(
1344 description='test description for test bug.',
1345 target=bug_target,
1346 title='testbug')
1347 id = bug.id
1348 self.assertTrue(id > 0)
1349 sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
1350
1351 r = apport.Report('Bug')
1352
1353 r['OneLiner'] = b'bogus\xe2\x86\x92'.decode('UTF-8')
1354 r['StacktraceTop'] = 'f()\ng()\nh(1)'
1355 r['ShortGoo'] = 'lineone\nlinetwo'
1356 r['DpkgTerminalLog'] = 'one\ntwo\nthree\nfour\nfive\nsix'
1357 r['VarLogDistupgradeBinGoo'] = b'\x01' * 1024
1358
1359 self.crashdb.update(id, r, 'NotMe', change_description=True)
1360
1361 r = self.crashdb.download(id)
1362
1363 self.assertEqual(r['OneLiner'], b'bogus\xe2\x86\x92'.decode('UTF-8'))
1364 self.assertEqual(r['ShortGoo'], 'lineone\nlinetwo')
1365 self.assertEqual(r['DpkgTerminalLog'], 'one\ntwo\nthree\nfour\nfive\nsix')
1366 self.assertEqual(r['VarLogDistupgradeBinGoo'], b'\x01' * 1024)
1367
1368 self.assertEqual(self.crashdb.launchpad.bugs[id].tags,
1369 ['apport-collected'])
1370
1371 def test_update_comment(self):
1372 '''update() with appending comment'''
1373
1374 bug_target = self.crashdb.lp_distro.getSourcePackage(name='bash')
1375 # we need to fake an apport description separator here, since we
1376 # want to be lazy and use download() for checking the result
1377 bug = self.crashdb.launchpad.bugs.createBug(
1378 description='Pr0blem\n\n--- \nProblemType: Bug',
1379 target=bug_target,
1380 title='testbug')
1381 id = bug.id
1382 self.assertTrue(id > 0)
1383 sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
1384
1385 r = apport.Report('Bug')
1386
1387 r['OneLiner'] = 'bogus→'
1388 r['StacktraceTop'] = 'f()\ng()\nh(1)'
1389 r['ShortGoo'] = 'lineone\nlinetwo'
1390 r['DpkgTerminalLog'] = 'one\ntwo\nthree\nfour\nfive\nsix'
1391 r['VarLogDistupgradeBinGoo'] = '\x01' * 1024
1392
1393 self.crashdb.update(id, r, 'meow', change_description=False)
1394
1395 r = self.crashdb.download(id)
1396
1397 self.assertFalse('OneLiner' in r)
1398 self.assertFalse('ShortGoo' in r)
1399 self.assertEqual(r['ProblemType'], 'Bug')
1400 self.assertEqual(r['DpkgTerminalLog'], 'one\ntwo\nthree\nfour\nfive\nsix')
1401 self.assertEqual(r['VarLogDistupgradeBinGoo'], '\x01' * 1024)
1402
1403 self.assertEqual(self.crashdb.launchpad.bugs[id].tags,
1404 ['apport-collected'])
1405
1406 def test_update_filter(self):
1407 '''update() with a key filter'''
1408
1409 bug_target = self.crashdb.lp_distro.getSourcePackage(name='bash')
1410 bug = self.crashdb.launchpad.bugs.createBug(
1411 description='test description for test bug',
1412 target=bug_target,
1413 title='testbug')
1414 id = bug.id
1415 self.assertTrue(id > 0)
1416 sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
1417
1418 r = apport.Report('Bug')
1419
1420 r['OneLiner'] = 'bogus→'
1421 r['StacktraceTop'] = 'f()\ng()\nh(1)'
1422 r['ShortGoo'] = 'lineone\nlinetwo'
1423 r['DpkgTerminalLog'] = 'one\ntwo\nthree\nfour\nfive\nsix'
1424 r['VarLogDistupgradeBinGoo'] = '\x01' * 1024
1425
1426 self.crashdb.update(id, r, 'NotMe', change_description=True,
1427 key_filter=['ProblemType', 'ShortGoo', 'DpkgTerminalLog'])
1428
1429 r = self.crashdb.download(id)
1430
1431 self.assertFalse('OneLiner' in r)
1432 self.assertEqual(r['ShortGoo'], 'lineone\nlinetwo')
1433 self.assertEqual(r['ProblemType'], 'Bug')
1434 self.assertEqual(r['DpkgTerminalLog'], 'one\ntwo\nthree\nfour\nfive\nsix')
1435 self.assertFalse('VarLogDistupgradeBinGoo' in r)
1436
1437 self.assertEqual(self.crashdb.launchpad.bugs[id].tags, [])
1438
1439 def test_get_distro_release(self):
1440 '''get_distro_release()'''
1441
1442 self.assertEqual(self.crashdb.get_distro_release(self.get_segv_report()),
1443 self.ref_report['DistroRelease'])
1444
1445 def test_get_affected_packages(self):
1446 '''get_affected_packages()'''
1447
1448 self.assertEqual(self.crashdb.get_affected_packages(self.get_segv_report()),
1449 [self.ref_report['SourcePackage']])
1450
1451 def test_is_reporter(self):
1452 '''is_reporter()'''
1453
1454 self.assertTrue(self.crashdb.is_reporter(self.get_segv_report()))
1455 self.assertFalse(self.crashdb.is_reporter(1))
1456
1457 def test_can_update(self):
1458 '''can_update()'''
1459
1460 self.assertTrue(self.crashdb.can_update(self.get_segv_report()))
1461 self.assertFalse(self.crashdb.can_update(1))
1462
1463 def test_duplicates(self):
1464 '''duplicate handling'''
1465
1466 # initially we have no dups
1467 self.assertEqual(self.crashdb.duplicate_of(self.get_segv_report()), None)
1468 self.assertEqual(self.crashdb.get_fixed_version(self.get_segv_report()), None)
1469
1470 segv_id = self.get_segv_report()
1471 known_test_id = self.get_uncommon_description_report()
1472 known_test_id2 = self.get_uncommon_description_report(force_fresh=True)
1473
1474 # dupe our segv_report and check that it worked; then undupe it
1475 r = self.crashdb.download(segv_id)
1476 self.crashdb.close_duplicate(r, segv_id, known_test_id)
1477 self.assertEqual(self.crashdb.duplicate_of(segv_id), known_test_id)
1478
1479 # this should be a no-op
1480 self.crashdb.close_duplicate(r, segv_id, known_test_id)
1481 self.assertEqual(self.crashdb.duplicate_of(segv_id), known_test_id)
1482
1483 self.assertEqual(self.crashdb.get_fixed_version(segv_id), 'invalid')
1484 self.crashdb.close_duplicate(r, segv_id, None)
1485 self.assertEqual(self.crashdb.duplicate_of(segv_id), None)
1486 self.assertEqual(self.crashdb.get_fixed_version(segv_id), None)
1487
1488 # this should have removed attachments; note that Stacktrace is
1489 # short, and thus inline
1490 r = self.crashdb.download(self.get_segv_report())
1491 self.assertFalse('CoreDump' in r)
1492 self.assertFalse('Disassembly' in r)
1493 self.assertFalse('ProcMaps' in r)
1494 self.assertFalse('ProcStatus' in r)
1495 self.assertFalse('Registers' in r)
1496 self.assertFalse('ThreadStacktrace' in r)
1497
1498 # now try duplicating to a duplicate bug; this should automatically
1499 # transition to the master bug
1500 self.crashdb.close_duplicate(apport.Report(), known_test_id,
1501 known_test_id2)
1502 self.crashdb.close_duplicate(r, segv_id, known_test_id)
1503 self.assertEqual(self.crashdb.duplicate_of(segv_id),
1504 known_test_id2)
1505
1506 self.crashdb.close_duplicate(apport.Report(), known_test_id, None)
1507 self.crashdb.close_duplicate(apport.Report(), known_test_id2, None)
1508 self.crashdb.close_duplicate(r, segv_id, None)
1509
1510 # this should be a no-op
1511 self.crashdb.close_duplicate(apport.Report(), known_test_id, None)
1512 self.assertEqual(self.crashdb.duplicate_of(known_test_id), None)
1513
1514 self.crashdb.mark_regression(segv_id, known_test_id)
1515 self._verify_marked_regression(segv_id)
1516
1517 def test_marking_segv(self):
1518 '''processing status markings for signal crashes'''
1519
1520 # mark_retraced()
1521 unretraced_before = self.crashdb.get_unretraced()
1522 self.assertTrue(self.get_segv_report() in unretraced_before)
1523 self.assertFalse(self.get_python_report() in unretraced_before)
1524 self.crashdb.mark_retraced(self.get_segv_report())
1525 unretraced_after = self.crashdb.get_unretraced()
1526 self.assertFalse(self.get_segv_report() in unretraced_after)
1527 self.assertEqual(unretraced_before,
1528 unretraced_after.union(set([self.get_segv_report()])))
1529 self.assertEqual(self.crashdb.get_fixed_version(self.get_segv_report()), None)
1530
1531 # mark_retrace_failed()
1532 self._mark_needs_retrace(self.get_segv_report())
1533 self.crashdb.mark_retraced(self.get_segv_report())
1534 self.crashdb.mark_retrace_failed(self.get_segv_report())
1535 unretraced_after = self.crashdb.get_unretraced()
1536 self.assertFalse(self.get_segv_report() in unretraced_after)
1537 self.assertEqual(unretraced_before,
1538 unretraced_after.union(set([self.get_segv_report()])))
1539 self.assertEqual(self.crashdb.get_fixed_version(self.get_segv_report()), None)
1540
1541 # mark_retrace_failed() of invalid bug
1542 self._mark_needs_retrace(self.get_segv_report())
1543 self.crashdb.mark_retraced(self.get_segv_report())
1544 self.crashdb.mark_retrace_failed(self.get_segv_report(), "I don't like you")
1545 unretraced_after = self.crashdb.get_unretraced()
1546 self.assertFalse(self.get_segv_report() in unretraced_after)
1547 self.assertEqual(unretraced_before,
1548 unretraced_after.union(set([self.get_segv_report()])))
1549 self.assertEqual(self.crashdb.get_fixed_version(self.get_segv_report()),
1550 'invalid')
1551
1552 def test_marking_project(self):
1553 '''processing status markings for a project CrashDB'''
1554
1555 # create a distro bug
1556 distro_bug = self.crashdb.launchpad.bugs.createBug(
1557 description='foo',
1558 tags=self.crashdb.arch_tag,
1559 target=self.crashdb.lp_distro,
1560 title='ubuntu distro retrace bug')
1561 #print('distro bug: https://staging.launchpad.net/bugs/%i' % distro_bug.id)
1562
1563 # create a project crash DB and a bug
1564 launchpad_instance = os.environ.get('APPORT_LAUNCHPAD_INSTANCE') or 'staging'
1565
1566 project_db = CrashDatabase(
1567 os.environ.get('LP_CREDENTIALS'),
1568 {'project': 'langpack-o-matic', 'launchpad_instance': launchpad_instance})
1569 project_bug = project_db.launchpad.bugs.createBug(
1570 description='bar',
1571 tags=project_db.arch_tag,
1572 target=project_db.lp_distro,
1573 title='project retrace bug')
1574 #print('project bug: https://staging.launchpad.net/bugs/%i' % project_bug.id)
1575
1576 # on project_db, we recognize the project bug and can mark it
1577 unretraced_before = project_db.get_unretraced()
1578 self.assertTrue(project_bug.id in unretraced_before)
1579 self.assertFalse(distro_bug.id in unretraced_before)
1580 project_db.mark_retraced(project_bug.id)
1581 unretraced_after = project_db.get_unretraced()
1582 self.assertFalse(project_bug.id in unretraced_after)
1583 self.assertEqual(unretraced_before,
1584 unretraced_after.union(set([project_bug.id])))
1585 self.assertEqual(self.crashdb.get_fixed_version(project_bug.id), None)
1586
1587 def test_marking_python(self):
1588 '''processing status markings for interpreter crashes'''
1589
1590 unchecked_before = self.crashdb.get_dup_unchecked()
1591 self.assertTrue(self.get_python_report() in unchecked_before)
1592 self.assertFalse(self.get_segv_report() in unchecked_before)
1593 self.crashdb._mark_dup_checked(self.get_python_report(), self.ref_report)
1594 unchecked_after = self.crashdb.get_dup_unchecked()
1595 self.assertFalse(self.get_python_report() in unchecked_after)
1596 self.assertEqual(unchecked_before,
1597 unchecked_after.union(set([self.get_python_report()])))
1598 self.assertEqual(self.crashdb.get_fixed_version(self.get_python_report()), None)
1599
1600 def test_update_traces_invalid(self):
1601 '''updating an invalid crash
1602
1603 This simulates a race condition where a crash being processed gets
1604 invalidated by marking it as a duplicate.
1605 '''
1606 id = self.get_segv_report(force_fresh=True)
1607
1608 r = self.crashdb.download(id)
1609
1610 self.crashdb.close_duplicate(r, id, self.get_segv_report())
1611
1612 # updating with a useful stack trace removes core dump
1613 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
1614 r['Stacktrace'] = 'long\ntrace'
1615 r['ThreadStacktrace'] = 'thread\neven longer\ntrace'
1616 self.crashdb.update_traces(id, r, 'good retrace!')
1617
1618 r = self.crashdb.download(id)
1619 self.assertFalse('CoreDump' in r)
1620
1621 @mock.patch.object(CrashDatabase, '_get_source_version')
1622 def test_get_fixed_version(self, *args):
1623 '''get_fixed_version() for fixed bugs
1624
1625 Other cases are already checked in test_marking_segv() (invalid
1626 bugs) and test_duplicates (duplicate bugs) for efficiency.
1627 '''
1628 # staging.launchpad.net often does not have Quantal, so mock-patch
1629 # it to a known value
1630 CrashDatabase._get_source_version.return_value = '3.14'
1631 self._mark_report_fixed(self.get_segv_report())
1632 fixed_ver = self.crashdb.get_fixed_version(self.get_segv_report())
1633 self.assertEqual(fixed_ver, '3.14')
1634 self._mark_report_new(self.get_segv_report())
1635 self.assertEqual(self.crashdb.get_fixed_version(self.get_segv_report()), None)
1636
1637 #
1638 # Launchpad specific implementation and tests
1639 #
1640
1641 @classmethod
1642 def _get_instance(klass):
1643 '''Create a CrashDB instance'''
1644
1645 launchpad_instance = os.environ.get('APPORT_LAUNCHPAD_INSTANCE') or 'staging'
1646
1647 return CrashDatabase(os.environ.get('LP_CREDENTIALS'),
1648 {'distro': 'ubuntu',
1649 'launchpad_instance': launchpad_instance})
1650
1651 def _get_bug_target(self, db, report):
1652 '''Return the bug_target for this report.'''
1653
1654 project = db.options.get('project')
1655 if 'SourcePackage' in report:
1656 return db.lp_distro.getSourcePackage(name=report['SourcePackage'])
1657 elif project:
1658 return db.launchpad.projects[project]
1659 else:
1660 return self.lp_distro
1661
1662 def _file_bug(self, bug_target, report, description=None):
1663 '''File a bug report for a report.
1664
1665 Return the bug ID.
1666 '''
1667 # unfortunately staging's +storeblob API hardly ever works, so we
1668 # must avoid using it. Fake it by manually doing the comments and
1669 # attachments that +filebug would ordinarily do itself when given a
1670 # blob handle.
1671
1672 if description is None:
1673 description = 'some description'
1674
1675 mime = self.crashdb._generate_upload_blob(report)
1676 msg = email.message_from_file(mime)
1677 mime.close()
1678 msg_iter = msg.walk()
1679
1680 # first one is the multipart container
1681 header = msg_iter.next()
1682 assert header.is_multipart()
1683
1684 # second part should be an inline text/plain attachments with all short
1685 # fields
1686 part = msg_iter.next()
1687 assert not part.is_multipart()
1688 assert part.get_content_type() == 'text/plain'
1689 description += '\n\n' + part.get_payload(decode=True).decode('UTF-8', 'replace')
1690
1691 # create the bug from header and description data
1692 bug = self.crashdb.launchpad.bugs.createBug(
1693 description=description,
1694 private=(header['Private'] == 'yes'),
1695 tags=header['Tags'].split(),
1696 target=bug_target,
1697 title=report.get('Title', report.standard_title()))
1698
1699 # nwo add the attachments
1700 for part in msg_iter:
1701 assert not part.is_multipart()
1702 bug.addAttachment(comment='',
1703 description=part.get_filename(),
1704 content_type=None,
1705 data=part.get_payload(decode=True),
1706 filename=part.get_filename(), is_patch=False)
1707
1708 for subscriber in header['Subscribers'].split():
1709 sub = self.crashdb.launchpad.people[subscriber]
1710 if sub:
1711 bug.subscribe(person=sub)
1712
1713 return bug.id
1714
1715 def _mark_needs_retrace(self, id):
1716 '''Mark a report ID as needing retrace.'''
1717
1718 bug = self.crashdb.launchpad.bugs[id]
1719 if self.crashdb.arch_tag not in bug.tags:
1720 bug.tags = bug.tags + [self.crashdb.arch_tag]
1721 bug.lp_save()
1722
1723 def _mark_needs_dupcheck(self, id):
1724 '''Mark a report ID as needing duplicate check.'''
1725
1726 bug = self.crashdb.launchpad.bugs[id]
1727 if 'need-duplicate-check' not in bug.tags:
1728 bug.tags = bug.tags + ['need-duplicate-check']
1729 bug.lp_save()
1730
1731 def _mark_report_fixed(self, id):
1732 '''Close a report ID as "fixed".'''
1733
1734 bug = self.crashdb.launchpad.bugs[id]
1735 tasks = list(bug.bug_tasks)
1736 assert len(tasks) == 1
1737 t = tasks[0]
1738 t.status = 'Fix Released'
1739 t.lp_save()
1740
1741 def _mark_report_new(self, id):
1742 '''Reopen a report ID as "new".'''
1743
1744 bug = self.crashdb.launchpad.bugs[id]
1745 tasks = list(bug.bug_tasks)
1746 assert len(tasks) == 1
1747 t = tasks[0]
1748 t.status = 'New'
1749 t.lp_save()
1750
1751 def _verify_marked_regression(self, id):
1752 '''Verify that report ID is marked as regression.'''
1753
1754 bug = self.crashdb.launchpad.bugs[id]
1755 self.assertTrue('regression-retracer' in bug.tags)
1756
1757 def test_project(self):
1758 '''reporting crashes against a project instead of a distro'''
1759
1760 launchpad_instance = os.environ.get('APPORT_LAUNCHPAD_INSTANCE') or 'staging'
1761 # crash database for langpack-o-matic project (this does not have
1762 # packages in any distro)
1763 crashdb = CrashDatabase(os.environ.get('LP_CREDENTIALS'),
1764 {'project': 'langpack-o-matic',
1765 'launchpad_instance': launchpad_instance})
1766 self.assertEqual(crashdb.distro, None)
1767
1768 # create Python crash report
1769 r = apport.Report('Crash')
1770 r['ExecutablePath'] = '/bin/foo'
1771 r['Traceback'] = '''Traceback (most recent call last):
1772 File "/bin/foo", line 67, in fuzz
1773 print(weird)
1774NameError: global name 'weird' is not defined'''
1775 r.add_os_info()
1776 r.add_user_info()
1777 self.assertEqual(r.standard_title(),
1778 "foo crashed with NameError in fuzz(): global name 'weird' is not defined")
1779
1780 # file it
1781 bug_target = self._get_bug_target(crashdb, r)
1782 self.assertEqual(bug_target.name, 'langpack-o-matic')
1783
1784 id = self._file_bug(bug_target, r)
1785 self.assertTrue(id > 0)
1786 sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id))
1787
1788 # update
1789 r = crashdb.download(id)
1790 r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so'
1791 r['Stacktrace'] = 'long\ntrace'
1792 r['ThreadStacktrace'] = 'thread\neven longer\ntrace'
1793 crashdb.update_traces(id, r, 'good retrace!')
1794 r = crashdb.download(id)
1795
1796 # test fixed version
1797 self.assertEqual(crashdb.get_fixed_version(id), None)
1798 crashdb.close_duplicate(r, id, self.get_uncommon_description_report())
1799 self.assertEqual(crashdb.duplicate_of(id), self.get_uncommon_description_report())
1800 self.assertEqual(crashdb.get_fixed_version(id), 'invalid')
1801 crashdb.close_duplicate(r, id, None)
1802 self.assertEqual(crashdb.duplicate_of(id), None)
1803 self.assertEqual(crashdb.get_fixed_version(id), None)
1804
1805 def test_download_robustness(self):
1806 '''download() of uncommon description formats'''
1807
1808 # only ProblemType/Architecture/DistroRelease in description
1809 r = self.crashdb.download(self.get_uncommon_description_report())
1810 self.assertEqual(r['ProblemType'], 'Package')
1811 self.assertEqual(r['Architecture'], 'amd64')
1812 self.assertTrue(r['DistroRelease'].startswith('Ubuntu '))
1813
1814 def test_escalation(self):
1815 '''Escalating bugs with more than 10 duplicates'''
1816
1817 launchpad_instance = os.environ.get('APPORT_LAUNCHPAD_INSTANCE') or 'staging'
1818 db = CrashDatabase(os.environ.get('LP_CREDENTIALS'),
1819 {'distro': 'ubuntu',
1820 'launchpad_instance': launchpad_instance,
1821 'escalation_tag': 'omgkittens',
1822 'escalation_subscription': 'apport-hackers'})
1823
1824 count = 0
1825 p = db.launchpad.people[db.options['escalation_subscription']].self_link
1826 first_dup = 59
1827 try:
1828 for b in range(first_dup, first_dup + 13):
1829 count += 1
1830 sys.stderr.write('%i ' % b)
1831 db.close_duplicate(apport.Report(), b, self.get_segv_report())
1832 b = db.launchpad.bugs[self.get_segv_report()]
1833 has_escalation_tag = db.options['escalation_tag'] in b.tags
1834 has_escalation_subscription = any([s.person_link == p for s in b.subscriptions])
1835 if count <= 10:
1836 self.assertFalse(has_escalation_tag)
1837 self.assertFalse(has_escalation_subscription)
1838 else:
1839 self.assertTrue(has_escalation_tag)
1840 self.assertTrue(has_escalation_subscription)
1841 finally:
1842 for b in range(first_dup, first_dup + count):
1843 sys.stderr.write('R%i ' % b)
1844 db.close_duplicate(apport.Report(), b, None)
1845 sys.stderr.write('\n')
1846
1847 def test_marking_python_task_mangle(self):
1848 '''source package task fixup for marking interpreter crashes'''
1849
1850 self._mark_needs_dupcheck(self.get_python_report())
1851 unchecked_before = self.crashdb.get_dup_unchecked()
1852 self.assertTrue(self.get_python_report() in unchecked_before)
1853
1854 # add an upstream task, and remove the package name from the
1855 # package task; _mark_dup_checked is supposed to restore the
1856 # package name
1857 b = self.crashdb.launchpad.bugs[self.get_python_report()]
1858 if b.private:
1859 b.private = False
1860 b.lp_save()
1861 t = b.bug_tasks[0]
1862 t.target = self.crashdb.launchpad.distributions['ubuntu']
1863 t.lp_save()
1864 b.addTask(target=self.crashdb.launchpad.projects['coreutils'])
1865
1866 self.crashdb._mark_dup_checked(self.get_python_report(), self.ref_report)
1867
1868 unchecked_after = self.crashdb.get_dup_unchecked()
1869 self.assertFalse(self.get_python_report() in unchecked_after)
1870 self.assertEqual(unchecked_before,
1871 unchecked_after.union(set([self.get_python_report()])))
1872
1873 # upstream task should be unmodified
1874 b = self.crashdb.launchpad.bugs[self.get_python_report()]
1875 self.assertEqual(b.bug_tasks[0].bug_target_name, 'coreutils')
1876 self.assertEqual(b.bug_tasks[0].status, 'New')
1877
1878 # package-less distro task should have package name fixed
1879 self.assertEqual(b.bug_tasks[1].bug_target_name, 'coreutils (Ubuntu)')
1880 self.assertEqual(b.bug_tasks[1].status, 'New')
1881
1882 # should not confuse get_fixed_version()
1883 self.assertEqual(self.crashdb.get_fixed_version(self.get_python_report()), None)
1884
1885 @classmethod
1886 def _generate_sigsegv_report(klass, signal='11'):
1887 '''Create a test executable which will die with a SIGSEGV, generate a
1888 core dump for it, create a problem report with those two arguments
1889 (ExecutablePath and CoreDump) and call add_gdb_info().
1890
1891 Return the apport.report.Report.
1892 '''
1893 workdir = None
1894 orig_cwd = os.getcwd()
1895 pr = apport.report.Report()
1896 try:
1897 workdir = tempfile.mkdtemp()
1898 atexit.register(shutil.rmtree, workdir)
1899 os.chdir(workdir)
1900
1901 # create a test executable
1902 with open('crash.c', 'w') as fd:
1903 fd.write('''
1904int f(x) {
1905 int* p = 0; *p = x;
1906 return x+1;
1907}
1908int main() { return f(42); }
1909''')
1910 assert subprocess.call(['gcc', '-g', 'crash.c', '-o', 'crash']) == 0
1911 assert os.path.exists('crash')
1912
1913 # call it through gdb and dump core
1914 subprocess.call(['gdb', '--batch', '--ex', 'run', '--ex',
1915 'generate-core-file core', './crash'], stdout=subprocess.PIPE)
1916 assert os.path.exists('core')
1917 subprocess.check_call(['sync'])
1918 assert subprocess.call(['readelf', '-n', 'core'],
1919 stdout=subprocess.PIPE) == 0
1920
1921 pr['ExecutablePath'] = os.path.join(workdir, 'crash')
1922 pr['CoreDump'] = (os.path.join(workdir, 'core'),)
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: