Merge lp:~xnox/ubuntu/raring/apport/ubuntu into lp:ubuntu/raring/apport
- Raring (13.04)
- ubuntu
- Merge into raring
Proposed by
Dimitri John Ledkov
Status: | Superseded |
---|---|
Proposed branch: | lp:~xnox/ubuntu/raring/apport/ubuntu |
Merge into: | lp:ubuntu/raring/apport |
Diff against target: |
125999 lines (+124810/-0) (has conflicts) 224 files modified
.bzr-builddeb/default.conf (+2/-0) .bzrignore (+9/-0) AUTHORS (+34/-0) COPYING (+339/-0) NEWS (+1801/-0) README (+87/-0) TODO (+21/-0) apport/REThread.py (+65/-0) apport/__init__.py (+73/-0) apport/com.ubuntu.apport.policy.in (+34/-0) apport/crashdb.py (+856/-0) apport/crashdb_impl/debian.py (+113/-0) apport/crashdb_impl/launchpad.py (+1993/-0) apport/crashdb_impl/memory.py (+300/-0) apport/fileutils.py (+376/-0) apport/hookutils.py (+897/-0) apport/packaging.py (+237/-0) apport/report.py (+1528/-0) apport/sandboxutils.py (+212/-0) apport/ui.py (+1560/-0) apport_python_hook.py (+197/-0) backends/packaging-apt-dpkg.py (+924/-0) bin/apport-bug (+93/-0) bin/apport-cli (+368/-0) bin/apport-retrace (+412/-0) bin/apport-unpack (+66/-0) bin/apport-valgrind (+174/-0) bin/crash-digger (+217/-0) bin/dupdb-admin (+96/-0) data/apport (+459/-0) data/apport-checkreports (+40/-0) data/apportcheckresume (+91/-0) data/dump_acpi_tables.py (+54/-0) data/gcc_ice_hook (+35/-0) data/general-hooks/automatix.py (+24/-0) data/general-hooks/generic.py (+94/-0) data/general-hooks/parse_segv.py (+376/-0) data/general-hooks/ubuntu.py (+477/-0) data/icons/scalable/apps/apport.svg (+444/-0) data/is-enabled (+19/-0) data/java_uncaught_exception (+92/-0) data/kernel_crashdump (+45/-0) data/kernel_oops (+39/-0) data/package-hooks/source_apport.py (+20/-0) data/package-hooks/source_debian-installer.py (+59/-0) data/package-hooks/source_linux-nexus7.py (+25/-0) data/package-hooks/source_linux.py (+113/-0) data/package-hooks/source_ubiquity.py (+129/-0) data/package_hook (+65/-0) data/recoverable_problem (+47/-0) data/root_info_wrapper (+3/-0) data/unkillable_shutdown (+108/-0) debhelper/apport.pm (+10/-0) debhelper/dh_apport (+83/-0) debian/apport-gtk.install (+2/-0) debian/apport-kde.install (+9/-0) debian/apport-retrace.install (+5/-0) debian/apport-valgrind.install (+2/-0) debian/apport.install (+33/-0) debian/apport.links (+4/-0) debian/apport.logrotate (+9/-0) debian/apport.maintscript (+1/-0) debian/apport.postinst (+10/-0) debian/apport.upstart (+50/-0) debian/changelog (+7406/-0) debian/clean (+2/-0) debian/compat (+1/-0) debian/control (+223/-0) debian/copyright (+15/-0) debian/dh-apport.install (+2/-0) debian/dh-apport.manpages (+1/-0) debian/python-apport.install (+2/-0) debian/python-problem-report.install (+1/-0) debian/python3-apport.install (+2/-0) debian/python3-problem-report.install (+1/-0) debian/rules (+39/-0) debian/source/format (+1/-0) debian/tests/control (+3/-0) debian/tests/upstream-system (+16/-0) debian/watch (+2/-0) doc/crashdb-conf.txt (+149/-0) doc/data-format.tex (+301/-0) doc/package-hooks.txt (+158/-0) doc/symptoms.txt (+73/-0) etc/apport/blacklist.d/README.blacklist (+4/-0) etc/apport/blacklist.d/apport (+5/-0) etc/apport/crashdb.conf (+37/-0) etc/bash_completion.d/apport_completion (+268/-0) etc/cron.daily/apport (+4/-0) etc/default/apport (+4/-0) etc/init.d/apport (+110/-0) gtk/apport-gtk (+590/-0) gtk/apport-gtk-mime.desktop.in (+12/-0) gtk/apport-gtk.desktop.in (+12/-0) gtk/apport-gtk.ui (+597/-0) java/README (+13/-0) java/com/ubuntu/apport/ApportUncaughtExceptionHandler.java (+108/-0) java/crash.java (+8/-0) kde/apport-kde (+522/-0) kde/apport-kde-mime.desktop.in (+12/-0) kde/apport-kde-mimelnk.desktop.in (+9/-0) kde/apport-kde.desktop.in (+11/-0) kde/bugreport.ui (+177/-0) kde/choices.ui (+95/-0) kde/error.ui (+136/-0) kde/progress.ui (+115/-0) kde/userpass.ui (+127/-0) man/apport-bug.1 (+129/-0) man/apport-cli.1 (+150/-0) man/apport-retrace.1 (+190/-0) man/apport-unpack.1 (+32/-0) man/apport-valgrind.1 (+125/-0) man/dupdb-admin.1 (+68/-0) pm-utils/sleep.d/000record-status (+16/-0) po/ace.po (+840/-0) po/af.po (+840/-0) po/am.po (+840/-0) po/an.po (+840/-0) po/apport.pot (+835/-0) po/ar.po (+1177/-0) po/ast.po (+1035/-0) po/be.po (+1071/-0) po/bg.po (+992/-0) po/bn.po (+858/-0) po/br.po (+840/-0) po/bs.po (+861/-0) po/ca.po (+1100/-0) po/ca@valencia.po (+900/-0) po/cs.po (+1053/-0) po/cv.po (+840/-0) po/da.po (+1042/-0) po/de.po (+1145/-0) po/el.po (+1116/-0) po/en_AU.po (+1094/-0) po/en_CA.po (+1081/-0) po/en_GB.po (+1094/-0) po/eo.po (+1053/-0) po/es.po (+1124/-0) po/et.po (+1211/-0) po/eu.po (+1023/-0) po/fa.po (+840/-0) po/fi.po (+1086/-0) po/fr.po (+1267/-0) po/ga.po (+840/-0) po/gd.po (+840/-0) po/gl.po (+1121/-0) po/gu.po (+884/-0) po/he.po (+1002/-0) po/hi.po (+881/-0) po/hr.po (+997/-0) po/hu.po (+1110/-0) po/hy.po (+840/-0) po/id.po (+1211/-0) po/is.po (+975/-0) po/it.po (+1094/-0) po/ja.po (+1166/-0) po/kab.po (+840/-0) po/kk.po (+840/-0) po/km.po (+958/-0) po/kn.po (+908/-0) po/ko.po (+962/-0) po/ku.po (+928/-0) po/lo.po (+840/-0) po/lt.po (+1095/-0) po/lv.po (+859/-0) po/mk.po (+840/-0) po/ml.po (+840/-0) po/ms.po (+983/-0) po/my.po (+844/-0) po/nb.po (+1019/-0) po/nds.po (+878/-0) po/ne.po (+852/-0) po/nl.po (+1045/-0) po/oc.po (+1120/-0) po/pl.po (+1281/-0) po/pt.po (+1030/-0) po/pt_BR.po (+1117/-0) po/ro.po (+1055/-0) po/ru.po (+1257/-0) po/sc.po (+840/-0) po/se.po (+840/-0) po/shn.po (+840/-0) po/si.po (+844/-0) po/sk.po (+1008/-0) po/sl.po (+1069/-0) po/sq.po (+1045/-0) po/sr.po (+1306/-0) po/sv.po (+1104/-0) po/ta.po (+874/-0) po/te.po (+850/-0) po/th.po (+879/-0) po/tr.po (+1262/-0) po/ug.po (+991/-0) po/uk.po (+1203/-0) po/uz.po (+839/-0) po/vi.po (+1008/-0) po/zh_CN.po (+965/-0) po/zh_HK.po (+844/-0) po/zh_TW.po (+979/-0) problem_report.py (+634/-0) setup.py (+129/-0) test/run (+128/-0) test/test_apport_unpack.py (+116/-0) test/test_apport_valgrind.py (+154/-0) test/test_backend_apt_dpkg.py (+781/-0) test/test_crash_digger.py (+237/-0) test/test_crashdb.py (+694/-0) test/test_fileutils.py (+342/-0) test/test_hooks.py (+270/-0) test/test_hookutils.py (+453/-0) test/test_java_crashes.py (+92/-0) test/test_packaging.py (+16/-0) test/test_parse_segv.py (+544/-0) test/test_problem_report.py (+988/-0) test/test_python_crashes.py (+398/-0) test/test_recoverable_problem.py (+76/-0) test/test_report.py (+2120/-0) test/test_rethread.py (+88/-0) test/test_signal_crashes.py (+672/-0) test/test_ui.py (+2106/-0) test/test_ui_gtk.py (+863/-0) test/test_ui_kde.py (+607/-0) use-local (+7/-0) xdg-mime/apport.xml (+11/-0) Conflict adding file AUTHORS. Moved existing file to AUTHORS.moved. Conflict adding file COPYING. Moved existing file to COPYING.moved. Conflict adding file NEWS. Moved existing file to NEWS.moved. Conflict adding file README. Moved existing file to README.moved. Conflict adding file TODO. Moved existing file to TODO.moved. Conflict adding file apport. Moved existing file to apport.moved. Conflict adding file apport_python_hook.py. Moved existing file to apport_python_hook.py.moved. Conflict adding file backends. Moved existing file to backends.moved. Conflict adding file bin. Moved existing file to bin.moved. Conflict adding file data. Moved existing file to data.moved. Conflict adding file debhelper. Moved existing file to debhelper.moved. Conflict adding file debian. Moved existing file to debian.moved. Conflict adding file doc. Moved existing file to doc.moved. Conflict adding file etc. Moved existing file to etc.moved. Conflict adding file gtk. Moved existing file to gtk.moved. Conflict adding file java. Moved existing file to java.moved. Conflict adding file kde. Moved existing file to kde.moved. Conflict adding file man. Moved existing file to man.moved. Conflict adding file pm-utils. Moved existing file to pm-utils.moved. Conflict adding file po. Moved existing file to po.moved. Conflict adding file problem_report.py. Moved existing file to problem_report.py.moved. Conflict adding file setup.py. Moved existing file to setup.py.moved. Conflict adding file test. Moved existing file to test.moved. Conflict adding file use-local. Moved existing file to use-local.moved. Conflict adding file xdg-mime. Moved existing file to xdg-mime.moved. |
To merge this branch: | bzr merge lp:~xnox/ubuntu/raring/apport/ubuntu |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu branches | Pending | ||
Review via email: mp+153223@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Unmerged revisions
- 2165. By Dimitri John Ledkov
-
Include ubiquity.log generated by upstart for the ubiquity.conf
upstart job. - 2164. By Martin Pitt
-
releasing version 2.9.1-0ubuntu1
- 2163. By Martin Pitt
-
Bump Standards-Version to 3.9.4 (no changes necessary).
- 2162. By Martin Pitt
-
new upstream release 2.9.1
- 2161. By Martin Pitt
-
releasing version 2.9-0ubuntu2
- 2160. By Martin Pitt
-
* Merge from trunk:
- test_signal_crashes. py: Fix test_crash_apport() when being run under
LD_PRELOAD. - 2159. By Martin Pitt
-
releasing version 2.9-0ubuntu1
- 2158. By Martin Pitt
-
data/general-
hooks/ubuntu. py: Drop unused pwd import - 2157. By Martin Pitt
-
new upstream release 2.9
- 2156. By Brian Murray
-
source_ubiquity.py: set a default value for cd_error
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added directory '.bzr-builddeb' | |||
2 | === added file '.bzr-builddeb/default.conf' | |||
3 | --- .bzr-builddeb/default.conf 1970-01-01 00:00:00 +0000 | |||
4 | +++ .bzr-builddeb/default.conf 2013-03-13 18:57:21 +0000 | |||
5 | @@ -0,0 +1,2 @@ | |||
6 | 1 | [BUILDDEB] | ||
7 | 2 | merge = True | ||
8 | 0 | 3 | ||
9 | === added file '.bzrignore' | |||
10 | --- .bzrignore 1970-01-01 00:00:00 +0000 | |||
11 | +++ .bzrignore 2013-03-13 18:57:21 +0000 | |||
12 | @@ -0,0 +1,9 @@ | |||
13 | 1 | apport/packaging_impl.py | ||
14 | 2 | debhelper/dh_apport.1 | ||
15 | 3 | doc/*.aux | ||
16 | 4 | doc/*.log | ||
17 | 5 | doc/*.pdf | ||
18 | 6 | doc/*.toc | ||
19 | 7 | build | ||
20 | 8 | dist | ||
21 | 9 | MANIFEST | ||
22 | 0 | 10 | ||
23 | === added file 'AUTHORS' | |||
24 | --- AUTHORS 1970-01-01 00:00:00 +0000 | |||
25 | +++ AUTHORS 2013-03-13 18:57:21 +0000 | |||
26 | @@ -0,0 +1,34 @@ | |||
27 | 1 | Copyright: | ||
28 | 2 | --------- | ||
29 | 3 | General: | ||
30 | 4 | Copyright (C) 2006 - 2011 Canonical Ltd. | ||
31 | 5 | |||
32 | 6 | backends/packaging_rpm.py: | ||
33 | 7 | Copyright (C) 2007 Red Hat Inc. | ||
34 | 8 | |||
35 | 9 | Authors and Contributors: | ||
36 | 10 | ------------------------- | ||
37 | 11 | Martin Pitt <martin.pitt@ubuntu.com>: | ||
38 | 12 | Lead developer, design, backend, GTK frontend development, | ||
39 | 13 | maintenance of other frontends | ||
40 | 14 | |||
41 | 15 | Michael Hofmann <mh21@piware.de>: | ||
42 | 16 | Creation of Qt4 and CLI frontends | ||
43 | 17 | |||
44 | 18 | Richard A. Johnson <nixternal@ubuntu.com>: | ||
45 | 19 | Changed Qt4 frontend to KDE frontend | ||
46 | 20 | |||
47 | 21 | Robert Collins <robert@ubuntu.com>: | ||
48 | 22 | Python crash hook | ||
49 | 23 | |||
50 | 24 | Will Woods <wwoods@redhat.com>: | ||
51 | 25 | RPM packaging backend | ||
52 | 26 | |||
53 | 27 | Matt Zimmerman <mdz@canonical.com>: | ||
54 | 28 | Convenience function library for hooks (apport/hookutils.py) | ||
55 | 29 | |||
56 | 30 | Troy James Sobotka <troy.sobotka@gmail.com>: | ||
57 | 31 | Apport icon (apport/apport.svg) | ||
58 | 32 | |||
59 | 33 | Kees Cook <kees.cook@canonical.com>: | ||
60 | 34 | Various fixes, additional GDB output, SEGV parser. | ||
61 | 0 | 35 | ||
62 | === renamed file 'AUTHORS' => 'AUTHORS.moved' | |||
63 | === added file 'COPYING' | |||
64 | --- COPYING 1970-01-01 00:00:00 +0000 | |||
65 | +++ COPYING 2013-03-13 18:57:21 +0000 | |||
66 | @@ -0,0 +1,339 @@ | |||
67 | 1 | GNU GENERAL PUBLIC LICENSE | ||
68 | 2 | Version 2, June 1991 | ||
69 | 3 | |||
70 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., | ||
71 | 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
72 | 6 | Everyone is permitted to copy and distribute verbatim copies | ||
73 | 7 | of this license document, but changing it is not allowed. | ||
74 | 8 | |||
75 | 9 | Preamble | ||
76 | 10 | |||
77 | 11 | The licenses for most software are designed to take away your | ||
78 | 12 | freedom to share and change it. By contrast, the GNU General Public | ||
79 | 13 | License is intended to guarantee your freedom to share and change free | ||
80 | 14 | software--to make sure the software is free for all its users. This | ||
81 | 15 | General Public License applies to most of the Free Software | ||
82 | 16 | Foundation's software and to any other program whose authors commit to | ||
83 | 17 | using it. (Some other Free Software Foundation software is covered by | ||
84 | 18 | the GNU Lesser General Public License instead.) You can apply it to | ||
85 | 19 | your programs, too. | ||
86 | 20 | |||
87 | 21 | When we speak of free software, we are referring to freedom, not | ||
88 | 22 | price. Our General Public Licenses are designed to make sure that you | ||
89 | 23 | have the freedom to distribute copies of free software (and charge for | ||
90 | 24 | this service if you wish), that you receive source code or can get it | ||
91 | 25 | if you want it, that you can change the software or use pieces of it | ||
92 | 26 | in new free programs; and that you know you can do these things. | ||
93 | 27 | |||
94 | 28 | To protect your rights, we need to make restrictions that forbid | ||
95 | 29 | anyone to deny you these rights or to ask you to surrender the rights. | ||
96 | 30 | These restrictions translate to certain responsibilities for you if you | ||
97 | 31 | distribute copies of the software, or if you modify it. | ||
98 | 32 | |||
99 | 33 | For example, if you distribute copies of such a program, whether | ||
100 | 34 | gratis or for a fee, you must give the recipients all the rights that | ||
101 | 35 | you have. You must make sure that they, too, receive or can get the | ||
102 | 36 | source code. And you must show them these terms so they know their | ||
103 | 37 | rights. | ||
104 | 38 | |||
105 | 39 | We protect your rights with two steps: (1) copyright the software, and | ||
106 | 40 | (2) offer you this license which gives you legal permission to copy, | ||
107 | 41 | distribute and/or modify the software. | ||
108 | 42 | |||
109 | 43 | Also, for each author's protection and ours, we want to make certain | ||
110 | 44 | that everyone understands that there is no warranty for this free | ||
111 | 45 | software. If the software is modified by someone else and passed on, we | ||
112 | 46 | want its recipients to know that what they have is not the original, so | ||
113 | 47 | that any problems introduced by others will not reflect on the original | ||
114 | 48 | authors' reputations. | ||
115 | 49 | |||
116 | 50 | Finally, any free program is threatened constantly by software | ||
117 | 51 | patents. We wish to avoid the danger that redistributors of a free | ||
118 | 52 | program will individually obtain patent licenses, in effect making the | ||
119 | 53 | program proprietary. To prevent this, we have made it clear that any | ||
120 | 54 | patent must be licensed for everyone's free use or not licensed at all. | ||
121 | 55 | |||
122 | 56 | The precise terms and conditions for copying, distribution and | ||
123 | 57 | modification follow. | ||
124 | 58 | |||
125 | 59 | GNU GENERAL PUBLIC LICENSE | ||
126 | 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | ||
127 | 61 | |||
128 | 62 | 0. This License applies to any program or other work which contains | ||
129 | 63 | a notice placed by the copyright holder saying it may be distributed | ||
130 | 64 | under the terms of this General Public License. The "Program", below, | ||
131 | 65 | refers to any such program or work, and a "work based on the Program" | ||
132 | 66 | means either the Program or any derivative work under copyright law: | ||
133 | 67 | that is to say, a work containing the Program or a portion of it, | ||
134 | 68 | either verbatim or with modifications and/or translated into another | ||
135 | 69 | language. (Hereinafter, translation is included without limitation in | ||
136 | 70 | the term "modification".) Each licensee is addressed as "you". | ||
137 | 71 | |||
138 | 72 | Activities other than copying, distribution and modification are not | ||
139 | 73 | covered by this License; they are outside its scope. The act of | ||
140 | 74 | running the Program is not restricted, and the output from the Program | ||
141 | 75 | is covered only if its contents constitute a work based on the | ||
142 | 76 | Program (independent of having been made by running the Program). | ||
143 | 77 | Whether that is true depends on what the Program does. | ||
144 | 78 | |||
145 | 79 | 1. You may copy and distribute verbatim copies of the Program's | ||
146 | 80 | source code as you receive it, in any medium, provided that you | ||
147 | 81 | conspicuously and appropriately publish on each copy an appropriate | ||
148 | 82 | copyright notice and disclaimer of warranty; keep intact all the | ||
149 | 83 | notices that refer to this License and to the absence of any warranty; | ||
150 | 84 | and give any other recipients of the Program a copy of this License | ||
151 | 85 | along with the Program. | ||
152 | 86 | |||
153 | 87 | You may charge a fee for the physical act of transferring a copy, and | ||
154 | 88 | you may at your option offer warranty protection in exchange for a fee. | ||
155 | 89 | |||
156 | 90 | 2. You may modify your copy or copies of the Program or any portion | ||
157 | 91 | of it, thus forming a work based on the Program, and copy and | ||
158 | 92 | distribute such modifications or work under the terms of Section 1 | ||
159 | 93 | above, provided that you also meet all of these conditions: | ||
160 | 94 | |||
161 | 95 | a) You must cause the modified files to carry prominent notices | ||
162 | 96 | stating that you changed the files and the date of any change. | ||
163 | 97 | |||
164 | 98 | b) You must cause any work that you distribute or publish, that in | ||
165 | 99 | whole or in part contains or is derived from the Program or any | ||
166 | 100 | part thereof, to be licensed as a whole at no charge to all third | ||
167 | 101 | parties under the terms of this License. | ||
168 | 102 | |||
169 | 103 | c) If the modified program normally reads commands interactively | ||
170 | 104 | when run, you must cause it, when started running for such | ||
171 | 105 | interactive use in the most ordinary way, to print or display an | ||
172 | 106 | announcement including an appropriate copyright notice and a | ||
173 | 107 | notice that there is no warranty (or else, saying that you provide | ||
174 | 108 | a warranty) and that users may redistribute the program under | ||
175 | 109 | these conditions, and telling the user how to view a copy of this | ||
176 | 110 | License. (Exception: if the Program itself is interactive but | ||
177 | 111 | does not normally print such an announcement, your work based on | ||
178 | 112 | the Program is not required to print an announcement.) | ||
179 | 113 | |||
180 | 114 | These requirements apply to the modified work as a whole. If | ||
181 | 115 | identifiable sections of that work are not derived from the Program, | ||
182 | 116 | and can be reasonably considered independent and separate works in | ||
183 | 117 | themselves, then this License, and its terms, do not apply to those | ||
184 | 118 | sections when you distribute them as separate works. But when you | ||
185 | 119 | distribute the same sections as part of a whole which is a work based | ||
186 | 120 | on the Program, the distribution of the whole must be on the terms of | ||
187 | 121 | this License, whose permissions for other licensees extend to the | ||
188 | 122 | entire whole, and thus to each and every part regardless of who wrote it. | ||
189 | 123 | |||
190 | 124 | Thus, it is not the intent of this section to claim rights or contest | ||
191 | 125 | your rights to work written entirely by you; rather, the intent is to | ||
192 | 126 | exercise the right to control the distribution of derivative or | ||
193 | 127 | collective works based on the Program. | ||
194 | 128 | |||
195 | 129 | In addition, mere aggregation of another work not based on the Program | ||
196 | 130 | with the Program (or with a work based on the Program) on a volume of | ||
197 | 131 | a storage or distribution medium does not bring the other work under | ||
198 | 132 | the scope of this License. | ||
199 | 133 | |||
200 | 134 | 3. You may copy and distribute the Program (or a work based on it, | ||
201 | 135 | under Section 2) in object code or executable form under the terms of | ||
202 | 136 | Sections 1 and 2 above provided that you also do one of the following: | ||
203 | 137 | |||
204 | 138 | a) Accompany it with the complete corresponding machine-readable | ||
205 | 139 | source code, which must be distributed under the terms of Sections | ||
206 | 140 | 1 and 2 above on a medium customarily used for software interchange; or, | ||
207 | 141 | |||
208 | 142 | b) Accompany it with a written offer, valid for at least three | ||
209 | 143 | years, to give any third party, for a charge no more than your | ||
210 | 144 | cost of physically performing source distribution, a complete | ||
211 | 145 | machine-readable copy of the corresponding source code, to be | ||
212 | 146 | distributed under the terms of Sections 1 and 2 above on a medium | ||
213 | 147 | customarily used for software interchange; or, | ||
214 | 148 | |||
215 | 149 | c) Accompany it with the information you received as to the offer | ||
216 | 150 | to distribute corresponding source code. (This alternative is | ||
217 | 151 | allowed only for noncommercial distribution and only if you | ||
218 | 152 | received the program in object code or executable form with such | ||
219 | 153 | an offer, in accord with Subsection b above.) | ||
220 | 154 | |||
221 | 155 | The source code for a work means the preferred form of the work for | ||
222 | 156 | making modifications to it. For an executable work, complete source | ||
223 | 157 | code means all the source code for all modules it contains, plus any | ||
224 | 158 | associated interface definition files, plus the scripts used to | ||
225 | 159 | control compilation and installation of the executable. However, as a | ||
226 | 160 | special exception, the source code distributed need not include | ||
227 | 161 | anything that is normally distributed (in either source or binary | ||
228 | 162 | form) with the major components (compiler, kernel, and so on) of the | ||
229 | 163 | operating system on which the executable runs, unless that component | ||
230 | 164 | itself accompanies the executable. | ||
231 | 165 | |||
232 | 166 | If distribution of executable or object code is made by offering | ||
233 | 167 | access to copy from a designated place, then offering equivalent | ||
234 | 168 | access to copy the source code from the same place counts as | ||
235 | 169 | distribution of the source code, even though third parties are not | ||
236 | 170 | compelled to copy the source along with the object code. | ||
237 | 171 | |||
238 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program | ||
239 | 173 | except as expressly provided under this License. Any attempt | ||
240 | 174 | otherwise to copy, modify, sublicense or distribute the Program is | ||
241 | 175 | void, and will automatically terminate your rights under this License. | ||
242 | 176 | However, parties who have received copies, or rights, from you under | ||
243 | 177 | this License will not have their licenses terminated so long as such | ||
244 | 178 | parties remain in full compliance. | ||
245 | 179 | |||
246 | 180 | 5. You are not required to accept this License, since you have not | ||
247 | 181 | signed it. However, nothing else grants you permission to modify or | ||
248 | 182 | distribute the Program or its derivative works. These actions are | ||
249 | 183 | prohibited by law if you do not accept this License. Therefore, by | ||
250 | 184 | modifying or distributing the Program (or any work based on the | ||
251 | 185 | Program), you indicate your acceptance of this License to do so, and | ||
252 | 186 | all its terms and conditions for copying, distributing or modifying | ||
253 | 187 | the Program or works based on it. | ||
254 | 188 | |||
255 | 189 | 6. Each time you redistribute the Program (or any work based on the | ||
256 | 190 | Program), the recipient automatically receives a license from the | ||
257 | 191 | original licensor to copy, distribute or modify the Program subject to | ||
258 | 192 | these terms and conditions. You may not impose any further | ||
259 | 193 | restrictions on the recipients' exercise of the rights granted herein. | ||
260 | 194 | You are not responsible for enforcing compliance by third parties to | ||
261 | 195 | this License. | ||
262 | 196 | |||
263 | 197 | 7. If, as a consequence of a court judgment or allegation of patent | ||
264 | 198 | infringement or for any other reason (not limited to patent issues), | ||
265 | 199 | conditions are imposed on you (whether by court order, agreement or | ||
266 | 200 | otherwise) that contradict the conditions of this License, they do not | ||
267 | 201 | excuse you from the conditions of this License. If you cannot | ||
268 | 202 | distribute so as to satisfy simultaneously your obligations under this | ||
269 | 203 | License and any other pertinent obligations, then as a consequence you | ||
270 | 204 | may not distribute the Program at all. For example, if a patent | ||
271 | 205 | license would not permit royalty-free redistribution of the Program by | ||
272 | 206 | all those who receive copies directly or indirectly through you, then | ||
273 | 207 | the only way you could satisfy both it and this License would be to | ||
274 | 208 | refrain entirely from distribution of the Program. | ||
275 | 209 | |||
276 | 210 | If any portion of this section is held invalid or unenforceable under | ||
277 | 211 | any particular circumstance, the balance of the section is intended to | ||
278 | 212 | apply and the section as a whole is intended to apply in other | ||
279 | 213 | circumstances. | ||
280 | 214 | |||
281 | 215 | It is not the purpose of this section to induce you to infringe any | ||
282 | 216 | patents or other property right claims or to contest validity of any | ||
283 | 217 | such claims; this section has the sole purpose of protecting the | ||
284 | 218 | integrity of the free software distribution system, which is | ||
285 | 219 | implemented by public license practices. Many people have made | ||
286 | 220 | generous contributions to the wide range of software distributed | ||
287 | 221 | through that system in reliance on consistent application of that | ||
288 | 222 | system; it is up to the author/donor to decide if he or she is willing | ||
289 | 223 | to distribute software through any other system and a licensee cannot | ||
290 | 224 | impose that choice. | ||
291 | 225 | |||
292 | 226 | This section is intended to make thoroughly clear what is believed to | ||
293 | 227 | be a consequence of the rest of this License. | ||
294 | 228 | |||
295 | 229 | 8. If the distribution and/or use of the Program is restricted in | ||
296 | 230 | certain countries either by patents or by copyrighted interfaces, the | ||
297 | 231 | original copyright holder who places the Program under this License | ||
298 | 232 | may add an explicit geographical distribution limitation excluding | ||
299 | 233 | those countries, so that distribution is permitted only in or among | ||
300 | 234 | countries not thus excluded. In such case, this License incorporates | ||
301 | 235 | the limitation as if written in the body of this License. | ||
302 | 236 | |||
303 | 237 | 9. The Free Software Foundation may publish revised and/or new versions | ||
304 | 238 | of the General Public License from time to time. Such new versions will | ||
305 | 239 | be similar in spirit to the present version, but may differ in detail to | ||
306 | 240 | address new problems or concerns. | ||
307 | 241 | |||
308 | 242 | Each version is given a distinguishing version number. If the Program | ||
309 | 243 | specifies a version number of this License which applies to it and "any | ||
310 | 244 | later version", you have the option of following the terms and conditions | ||
311 | 245 | either of that version or of any later version published by the Free | ||
312 | 246 | Software Foundation. If the Program does not specify a version number of | ||
313 | 247 | this License, you may choose any version ever published by the Free Software | ||
314 | 248 | Foundation. | ||
315 | 249 | |||
316 | 250 | 10. If you wish to incorporate parts of the Program into other free | ||
317 | 251 | programs whose distribution conditions are different, write to the author | ||
318 | 252 | to ask for permission. For software which is copyrighted by the Free | ||
319 | 253 | Software Foundation, write to the Free Software Foundation; we sometimes | ||
320 | 254 | make exceptions for this. Our decision will be guided by the two goals | ||
321 | 255 | of preserving the free status of all derivatives of our free software and | ||
322 | 256 | of promoting the sharing and reuse of software generally. | ||
323 | 257 | |||
324 | 258 | NO WARRANTY | ||
325 | 259 | |||
326 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY | ||
327 | 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN | ||
328 | 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES | ||
329 | 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED | ||
330 | 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | ||
331 | 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS | ||
332 | 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE | ||
333 | 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, | ||
334 | 268 | REPAIR OR CORRECTION. | ||
335 | 269 | |||
336 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | ||
337 | 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR | ||
338 | 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, | ||
339 | 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING | ||
340 | 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED | ||
341 | 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY | ||
342 | 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER | ||
343 | 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE | ||
344 | 278 | POSSIBILITY OF SUCH DAMAGES. | ||
345 | 279 | |||
346 | 280 | END OF TERMS AND CONDITIONS | ||
347 | 281 | |||
348 | 282 | How to Apply These Terms to Your New Programs | ||
349 | 283 | |||
350 | 284 | If you develop a new program, and you want it to be of the greatest | ||
351 | 285 | possible use to the public, the best way to achieve this is to make it | ||
352 | 286 | free software which everyone can redistribute and change under these terms. | ||
353 | 287 | |||
354 | 288 | To do so, attach the following notices to the program. It is safest | ||
355 | 289 | to attach them to the start of each source file to most effectively | ||
356 | 290 | convey the exclusion of warranty; and each file should have at least | ||
357 | 291 | the "copyright" line and a pointer to where the full notice is found. | ||
358 | 292 | |||
359 | 293 | <one line to give the program's name and a brief idea of what it does.> | ||
360 | 294 | Copyright (C) <year> <name of author> | ||
361 | 295 | |||
362 | 296 | This program is free software; you can redistribute it and/or modify | ||
363 | 297 | it under the terms of the GNU General Public License as published by | ||
364 | 298 | the Free Software Foundation; either version 2 of the License, or | ||
365 | 299 | (at your option) any later version. | ||
366 | 300 | |||
367 | 301 | This program is distributed in the hope that it will be useful, | ||
368 | 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
369 | 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
370 | 304 | GNU General Public License for more details. | ||
371 | 305 | |||
372 | 306 | You should have received a copy of the GNU General Public License along | ||
373 | 307 | with this program; if not, write to the Free Software Foundation, Inc., | ||
374 | 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
375 | 309 | |||
376 | 310 | Also add information on how to contact you by electronic and paper mail. | ||
377 | 311 | |||
378 | 312 | If the program is interactive, make it output a short notice like this | ||
379 | 313 | when it starts in an interactive mode: | ||
380 | 314 | |||
381 | 315 | Gnomovision version 69, Copyright (C) year name of author | ||
382 | 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | ||
383 | 317 | This is free software, and you are welcome to redistribute it | ||
384 | 318 | under certain conditions; type `show c' for details. | ||
385 | 319 | |||
386 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate | ||
387 | 321 | parts of the General Public License. Of course, the commands you use may | ||
388 | 322 | be called something other than `show w' and `show c'; they could even be | ||
389 | 323 | mouse-clicks or menu items--whatever suits your program. | ||
390 | 324 | |||
391 | 325 | You should also get your employer (if you work as a programmer) or your | ||
392 | 326 | school, if any, to sign a "copyright disclaimer" for the program, if | ||
393 | 327 | necessary. Here is a sample; alter the names: | ||
394 | 328 | |||
395 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program | ||
396 | 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. | ||
397 | 331 | |||
398 | 332 | <signature of Ty Coon>, 1 April 1989 | ||
399 | 333 | Ty Coon, President of Vice | ||
400 | 334 | |||
401 | 335 | This General Public License does not permit incorporating your program into | ||
402 | 336 | proprietary programs. If your program is a subroutine library, you may | ||
403 | 337 | consider it more useful to permit linking proprietary applications with the | ||
404 | 338 | library. If this is what you want to do, use the GNU Lesser General | ||
405 | 339 | Public License instead of this License. | ||
406 | 0 | 340 | ||
407 | === renamed file 'COPYING' => 'COPYING.moved' | |||
408 | === added file 'NEWS' | |||
409 | --- NEWS 1970-01-01 00:00:00 +0000 | |||
410 | +++ NEWS 2013-03-13 18:57:21 +0000 | |||
411 | @@ -0,0 +1,1801 @@ | |||
412 | 1 | This file summarizes the major and interesting changes for each release. For a | ||
413 | 2 | detailled list of changes, please see ChangeLog. | ||
414 | 3 | |||
415 | 4 | 2.9.1 (2013-03-07): | ||
416 | 5 | ------------------- | ||
417 | 6 | Improvements: | ||
418 | 7 | * launchpad.py: Add support for filing bugs as private. Document this in | ||
419 | 8 | doc/crashdb-conf.txt. (LP: #1144647) | ||
420 | 9 | |||
421 | 10 | Bug fixes: | ||
422 | 11 | * test_signal_crashes.py: Fix test_crash_apport() when being run under | ||
423 | 12 | LD_PRELOAD. | ||
424 | 13 | * Fix crash in error() and warning() if there is no sys.stderr. (LP: #1012445) | ||
425 | 14 | * Fix Turkish translation to add missing keyboard accelerator. (LP: #648750) | ||
426 | 15 | * fileutils.py, find_package_desktopfile(): Restrict to autostart and | ||
427 | 16 | application .desktop files. (LP: #1147528) | ||
428 | 17 | * apt/dpkg get_modified_files(): Fix crash when encountering non-ASCII file | ||
429 | 18 | names in an ASCII locale. (LP: #1044014) | ||
430 | 19 | |||
431 | 20 | 2.9 (2013-03-01): | ||
432 | 21 | ----------------- | ||
433 | 22 | Improvements: | ||
434 | 23 | * fileutils.py, shared_libraries(): Return a "name → path" dict instead of | ||
435 | 24 | just a set of names. Thanks Kyle Nitzsche. | ||
436 | 25 | * sandboxutils.py: Support unpackaged executables, i. e. reports which do not | ||
437 | 26 | have "Package" and "Dependencies" fields. For those, get required libraries | ||
438 | 27 | from "ProcMaps". Thanks Kyle Nitzsche. | ||
439 | 28 | |||
440 | 29 | Bug fixes: | ||
441 | 30 | * Add "com.ubuntu.apport.apport-gtk-root" polkit action for running apport-gtk | ||
442 | 31 | through pkexec to access system crash reports. Thanks Brian Murray. | ||
443 | 32 | * ui.py: Check $PKEXEC_UID in addition to $SUDO_UID for opening a browser. | ||
444 | 33 | * apport/report.py: report if LD_PRELOAD and LD_LIBRARY_PATH are set. Thanks | ||
445 | 34 | James Hunt. | ||
446 | 35 | * apport-valgrind: Cleanly exit on keyboard interrupts. Thanks Kyle Nitzsche. | ||
447 | 36 | * debian.py: Fix "string payload expected" crash when building the report. | ||
448 | 37 | Thanks Dmitry Shachnev. (Debian #698010) | ||
449 | 38 | * Move shared_libraries() and links_with_shared_library() from hookutils into | ||
450 | 39 | fileutils, so that we can use it from apport-valgrind. Thanks to Kyle | ||
451 | 40 | Nitzsche for the initial patch. | ||
452 | 41 | * fileutils.shared_libraries(): Filter out virtual "linux-vdso" from result. | ||
453 | 42 | Thanks Kyle Nitzsche. | ||
454 | 43 | * apport-valgrind: Fix path to debug symbols in the sandbox. | ||
455 | 44 | * ui.py, get_desktop_entry(): Fix for Python 2. | ||
456 | 45 | |||
457 | 46 | 2.8 (2013-01-08): | ||
458 | 47 | ----------------- | ||
459 | 48 | Improvements: | ||
460 | 49 | * Factor out sandbox management functions from apport-retrace into | ||
461 | 50 | apport/sandboxutils.py, so that other programs can re-use the API easily. | ||
462 | 51 | Thanks to Kyle Nitzsche for the initial work on this. | ||
463 | 52 | * Generate a crash signature for kernel OOPSes. | ||
464 | 53 | * Add "apport-valgrind" tool to produce valgrind reports in a temporary | ||
465 | 54 | sandbox with debug symbols (similar to apport-retrace). Thanks Alex Chiang | ||
466 | 55 | and Kyle Nitzsche! | ||
467 | 56 | |||
468 | 57 | Bug fixes: | ||
469 | 58 | * Fix StacktraceAddressSignature generation on ARM. (LP: #1089778) | ||
470 | 59 | * debian.py: Fix TypeError crash in payload generation. Thanks Ritesh Raj | ||
471 | 60 | Sarraf. | ||
472 | 61 | * apport_python_hook.py: Update "ExecutableTimestamp" field when mangling | ||
473 | 62 | "ExecutablePath". (LP: #1077253) | ||
474 | 63 | |||
475 | 64 | 2.7 (2012-12-10): | ||
476 | 65 | ----------------- | ||
477 | 66 | Improvements: | ||
478 | 67 | * packaging.py, get_file_package(): Add optional "release" and "arch" | ||
479 | 68 | arguments for getting a file's package for a foreign release or | ||
480 | 69 | architecture. Implement this for apt/dpkg backend. | ||
481 | 70 | * packaging.py, install_packages(): Add optional "architecture" argument for | ||
482 | 71 | creating a sandbox for a foreign architecture. Implement this for apt/dpkg | ||
483 | 72 | backend. | ||
484 | 73 | * When a report's architecture does not match the system architecture, try to | ||
485 | 74 | use gdb-multiarch (if available, as packaged on Debian/Ubuntu), and set | ||
486 | 75 | architecture and gnutarget accordingly in gdb. This supports x86, x86_64, | ||
487 | 76 | and ARM for now, so that reports from all these architectures can be | ||
488 | 77 | retraced on an x86_84 machine. (LP: #1044437) | ||
489 | 78 | * launchpad.py: Add "architecture" option to process reports for a foreign | ||
490 | 79 | architecture. | ||
491 | 80 | * Add exceptions from package hooks to new HookError_<filename> report field, | ||
492 | 81 | to make them more visible. Until now they were only written to stderr. | ||
493 | 82 | (LP: #1086309) | ||
494 | 83 | |||
495 | 84 | Bug fixes: | ||
496 | 85 | * Fix test_find_package_desktopfile test to not consider packages with only | ||
497 | 86 | one "NoDisplay=true" .desktop file for the "has one desktop file" test. | ||
498 | 87 | * report.py, mark_ignore(): Use home directory of actual effective user, not | ||
499 | 88 | of $HOME. Fixes ignore file when using through sudo. | ||
500 | 89 | * apport-cli: Fix showing of prompt. Thanks Dmitry Shachnev! | ||
501 | 90 | * fileutils.py, mark_report_upload(): Do not try to remove the .uploaded file, | ||
502 | 91 | as this is not owned by the user. | ||
503 | 92 | * backends/packaging-apt-dpkg.py, install_packages(): Set mirror to the one in | ||
504 | 93 | the sandbox config. | ||
505 | 94 | * apportcheckresume: Fix crash if state file does not exist. | ||
506 | 95 | |||
507 | 96 | 2.6.3 (2012-11-30): | ||
508 | 97 | ------------------- | ||
509 | 98 | * test_signal_crashes.py: Fix incompatibility with Python 3.3. | ||
510 | 99 | * test_signal_crashes.py: Allow XDG_RUNTIME_DIR environment variable, as it | ||
511 | 100 | only shows whether or not it is set. (Test regression from 2.6) | ||
512 | 101 | * debian.py: Only reject reports with useless stack traces if the report | ||
513 | 102 | actually has a stack trace at all. | ||
514 | 103 | * debian.py: Fix UTF-8 string handling. Thanks Ritesh Raj Sarraf. | ||
515 | 104 | * debian.py: Fix crash on broken "Package" fields, as generated by current | ||
516 | 105 | Debian/Ubuntu dkms package. | ||
517 | 106 | * data/apport: Call fsync when writing upstart crash reports. | ||
518 | 107 | * report.py, add_gdb_info(): Handle libnih's assertion messages. | ||
519 | 108 | (LP: #997359) | ||
520 | 109 | * apport-gtk, apport-kde: Don't provide an option to restart a crashed | ||
521 | 110 | application when the crash occurred in a thread (LP: #1033902). | ||
522 | 111 | * apport-retrace: Disallow option -C without option -S. Thanks Kyle Nitzsche. | ||
523 | 112 | * fileutils.py, mark_report_upload(): Refresh the .upload stamps if a previous | ||
524 | 113 | version of the report was already uploaded, but another instance of the | ||
525 | 114 | problem happened since then. Thanks Brian Murray. (LP: #1084296) | ||
526 | 115 | * Ignore implausibly low addresses when computing StacktraceAddressSignature. | ||
527 | 116 | These are usually artifacts from gdb when not having debug symbols, and | ||
528 | 117 | having too many of them prevents proper client-side duplicate detection and | ||
529 | 118 | proper bucketing in daisy. (LP: #1084996) | ||
530 | 119 | * fileutils.py: Ignore .desktop files with NoDisplay=true. (LP: #1048524) | ||
531 | 120 | |||
532 | 121 | 2.6.2 (2012-11-06): | ||
533 | 122 | ------------------- | ||
534 | 123 | * problem_report.py: Fix UnicodeDecodeError crash under Python 2 when the | ||
535 | 124 | report has an unicode field with an unprintable ASCII character < 20. | ||
536 | 125 | * debian.py: Fix calling of parent accepts() method and return value. Thanks | ||
537 | 126 | Ritesh Raj Sarraf. | ||
538 | 127 | * bin/apport-retrace: Fix crash when not using --sandbox mode. | ||
539 | 128 | * report.py, add_proc_info(): Throw correct exception if the executable path | ||
540 | 129 | does not exist, to provide a more appropriate error message. (LP: #1065129) | ||
541 | 130 | * report.py, add_gdb_info(): Check __glib_assert_msg for assertion messages, | ||
542 | 131 | too. | ||
543 | 132 | * REThread.py: Fix for Python 3.3. | ||
544 | 133 | |||
545 | 134 | 2.6.1 (2012-10-01): | ||
546 | 135 | ------------------- | ||
547 | 136 | * setup.py: Specify "-source 1.5" javac option as well, to avoid build failure | ||
548 | 137 | with OpenJDK 7. | ||
549 | 138 | |||
550 | 139 | 2.6 (2012-10-01): | ||
551 | 140 | ----------------- | ||
552 | 141 | * setup.py: Build java module with "-target 1.5" option, so that you can run | ||
553 | 142 | it with OpenJDK 6 even if you build with OpenJDK 7. | ||
554 | 143 | * report.py, add_proc_info(): Show if $XDG_RUNTIME_DIR is set. | ||
555 | 144 | * Add apport/crashdb_impl/debian.py: Initial crash database implementation for | ||
556 | 145 | the Debian BTS. Add configuration for it to etc/apport/crashdb.conf. Thanks | ||
557 | 146 | Ritesh Raj Sarraf! | ||
558 | 147 | * test_python_crashes.py: Robustify "$PYTHONPATH in ProcEnviron" check. | ||
559 | 148 | |||
560 | 149 | 2.5.3 (2012-09-28): | ||
561 | 150 | ------------------- | ||
562 | 151 | * data/apportcheckresume: Open report file in binary mode. (LP: #1040353) | ||
563 | 152 | * packaging-apt-dpkg.py: When throwing ValueErrors, show the non-existing | ||
564 | 153 | package name. This makes it easier to debug such crashes. | ||
565 | 154 | * launchpad.py: Replace characters from tags which are not allowed by | ||
566 | 155 | Launchpad with '.' (LP: #1029479) | ||
567 | 156 | * launchpad.py: Temporarily disable filing private bugs in the test suite, to | ||
568 | 157 | work around the SSLHandshakeError error when downloading private attachments | ||
569 | 158 | from staging. | ||
570 | 159 | * hookutils.py, attach_root_command_outputs(): Ignore IOError crash about | ||
571 | 160 | nonexisting files, which can happen if the user dismisses authorization. | ||
572 | 161 | (LP: #1051222) | ||
573 | 162 | * report.py, search_bug_patterns(): Fix bug patterns containing non-ASCII | ||
574 | 163 | characters. Thanks Brian Murray. | ||
575 | 164 | * apport_python_hook.py: Capture $PYTHONPATH and $PYTHONHOME environment | ||
576 | 165 | variables for Python crashes. Thanks Brian Murray. | ||
577 | 166 | |||
578 | 167 | 2.5.2 (2012-09-17): | ||
579 | 168 | ------------------- | ||
580 | 169 | * test/run: Ignore root_info_wrapper with pyflakes. | ||
581 | 170 | * packaging-apt-dpkg.py: Add recommended packages to "Dependencies:" field. | ||
582 | 171 | (LP: #1014428) | ||
583 | 172 | * test_hookutils.py, test_in_session_of_problem(): Use year 2038 for a future | ||
584 | 173 | date instead of 2211, as current Python 3.2 now crashes with an | ||
585 | 174 | OverflowError on 32 bit machines with later years. | ||
586 | 175 | * Fix crash on broken .desktop files. (LP: #1039889) | ||
587 | 176 | * apport-gtk: For console program crashes, say "stopped" instead of "closed". | ||
588 | 177 | Add a subtitle label with a hint about hanging programs. Thanks Matt Price | ||
589 | 178 | and Matthew Paul Thomas! | ||
590 | 179 | * report.py: Fix crash on determination of Python module path when examining a | ||
591 | 180 | crash of "python -m ...". | ||
592 | 181 | * apport-kde: Fix crash with undefined QString under Python 3. Thanks Jonathan | ||
593 | 182 | Riddell! (LP: #1028984) | ||
594 | 183 | * launchpad.py: Add missing "Pre-release Freeze" status. Thanks Brian Murray! | ||
595 | 184 | * report.py, _check_bug_pattern(): Fix bug pattern matching against binary | ||
596 | 185 | values. Thanks Brian Murray for the original patch. (LP: #1016380) | ||
597 | 186 | |||
598 | 187 | 2.5.1 (2012-08-22): | ||
599 | 188 | ------------------- | ||
600 | 189 | * data/root_info_wrapper: Turn into a real file, a symlink can cause some | ||
601 | 190 | packaging problems. | ||
602 | 191 | |||
603 | 192 | 2.5 (2012-08-22): | ||
604 | 193 | ------------------- | ||
605 | 194 | Bug fixes: | ||
606 | 195 | * test_recoverable_problem.py: Fix test for calling test runner with absolute | ||
607 | 196 | path. | ||
608 | 197 | * packaging-apt-dpkg.py: Fix crash on writing virtual_mapping.db when running | ||
609 | 198 | with --sandbox-dir and -S system or giving no --cache. | ||
610 | 199 | * REThread.py: Fix re-raising of exceptions in Python 3. Thanks Martin | ||
611 | 200 | Packman! (LP: #1024836) | ||
612 | 201 | * apport-retrace: Keep compressed CoreDump from .crash files instead of | ||
613 | 202 | uncompressing them into memory. This dramatically reduces memory usage. | ||
614 | 203 | (LP: #981155) | ||
615 | 204 | |||
616 | 205 | Improvements: | ||
617 | 206 | * Add an apport.memdbg() function which prints out current memory usage if | ||
618 | 207 | APPORT_MEMDEBUG is set. Annotate apport-retrace with it. | ||
619 | 208 | * hookutils.py: Allow specifying a list of profile names when using | ||
620 | 209 | attach_mac_events(). Thanks Marc Deslauriers. | ||
621 | 210 | * hookutils.py, attach_root_command_outputs() and root_command_output(): Drop | ||
622 | 211 | usage of sudo, kdesudo, and gksu, and replace with pkexec. For | ||
623 | 212 | attach_root_command_outputs(), use a wrapper and proper .policy file which | ||
624 | 213 | explains the action and works under every environment; thus | ||
625 | 214 | attach_root_command_outputs() is preferred over root_command_output() now, | ||
626 | 215 | as it provides a better user experience. | ||
627 | 216 | * Package hooks which want to send the report to a different crash database | ||
628 | 217 | than "default" can now also give the database specification itself in the | ||
629 | 218 | "CrashDB" field, not just the DB name. With this, packages do not need to | ||
630 | 219 | ship a separate /etc/apport/crashdb.conf.d/ file. Please see | ||
631 | 220 | doc/package-hooks.txt for details. (LP: #551330) | ||
632 | 221 | * report.py, add_hooks_info(): If reporting against a package/program in /opt, | ||
633 | 222 | also search for package hooks in the corresponding /opt directory. This | ||
634 | 223 | allows such hooks to define a custom crash database and thus report bugs | ||
635 | 224 | against their own project instead of against the distribution. | ||
636 | 225 | (LP: #1020503) | ||
637 | 226 | |||
638 | 227 | 2.4 (2012-07-18): | ||
639 | 228 | ----------------- | ||
640 | 229 | Improvements: | ||
641 | 230 | * apport_python_hook.py: For org.freedesktop.DBus.Error.ServiceUnknown | ||
642 | 231 | exceptions, add a 'DbusErrorAnalysis' field to the report which points out | ||
643 | 232 | whether any .service file provides the service it tried to talk to, and | ||
644 | 233 | whether the processes for those are running. This helps to determine the | ||
645 | 234 | root cause for such errors (missing dependencies, broken .service files, | ||
646 | 235 | talking to the wrong bus, etc.) (LP: #1020572) | ||
647 | 236 | * hookutils.py, attach_alsa(): Use alsa-info.sh when available. Thanks David | ||
648 | 237 | Henningson. | ||
649 | 238 | * Add new "RecoverableProblem" report type for problems which the application | ||
650 | 239 | can handle, but still wishes to notify the user and send a problem report | ||
651 | 240 | about. As an example, the application may wish to notify the user because | ||
652 | 241 | handling the error resulted in degraded functionality. The user interface | ||
653 | 242 | may fail to load items, or the action just performed may not return any | ||
654 | 243 | data. Applications call /usr/share/apport/recoverable_problem with a | ||
655 | 244 | string of arbitrary NUL-separated key/value pairs that are added to the | ||
656 | 245 | report. Thanks Evan Dandrea! | ||
657 | 246 | |||
658 | 247 | Bug fixes: | ||
659 | 248 | * ui tests, test_wait_for_pid(): Fix eternal hang when running as root. | ||
660 | 249 | * testsuite: Fix ResourceWarnings when running with Python 3. | ||
661 | 250 | * test_python_crashes.py: Fix race condition in timeout test. | ||
662 | 251 | * launchpad.py: Fix setting of 'Medium' importance on duplicate checking. | ||
663 | 252 | * apport-retrace: Fix StacktraceSource generation for relative --cache paths. | ||
664 | 253 | * crashdb.py, check_duplicate(): Do not try to mark a bug as duplicate of | ||
665 | 254 | itself. This can happen when re-processing a previously retraced bug. | ||
666 | 255 | * apport-retrace: Fix UnicodeDecodeError when encountering a non-ASCII source | ||
667 | 256 | code file and running under a non-UTF-8 locale. | ||
668 | 257 | |||
669 | 258 | 2.3 (2012-07-09): | ||
670 | 259 | ----------------- | ||
671 | 260 | Improvements: | ||
672 | 261 | * launchpad.py: Rework test suite to not use Launchpad's +storeblob facility | ||
673 | 262 | at all any more. It almost never works on staging and is horribly slow. Fake | ||
674 | 263 | the bug creation from a blob by manually creating the comment and | ||
675 | 264 | attachments ourselves, and just assume that storeblob works on production. | ||
676 | 265 | Also change the structure to allow running every test individually. | ||
677 | 266 | * crash-digger: Add --crash-db option to specify a non-default crash databae | ||
678 | 267 | name. (LP: #1003506) | ||
679 | 268 | * apport-gtk: Add --hanging option to specify the process ID of a hanging | ||
680 | 269 | application. If the user chooses to report this error, apport will terminate | ||
681 | 270 | the pid with SIGABRT, otherwise it will send SIGKILL. The normal core pipe | ||
682 | 271 | handler will be used to process the resulting report file, with a .hanging | ||
683 | 272 | file in /var/crash to separate these from regular crashes. | ||
684 | 273 | |||
685 | 274 | Bug fixes: | ||
686 | 275 | * apport: Also treat a binary as modified if the /proc/pid/exe symlink does | ||
687 | 276 | not point to an existing file any more. (LP: #984944) | ||
688 | 277 | * Fix PEP-8 violations picked up by latest pep8 checker. | ||
689 | 278 | * ui.py: Do not ignore certain exceptions during upload which are not likely | ||
690 | 279 | to be a network error. | ||
691 | 280 | * launchpad.py: Recongize Launchpad projects for bug query and marking | ||
692 | 281 | operations. (LP: #1003506) | ||
693 | 282 | * packaging-apt-dpkg.py: Fix get_source_tree() to work with apt sandboxes. | ||
694 | 283 | * apport-retrace: Turn StacktraceSource generation back on, now that it works | ||
695 | 284 | with the current sandboxing. | ||
696 | 285 | * launchpad.py: Ensure that upload chunk size does not underrun. (LP: #1013334) | ||
697 | 286 | * apport_python_hook: Fix UnicodeEncodeError crash with Python 2 for | ||
698 | 287 | exceptions with non-ASCII characters. (LP: #972436) | ||
699 | 288 | * test_ui_kde.py: Fix occasional test failure in test_1_crash_details if the | ||
700 | 289 | application ends before the "is progress bar visible" check is done. | ||
701 | 290 | |||
702 | 291 | 2.2.5 (2012-06-21): | ||
703 | 292 | ------------------- | ||
704 | 293 | * launchpad.py: Fix str vs. bytes crash for already known bugs, take 2. (LP: #1015788) | ||
705 | 294 | * apport/ui.py, get_desktop_entry(): Disable interpolation, to correctly read | ||
706 | 295 | desktop files with % signs. (LP: #1014341) | ||
707 | 296 | * apport/ui.py: Fix rare crash if a report is already being updated in the | ||
708 | 297 | background when the UI tries to update a previous version. (LP: #949196) | ||
709 | 298 | * GTK and KDE UI tests: Avoid eternal hangs due to "this is not a distro | ||
710 | 299 | package" error messages. | ||
711 | 300 | |||
712 | 301 | 2.2.4 (2012-06-21): | ||
713 | 302 | -------------------- | ||
714 | 303 | Bug fixes: | ||
715 | 304 | * test_apport_unpack.py: Fix test_unpack_python() test when running the | ||
716 | 305 | system-installed tests. | ||
717 | 306 | * data/java_uncaught_exception: Fix for Python 3. | ||
718 | 307 | * test_signal_crashes.py: Show crash reports in /var/crash/. | ||
719 | 308 | * test_crash_digger.py: Do not write crash reports of crash-digger into system | ||
720 | 309 | /var/crash, use a temporary directory. | ||
721 | 310 | * test/run: Wait for a previous xvfb server to finish before trying to start | ||
722 | 311 | one. This fixes a race condition in the KDE UI tests which often failed to | ||
723 | 312 | start up xvfb. | ||
724 | 313 | * apport-cli: Unbreak "keep" option. (LP: #1007826) | ||
725 | 314 | * launchpad.py: Fix str vs. bytes crash for already known bugs. (LP: #1015788) | ||
726 | 315 | |||
727 | 316 | 2.2.3 (2012-06-15): | ||
728 | 317 | ------------------- | ||
729 | 318 | Bug fixes: | ||
730 | 319 | * test/run: Do not run pep8 and pyflakes when running against the sytem | ||
731 | 320 | installed Apport. | ||
732 | 321 | * test_backend_apt_dpkg.py: For the "are we online" check, verify that we can | ||
733 | 322 | download from http://ddebs.ubuntu.com/, not just whether we have a default | ||
734 | 323 | route. The latter is not sufficient for e. g. buildd environments which are | ||
735 | 324 | online, but are restricted by proxies or firewalls. | ||
736 | 325 | * test_report.py: Call "sync" after test script write core dumps, to ensure | ||
737 | 326 | that subsequent operations have a complete one. | ||
738 | 327 | * test_signal_crashes.py: Drop the broken and obsolete test_local_python() | ||
739 | 328 | test. Instead, add two tests which check proper logging. | ||
740 | 329 | * launchpad.py: Fix urlopen() for Python3. Thanks Steve Langasek. | ||
741 | 330 | * test/run: Run the tests under LC_MESSAGES=C, to avoid failing tests on | ||
742 | 331 | translated strings. | ||
743 | 332 | |||
744 | 333 | 2.2.2 (2012-06-13): | ||
745 | 334 | ------------------- | ||
746 | 335 | Improvements: | ||
747 | 336 | * testsuite: Run with Python 3 by default. To test with Python 2, run | ||
748 | 337 | "PYTHON=python2 test/run". | ||
749 | 338 | |||
750 | 339 | Bug fixes: | ||
751 | 340 | * apport: Redefine sys.std{out,err} when redirecting output, as they are None | ||
752 | 341 | in Python 3 when being called from the kernel. | ||
753 | 342 | * test/test_signal_crashes.py: Clean up unexpected core dumps on failed test | ||
754 | 343 | cases. | ||
755 | 344 | * apport-gtk: Fix crash when closing the crash dialog while the information is | ||
756 | 345 | being collected. | ||
757 | 346 | * hookutils.py, xsession_errors(): Fix crash when running under a non-UTF8 locale. | ||
758 | 347 | * data/apport: Do not use sys.stdin.fileno(), it is invalid when being called | ||
759 | 348 | from the kernel with Python 3. | ||
760 | 349 | * data/apport: When core dumps are enabled, read them from the written report | ||
761 | 350 | instead of directly from stdin (and then reading the written core file into | ||
762 | 351 | the .crash report). If the core file size is limited, we otherwise stop | ||
763 | 352 | reading the core dump from the kernel in the middle and have no (or a | ||
764 | 353 | broken) core dump to be put into the report. | ||
765 | 354 | * data/apport: Properly close the written crash report before changing its | ||
766 | 355 | permissions to be readable. This prevents having crash reporting UI from | ||
767 | 356 | looking at incomplete .crash files. | ||
768 | 357 | |||
769 | 358 | 2.2.1 (2012-06-11) | ||
770 | 359 | ------------------ | ||
771 | 360 | Bug fixes: | ||
772 | 361 | * apport-cli: Port to work with Python 3. | ||
773 | 362 | * setup.py: When fixing hashbang lines of installed scripts, only include the | ||
774 | 363 | major Python version. | ||
775 | 364 | * hookutils.py, read_file, attach_file(), attach_file_if_exists(): Convert | ||
776 | 365 | file contents to unicode if the contents is UTF-8, or the newly added | ||
777 | 366 | force_unicode argument is True. | ||
778 | 367 | * hooktuils, command_output(): Convert output to unicode by default, and add | ||
779 | 368 | a "decode_utf8" parameter to disable this. | ||
780 | 369 | * hookutils.py, recent_logfile(): Fix fd leak. | ||
781 | 370 | * data/apport: Do not assume that sys.stdout and sys.stderr always have a | ||
782 | 371 | name; they can be None in Python 3. | ||
783 | 372 | * data/dump_acpi_tables.py: Fix for Python 3. | ||
784 | 373 | |||
785 | 374 | 2.2 (2012-06-11) | ||
786 | 375 | ---------------- | ||
787 | 376 | Improvements: | ||
788 | 377 | * Clean up module imports. | ||
789 | 378 | * test/run: Run pyflakes, if available. | ||
790 | 379 | * package_hook: Add --tags option. Thanks to Brian Murray. | ||
791 | 380 | * launchpad.py: Drop the external multipartpost_handler.py (which is not | ||
792 | 381 | portable to Python 3) and replace it with using the standard email module. | ||
793 | 382 | * launchpad.py: Also work with Python 3. Deal gracefully with a missing | ||
794 | 383 | "launchpadlib" module; this is not yet available for Python 3, but not | ||
795 | 384 | required for client-side reporting. | ||
796 | 385 | * apport-kde: Port to work with Python 3. | ||
797 | 386 | |||
798 | 387 | Bug fixes: | ||
799 | 388 | * apport-retrace: Fix crash when using the --procmaps option. | ||
800 | 389 | * setup.py: Update hashbang lines of installed scripts in data directory to | ||
801 | 390 | the python executable setup.py was run with, similar to what already happens | ||
802 | 391 | to scripts installed to ../bin/. | ||
803 | 392 | |||
804 | 393 | 2.1.1 (2012-05-30) | ||
805 | 394 | ------------------ | ||
806 | 395 | Improvements: | ||
807 | 396 | * launchpad.py: When closing a bug as a duplicate, copy some well-known tags | ||
808 | 397 | to the master bug. Thanks Brian Murray. | ||
809 | 398 | * launchpad.py: Set importance of Python crash reports to "Medium" by default, | ||
810 | 399 | similar to signal crashes. Thanks Brian Murray. | ||
811 | 400 | * hookutils.py: Add attach_default_grub() convenience function from the grub2 | ||
812 | 401 | package hook so it can be used by other packages. Thanks Brian Murray. | ||
813 | 402 | * launchpad.py: Make Launchpad bug subscription user/team configurable: The | ||
814 | 403 | initial subscriber after filing a bug can be set with the | ||
815 | 404 | "initial_subscriber" crashdb option, and the team which gets subscribed | ||
816 | 405 | after retracing with "triaging_team". (LP: #980726) | ||
817 | 406 | |||
818 | 407 | Bug fixes: | ||
819 | 408 | * report.py: Do not change the SourcePackage: field if the binary package is | ||
820 | 409 | not installed and does not exist. This fixes source package hooks to | ||
821 | 410 | actually work in some cases where source and binary package names overlap. | ||
822 | 411 | (part of LP: #993810) | ||
823 | 412 | * apport-gtk, apport-kde: Avoid collecting information twice in "bug update" | ||
824 | 413 | mode. This caused a crash in cases where the source package in a bug report | ||
825 | 414 | does not correspond to an installed binary package. (LP: #993810) | ||
826 | 415 | |||
827 | 416 | 2.1 (2012-05-18) | ||
828 | 417 | ---------------- | ||
829 | 418 | Improvements: | ||
830 | 419 | * packaging.py, install_packages(): Add permanent_rootdir flag and if set, | ||
831 | 420 | only unpack newly downloaded packages. Implement it for the apt/dpkg | ||
832 | 421 | backend. Thanks Evan Dandrea. | ||
833 | 422 | * apport-retrace: Add --sandbox-dir option for keeping a permanent sandbox | ||
834 | 423 | (unpacked packages). This provides a considerable speedup. Thanks Evan | ||
835 | 424 | Dandrea. | ||
836 | 425 | * crash-digger: Add --sandbox-dir option and pass it to apport-retrace. | ||
837 | 426 | * Fix the whole code to be PEP-8 compatible, and enforce this in test/run by | ||
838 | 427 | running the "pep8" tool. | ||
839 | 428 | * GTK UI tests: Ensure that there are no GLib/GTK warnings or criticals. | ||
840 | 429 | * Support Python 3. Everything except the launchpad crashdb backend now works | ||
841 | 430 | with both Python 2 and 3. An important change is that the load(), | ||
842 | 431 | write(), and write_mime() methods of a ProblemReport and apport.Report | ||
843 | 432 | object now require the file stream to be opened in binary mode. | ||
844 | 433 | * data/apport: Ignore a crash if the executable was modified after the process | ||
845 | 434 | started. This often happens if the package is upgraded and a long-running | ||
846 | 435 | process is not stopped before. (LP: #984944) | ||
847 | 436 | * Add test cases for apport-unpack. | ||
848 | 437 | * apport-retrace: Add information about outdated packages to the | ||
849 | 438 | "RetraceOutdatedPackages" field. | ||
850 | 439 | * ui.py: Drop python-xdg dependency, use ConfigParser to read the .desktop | ||
851 | 440 | files. | ||
852 | 441 | |||
853 | 442 | Bug fixes: | ||
854 | 443 | * apport-gtk: Work around GTK crash when trying to set pixmap on an already | ||
855 | 444 | destroyed parent window. (LP: #938090) | ||
856 | 445 | * data/dump_acpi_tables.py: Fix crash on undefined variable with non-standard | ||
857 | 446 | tables. (LP: #982267) | ||
858 | 447 | * backends/packaging-apt-dpkg.py: Fix crash if a package is installed, but has | ||
859 | 448 | no candidates in apt. (LP: #980094) | ||
860 | 449 | * data/general-hooks/generic.py: Bump minimum free space requirement from 10 | ||
861 | 450 | to 50 MB. 10 is not nearly enough particularly for /tmp. (LP: #979928) | ||
862 | 451 | * hookutils.py, recent_logfile(): Use a default limit of 10000 lines and call | ||
863 | 452 | "tail" instead of reading the whole file. This protects against using up all | ||
864 | 453 | memory when there are massive repeated log messages. (LP: #984256) | ||
865 | 454 | * apport-gtk: Do not assume that an icon requested for size 42 actually | ||
866 | 455 | delivers size 42; some themes do not have this available and deliver a | ||
867 | 456 | smaller one instead, causing overflows. Also, copy the image as | ||
868 | 457 | gtk_icon_theme_load_icon() returns a readonly result which we must not | ||
869 | 458 | modify. (LP: #937249) | ||
870 | 459 | * ui.py: Don't show the duplicate warning when the crash database does not | ||
871 | 460 | accept the problem type, and they are just being sent to whoopsie. Thanks | ||
872 | 461 | Evan Dandrea. (LP: #989779) | ||
873 | 462 | * report.py: Correctly escape the file path passed to gdb. | ||
874 | 463 | * apport-gtk, apport-kde: Do not show the information collection progress | ||
875 | 464 | dialog if the crash database does not accept this kind of report. In that | ||
876 | 465 | case whoopsie will upload it in the background and the dialog is not | ||
877 | 466 | necessary. (LP: #989698) | ||
878 | 467 | |||
879 | 468 | 2.0.1 (2012-04-10) | ||
880 | 469 | ------------------ | ||
881 | 470 | Bug fixes: | ||
882 | 471 | * test_ui_gtk.py: Disable package hooks for the tests, as they might ask for | ||
883 | 472 | sudo passwords and other interactive bits, and thus make the tests hang. | ||
884 | 473 | * test_backend_apt_dpkg.py: Fix checks for the installation of -dbgsym | ||
885 | 474 | packages. This should always happen, as the sandboxes have a ddeb apt | ||
886 | 475 | source. Only make it conditional on the system apt sources in the "use | ||
887 | 476 | system config" test. | ||
888 | 477 | * test_report.py: Sleep a bit after calling our test crash script, to ensure | ||
889 | 478 | the kernel has time to finish writing the core file. | ||
890 | 479 | * generic package hook: Also check /tmp for enough space. Thanks Brian Murray. | ||
891 | 480 | (LP: #972933) | ||
892 | 481 | * problem_report.py, write_mime(): Fix regression from version 1.95: Add a | ||
893 | 482 | value as attachment if it is bigger than 1000 bytes, not if it is bigger | ||
894 | 483 | than 100. (LP: #977882) | ||
895 | 484 | |||
896 | 485 | Improvements: | ||
897 | 486 | * packaging-apt-dpkg.py: Avoid constructing and updating the apt.Cache() | ||
898 | 487 | objects multiple times, to speed up retracing. Thanks Evan Dandrea. | ||
899 | 488 | (LP: #973494) | ||
900 | 489 | |||
901 | 490 | 2.0 (2012-03-30) | ||
902 | 491 | ---------------- | ||
903 | 492 | This is the final 2.0 release, featuring the overhauled and simplified GUI, | ||
904 | 493 | support for whoopsie-daemon, and client-side duplicate checking. | ||
905 | 494 | |||
906 | 495 | Bug fixes: | ||
907 | 496 | - report.py, anonymize(): Only replace whole words, not substrings. | ||
908 | 497 | (LP: #966562) | ||
909 | 498 | - apport_python_hook.py: Fix filtering of org.freedesktop.DBus.Error.NoReply | ||
910 | 499 | exceptions. (LP: #958575) | ||
911 | 500 | - crashdb.py: When publishing the crash database, cut hash file names after | ||
912 | 501 | quoting, to avoid that the quoting causes them to become too long. | ||
913 | 502 | (LP: #968070) This also uncovered that known() did not actually find any | ||
914 | 503 | signature which contained an URL-quoted character, therefore breaking | ||
915 | 504 | client-side duplicate checking in a lot of cases. Double-quote the file name | ||
916 | 505 | now, as urlopen() unquotes it. | ||
917 | 506 | - Add a new crash database option "problem_types" and a CrashDatabase method | ||
918 | 507 | "accepts(report)". This can be used to stop uploading particular problem | ||
919 | 508 | report types to that database. E. g. a distribution might decide to not get | ||
920 | 509 | "Crash" reports any more after release. Document the new option in | ||
921 | 510 | doc/crashdb-conf.txt. | ||
922 | 511 | - ui.py: Do not upload a report if the crash database does not accept the | ||
923 | 512 | report's type. This behaviour is not really correct, but necessary as long | ||
924 | 513 | as we only support a single crashdb and have whoopsie hardcoded. Once we | ||
925 | 514 | have multiple crash dbs, we need to not even present the data if none of the | ||
926 | 515 | DBs wants the report. See LP #957177 for details. (LP: #968121) | ||
927 | 516 | - ui.py: Do not short-circuit information collection if report already has a | ||
928 | 517 | "DistroRelease" field, as the GUIs add that in some cases. Check for | ||
929 | 518 | "Dependencies" instead. This fixes information collection for kernel | ||
930 | 519 | problems (which now has a full GTK GUI test case). (LP: #968488) | ||
931 | 520 | |||
932 | 521 | 1.95 (2012-03-22) | ||
933 | 522 | ----------------- | ||
934 | 523 | Bug fixes: | ||
935 | 524 | - ui.py: Ensure that the report file is readable by the crash reporting daemon | ||
936 | 525 | after running through collect_info(). Thanks Evan Dandrea. | ||
937 | 526 | - apport-gtk, apport-kde: Set the window title to the distribution name, as | ||
938 | 527 | per http://wiki.ubuntu.com/ErrorTracker#error . Thanks Evan Dandrea. | ||
939 | 528 | (LP: #948015) | ||
940 | 529 | - test/run: Ignore obsolete packages on the system, to avoid breaking the GUI | ||
941 | 530 | tests due to them. | ||
942 | 531 | - apport-gtk, apport-kde: When reporting a "system crash", don't say "... of | ||
943 | 532 | this program version", but "...of this type", as we don't show a program | ||
944 | 533 | version in the initial dialog (https://wiki.ubuntu.com/ErrorTracker#error) | ||
945 | 534 | (LP: #961065) | ||
946 | 535 | - problem_report.py, write_mime(): Do not put a key inline if it is bigger | ||
947 | 536 | than 1 kB, to guard against very long lines. (LP: #957326) | ||
948 | 537 | - etc/cron.daily/apport: Do not remove whoopsie's *.upload* stamps every day, | ||
949 | 538 | only if they are older than a week. whoopsie comes with its own cron job | ||
950 | 539 | which deals with them. Thanks Steve Langasek. (LP: #957102) | ||
951 | 540 | - report.py, mark_ignore(): Fix crash if executable went away underneath us. | ||
952 | 541 | (LP: #961410) | ||
953 | 542 | - apport-gtk: Do not compare current continue button label against a | ||
954 | 543 | translated string. Instead just remember whether or not we can restart the | ||
955 | 544 | application. (LP: #960439) | ||
956 | 545 | - hookutils.py, command_output(): Add option to keep the locale instead of | ||
957 | 546 | disabling it. | ||
958 | 547 | - hookutils.py, command_output(): Actually make the "input" parameter work, | ||
959 | 548 | instead of causing an eternal hang. Add tests for all possible modes of | ||
960 | 549 | operation. | ||
961 | 550 | - hooktuils.py: Change root_command_output() and attach_root_command_outputs() | ||
962 | 551 | to disable translated messages (LC_MESSAGES=C) only as part of the command | ||
963 | 552 | to be run, not already for the root prefix command. This will keep the | ||
964 | 553 | latter (gksu, kdesudo, etc.) translated. (LP: #961659) | ||
965 | 554 | - apport-gtk: Cut off text values after 4000 characters, as Gtk's TreeView | ||
966 | 555 | does not get along well with huge values. KDE's copes fine, so continue to | ||
967 | 556 | display the complete value there. (LP: #957062) | ||
968 | 557 | - apport-gtk: Make details window resizable in bug reporting mode. | ||
969 | 558 | - crashdb.py, known(): Check the address signature duplicate database if the | ||
970 | 559 | symbolic signature exists, but did not find any result. (LP: #103083) | ||
971 | 560 | - ui.py: Run anonymization after checking for duplicates, to prevent host or | ||
972 | 561 | user names which look like hex numbers to corrupt the stack trace. | ||
973 | 562 | (LP: #953104) | ||
974 | 563 | - apport-gtk: Require an application to both have TERM and SHELL in its | ||
975 | 564 | environment to consider it a command line application that was started by | ||
976 | 565 | the user. (LP: #962130) | ||
977 | 566 | - backends/packaging-apt-dpkg.py, _check_files_md5(): Fix double encoding, | ||
978 | 567 | which caused UnicodeDecodeErrors on non-ASCII characters in an md5sum file. | ||
979 | 568 | (LP: #953682) | ||
980 | 569 | - apport-kde, apport-gtk: Only show "Relaunch" if the report has a | ||
981 | 570 | ProcCmdline, otherwise we cannot restart it. (LP: #956173) | ||
982 | 571 | |||
983 | 572 | Improvements: | ||
984 | 573 | - hookutils.py, attach_alsa(): Add the full "pacmd list" output instead of | ||
985 | 574 | just sinks and sources. Thanks David Henningsson. | ||
986 | 575 | - apport-gtk, apport-kde: Show the ExecutablePath while we're collecting data | ||
987 | 576 | for the crash report. Thanks Evan Dandrea. (LP: #938707). | ||
988 | 577 | |||
989 | 578 | 1.94.1 (2012-03-07) | ||
990 | 579 | ------------------- | ||
991 | 580 | Bug fixes: | ||
992 | 581 | - test_ui_kde.py: Re-enable inadvertently disabled "bug report for uninstalled | ||
993 | 582 | package" test. | ||
994 | 583 | - ui.py, collect_info(): Do not assume that reports have a "ProblemType" | ||
995 | 584 | field. This is not the case when updating a bug. (LP: #947519) | ||
996 | 585 | - apport-cli: Consistently handle unicode vs. byte arrays. (LP: #946207) | ||
997 | 586 | - report.py, anonymize(): Fix crash when the hostname or user name contain | ||
998 | 587 | non-ASCII characters. (LP: #945230) | ||
999 | 588 | - packaging-apt-dpkg.py: Fix UnicodeDecodeError on unexpected md5sum output. | ||
1000 | 589 | (LP: #921037) | ||
1001 | 590 | - apport-gtk: Fix handling of non-ASCII strings in message dialogs. | ||
1002 | 591 | (LP: #865394) | ||
1003 | 592 | |||
1004 | 593 | 1.94 (2012-03-02) | ||
1005 | 594 | ----------------- | ||
1006 | 595 | Bug fixes: | ||
1007 | 596 | - apport: Set the group of written reports to "whoopsie" if that group exists. | ||
1008 | 597 | - Fix tests to run properly against the system-installed modules and binaries. | ||
1009 | 598 | - test/run: Run under LC_MESSAGES=C to avoid test failures due to translated | ||
1010 | 599 | strings. | ||
1011 | 600 | - general-hooks/generic.py: Also attach xsession-errors for programs that link | ||
1012 | 601 | to libgtk-3. | ||
1013 | 602 | - launchpad.py: Properly handle "Expired" status, to avoid marking new bugs as | ||
1014 | 603 | duplicates of expired ones. (LP: #941854) | ||
1015 | 604 | - apport: Fix crash if the "whoopsie" group does not exist. (LP: #942326) | ||
1016 | 605 | - report.py, crash_signature(): Do not put "<module>" frames into Python crash | ||
1017 | 606 | signatures that happen outside of function/method calls. Fall back to the | ||
1018 | 607 | file/line number as a frame description instead. This will do a much better | ||
1019 | 608 | job at disambiguating e. g. different ImportError crashes. (LP: #920403) | ||
1020 | 609 | - Make "binary changed since the time of the crash" error message more | ||
1021 | 610 | comprehensible, thanks Paolo Rotolo. (LP: #942830) | ||
1022 | 611 | - crashdb.py, check_duplicate(): It can happen that a bug gets identified as | ||
1023 | 612 | being a duplicate of bug S by symbolic signatures and a duplicate of bug A | ||
1024 | 613 | by address signatures. Empirical evidence shows that this is due to the | ||
1025 | 614 | unavoidable jitter in stack traces (A and S not being identified as | ||
1026 | 615 | duplicates as their signatures differ slightly) and not a logic error. So | ||
1027 | 616 | instead of erroring out, duplicate all three bugs and keep the lowest number | ||
1028 | 617 | as the master ID. (LP: #943117) | ||
1029 | 618 | - Revert the usage of multiple nested threads during data collection, and | ||
1030 | 619 | switch back to only using one UI thread. The UI implementations can, and now | ||
1031 | 620 | do, decide between showing a spinner and showing a progress dialog in the | ||
1032 | 621 | ui_*_info_collection_progress() methods. This fixes libX11 crashes when | ||
1033 | 622 | multiple UI threads do changes concurrently (LP: #901675), and also avoids | ||
1034 | 623 | multi-thread induced crashes in Pango (LP: #943661). The removal of the | ||
1035 | 624 | collect() method also fixes the new crashes in it. (LP: #942098, #939803) | ||
1036 | 625 | - ui.py, get_desktop_entry(): Fix crash on uninstalled package. (LP: #940984) | ||
1037 | 626 | - data/unkillable_shutdown: Fix crash on race condition when PID goes away | ||
1038 | 627 | while the report is created. (LP: #546369) | ||
1039 | 628 | - apport/hookutils.py, pci_devices(): Fix crash on unexpected lines from | ||
1040 | 629 | lspci. (LP: #904489) | ||
1041 | 630 | - Drop hardcoded "Ubuntu" words again which crept in with the whoopsie support | ||
1042 | 631 | merge. Use the DistroRelease: field. | ||
1043 | 632 | - apport-kde: Fix Home page URL in KApplication metadata. | ||
1044 | 633 | - apport-gtk: Fix resizability and size after hiding details. (LP: #405418) | ||
1045 | 634 | |||
1046 | 635 | Improvements: | ||
1047 | 636 | - test/run: Drop "local" argument. This now tests against the source tree when | ||
1048 | 637 | run in the source tree root, and against the system libraries/programs when | ||
1049 | 638 | run from anywhere else. | ||
1050 | 639 | - test/run: Consider command line arguments as test names and only run those | ||
1051 | 640 | when given. Also support just running a single test. | ||
1052 | 641 | - testsuite: Force the skipping of online tests when $SKIP_ONLINE_TESTS is | ||
1053 | 642 | set. | ||
1054 | 643 | - hookutils.py, xsession_errors(): Add a reasonable default pattern which | ||
1055 | 644 | matches glib-style warnings, errors, criticals etc. and X window errors. | ||
1056 | 645 | In data/general-hooks/generic.py, call it with that default instead of the | ||
1057 | 646 | rather incomplete custom pattern. (LP: #932660) | ||
1058 | 647 | - packaging.py: Add get_package_origin() method, and implement it for | ||
1059 | 648 | apt-dpkg. | ||
1060 | 649 | - report.py, add_package_info(): Add "[origin: ...]" tag to "Package" and | ||
1061 | 650 | "Dependencies" fields for any package which is not native to the | ||
1062 | 651 | distribution. If any such package is present, tag the report with | ||
1063 | 652 | "third-party-packages" in data/general-hooks/generic.py. (LP: #927912) | ||
1064 | 653 | - apport/packaging.py: Add get_uninstalled_package() method as a helper method | ||
1065 | 654 | for the test suite. Use it instead of a hardcoded Debian/Ubuntu specific | ||
1066 | 655 | name in test/test_hooks.py. | ||
1067 | 656 | - test/test_ui_{gtk,kde}.py: Add test cases for complete UI workflow runs for | ||
1068 | 657 | reporting a bug against an installed/uninstalled package, and reporting a | ||
1069 | 658 | crash with and without showing details. This reproduces the recent crashes | ||
1070 | 659 | like LP #901675 or LP #943661. | ||
1071 | 660 | - test_ui.py: Add a test case for reporting a complete report on uninstalled | ||
1072 | 661 | package. This happens when reporting a problem from a different machine | ||
1073 | 662 | through copying a .crash file. | ||
1074 | 663 | - test/run: Add a test that there are no hardcoded "Ubuntu" words in the | ||
1075 | 664 | source. The code should use the DistroRelease: field or lsb_release. | ||
1076 | 665 | |||
1077 | 666 | 1.93 (2012-02-23): | ||
1078 | 667 | ------------------ | ||
1079 | 668 | Bug fixes: | ||
1080 | 669 | - apport-gtk: Fix crash on nonexisting icon. Thanks Evan Dandrea. | ||
1081 | 670 | (LP: #937354) | ||
1082 | 671 | - ui.py, open_url(): Revert back to calling sudo instead of dropping | ||
1083 | 672 | privileges ourselves; with the latter, calling firefox as the sudo'ing user | ||
1084 | 673 | fails. (LP: #916810, #938128) | ||
1085 | 674 | - ui.py: Fix aborting with "AssertionError" if the report is already known, | ||
1086 | 675 | but without an URL. (LP: #938778) | ||
1087 | 676 | - launchpad.py: If a bug is already known, but the report is private, do not | ||
1088 | 677 | send the report. There is little sense piling up lots of duplicates. | ||
1089 | 678 | (LP: #938700) | ||
1090 | 679 | - test/crash: Fix regression of test_crash_apport(), consider $TERM a | ||
1091 | 680 | non-sensitive variable. | ||
1092 | 681 | - ui.py: Fix test failures for data collection progress, they are not expected | ||
1093 | 682 | to happen for "ProblemType: Crash" any more (happens in the background | ||
1094 | 683 | during sending, or if user clicks on "Show Details"). | ||
1095 | 684 | - test/hooks: Use a package from Debian/Ubuntu main, so that this works better | ||
1096 | 685 | during package builds on build servers. | ||
1097 | 686 | - test/python: Do not assume that /var/crash/ exists. Use /var/tmp/ for the | ||
1098 | 687 | fake binaries instead. | ||
1099 | 688 | - data/general-hooks/parse_segv.py: Fix test case name. | ||
1100 | 689 | - ui.py: Fix crash on invalid core dumps. (LP: #937215) | ||
1101 | 690 | - launchpad.py: Fix crash on unicode report titles. (LP: #896626) | ||
1102 | 691 | |||
1103 | 692 | Improvements: | ||
1104 | 693 | - apport-gtk: Show the most interesting fields first in the details view. | ||
1105 | 694 | - do-release: Call pyflakes and abort on errors other than unused imports. | ||
1106 | 695 | - Move all test suites out of the code modules into test/test_<module>.py. | ||
1107 | 696 | This avoids having to load it every time the program runs, and also allows | ||
1108 | 697 | running the tests against the installed version of Apport. | ||
1109 | 698 | - Clean up the other executable test script in test/* and change them to the | ||
1110 | 699 | same structure as the module tests. | ||
1111 | 700 | |||
1112 | 701 | 1.92 (2012-02-20): | ||
1113 | 702 | ------------------ | ||
1114 | 703 | Bug fixes: | ||
1115 | 704 | - ui.py: Fix wrong creation of "~" folder instead of expanding it to home | ||
1116 | 705 | directory when using "Examine locally". Thanks Jason Conti! (LP: #909149) | ||
1117 | 706 | - Replace file() calls with open() for Python 3 compatibility. Thanks Colin | ||
1118 | 707 | Watson! | ||
1119 | 708 | - launchpad.py: Avoid sending tag names with upper case. (LP: #924181) | ||
1120 | 709 | - report.py, crash_signature_addresses(): Fix crash if report does not have | ||
1121 | 710 | "Signal". | ||
1122 | 711 | - apport-gtk: Fix resize handling of expander in details window. Thanks Thomas | ||
1123 | 712 | Bechtold! (LP: #930562) | ||
1124 | 713 | - Clean up unnecessary imports. Thanks Evan Dandrea! | ||
1125 | 714 | |||
1126 | 715 | Improvements: | ||
1127 | 716 | - man/apport-bug.1: Mention where crash files are stored. Thanks David | ||
1128 | 717 | Kastrup. | ||
1129 | 718 | - hookutils.py, attach_hardware(): Sort ProcModules, thanks Brian Murray. | ||
1130 | 719 | - launchpad.py: Keep "Dependencies" attachment in duplicates. Thanks Brian | ||
1131 | 720 | Murray. | ||
1132 | 721 | - Reorganize the GNOME and KDE user interface to do the crash notifications | ||
1133 | 722 | and detail browser in a single dialog. Add test/gtk and test/kde tests to | ||
1134 | 723 | check expected dialog layout for different cases. Thanks Evan Dandrea! | ||
1135 | 724 | - Add support for the whoopsie-daisy crash reporting daemon by creating | ||
1136 | 725 | zero-byte .upload file stamps for crash reports. Thanks Evan Dandrea! | ||
1137 | 726 | |||
1138 | 727 | 1.91 (2012-01-18): | ||
1139 | 728 | ------------------ | ||
1140 | 729 | Bug fixes: | ||
1141 | 730 | - crashdb.py, check_duplicate(): If a crash has a signature but no existing | ||
1142 | 731 | duplicate in the DB, also check for an existing address signature duplicate | ||
1143 | 732 | in the DB. | ||
1144 | 733 | - apport-retrace: Use DistroRelease specific subdirectory of the cache dir for | ||
1145 | 734 | mapping a file to a package, as these maps are release specific. | ||
1146 | 735 | - packaging-apt-dpkg.py: Refresh Contents.gz cache if it is older than one | ||
1147 | 736 | day. | ||
1148 | 737 | - crashdb.py: Ensure that address_signature duplicate db table does not have | ||
1149 | 738 | multiple identical signatures by making it a primary key. Bump the db format | ||
1150 | 739 | to "3". Existing databases need to be migrated manually as SQLite does not | ||
1151 | 740 | allow adding a "PRIMARY KEY" constraint to existing tables. | ||
1152 | 741 | - crashdb.py: Do not add a new address signature entry if one already exists. | ||
1153 | 742 | - apport-cli: Fix UnicodeDecodeError on unicode report values. (LP: #275972) | ||
1154 | 743 | - launchpad.py: Only set bug task importance if it is undecided. | ||
1155 | 744 | - apport-retrace: Fix "an useful" typo. (LP: #911437) | ||
1156 | 745 | - report.py: Filter out frames which are internal kernel/glibc implementation | ||
1157 | 746 | details and not stable across duplicates. In particular, filter out | ||
1158 | 747 | __kernel-syscall() and the SSE stubs. | ||
1159 | 748 | - crashdb.py: Remove debugging leftover which completely disabled bug pattern | ||
1160 | 749 | checking. | ||
1161 | 750 | - report.py: Update reading AssertionMessage. Current (e)glibc turned | ||
1162 | 751 | __abort_msg from a simple static string into a struct. | ||
1163 | 752 | |||
1164 | 753 | Improvements: | ||
1165 | 754 | - Change permissions of .crash files from 0600 to 0640, so that /var/crash can | ||
1166 | 755 | be made g+s and crash handling daemons can access those. | ||
1167 | 756 | - Python exceptions: Blacklist DBus.Error.NoReply. It does not help to get | ||
1168 | 757 | these traces from the client-side application, you need the actual exception | ||
1169 | 758 | in the D-Bus server backend instead. (LP: #914220) | ||
1170 | 759 | - Support /etc/apport/whitelist.d/ similarly to /etc/apport/blacklist.d/, for | ||
1171 | 760 | cases like installer environments where only crashes of a few selected | ||
1172 | 761 | programs should be reported. | ||
1173 | 762 | |||
1174 | 763 | 1.90 (2011-11-24): | ||
1175 | 764 | ------------------ | ||
1176 | 765 | First beta release of 2.0 which introduces client-side duplicate checking. | ||
1177 | 766 | |||
1178 | 767 | Bug fixes: | ||
1179 | 768 | - backends/packaging-apt-dpkg.py: Fix another test case failure when ddeb | ||
1180 | 769 | repository is not enabled. | ||
1181 | 770 | - backends/packaging-apt-dpkg.py: Fix handling of explicit cache directory | ||
1182 | 771 | name when it is a relative path. | ||
1183 | 772 | - launchpad.py: Only query for bugs after 2011-08-01, to avoid timeouts. | ||
1184 | 773 | - ui.py: Also anonymize standard bug title. (LP: #893863) | ||
1185 | 774 | - launchpad.py: Current Launchpad cannot have private bugs which affect | ||
1186 | 775 | multiple projects. Fix test suite accordingly. | ||
1187 | 776 | |||
1188 | 777 | Improvements: | ||
1189 | 778 | - report.py: Break out new method stacktrace_top_function() from | ||
1190 | 779 | standard_title(), so that other parts of the code can use this as well. | ||
1191 | 780 | - launchpad.net: When sending retraced results back to the bug report, update | ||
1192 | 781 | the topmost function in the bug title. (LP: #869970) | ||
1193 | 782 | - report.py, add_gdb_info(): Add a new field "StacktraceAddressSignature" | ||
1194 | 783 | which is a heuristic signature for signal crashes. This should be used if | ||
1195 | 784 | crash_signature() fails, i. e. the Stacktrace field does not have enough | ||
1196 | 785 | symbols. This can be used to check for duplicates on the client side, | ||
1197 | 786 | provided that the crash database server supports querying for these. | ||
1198 | 787 | Do not expose this field when uploading to crash databases though, as it can | ||
1199 | 788 | be recomputed from the already existing information (ProcMaps and | ||
1200 | 789 | Stacktrace) and thus would just clutter the reports. | ||
1201 | 790 | - crashdb.py: Add a table "version" with the database format version. Add | ||
1202 | 791 | automatic upgrading to the most current format. | ||
1203 | 792 | - crashdb.py: Put address signatures from reports checked with | ||
1204 | 793 | check_duplicate() into the duplicate database, so that implementations of | ||
1205 | 794 | known() can check for these. | ||
1206 | 795 | - dupdb-admin: Add "publish" dupdb-admin command which exports the | ||
1207 | 796 | duplicate database into a set of text files suitable for WWW publishing. | ||
1208 | 797 | - crashdb.py: Add new method "known(report)" which can be implemented to check | ||
1209 | 798 | if the crash db already knows about the crash signature. If so, the report | ||
1210 | 799 | will not be uploaded, and instead the user will be directed to the existing | ||
1211 | 800 | report URL (if available), similar to bug patterns. The default | ||
1212 | 801 | implementation checks this format, if the crash database is initialized with | ||
1213 | 802 | a "dupdb_url" option pointing to the exported database. | ||
1214 | 803 | - launchpad.py: Override known() to check if the master bug is actually | ||
1215 | 804 | accessible by the reporter, and is not tagged with "apport-failed-retrace" | ||
1216 | 805 | or "apport-request-retrace"; otherwise file it anyway. | ||
1217 | 806 | - crash-digger: Add --publish-db option to conveniently integrate duplicate DB | ||
1218 | 807 | publication (similar to dupdb-admin publish) into retracer setups. | ||
1219 | 808 | - launchpad.py: Attach updated stack traces from a duplicate to the master bug | ||
1220 | 809 | if it failed retracing previously or has an "apport-request-retrace" tag. | ||
1221 | 810 | (LP: #869982) | ||
1222 | 811 | - apport-kde, apport-gtk: Support the "Annotation" field for custom dialog | ||
1223 | 812 | titles for "Crash" and "Package" problem types as well, not just for | ||
1224 | 813 | "Kernel". (LP: #664378) | ||
1225 | 814 | |||
1226 | 815 | 1.26 (2011-11-11): | ||
1227 | 816 | ------------------ | ||
1228 | 817 | Bug fixes: | ||
1229 | 818 | - backends/packaging-apt-dpkg.py: Port to current python-apt API. | ||
1230 | 819 | - hookutils.py: Fix path_to_key() to also work with unicode arguments. | ||
1231 | 820 | - test/crash: Exit successfully if apport is not enabled in the system. This | ||
1232 | 821 | allows packages to run the test suite during build. | ||
1233 | 822 | - report.py, add_proc_info(): Correctly handle "python -m <modulename>" | ||
1234 | 823 | programs as being interpreted and determine the appropriate module path. | ||
1235 | 824 | - Fix some import statements to also work for the system-installed test suite. | ||
1236 | 825 | - test/run: Fix testing data/general-hooks/parse_segv.py when called in | ||
1237 | 826 | system-installed mode. | ||
1238 | 827 | - apport/ui.py: Clean up test .crash file after test cases. | ||
1239 | 828 | - Fix tests when running as root. | ||
1240 | 829 | - setup.py: Fix crash when "javac -version" fails. | ||
1241 | 830 | - README: Update command for one-time enablement. | ||
1242 | 831 | - backends/packaging-apt-dpkg.py: Fix interleaving usage of install_packages() | ||
1243 | 832 | with other operations such as get_version(), by resetting the apt status | ||
1244 | 833 | after building and using the sandbox. | ||
1245 | 834 | - report.py test suite: Remove requirement that $USER is set, which makes it | ||
1246 | 835 | easier to run this from package build environments. | ||
1247 | 836 | - apport/ui.py, test/crash: Use "yes" as test process instead of "cat". The | ||
1248 | 837 | former is less likely to run already, and does not depend on having a stdin, | ||
1249 | 838 | so it runs better in test environments like autopkgtest. | ||
1250 | 839 | - backends/packaging-apt-dpkg.py: Fix tests if system does not have a dbgsym | ||
1251 | 840 | apt source. | ||
1252 | 841 | |||
1253 | 842 | Improvements: | ||
1254 | 843 | - Ignore a crash if gnome-session is running and says that the session is | ||
1255 | 844 | being shut down. These often die because X.org or other services are going | ||
1256 | 845 | away, are usually harmless, and just cause a lot of clutter in bug trackers. | ||
1257 | 846 | (LP: #460932) | ||
1258 | 847 | - test/crash: Rewrite using Python's unittest, to be in line with other tests, | ||
1259 | 848 | and be easier to maintain and extend. | ||
1260 | 849 | |||
1261 | 850 | 1.25 (2011-11-02): | ||
1262 | 851 | ------------------ | ||
1263 | 852 | Improvements: | ||
1264 | 853 | - Add new response "Examine locally" to presenting the report details, which | ||
1265 | 854 | runs apport-retrace in the chosen mode in a terminal. This should be made | ||
1266 | 855 | available for crash reports if apport-retrace and a Terminal application are | ||
1267 | 856 | installed; add an abstrace UI method for this. (LP: #75901) | ||
1268 | 857 | - apport-gtk: Add "Examine locally..." button, and implement | ||
1269 | 858 | ui_run_terminal(). | ||
1270 | 859 | - apport-cli: Add "Examine locally..." responses, and implement | ||
1271 | 860 | ui_run_terminal(). | ||
1272 | 861 | - apport-cli: Greatly speed up displaying large reports. This also changes the | ||
1273 | 862 | format to avoid indenting each line with a space, and visually set apart the | ||
1274 | 863 | keys in a better way. | ||
1275 | 864 | - apport_python_hook.py: Move tests out of this file into test/python, to | ||
1276 | 865 | avoid having to parse the unit tests at each Python startup. | ||
1277 | 866 | - test/python: Also make tests work if Python hook is not installed in | ||
1278 | 867 | system's sitecustomize.py. | ||
1279 | 868 | - packaging.py: Add get_modified_conffiles() API, and implement it in | ||
1280 | 869 | packaging-apt-dpkg.py. | ||
1281 | 870 | - hookutils.py: Add attach_conffiles(). | ||
1282 | 871 | - hookutils.py: Add attach_upstart_overrides(). | ||
1283 | 872 | |||
1284 | 873 | Bug fixes: | ||
1285 | 874 | - launchpad.py: Remove "Ubuntu" in bug response, replace with "this software". | ||
1286 | 875 | (LP: #883234) | ||
1287 | 876 | - apport-kde: Rearrange order of imports to get intended error message if | ||
1288 | 877 | PyKDE is not installed. | ||
1289 | 878 | - packaging-apt-dpkg.py: Ignore hardening-wrapper diversions, to make | ||
1290 | 879 | gcc_ice_hook work if hardening-wrapper is installed. | ||
1291 | 880 | - apport_python_hook: Respect $APPORT_REPORT_DIR. | ||
1292 | 881 | - apport_python_hook: Limit successive crashes per program and user to 3 per | ||
1293 | 882 | day, just like signal crashes. (LP: #603503) | ||
1294 | 883 | - packaging-apt-dpkg.py: Skip online tests when there is no default route. | ||
1295 | 884 | - ui.py: Fix test suite to not fail if system has some obsolete or non-distro | ||
1296 | 885 | packages. | ||
1297 | 886 | |||
1298 | 887 | 1.24 (2011-10-19): | ||
1299 | 888 | ------------------ | ||
1300 | 889 | Bug fixes: | ||
1301 | 890 | - backends/packaging-apt-dpkg.py, install_packages(): Also copy | ||
1302 | 891 | apt/sources.list.d/ into sandbox. | ||
1303 | 892 | - backends/packaging-apt-dpkg.py, install_packages(): Install apt keyrings | ||
1304 | 893 | from config dir or from system into sandbox. (LP: #856216) | ||
1305 | 894 | - packaging.py, backends/packaging-apt-dpkg.py: Define that install_packages() | ||
1306 | 895 | should return a SystemError for broken configs/unreachable servers etc., and | ||
1307 | 896 | fix the apt/dpkg implementation accordingly. | ||
1308 | 897 | - apport-retrace: Don't crash, just give a proper error message if servers are | ||
1309 | 898 | unreachable, or configuration files are broken. (LP: #859248) | ||
1310 | 899 | - backends/packaging-apt-dpkg.py: Fix crash when /etc/apport/native-origins.d | ||
1311 | 900 | contains any files. (LP: #865199) | ||
1312 | 901 | - hookutils, recent_logfile(): Fix invalid return value if log file is not | ||
1313 | 902 | readable. (LP: #819357) | ||
1314 | 903 | - test/crash: Fix race condition in the "second instance terminates | ||
1315 | 904 | immediately" check. | ||
1316 | 905 | - hookutils.py: Replace attach_gconf() with a no-op stub. It used static | ||
1317 | 906 | python modules like "gconf" which broke the PyGI GTK user interface, and | ||
1318 | 907 | gconf is rather obsolete these days. | ||
1319 | 908 | - ui.py, open_url(): Greatly simply and robustify by just using xdg-open. This | ||
1320 | 909 | already does the right thing wrt. reading the default browser from GNOME, | ||
1321 | 910 | KDE, XCE, and other desktops. (LP: #198449) | ||
1322 | 911 | - data/general-hooks/generic.py: Only attach ~/.xsession_errors if the bug is | ||
1323 | 912 | reported in the same XDG session as the crash happened. (LP: #869974) | ||
1324 | 913 | - Ignore crashes for programs which got updated in between the crash and | ||
1325 | 914 | reporting. (LP: #132904) | ||
1326 | 915 | - Special-case crashes of 'twistd': Try to determine the client program and | ||
1327 | 916 | assign the report to that, or fail with an UnreportableReason. (LP: #755025) | ||
1328 | 917 | - apport-gtk: In bug update mode, make details dialog resizable and fix | ||
1329 | 918 | default size. (LP: #865754) | ||
1330 | 919 | - apport-gtk: Fix crash if report does not have ProcCmdline. (LP: #854452) | ||
1331 | 920 | - hookutils.py, attach_wifi(): Anonymize ESSID and AP MAC from "iwconfig" | ||
1332 | 921 | output. (LP: #746900) | ||
1333 | 922 | - test/crash: Fix test failure if user is not in any system groups. | ||
1334 | 923 | - test/crash: Change to /tmp/ for test crash process, to fix failure if the | ||
1335 | 924 | user that runs the test suite cannot write into the current directory. | ||
1336 | 925 | (LP: #868695) | ||
1337 | 926 | - ui.py: Improve error message if package is not a genuine distro package. | ||
1338 | 927 | Thanks to Ronan Jouchet. (LP: #559345) | ||
1339 | 928 | |||
1340 | 929 | Improvements: | ||
1341 | 930 | - apport-retrace: Add --timestamp option to prepend a timestamp to log | ||
1342 | 931 | messages. This is useful for batch operations. | ||
1343 | 932 | - crash-digger: Call apport-retrace with --timestamps, to get consistent | ||
1344 | 933 | timestamps in log output. | ||
1345 | 934 | - hookutils.py: Add two new functions attach_gsettings_package() and | ||
1346 | 935 | attach_gsettings_schema() for adding user-modified gsettings keys to a | ||
1347 | 936 | report. (LP: #836489) | ||
1348 | 937 | - hookutils.py: Add new function in_session_of_problem() which returns whether | ||
1349 | 938 | the given report happened in the currently running XDG session. This can be | ||
1350 | 939 | used to determine if e. g. ~/.xsession-errors is relevant and should be | ||
1351 | 940 | attached. | ||
1352 | 941 | |||
1353 | 942 | 1.23.1 (2011-09-29) | ||
1354 | 943 | ------------------- | ||
1355 | 944 | Bug fixes: | ||
1356 | 945 | - apport/crashdb.py: Ensure that duplicate table only has one entry per report | ||
1357 | 946 | ID. | ||
1358 | 947 | - apport-retrace: Pass correct executable path to gdb in --gdb with --sandbox | ||
1359 | 948 | mode. | ||
1360 | 949 | - apport-retrace: Do not leave behind temporary directories on errors. | ||
1361 | 950 | - apport-retrace: Drop assertion failure for existance of "Stacktrace". This | ||
1362 | 951 | isn't present in the case of gdb crashing, and there is not much we can do | ||
1363 | 952 | about it. This should not break the retracer. | ||
1364 | 953 | - apport/report.py: Unwind XError() from stack traces for the "StacktraceTop" | ||
1365 | 954 | field, as they take a significant part of the trace. This causes bugs to be | ||
1366 | 955 | duplicated which really have different causes. | ||
1367 | 956 | |||
1368 | 957 | 1.23 (2011-09-14) | ||
1369 | 958 | ----------------- | ||
1370 | 959 | Improvements: | ||
1371 | 960 | - crashdb.py, crash-digger, dupdb-admin: Drop the concept of "duplicate DB | ||
1372 | 961 | consolidation". Such massive queries cause timeouts with e. g. Launchpad. | ||
1373 | 962 | Instead, update the status of potential master bugs in the crash DB whenever | ||
1374 | 963 | check_duplicate() is called. | ||
1375 | 964 | |||
1376 | 965 | Bug fixes: | ||
1377 | 966 | - launchpad.py: Fix crash in close_duplicate() if master bug was already | ||
1378 | 967 | marked as a duplicate of the examined bug. | ||
1379 | 968 | - problem_report.py, load(): Fix missing last character if the last line in a | ||
1380 | 969 | multi-line field is not terminated with a newline. | ||
1381 | 970 | - launchpad.py: Fix test_marking_python_task_mangle() check to work with | ||
1382 | 971 | current Launchpad. | ||
1383 | 972 | - apport-retrace: If the user did not specify a --cache directory, create a | ||
1384 | 973 | shared one instead of letting the two install_packages() calls create their | ||
1385 | 974 | own. This ensures that the apt and dpkg status is up to date, and avoids | ||
1386 | 975 | downloading the package indexes multiple times. (LP: #847951) | ||
1387 | 976 | - apport-retrace: Give proper error mesage instead of AssertionError crash if | ||
1388 | 977 | a report does not contain standard Apport format data. (LP: #843221) | ||
1389 | 978 | - fileutils.py, get_new_reports(): Fix crash if report file disappears in the | ||
1390 | 979 | middle of the operation. (LP: #640216) | ||
1391 | 980 | - apport/ui.py, load_report(): Intercept another case of broken report files. | ||
1392 | 981 | (LP: #445142) | ||
1393 | 982 | - apport/report.py, standard_title(): Escape regular expression control | ||
1394 | 983 | characters in custom exception names. (LP: #762998) | ||
1395 | 984 | |||
1396 | 985 | 1.22.1 (2011-09-06) | ||
1397 | 986 | ------------------- | ||
1398 | 987 | Improvements: | ||
1399 | 988 | - dupdb-admin: Add "removeid" command. | ||
1400 | 989 | |||
1401 | 990 | Bug fixes: | ||
1402 | 991 | - dupdb-admin: Use the in-memory CrashDB implementation for simple operations | ||
1403 | 992 | like dump or changeid, which do not require an actual backend. This makes | ||
1404 | 993 | the command work in checkouts without a /etc/apport/crashdb.conf. | ||
1405 | 994 | - dupdb-admin: Fix UnicodeEncodeError crash. | ||
1406 | 995 | - launchpad.py: Fix crash if a crash report does not have a DistroRelease. | ||
1407 | 996 | - Set the default "Apport" title for choice dialogs instead of the default | ||
1408 | 997 | apport-gtk title. Thanks Robert Roth. (LP: #608222) | ||
1409 | 998 | - apport-gtk: Update markup_escape_text() call to current glib. (LP: #829635) | ||
1410 | 999 | |||
1411 | 1000 | 1.22 (2011-08-25) | ||
1412 | 1001 | ----------------- | ||
1413 | 1002 | Improvements: | ||
1414 | 1003 | - Completely rework apport-retrace to use gdb's "debug-file-directory" and | ||
1415 | 1004 | "solib-absolute-prefix" settings and only unpack the necessary packages in a | ||
1416 | 1005 | temporary directory. This makes it possible to use it in a running system | ||
1417 | 1006 | without actually touching installed packages, does not need any root | ||
1418 | 1007 | privileges, and stops the requirement of using chroots with fakechroot and | ||
1419 | 1008 | fakeroot. This is a lot easier to maintain and use, and a lot faster, too. | ||
1420 | 1009 | As a consequence, drop the chroot module, and update crash-digger | ||
1421 | 1010 | accordingly. See "man apport-retrace" for the new usage. | ||
1422 | 1011 | It is now also easier to port to other packaging backends, as a lot of the | ||
1423 | 1012 | common logic moved out of the packaging API; | ||
1424 | 1013 | packaging.install_retracing_packages() got dropped in favor of the simpler | ||
1425 | 1014 | packaging.install_packages(). | ||
1426 | 1015 | - crash-digger: Show how many bugs are left in the pool with each new retrace. | ||
1427 | 1016 | |||
1428 | 1017 | Bug fixes: | ||
1429 | 1018 | - apport-gtk: Fix crash in GLib.markup_escape_text() call, regression from | ||
1430 | 1019 | 1.21.3. (LP: #828010) | ||
1431 | 1020 | - launchpad.py: When searchTasks() times out, exit with 99 as this is a | ||
1432 | 1021 | transient error. | ||
1433 | 1022 | - crash-digger: Intercept OverflowError from downloaded compressed | ||
1434 | 1023 | attachments. | ||
1435 | 1024 | |||
1436 | 1025 | 1.21.3 (2011-08-17) | ||
1437 | 1026 | ------------------- | ||
1438 | 1027 | Bug fixes: | ||
1439 | 1028 | - gtk/apport-gtk.desktop.in: Also show in Unity. (LP: #803519) | ||
1440 | 1029 | - apport-unpack: Fix crash on file errors. | ||
1441 | 1030 | - Add apport.packaging.get_library_paths() interface and implement it for | ||
1442 | 1031 | backends/packaging-apt-dpkg.py using dpkg multiarch directories. Use it in | ||
1443 | 1032 | chroot.py. | ||
1444 | 1033 | - hookutils.py: Don't attach empty values. Thanks Bryce Harrington. | ||
1445 | 1034 | (LP: #813798) | ||
1446 | 1035 | - apport-gtk: Correctly pass message dialog type. | ||
1447 | 1036 | - apport-gtk: Fix GLib and GObject imports to be compatible with the future | ||
1448 | 1037 | pygobject 3.0. | ||
1449 | 1038 | |||
1450 | 1039 | Improvements: | ||
1451 | 1040 | - hookutils.py: Add attach_mac_events() for reporting logs of MAC systems. | ||
1452 | 1041 | Looks for AppArmor messages for now. Thanks Marc Deslauriers! | ||
1453 | 1042 | - hookutils.py, attach_alsa(): Get a list of outputs/inputs that PulseAudio | ||
1454 | 1043 | knows about, which also shows the currently selected output/input, as well | ||
1455 | 1044 | as volumes. This should help with "no sound" bug troubleshooting. Thanks | ||
1456 | 1045 | Luke Yelavich. | ||
1457 | 1046 | |||
1458 | 1047 | 1.21.2 (2011-07-01) | ||
1459 | 1048 | ------------------- | ||
1460 | 1049 | Improvements: | ||
1461 | 1050 | - test/run: Check $PYTHON for using a different Python interpreter (such as | ||
1462 | 1051 | "python3") for the tests. | ||
1463 | 1052 | - generic hook: Don't report package installation failures due to segfaulting | ||
1464 | 1053 | maintainer scripts. We want the actual crash report only. Thanks Brian | ||
1465 | 1054 | Murray. | ||
1466 | 1055 | - hookutils.py, attach_wifi(): Also include wpasupplicant logs. Thanks Mathieu | ||
1467 | 1056 | Trudel-Lapierre! | ||
1468 | 1057 | |||
1469 | 1058 | Bug fixes: | ||
1470 | 1059 | - backends/packaging-apt-dpkg.py: Fix crash introduced in 1.21.1's multiarch | ||
1471 | 1060 | fixes. | ||
1472 | 1061 | - report.py: Fix bug patterns to correctly match against compressed report | ||
1473 | 1062 | fields. | ||
1474 | 1063 | |||
1475 | 1064 | 1.21.1 (2011-06-20) | ||
1476 | 1065 | ------------------- | ||
1477 | 1066 | Improvements: | ||
1478 | 1067 | - data/general-hooks/generic.py: Also check for low space on /var. Thanks | ||
1479 | 1068 | Brian Murray. | ||
1480 | 1069 | - hookutils.py, attach_file() and attach_file_if_exists(): Add a new | ||
1481 | 1070 | "overwrite" flag option. If not given, now default to overwriting an | ||
1482 | 1071 | existing key, as this is usually what you need when attaching files | ||
1483 | 1072 | (instead of attaching it several times with '_' appended to the keys). You | ||
1484 | 1073 | can get the old behaviour by setting overwrite=False. | ||
1485 | 1074 | |||
1486 | 1075 | Bug fixes: | ||
1487 | 1076 | - When showing the size of the full report, take the compressed size of binary | ||
1488 | 1077 | values instead of their uncompressed size, as the crash db upload will use | ||
1489 | 1078 | the compressed values. | ||
1490 | 1079 | - backends/packaging-apt-dpkg.py: Fix for current dpkg with multiarch support. | ||
1491 | 1080 | - test/run: Fix the test suite to run against the system installed libraries | ||
1492 | 1081 | with current Python versions (2.6, 2.7) where __file__ does not work any | ||
1493 | 1082 | more with imports. | ||
1494 | 1083 | |||
1495 | 1084 | 1.21 (2011-06-08) | ||
1496 | 1085 | ----------------- | ||
1497 | 1086 | Improvements: | ||
1498 | 1087 | - Supply --desktop option to kdesudo to improve the description which program | ||
1499 | 1088 | is requesting administrative privileges. | ||
1500 | 1089 | - apport-checkreports: Exit with status 2 if there are new reports, but apport | ||
1501 | 1090 | is disabled. This helps crash notification GUIs to not display new crash | ||
1502 | 1091 | reports in that case. Thanks to Michael Vogt for the original patch. | ||
1503 | 1092 | - Add data/is-enabled: Shell script to check if apport is enabled. Non-Python | ||
1504 | 1093 | programs (which can't use apport.packaging.enabled() ) can call this instead | ||
1505 | 1094 | of having to parse /etc/default/apport themselves, and just check the exit | ||
1506 | 1095 | code. Inspired by original patch from Michael Vogt, thanks! | ||
1507 | 1096 | |||
1508 | 1097 | Bug fixes: | ||
1509 | 1098 | - apport-gtk: HTML-escape text for dialogs with URLs. (LP: #750870) | ||
1510 | 1099 | - dump_acpi_tables.py: Check to see if acpi/tables dir is mounted first. | ||
1511 | 1100 | Thanks Brian Murray. (LP: #729622) | ||
1512 | 1101 | - man/apport-cli.1: Document recently added -w/--window option. Thanks Abhinav | ||
1513 | 1102 | Upadhyay! (LP: #765600) | ||
1514 | 1103 | - Use kde-open instead of kfmclient to open URLs under KDE. Thanks Philip | ||
1515 | 1104 | Muškovac. (LP: #765808) | ||
1516 | 1105 | |||
1517 | 1106 | 1.20.1 (2011-03-31) | ||
1518 | 1107 | ------------------- | ||
1519 | 1108 | Bug fixes: | ||
1520 | 1109 | - Add bash completion support for new -w/--window option that was introduced | ||
1521 | 1110 | in 1.20. Thanks Philip Muškovac. | ||
1522 | 1111 | - apport-unpack: Fix crash if target directory already exists. | ||
1523 | 1112 | - Fix crash if UnreportableReason is a non-ASCII string. (LP: #738632) | ||
1524 | 1113 | - Fix crash if application from desktop name is a non-ASCII string. | ||
1525 | 1114 | (LP: #737799) | ||
1526 | 1115 | - unkillable_shutdown: Fix rare crash if ExecutablePath does not exist (any | ||
1527 | 1116 | more). (LP: #537904) | ||
1528 | 1117 | - kernel_crashdump: Fix crash if the vmcore file disappeared underneath us. | ||
1529 | 1118 | (LP: #450295) | ||
1530 | 1119 | - unkillable_shutdown: Fix crash if the checked process terminated underneath | ||
1531 | 1120 | us. (LP: #540436) | ||
1532 | 1121 | - ui.py: Properly raise exceptions from the upload thread that happen at its | ||
1533 | 1122 | very end. (LP: #469943) | ||
1534 | 1123 | |||
1535 | 1124 | 1.20 (2011-03-17) | ||
1536 | 1125 | ----------------- | ||
1537 | 1126 | Improvements: | ||
1538 | 1127 | - Add support for -w/--window option which will enable user to select a | ||
1539 | 1128 | window as a target for filing a problem report. Thanks Abhinav Upadhyay for | ||
1540 | 1129 | the patch! (LP: #357847) | ||
1541 | 1130 | - Disable the filtering on SIGABRT without assertion messages. Turns out that | ||
1542 | 1131 | developers want these crash reports after all. (LP: #729223) | ||
1543 | 1132 | - Add support for a "DuplicateSignature" report fields. This allows package | ||
1544 | 1133 | hooks to implement custom duplicate problem handling which doesn't need to | ||
1545 | 1134 | be hardcoded in Apport itself. Update the launchpad backend to tag such bugs | ||
1546 | 1135 | as "need-duplicate-check". | ||
1547 | 1136 | |||
1548 | 1137 | Bug fixes: | ||
1549 | 1138 | - report.py, add_hooks_info(): Properly report TypeErrors from hooks. | ||
1550 | 1139 | - apport-retrace: Intercept SystemErrors from ill-formed gzip attachments as | ||
1551 | 1140 | well. | ||
1552 | 1141 | - Fix crash if crash database configuration does not specify a | ||
1553 | 1142 | bug_pattern_url. Just assume None. (LP: #731526) | ||
1554 | 1143 | - If a custom crash database does not specify a bug_pattern_url, fall back to | ||
1555 | 1144 | using the default database's. (LP: #731526) | ||
1556 | 1145 | - hookutils.py Update WifiSyslog regex to correctly catch application log | ||
1557 | 1146 | messages in syslog. Thanks Mathieu Trudel-Lapierre. (LP: #732917) | ||
1558 | 1147 | - hookutils.py, attach_hardware(): Avoid error message if machine does not | ||
1559 | 1148 | have a PCI bus. Thanks Marcin Juszkiewicz! (LP: #608449) | ||
1560 | 1149 | - backends/packaging-apt-dpkg.py: Replace deprecated getChanges() call with | ||
1561 | 1150 | get_changes(). | ||
1562 | 1151 | - apport-gtk: Fix broken dialog heading if the name of the crashed program | ||
1563 | 1152 | contains an & or other markup specific characters. | ||
1564 | 1153 | - apport-gtk: Don't crash if GTK cannot be initialized. This usually happens | ||
1565 | 1154 | without a $DISPLAY or when the session is being shut down. Just print an | ||
1566 | 1155 | error message. If there are pending crashes, they will be shown again the | ||
1567 | 1156 | next time a session starts. (LP: #730569) | ||
1568 | 1157 | |||
1569 | 1158 | 1.19 (2011-02-28) | ||
1570 | 1159 | ----------------- | ||
1571 | 1160 | Bug fixes: | ||
1572 | 1161 | - Update stack unwind patterns for current glib (slightly changed function | ||
1573 | 1162 | names), and also ignore a preceding '*'. (LP: #716251) | ||
1574 | 1163 | - Fix crash_signature() to fail if there is an empty or too short | ||
1575 | 1164 | StacktraceTop. | ||
1576 | 1165 | - apt backend: Do not generate a warning if the opportunistically added -dbg | ||
1577 | 1166 | package does not exist. | ||
1578 | 1167 | - apt backend: Only add -dbg in --no-pkg mode, as there will be conflicts in | ||
1579 | 1168 | normal package mode. | ||
1580 | 1169 | - apt backend: Call tar with target cwd instead of using -C; the latter causes | ||
1581 | 1170 | an extra openat() call which breaks with current fakechroot. | ||
1582 | 1171 | - launchpad.py: Fix retracer crash if DistroRelease field does not exist. | ||
1583 | 1172 | - Convert deprecated failIf()/assert_() TestCase method calls to | ||
1584 | 1173 | assertFalse()/assertTrue(). | ||
1585 | 1174 | |||
1586 | 1175 | Improvements: | ||
1587 | 1176 | - In apport-bug, if the user specifies a PID referring to a kernel thread, | ||
1588 | 1177 | do the right thing and file the bug against the kernel | ||
1589 | 1178 | - In hookutils.attach_dmesg, skip over an initial truncated message if one | ||
1590 | 1179 | is present (this happens when the ring buffer overflows) | ||
1591 | 1180 | - Change bug patterns to just use one central file instead of per-package | ||
1592 | 1181 | files. This allows bug patterns to be written which are not package | ||
1593 | 1182 | specific, and is easier to maintain as well. IMPORTANT: This changed the | ||
1594 | 1183 | format of crashdb.conf: bug_pattern_base is now obsolete, and the new | ||
1595 | 1184 | attribute bug_pattern_url now points to the full URL/path of the patterns | ||
1596 | 1185 | file. Thanks to Matt Zimmerman! | ||
1597 | 1186 | |||
1598 | 1187 | 1.18 (2011-02-16) | ||
1599 | 1188 | ----------------- | ||
1600 | 1189 | Bug fixes: | ||
1601 | 1190 | - Ensure that symptom scripts define a run() function, and don't show them if | ||
1602 | 1191 | not. | ||
1603 | 1192 | - Do not show symptom scripts which start with an underscore. These can be | ||
1604 | 1193 | used for private libraries for the actual symptom scripts. | ||
1605 | 1194 | - Update bash completion. Thanks Philip Muškovac. | ||
1606 | 1195 | - etc/default/apport: Remove obsolete "maxsize" setting. (LP: #719564) | ||
1607 | 1196 | |||
1608 | 1197 | Improvements: | ||
1609 | 1198 | - Remove explicit handling of KDE *.ui files in setup.py, as | ||
1610 | 1199 | python-distutils-extra 2.24 fixes this. Bump version check. | ||
1611 | 1200 | - hookutils.py: Add attach_root_command_outputs() to run several commands | ||
1612 | 1201 | at once. This avoids asking for the password several times. (LP: #716595) | ||
1613 | 1202 | |||
1614 | 1203 | 1.17.2 (2011-02-04) | ||
1615 | 1204 | ------------------- | ||
1616 | 1205 | Improvements: | ||
1617 | 1206 | - Be more Python 3 compatible (not fully working with Python 3 yet, though). | ||
1618 | 1207 | - apt/dpkg backend: Drop support for pre-0.7.9 python-apt API. | ||
1619 | 1208 | - Add --tag option to add extra tags to reports. (LP: #572504) | ||
1620 | 1209 | |||
1621 | 1210 | Bug fixes: | ||
1622 | 1211 | - hookutils.py, attach_dmesg(): Do not overwrite already existing dmesg. | ||
1623 | 1212 | - hookutils.py: Be more robust against file permission errors. (LP: #444678) | ||
1624 | 1213 | - ui.py: Do not show all the options in --help when invoked as *-bug. | ||
1625 | 1214 | (LP: #665953) | ||
1626 | 1215 | - launchpad.py: Adapt test cases to current standard_title() behaviour. | ||
1627 | 1216 | |||
1628 | 1217 | 1.17.1 (2011-01-10) | ||
1629 | 1218 | ------------------- | ||
1630 | 1219 | Bug fixes: | ||
1631 | 1220 | - Make the GTK frontend work with GTK 2.0 as well, and drop "3.0" requirement. | ||
1632 | 1221 | |||
1633 | 1222 | 1.17 (2010-12-31) | ||
1634 | 1223 | ----------------- | ||
1635 | 1224 | Improvements: | ||
1636 | 1225 | - Better standard bug titles for Python crashes. Thanks Matt Zimmerman! | ||
1637 | 1226 | (LP: #681574) | ||
1638 | 1227 | - Add handler for uncaught Java exceptions. There is no integration for | ||
1639 | 1228 | automatically intercepting all Java crashes yet, see java/README. | ||
1640 | 1229 | Thanks Matt Zimmerman! (LP: #548877) | ||
1641 | 1230 | |||
1642 | 1231 | Bug fixes: | ||
1643 | 1232 | - GTK frontend: Require GTK 3.0. | ||
1644 | 1233 | - launchpad.py: Default to "production" instance, not "edge", since edge is | ||
1645 | 1234 | obsolete now. | ||
1646 | 1235 | - hookutils.py, attach_alsa(): Fix crash if /proc/asound/cards does not exist. | ||
1647 | 1236 | (LP: #626215) | ||
1648 | 1237 | - ui.py, format_filesize(): Fix to work with stricter locale.format() in | ||
1649 | 1238 | Python 2.7. (LP: #688535). While we are at it, also change it to use base-10 | ||
1650 | 1239 | units. | ||
1651 | 1240 | - hookutils.py, package_versions(): Always include all requested package names | ||
1652 | 1241 | even if they're unknown to us. Thanks Matt Zimmerman! (LP: #695188) | ||
1653 | 1242 | - launchpad.py: When updating a bug, also add new tags. Thanks Brian Murray! | ||
1654 | 1243 | |||
1655 | 1244 | 1.16 (2010-11-19) | ||
1656 | 1245 | ----------------- | ||
1657 | 1246 | New features: | ||
1658 | 1247 | - Port GTK frontend from pygtk2 to GTK+3.0 and gobject-introspection. | ||
1659 | 1248 | |||
1660 | 1249 | Bug fixes: | ||
1661 | 1250 | - Fix symptoms again. Version 1.15 broke the default symptom directory. | ||
1662 | 1251 | - Fix memory test case to work with current Python versions, where the SQLite | ||
1663 | 1252 | integrity check throws a different exception. | ||
1664 | 1253 | |||
1665 | 1254 | 1.15 (2010-11-11) | ||
1666 | 1255 | ----------------- | ||
1667 | 1256 | New features: | ||
1668 | 1257 | - Add dump_acpi_tables.py script. This can be called by package hooks which | ||
1669 | 1258 | need ACPI table information (in particular, kernel bug reports). Thanks to | ||
1670 | 1259 | Brad Figg for the script! | ||
1671 | 1260 | - Order symptom descriptions alphabetically. Thanks to Javier Collado. | ||
1672 | 1261 | - Check $APPORT_SYMPTOMS_DIR environment variable for overriding the system | ||
1673 | 1262 | default path. Thanks to Javier Collado. | ||
1674 | 1263 | |||
1675 | 1264 | Bug fixes: | ||
1676 | 1265 | - testsuite: Check that crashdb.conf can have dynamic code to determine DB | ||
1677 | 1266 | names and options. | ||
1678 | 1267 | - ui.py test suite: Rewrite _gen_test_crash() to have the test process core | ||
1679 | 1268 | dump itself, instead of using gdb to do it. The latter fails in ptrace | ||
1680 | 1269 | restricted environments, such as Ubuntu 10.10. | ||
1681 | 1270 | - packaging-apt-dpkg.py: Fix handling of /etc/apport/native-origins.d to | ||
1682 | 1271 | actually work. Thanks Steve Langasek. (LP: #627777) | ||
1683 | 1272 | - apport-kde: Load correct translation catalogue. Thanks Jonathan Riddell. | ||
1684 | 1273 | (LP: #633483) | ||
1685 | 1274 | - launchpad.py: Use launchpadlib to file a bug instead of screen scraping. | ||
1686 | 1275 | The latter was completely broken with current Launchpad, so this makes the | ||
1687 | 1276 | test suite actually work again. Thanks to Diogo Matsubara! | ||
1688 | 1277 | - launchpad.py: Change $APPORT_STAGING to $APPORT_LAUNCHPAD_INSTANCE, so that | ||
1689 | 1278 | you can now specify "staging", "edge", or "dev" (for a local | ||
1690 | 1279 | http://launchpad.dev installation). Thanks to Diogo Matsubara! | ||
1691 | 1280 | - backends/packaging-apt-dpkg.py: Fix crash on empty lines in ProcMaps | ||
1692 | 1281 | attachment. | ||
1693 | 1282 | - doc/symptoms.txt: Fix typo, thanks Philip Muskovac. (LP: #590521) | ||
1694 | 1283 | - apport/hookutils.py: rename ProcCmdLine to ProcKernelCmdLine to not wipe | ||
1695 | 1284 | wipe out /proc/$pid/cmdline information. (LP: #657091) | ||
1696 | 1285 | - apport/hookutils.py: attach_file() will not overwrite existing report | ||
1697 | 1286 | keys, instead appending "_" until the key is unique. | ||
1698 | 1287 | - Fix --save option to recognise ~, thanks Philip Muškovac. (LP: #657278) | ||
1699 | 1288 | - Remove escalation_subscription from Ubuntu bug DB definition, turned out to | ||
1700 | 1289 | not be useful; thanks Brian Murray. | ||
1701 | 1290 | - launchpad.py: Fix APPORT_LAUNCHPAD_INSTANCE values with a https:// prefix. | ||
1702 | 1291 | - apt backend: Opportunistically try to install a -dbg package in addition to | ||
1703 | 1292 | -dbgsym, to increase the chance that at least one of it exists. Thanks | ||
1704 | 1293 | Daniel J Blueman! | ||
1705 | 1294 | |||
1706 | 1295 | 1.14.1 (2010-06-24) | ||
1707 | 1296 | ------------------- | ||
1708 | 1297 | Bug fixes: | ||
1709 | 1298 | - hookutils.py, attach_drm_info(): Sanitize connector names. Thanks Chris | ||
1710 | 1299 | Halse Rogers! (LP: #597558) | ||
1711 | 1300 | - bash completion: Complete all path names, apport-bug can be invoked with a | ||
1712 | 1301 | path to a program. Thanks Philip Muskovac. | ||
1713 | 1302 | |||
1714 | 1303 | 1.14 (2010-06-16) | ||
1715 | 1304 | ----------------- | ||
1716 | 1305 | New features: | ||
1717 | 1306 | - hookutils.py: Add new method attach_drm_info() to read and format | ||
1718 | 1307 | /sys/class/drm/*. | ||
1719 | 1308 | |||
1720 | 1309 | Bug fixes: | ||
1721 | 1310 | - packaging-apt-dpkg.py: Fix deprecated python-apt variables, thanks David | ||
1722 | 1311 | Stansby. (LP: #591695) | ||
1723 | 1312 | - launchpad.py: Fix crash on attachments which are named *.gz, but | ||
1724 | 1313 | uncompressed. (LP: #574360) | ||
1725 | 1314 | - hookutils.py, attach_gconf(): Fix defaults parsing for boolean keys. | ||
1726 | 1315 | (LP: #583109) | ||
1727 | 1316 | |||
1728 | 1317 | 1.13.4 (2010-05-04) | ||
1729 | 1318 | ------------------- | ||
1730 | 1319 | - bash completion: Fix error message if /usr/share/apport/symptoms does not | ||
1731 | 1320 | exist. Thanks Philip Muškovac! (LP: #562118) | ||
1732 | 1321 | - general-hooks/parse_segv.py: Report stack exhaustion more clearly and | ||
1733 | 1322 | correctly handle register dereferencing calls. | ||
1734 | 1323 | - Save/restore environment when calling hooks, in case they change the locale, | ||
1735 | 1324 | etc. (LP: #564422) | ||
1736 | 1325 | - hookutils.py, command_output(): Do not set $LC_MESSAGES for the calling | ||
1737 | 1326 | process/hook, just for the command to be called. | ||
1738 | 1327 | - ui.py: When displaying strings from system exceptions, decode them into an | ||
1739 | 1328 | unicode string, to avoid crashing the KDE UI. (LP: #567253) | ||
1740 | 1329 | - apport-retrace: Fix crash for retracing kernel vmcores, which do not have an | ||
1741 | 1330 | ExecutablePath. | ||
1742 | 1331 | - apport-bug manpage: Clarify when apport-collect may be used. Thanks Brian | ||
1743 | 1332 | Murray! (LP: #537273) | ||
1744 | 1333 | - generic hook: Check ProcMaps for unpackaged libraries, and ask the user if | ||
1745 | 1334 | he really wants to continue. If he does, tag the report as "local-libs" and | ||
1746 | 1335 | add a "LocalLibraries" field to the report with a list of them. | ||
1747 | 1336 | (LP: #545227) | ||
1748 | 1337 | |||
1749 | 1338 | 1.13.3 (2010-04-14) | ||
1750 | 1339 | ------------------- | ||
1751 | 1340 | - data/general-hooks/parse_segv.py: suggest segv-in-kernel possibility. | ||
1752 | 1341 | - ui.py: When running as root, only show system crash reports, to avoid | ||
1753 | 1342 | restarting user programs as root. (LP: #445017) | ||
1754 | 1343 | |||
1755 | 1344 | 1.13.2 (2010-03-31) | ||
1756 | 1345 | ------------------- | ||
1757 | 1346 | - problem_report.py, write_mime(): Add new optional argument "priority_fields" | ||
1758 | 1347 | for ordering report keys. Patch by Brian Murray, thanks! | ||
1759 | 1348 | - launchpad.py: Put some interesting fields first in the report, with the new | ||
1760 | 1349 | priority_fields argument. Patch by Brian Murray, thanks! | ||
1761 | 1350 | - packaging-apt-dpkg.py, _install_debug_kernel(): Do not crash on an outdated | ||
1762 | 1351 | kernel, just return that it is outdated. (LP: #532923) | ||
1763 | 1352 | - launchpad.py test suite: Add "Referer" HTTP header, now required by | ||
1764 | 1353 | launchpad. | ||
1765 | 1354 | - launchpad.py: Fix crash if configuration does not have an "escalated_tag" | ||
1766 | 1355 | option. | ||
1767 | 1356 | - launchpad.py: Port to launchpadlib 1.0 API, thanks Michael Bienia for the | ||
1768 | 1357 | initial patch! (LP: #545009) | ||
1769 | 1358 | - gtk/apport-gtk-mime.desktop.in, kde/apport-kde-mime.desktop.in: Change | ||
1770 | 1359 | categories so that these do not ever appear in menu editors. (LP: #449215) | ||
1771 | 1360 | - launchpad.py: Some LP bugs have broken attachments (this is a bug in | ||
1772 | 1361 | Launchpad itself). Ignore those instead of crashing. | ||
1773 | 1362 | - apport-gtk: Turn http:// and https:// links into clickable hyperlinks in | ||
1774 | 1363 | information and error dialogs. (LP: #516323) | ||
1775 | 1364 | - apport-retrace: Fix crash when trying to rebuild package info for reports | ||
1776 | 1365 | without an ExecutablePath. (LP: #436157) | ||
1777 | 1366 | - ui.py: Fix crash when package information cannot be determined due to broken | ||
1778 | 1367 | apt status. (LP: #362743) | ||
1779 | 1368 | - ui.py: Fix crash when /etc/apport/crashdb.conf is damaged; print an | ||
1780 | 1369 | appropriate error message instead. (LP: #528327) | ||
1781 | 1370 | - data/kernel_crashdump: Fix crash if log file disappeared underneath us. | ||
1782 | 1371 | (LP: #510327) | ||
1783 | 1372 | - data/apport: Fix IOError when apport is called with invalid number of | ||
1784 | 1373 | arguments, and stderr is not a valid fd. (LP: #467363) | ||
1785 | 1374 | - hookutils.py: Factor out the DMI collection code from attach_hardware() | ||
1786 | 1375 | into attach_dmi(), and call that in attach_alsa() as well. Thanks to Brad | ||
1787 | 1376 | Figg for the patch! (LP: #552091) | ||
1788 | 1377 | - apport/ui.py: Fix the help output if Apport is invoked under an alternative | ||
1789 | 1378 | name (like apport-collect). (LP: #539427) | ||
1790 | 1379 | |||
1791 | 1380 | 1.13.1 (2010-03-20) | ||
1792 | 1381 | ------------------- | ||
1793 | 1382 | Bug fixes: | ||
1794 | 1383 | - Update parse-segv to handle gdb 7.1 output. | ||
1795 | 1384 | - Enhance test suite to work with gdb 7.1 as well, and catch future outputs. | ||
1796 | 1385 | - UI: Add exception string to the "network error" dialog, to better tell what | ||
1797 | 1386 | the problem is. | ||
1798 | 1387 | - UI: Add back -p option to apport-collect/apport-update-bug (regression from | ||
1799 | 1388 | 1.13). (LP: #538944) | ||
1800 | 1389 | - launchpad.py: Add yet another workaround for LP#336866. (LP: #516381) | ||
1801 | 1390 | - launchpad.py, download(): Ignore attachments with invalid key names. | ||
1802 | 1391 | - Fix regression from 1.10 which made it impossible for a package hook to set | ||
1803 | 1392 | a third-party crash database for non-native packages. (LP: #517272) | ||
1804 | 1393 | - apport-cli: Create the 'details' string only if user wants to view details, | ||
1805 | 1394 | and do not show files larger than 1MB. Thanks Scott Moser! (LP: #486122) | ||
1806 | 1395 | - packaging-apt-dpkg.py: Silence apt.Cache() spewage to stdout with newer | ||
1807 | 1396 | python-apt versions. (LP: #531518) | ||
1808 | 1397 | |||
1809 | 1398 | Improvements: | ||
1810 | 1399 | - unkillable_shutdown: Add list of running processes and blacklisted pids to | ||
1811 | 1400 | report. (LP: #537262) | ||
1812 | 1401 | - Sort the report by key in the details view. (LP: #519416) | ||
1813 | 1402 | |||
1814 | 1403 | 1.13 (2010-03-10) | ||
1815 | 1404 | ----------------- | ||
1816 | 1405 | New features: | ||
1817 | 1406 | - Add "unkillable_shutdown" script to collect information about processes | ||
1818 | 1407 | which are still running after sending SIGTERM to them. This can be hooked | ||
1819 | 1408 | into e. g. /etc/init.d/sendsigs on Debian/Ubuntu systems. | ||
1820 | 1409 | |||
1821 | 1410 | Improvements: | ||
1822 | 1411 | - apport_python_hook.py: Directly check /etc/default/apport instead of | ||
1823 | 1412 | querying packaging.enabled(), to avoid importing lots of modules for | ||
1824 | 1413 | non-packaged scripts. Thanks Stuart Colville! (LP: #528355) | ||
1825 | 1414 | |||
1826 | 1415 | Bug fixes: | ||
1827 | 1416 | - Fix SegV parser to notice walking off the stack during "call" or "ret" | ||
1828 | 1417 | (LP: #531672). | ||
1829 | 1418 | - Fix --help output for bug updating mode (invocation as apport-collect or | ||
1830 | 1419 | apport-update-bug). (LP: #504116) | ||
1831 | 1420 | - Fix bug escalation tagging, thanks to Brian Murray. | ||
1832 | 1421 | - Fix option processing when being invoked as apport-bug. Thanks to Daniel | ||
1833 | 1422 | Hahler for the patch! (LP: #532944) | ||
1834 | 1423 | |||
1835 | 1424 | 1.12.1 (2010-02-22) | ||
1836 | 1425 | ------------------- | ||
1837 | 1426 | Bug fixes: | ||
1838 | 1427 | - launchpad.py: Do not keep escalating bugs, just escalate at the 10th | ||
1839 | 1428 | duplicate. | ||
1840 | 1429 | - Improve error message if a symptom script did not determine a package name. | ||
1841 | 1430 | (LP: #503834) | ||
1842 | 1431 | - general-hooks/generic.py: Fix crash on libGL check with empty StacktraceTop. | ||
1843 | 1432 | - Review and clean up usage of chmod(). This fixes a small race condition in the | ||
1844 | 1433 | Python exception hook where a local attacker could read the information from | ||
1845 | 1434 | another user's crash report. (LP: #516029) | ||
1846 | 1435 | - hookutils, package_versions(): Ignore "None" packages, for more robust | ||
1847 | 1436 | package hooks. (LP: #518295) | ||
1848 | 1437 | |||
1849 | 1438 | 1.12 (2010-01-20) | ||
1850 | 1439 | ----------------- | ||
1851 | 1440 | Improvements: | ||
1852 | 1441 | - launchpad.py: Add options 'escalation_subscription' and 'escalation_tag' for | ||
1853 | 1442 | handling bugs with more than 10 duplicates. | ||
1854 | 1443 | - crashdb.conf: For Ubuntu, escalate bugs with >= 10 duplicates to | ||
1855 | 1444 | "ubuntu-bugcontrol" and tag them with "bugpattern-needed". (LP: #487900) | ||
1856 | 1445 | - general-hooks/generic.py: Filter out crashes on missing GLX (LP: #327673) | ||
1857 | 1446 | - Add bash completion script. Thanks to Philip Muškovac. (LP: #218933) | ||
1858 | 1447 | |||
1859 | 1448 | Bug fixes: | ||
1860 | 1449 | - launchpad.py: Drop APPORT_FILES whitelist for download() and instead just | ||
1861 | 1450 | filter out file extensions that we know about (*.txt and *.gz). | ||
1862 | 1451 | (LP: #444975) | ||
1863 | 1452 | - launchpad.py: Do not put the Tags: field into the bug description, since | ||
1864 | 1453 | they are already proper tags. In download(), convert the real tags back to | ||
1865 | 1454 | the Tags: field. (LP: #505671) | ||
1866 | 1455 | - test/crash: Update expected core dump flags for changed rlimit behaviour in | ||
1867 | 1456 | Linux 2.6.32. | ||
1868 | 1457 | - launchpad.py: Fix marking of 'checked for duplicate' for bugs with upstream | ||
1869 | 1458 | tasks. | ||
1870 | 1459 | - launchpad.py, get_fixed_version(): Do not consider a bug as invalid just | ||
1871 | 1460 | because it has any invalid distro package task. | ||
1872 | 1461 | |||
1873 | 1462 | 1.11 (2009-12-23) | ||
1874 | 1463 | ----------------- | ||
1875 | 1464 | Improvements: | ||
1876 | 1465 | - Add "--save" UI option to store the collected information into an .apport | ||
1877 | 1466 | file instead of sending it right away. The file can then later be sent | ||
1878 | 1467 | through apport-bug. Update manpages accordingly. | ||
1879 | 1468 | - Update all copyright and description headers and consistently format them. | ||
1880 | 1469 | - Rename all TestCase classes to "_T", which makes it much easier to run | ||
1881 | 1470 | individual tests from the command line. | ||
1882 | 1471 | - Testsuite: Verify that report details are/are not shown. This uncovered that | ||
1883 | 1472 | details about package installation failures were not shown before sending | ||
1884 | 1473 | them, which is fixed now. | ||
1885 | 1474 | |||
1886 | 1475 | Bug fixes: | ||
1887 | 1476 | - test/hooks: Do not try to add hook information to kernel_crashdump test | ||
1888 | 1477 | case, since we do not have an UI here. This test case broke when the system | ||
1889 | 1478 | had an interactive package hook for the kernel. | ||
1890 | 1479 | - When reporting a bug from a saved .apport file, let the user review/confirm | ||
1891 | 1480 | the content before sending. | ||
1892 | 1481 | |||
1893 | 1482 | 1.10.1 (2009-12-23) | ||
1894 | 1483 | ------------------- | ||
1895 | 1484 | Improvements: | ||
1896 | 1485 | - Install apport-collect symlink. | ||
1897 | 1486 | - Update translations from Launchpad. | ||
1898 | 1487 | |||
1899 | 1488 | Bug fixes: | ||
1900 | 1489 | - Move all remaining option/argument parsing from apport-bug into ui.py. This | ||
1901 | 1490 | allows the user to add options to apport-bug/apport-collect, and also avoids | ||
1902 | 1491 | unwieldy dissection of options/arguments in shell. | ||
1903 | 1492 | |||
1904 | 1493 | 1.10 (2009-12-19) | ||
1905 | 1494 | ----------------- | ||
1906 | 1495 | New features: | ||
1907 | 1496 | - Add a mode for updating an existing problem report to ui.py (-u/--update). | ||
1908 | 1497 | This is similar to the Ubuntu specific "apport-collect" tool, but | ||
1909 | 1498 | implemented the right way now: In particular, this has access to the UI and | ||
1910 | 1499 | thus can use interactive hooks (LP: #385811) and show you what is being sent | ||
1911 | 1500 | for confirmation/cancelling (LP: #371827) | ||
1912 | 1501 | - apport-bug: If invoked as "apport-collect" or "apport-update-bug" (i. e. | ||
1913 | 1502 | through a symlink), run apport in update mode (-u <number>). This provides a | ||
1914 | 1503 | convenient no-options command line program. Please note that setup.py does | ||
1915 | 1504 | not currently install such a symlink. Update the apport-bug manpage | ||
1916 | 1505 | accordingly. | ||
1917 | 1506 | |||
1918 | 1507 | Improvements: | ||
1919 | 1508 | - launchpad.py: Use new login_with() to clean up code, and specify allowed | ||
1920 | 1509 | access levels (WRITE_PRIVATE is the only sensible one anyway). (LP: #410205) | ||
1921 | 1510 | - New hookutils functions: | ||
1922 | 1511 | xsession_errors (match lines from ~/.xsession-errors) | ||
1923 | 1512 | shared_libraries (determine which libraries a binary links with) | ||
1924 | 1513 | links_with_shared_library (test if a binary links with a particular library) | ||
1925 | 1514 | - New CrashDatabase API: get_affected_packages(), can_update(), is_reporter() | ||
1926 | 1515 | - Rename CrashDatabase.update() to update_traces(). | ||
1927 | 1516 | - Add CrashDatabase.update() for adding all new fields of a report. This is | ||
1928 | 1517 | primarily useful for collecting local standard and package hook data for an | ||
1929 | 1518 | already existing bug report which was not filed through Apport. This checks | ||
1930 | 1519 | can_update()/is_reporter() if the user is eligible for updating that | ||
1931 | 1520 | particular bug. (LP: #485880) | ||
1932 | 1521 | |||
1933 | 1522 | Bug fixes: | ||
1934 | 1523 | - Ignore SIGXCPU and SIGXFSZ; thanks to Kees Cook. (LP: #498074) | ||
1935 | 1524 | - launchpad.py: Do not mark non-Ubuntu bugs as needs-retrace, since there is | ||
1936 | 1525 | no retracer right now. (LP: #489794) | ||
1937 | 1526 | - packaging-apt-dpkg.py, install_retracing_packages(): Do not crash on | ||
1938 | 1527 | malformed Dependencies.txt lines. (LP: #441709) | ||
1939 | 1528 | - use-local: Fix for new source tree location of "apport" binary. | ||
1940 | 1529 | |||
1941 | 1530 | 1.9.6 (2009-12-01) | ||
1942 | 1531 | ------------------ | ||
1943 | 1532 | Improvements: | ||
1944 | 1533 | - Add pm-utils hook to record current operation, so that apportcheckresume can | ||
1945 | 1534 | check it. Before this was kept in Ubuntu's pm-utils package. | ||
1946 | 1535 | - general-hooks/generic.py: Check if using ecryptfs, and which directory. | ||
1947 | 1536 | (LP: #444656) | ||
1948 | 1537 | |||
1949 | 1538 | Bug fixes: | ||
1950 | 1539 | - launchpad.py: Ensure that text attachments on initial bug filing are valid | ||
1951 | 1540 | UTF-8. (LP: #453203) | ||
1952 | 1541 | - man/apport-retrace.1: Document -R option. | ||
1953 | 1542 | |||
1954 | 1543 | 1.9.5 (2009-11-20) | ||
1955 | 1544 | ------------------ | ||
1956 | 1545 | Bug fixes: | ||
1957 | 1546 | - apport-retrace: Fix crash if InterpreterPath/ExecutablePath do not exist. | ||
1958 | 1547 | - hookutils.py, attach_alsa(): Attach /proc/cpuinfo too, for CPU flags. | ||
1959 | 1548 | - Fix crash if InterpreterPath does not exist any more at the time of | ||
1960 | 1549 | reporting. (LP: #428289) | ||
1961 | 1550 | - apport-gtk: Connect signals properly, to repair cancel/window close buttons. | ||
1962 | 1551 | (LP: #427814) | ||
1963 | 1552 | - Update German translations and fix "konnre" typo. (LP: #484119) | ||
1964 | 1553 | |||
1965 | 1554 | 1.9.4 (2009-11-06) | ||
1966 | 1555 | ------------------ | ||
1967 | 1556 | Bug fixes: | ||
1968 | 1557 | - Fix crash when ExecutablePath isn't part of a package. (LP: #424965) | ||
1969 | 1558 | - hookutils.py, attach_hardware(): Anonymize disk labels. Thanks to Marco | ||
1970 | 1559 | Rodrigues. (LP: #394411) | ||
1971 | 1560 | - hookutils.py, attach_wifi(): Anonymize encryption key (which appeared in hex | ||
1972 | 1561 | when being called as root). Thanks to Marco Rodrigues. (LP: #446299) | ||
1973 | 1562 | - launchpad.py: If unset, set bug task source package also for interpreter | ||
1974 | 1563 | crashes. | ||
1975 | 1564 | - apport-gtk: Give details window a minimize/maximize button, which were | ||
1976 | 1565 | missing in some window managers. Thanks to Marien Zwart. (LP: #447749) | ||
1977 | 1566 | - apport-kde: Properly terminate program after closing the last dialog. | ||
1978 | 1567 | (LP: #458662) | ||
1979 | 1568 | - hookutils.py, attach_alsa(): Attach /proc/asound/version. (LP: #467233) | ||
1980 | 1569 | - general-hooks/generic.py: Only collect ~/.xsession-errors bits when we have | ||
1981 | 1570 | an ExecutablePath linked to libgtk. | ||
1982 | 1571 | |||
1983 | 1572 | 1.9.3 (2009-10-14) | ||
1984 | 1573 | ------------------ | ||
1985 | 1574 | Changes: | ||
1986 | 1575 | - Drop handling of the APPORT_REPORT_THIRDPARTY environment variable and | ||
1987 | 1576 | "thirdparty" configuration file option. This has never been documented, and | ||
1988 | 1577 | conceptually does not work. There is a proper mechanism for this in place | ||
1989 | 1578 | now, e. g. launchpad.py's "project" option. | ||
1990 | 1579 | |||
1991 | 1580 | Bug fixes: | ||
1992 | 1581 | - hookutils.py: Fix error codes from "comm", thanks to Brian Murray. | ||
1993 | 1582 | - general-hooks/generic.py: Catch xkbcomp error messages. (LP: #431807) | ||
1994 | 1583 | - launchpad.py: Assert that we have exactly one of "distro" or "project" | ||
1995 | 1584 | option. | ||
1996 | 1585 | - doc/crashdb-conf.txt: Improve documentation of crash database options. | ||
1997 | 1586 | - apport-gtk: Make Cancel/Send buttons focusable. Thanks to Marco Rodrigues. | ||
1998 | 1587 | (LP: #447780) | ||
1999 | 1588 | |||
2000 | 1589 | 1.9.2 (2009-10-02) | ||
2001 | 1590 | ------------------ | ||
2002 | 1591 | Improvements: | ||
2003 | 1592 | - apport-cli: Print the URL and ask whether to open a browser. In many | ||
2004 | 1593 | situations (such as usage on a server through ssh), it's preferable to not | ||
2005 | 1594 | open the browser on the reporting computer. Thanks to Matt Zimmerman for the | ||
2006 | 1595 | initial patch! (LP: #286415) | ||
2007 | 1596 | - general-hooks/generic.py: Collect important glib errors/assertions (which | ||
2008 | 1597 | should not have private data) from ~/.xsession-errors (LP: #431807) | ||
2009 | 1598 | - launchpad.py: Link hardware data submission key if it exists. (LP: #424382) | ||
2010 | 1599 | |||
2011 | 1600 | Bug fixes: | ||
2012 | 1601 | - apport-cli: Fix crash with non-ASCII characters in prompts. | ||
2013 | 1602 | - Fix "apport-bug symptomname" to actually work. | ||
2014 | 1603 | - launchpad.py: Fix crash on invalid credentials file. Thanks to Marco | ||
2015 | 1604 | Rodrigues for the initial patch! (LP: #414055) | ||
2016 | 1605 | |||
2017 | 1606 | 1.9.1 (2009-09-22) | ||
2018 | 1607 | ------------------ | ||
2019 | 1608 | Bug fixes: | ||
2020 | 1609 | - hookutils.py, attach_hardware(): Do not attach empty Pccardctl*. | ||
2021 | 1610 | - apport/report.py, add_gdb_info(): Do not throw away stderr from gdb. | ||
2022 | 1611 | - data/general-hooks/parse_segv.py: | ||
2023 | 1612 | + Handle arithmetic wrapping correctly. | ||
2024 | 1613 | + Handle empty base, scale, or index registers in disassembly. | ||
2025 | 1614 | + Handle in/out ioport faults. | ||
2026 | 1615 | - Various improvements to user-visible strings, thanks to Marco Rodrigues! | ||
2027 | 1616 | (LP: #178507) | ||
2028 | 1617 | - Various apport-retrace robustifications. | ||
2029 | 1618 | - setup.py: Fix DistUtilsExtra version check. (LP: #428337) | ||
2030 | 1619 | - hookutils.py, attach_gconf(): Do not overwrite previous values from other | ||
2031 | 1620 | packages, thanks LoĂŻc Minier! | ||
2032 | 1621 | - hookutils.py, attach_gconf(): Fix crash with nonexisting <applyto> tags. | ||
2033 | 1622 | |||
2034 | 1623 | 1.9 (2009-09-08) | ||
2035 | 1624 | ---------------- | ||
2036 | 1625 | New features: | ||
2037 | 1626 | - Add "do what I mean" mode to command line argument parsing (applies to all | ||
2038 | 1627 | interfaces: -cli, -gtk, -kde). When giving a single argument and no options, | ||
2039 | 1628 | determine the most likely mode, like reporting a bug against a symptom, | ||
2040 | 1629 | package, executable name, or PID. | ||
2041 | 1630 | - Add program "apport-bug" which determines the most appropriate user | ||
2042 | 1631 | interface (GTK, KDE, CLI) and files a bug through it, using the single | ||
2043 | 1632 | argument "do what I mean" mode. This is an improved version of Ubuntu's | ||
2044 | 1633 | "ubuntu-bug" script. | ||
2045 | 1634 | |||
2046 | 1635 | Bug fixes: | ||
2047 | 1636 | - Update apport-cli manpage to current set of options and behaviour. Also | ||
2048 | 1637 | point out that apport-gtk and apport-kde share the same CLI. | ||
2049 | 1638 | - setup.py now installs apport-{gtk,kde} into $prefix/share/apport/, they are | ||
2050 | 1639 | not supposed to be called directly. This also reflects the path which the | ||
2051 | 1640 | .desktop files expect. | ||
2052 | 1641 | - setup.py now installs the internal helper scripts like "kernel_crashdump", | ||
2053 | 1642 | "apport", or "apportcheckresume" into $prefix/share/apport instead of | ||
2054 | 1643 | $prefix/bin. | ||
2055 | 1644 | - Update usage of gettext to work around Python bug of gettext() not returning | ||
2056 | 1645 | unicodes, but str. Fixes UnicodeDecodeErrors on translated --help output. | ||
2057 | 1646 | |||
2058 | 1647 | 1.8.2 (2009-09-05) | ||
2059 | 1648 | ------------------ | ||
2060 | 1649 | Bug fixes. | ||
2061 | 1650 | |||
2062 | 1651 | 1.8.1 (2009-09-03) | ||
2063 | 1652 | ------------------ | ||
2064 | 1653 | Lots of bug fixes. | ||
2065 | 1654 | |||
2066 | 1655 | 1.8 (2009-08-26) | ||
2067 | 1656 | ---------------- | ||
2068 | 1657 | New features: | ||
2069 | 1658 | - Do not generally ignore SIGABRT any more. Try to extract the assertion | ||
2070 | 1659 | message from the core dump, and add it as "AssertionMessage" field. Mark | ||
2071 | 1660 | reports as unreportable if they do not have an assertion message and crashed | ||
2072 | 1661 | with SIGABRT. This requires your glibc to have this patch: | ||
2073 | 1662 | http://sourceware.org/git/?p=glibc.git;a=commitdiff;h=48dcd0ba | ||
2074 | 1663 | - report.py, add_hooks_info(): Add optional package/srcpackage argument. Hooks | ||
2075 | 1664 | can use that to change the affected package or call hooks from different | ||
2076 | 1665 | packages. | ||
2077 | 1666 | - KDE frontend implementation of ui_question_userpass(), for crash databases | ||
2078 | 1667 | which need to ask for credentials. | ||
2079 | 1668 | - hookutils.py: New funtion attach_wifi() to add wireless network related | ||
2080 | 1669 | information to reports. | ||
2081 | 1670 | |||
2082 | 1671 | Important bug fixes: | ||
2083 | 1672 | - Fix the test suite on current kernels; test/crash previously often failed | ||
2084 | 1673 | with python segfaults, since it killed the test processes too early. | ||
2085 | 1674 | |||
2086 | 1675 | 1.7 (2009-08-05): | ||
2087 | 1676 | ----------------- | ||
2088 | 1677 | New features: | ||
2089 | 1678 | - Support for "symptom" scripts, which figure out the package for a bug report | ||
2090 | 1679 | based on interactive questions. | ||
2091 | 1680 | |||
2092 | 1681 | 1.6 (2009-07-15) | ||
2093 | 1682 | ---------------- | ||
2094 | 1683 | New features: | ||
2095 | 1684 | - Integrate analysis and retracing of kernel vmcore crashes with the "crash" | ||
2096 | 1685 | tool. Courtesy of Michael Vogt. | ||
2097 | 1686 | |||
2098 | 1687 | Various little bug fixes. | ||
2099 | 1688 | |||
2100 | 1689 | 1.5 (2009-06-29) | ||
2101 | 1690 | ---------------- | ||
2102 | 1691 | New features: | ||
2103 | 1692 | - Drop all Makefiles, po/POTFILES.in, and most code from setup.py, and use | ||
2104 | 1693 | DistUtilsExtras.auto which "just does the right thing" for most build system | ||
2105 | 1694 | tasks. This requires python-distutils-extra >= 2.2, see | ||
2106 | 1695 | https://launchpad.net/python-distutils-extra | ||
2107 | 1696 | |||
2108 | 1697 | Cleanup: | ||
2109 | 1698 | - Move all test scripts into test/, to unclutter source tree. | ||
2110 | 1699 | - setup.py now auto-detects the required packaging backend if | ||
2111 | 1700 | apport/packaging_impl.py is not manually installed. | ||
2112 | 1701 | |||
2113 | 1702 | 1.4 (2009-06-26) | ||
2114 | 1703 | ---------------- | ||
2115 | 1704 | New features: | ||
2116 | 1705 | - Replaced Qt4 frontend with a KDE frontend for better KDE integration. | ||
2117 | 1706 | |||
2118 | 1707 | Major bug fixes: | ||
2119 | 1708 | - packaging-apt-dpkg.py: Add backwards compatibility code for python-apt < | ||
2120 | 1709 | 0.7.9 to not break backportability. | ||
2121 | 1710 | |||
2122 | 1711 | 1.3 (2009-06-10) | ||
2123 | 1712 | ---------------- | ||
2124 | 1713 | New features: | ||
2125 | 1714 | - Interactive package hooks: | ||
2126 | 1715 | * Add apport.ui.HookUI class which provides GUI functionality such as yes/no | ||
2127 | 1716 | questions or file dialogs to hooks. | ||
2128 | 1717 | * add_info() in package hooks now can (optionally) take a second argument which | ||
2129 | 1718 | is the HookUI instance. | ||
2130 | 1719 | * See doc/package-hooks.txt for details. | ||
2131 | 1720 | - New function apport.hookutils.root_command_output() to run a command as root, | ||
2132 | 1721 | through gksu/kdesudo/sudo, depending on the desktop environment. | ||
2133 | 1722 | - Add general hook for analyzing reason of a segfault. | ||
2134 | 1723 | |||
2135 | 1724 | Bug fixes: | ||
2136 | 1725 | - Drop "UnsupportableReason" field, it is too similar to UnreportableReason and | ||
2137 | 1726 | just confusing. | ||
2138 | 1727 | - Report key names can now contain dashes ('-') and underscores ('_'). | ||
2139 | 1728 | (LP #380811) | ||
2140 | 1729 | |||
2141 | 1730 | 1.2.1 (2009-05-15) | ||
2142 | 1731 | ------------------ | ||
2143 | 1732 | Bug fixes: | ||
2144 | 1733 | - Fix setup.py and PO file merging for recent .glade -> .ui renaming. | ||
2145 | 1734 | |||
2146 | 1735 | Translations: | ||
2147 | 1736 | - Update German translations. | ||
2148 | 1737 | |||
2149 | 1738 | 1.2.0 (2009-05-15) | ||
2150 | 1739 | ------------------ | ||
2151 | 1740 | Moving away from deprecated APIs: | ||
2152 | 1741 | - packaging-apt-dpkg.py: Use python-apt >= 0.7.9 official API and drop usage of | ||
2153 | 1742 | internal symbols. | ||
2154 | 1743 | - hookutils.py: Drop hal related functions and queries, replace with udev | ||
2155 | 1744 | database, udev log file, and DMI information from sysfs. | ||
2156 | 1745 | - gtk UI: Convert from libglade to gtk.Builder. | ||
2157 | 1746 | |||
2158 | 1747 | Bug fixes: | ||
2159 | 1748 | - hookutils.py: Drop /proc/version_signature collection, it is Ubuntu specific. | ||
2160 | 1749 | - apportcheckresume: Fix log collection from pm-utils. | ||
2161 | 1750 | - Fix various crashes and report properties for reporting against uninstalled | ||
2162 | 1751 | packages. | ||
2163 | 1752 | |||
2164 | 1753 | 1.1.1 (2009-04-30) | ||
2165 | 1754 | ------------------ | ||
2166 | 1755 | Security fix: | ||
2167 | 1756 | - etc/cron.daily/apport: Only attempt to remove files and symlinks, do not | ||
2168 | 1757 | descend into subdirectories of /var/crash/. Doing so might be exploited by a | ||
2169 | 1758 | race condition between find traversing a huge directory tree, changing an | ||
2170 | 1759 | existing subdir into a symlink to e. g. /etc/, and finally getting that piped | ||
2171 | 1760 | to rm. This also changes the find command to not use GNU extensions. Thanks | ||
2172 | 1761 | to Stephane Chazelas for discovering this! (LP #357024, CVE-2009-1295) | ||
2173 | 1762 | |||
2174 | 1763 | Bug fixes: | ||
2175 | 1764 | - launchpad.py: Send and read Date: field again, reverting r1128; it is useful | ||
2176 | 1765 | after all. (LP #349139) | ||
2177 | 1766 | - Only add ProcAttrCurrent to reports if it is not "unconfined", to remove some | ||
2178 | 1767 | noise from reports. | ||
2179 | 1768 | - Detect invalid PIDs in the UI (such as for kernel processes) and give a | ||
2180 | 1769 | friendly error message instead of silently doing nothing. (LP #360608) | ||
2181 | 1770 | - Always run common hooks, and run source package hooks if we do not have a | ||
2182 | 1771 | binary package name. (LP #350131) | ||
2183 | 1772 | - launchpad.py: Consider socket errors when connecting as transient, so | ||
2184 | 1773 | that crash-digger doesn't stop completely on them. | ||
2185 | 1774 | |||
2186 | 1775 | 1.1 (2009-04-20) | ||
2187 | 1776 | ---------------- | ||
2188 | 1777 | New features: | ||
2189 | 1778 | - Add hookutils methods for attaching relevant packages, greatly improve | ||
2190 | 1779 | attach_alsa() for sound problem debugging. | ||
2191 | 1780 | - Move launchpad crash database implementation from ever-breaking | ||
2192 | 1781 | python-launchpad-bugs (screenscraping) to launchpadlib (official and stable | ||
2193 | 1782 | Launchpad API). | ||
2194 | 1783 | |||
2195 | 1784 | Bug fixes: | ||
2196 | 1785 | - Drop some remaining distro specific pieces of code. | ||
2197 | 1786 | - Add new field Report.pid which gets set on add_proc_info() and can be used by | ||
2198 | 1787 | hooks. | ||
2199 | 1788 | - setup.py: Properly clean up all generated files, install missing | ||
2200 | 1789 | mimetypes/text-x-apport.svg icon symlink. | ||
2201 | 1790 | - Add README file. | ||
2202 | 1791 | - Add translations from Launchpad. | ||
2203 | 1792 | - Remove preloadlib/*; it's undermaintained, and not really useful any more | ||
2204 | 1793 | these days. | ||
2205 | 1794 | - Various bug fixes; most visible being the misnamed etc/default/apport.default | ||
2206 | 1795 | file (which should just be etc/default/apport). | ||
2207 | 1796 | |||
2208 | 1797 | 1.0 (2009-04-06) | ||
2209 | 1798 | ---------------- | ||
2210 | 1799 | First upstream release, based on Ubuntu packaging branch; that had been the | ||
2211 | 1800 | de-facto trunk for many years, but this becomes unpractical with several | ||
2212 | 1801 | distributions using it now. | ||
2213 | 0 | 1802 | ||
2214 | === renamed file 'NEWS' => 'NEWS.moved' | |||
2215 | === added file 'README' | |||
2216 | --- README 1970-01-01 00:00:00 +0000 | |||
2217 | +++ README 2013-03-13 18:57:21 +0000 | |||
2218 | @@ -0,0 +1,87 @@ | |||
2219 | 1 | Apport crash detection/reporting | ||
2220 | 2 | ================================ | ||
2221 | 3 | |||
2222 | 4 | Apport intercepts Program crashes, collects debugging information about the | ||
2223 | 5 | crash and the operating system environment, and sends it to bug trackers in a | ||
2224 | 6 | standardized form. It also offers the user to report a bug about a package, | ||
2225 | 7 | with again collecting as much information about it as possible. | ||
2226 | 8 | |||
2227 | 9 | It currently supports | ||
2228 | 10 | |||
2229 | 11 | - Crashes from standard signals (SIGSEGV, SIGILL, etc.) through the kernel | ||
2230 | 12 | coredump handler (in piping mode) | ||
2231 | 13 | - Unhandled Python exceptions | ||
2232 | 14 | - GTK, KDE, and command line user interfaces | ||
2233 | 15 | - Packages can ship hooks for collecting specific data (such as | ||
2234 | 16 | /var/log/Xorg.0.log for X.org, or modified gconf settings for GNOME | ||
2235 | 17 | programs) | ||
2236 | 18 | - apt/dpkg and rpm backend (in production use in Ubuntu and OpenSUSE) | ||
2237 | 19 | - Reprocessing a core dump and debug symbols for post-mortem (and preferably | ||
2238 | 20 | server-side) generation of fully symbolic stack traces (apport-retrace) | ||
2239 | 21 | - Reporting bugs to Launchpad (more backends can be easily added) | ||
2240 | 22 | |||
2241 | 23 | Please see https://wiki.ubuntu.com/Apport for more details and further links. | ||
2242 | 24 | The files in doc/ document particular details such as package hooks, crash | ||
2243 | 25 | database configuration, or the internal data format. | ||
2244 | 26 | |||
2245 | 27 | Temporarily enabling apport | ||
2246 | 28 | =========================== | ||
2247 | 29 | |||
2248 | 30 | The automatic crash interception component of apport is disabled by default in | ||
2249 | 31 | stable releases for a number of reasons [1]. To enable it just for the current | ||
2250 | 32 | session, do | ||
2251 | 33 | |||
2252 | 34 | sudo service apport start force_start=1 | ||
2253 | 35 | |||
2254 | 36 | Then you can simply trigger the crash again, and Apport's dialog will show up | ||
2255 | 37 | with instructions to report a bug with traces. Apport will be automatically | ||
2256 | 38 | disabled on next start. | ||
2257 | 39 | |||
2258 | 40 | If you are triaging bugs, this is the best way to get traces from bug reporters | ||
2259 | 41 | that didn't use Apport in the first place. | ||
2260 | 42 | |||
2261 | 43 | To enable it permanently, do: | ||
2262 | 44 | |||
2263 | 45 | sudo nano /etc/default/apport | ||
2264 | 46 | |||
2265 | 47 | and change enabled from "0" to "1". | ||
2266 | 48 | |||
2267 | 49 | [1] https://wiki.ubuntu.com/Apport#How%20to%20enable%20apport | ||
2268 | 50 | |||
2269 | 51 | Crash notification on servers | ||
2270 | 52 | ============================= | ||
2271 | 53 | |||
2272 | 54 | You can add | ||
2273 | 55 | |||
2274 | 56 | if [ -x /usr/bin/apport-cli ]; then | ||
2275 | 57 | if groups | grep -qw admin && /usr/share/apport/apport-checkreports -s; then | ||
2276 | 58 | cat <<-EOF | ||
2277 | 59 | You have new problem reports waiting in /var/crash. | ||
2278 | 60 | To take a look at them, run "sudo apport-cli". | ||
2279 | 61 | |||
2280 | 62 | EOF | ||
2281 | 63 | elif /usr/share/apport/apport-checkreports; then | ||
2282 | 64 | cat <<-EOF | ||
2283 | 65 | You have new problem reports waiting in /var/crash. | ||
2284 | 66 | To take a look at them, run "apport-cli". | ||
2285 | 67 | |||
2286 | 68 | EOF | ||
2287 | 69 | fi | ||
2288 | 70 | fi | ||
2289 | 71 | |||
2290 | 72 | to your ~/.bashrc to get automatic notification of problem reports. | ||
2291 | 73 | |||
2292 | 74 | Contributing | ||
2293 | 75 | ============ | ||
2294 | 76 | |||
2295 | 77 | Please visit Apport's Launchpad homepage for links to the source code revision | ||
2296 | 78 | control, the bug tracker, translations, downloads, etc.: | ||
2297 | 79 | |||
2298 | 80 | https://launchpad.net/apport | ||
2299 | 81 | |||
2300 | 82 | The preferred mode of operation for Linux distribution packagers is to create | ||
2301 | 83 | their own branch from 'trunk' and add the distro specific packaging and patches | ||
2302 | 84 | to it. Please send patches which are applicable to trunk as merge requests or | ||
2303 | 85 | bug reports, so that (1) other distributions can benefit from them as well, and | ||
2304 | 86 | (2) you reduce the code delta to upstream. | ||
2305 | 87 | |||
2306 | 0 | 88 | ||
2307 | === renamed file 'README' => 'README.moved' | |||
2308 | === added file 'TODO' | |||
2309 | --- TODO 1970-01-01 00:00:00 +0000 | |||
2310 | +++ TODO 2013-03-13 18:57:21 +0000 | |||
2311 | @@ -0,0 +1,21 @@ | |||
2312 | 1 | apport: | ||
2313 | 2 | - check crashes of root processes with dropped privs in test suite | ||
2314 | 3 | |||
2315 | 4 | dup detection: | ||
2316 | 5 | - add merging of two databases -> needs time stamp of last change | ||
2317 | 6 | |||
2318 | 7 | GUI: | ||
2319 | 8 | - point out bug privacy and to leave it private by default | ||
2320 | 9 | |||
2321 | 10 | hooks: | ||
2322 | 11 | - add hooks which run during program crash, to collect more runtime data | ||
2323 | 12 | |||
2324 | 13 | hookutils: | ||
2325 | 14 | - run hooks for related packages in attach_related_packages | ||
2326 | 15 | |||
2327 | 16 | apt-dpkg backend: | ||
2328 | 17 | - use python-apt's Version.get_source() instead of apt-get source | ||
2329 | 18 | |||
2330 | 19 | apport-retrace: | ||
2331 | 20 | - add "gdb" mode: apport-gdb /usr/bin/myprog -> imply -S system, throw you into | ||
2332 | 21 | gdb session with debug symbols | ||
2333 | 0 | 22 | ||
2334 | === renamed file 'TODO' => 'TODO.moved' | |||
2335 | === added directory 'apport' | |||
2336 | === renamed directory 'apport' => 'apport.moved' | |||
2337 | === added file 'apport/REThread.py' | |||
2338 | --- apport/REThread.py 1970-01-01 00:00:00 +0000 | |||
2339 | +++ apport/REThread.py 2013-03-13 18:57:21 +0000 | |||
2340 | @@ -0,0 +1,65 @@ | |||
2341 | 1 | '''Enhanced Thread with support for return values and exception propagation.''' | ||
2342 | 2 | |||
2343 | 3 | # Copyright (C) 2007 Canonical Ltd. | ||
2344 | 4 | # Author: Martin Pitt <martin.pitt@ubuntu.com> | ||
2345 | 5 | # | ||
2346 | 6 | # This program is free software; you can redistribute it and/or modify it | ||
2347 | 7 | # under the terms of the GNU General Public License as published by the | ||
2348 | 8 | # Free Software Foundation; either version 2 of the License, or (at your | ||
2349 | 9 | # option) any later version. See http://www.gnu.org/copyleft/gpl.html for | ||
2350 | 10 | # the full text of the license. | ||
2351 | 11 | |||
2352 | 12 | import threading, sys | ||
2353 | 13 | |||
2354 | 14 | |||
2355 | 15 | class REThread(threading.Thread): | ||
2356 | 16 | '''Thread with return values and exception propagation.''' | ||
2357 | 17 | |||
2358 | 18 | def __init__(self, group=None, target=None, name=None, args=(), kwargs={}): | ||
2359 | 19 | '''Initialize Thread, identical to threading.Thread.__init__().''' | ||
2360 | 20 | |||
2361 | 21 | threading.Thread.__init__(self, group, target, name, args, kwargs) | ||
2362 | 22 | self.__target = target | ||
2363 | 23 | self.__args = args | ||
2364 | 24 | self.__kwargs = kwargs | ||
2365 | 25 | self._retval = None | ||
2366 | 26 | self._exception = None | ||
2367 | 27 | |||
2368 | 28 | def run(self): | ||
2369 | 29 | '''Run target function, identical to threading.Thread.run().''' | ||
2370 | 30 | |||
2371 | 31 | if self.__target: | ||
2372 | 32 | try: | ||
2373 | 33 | self._retval = self.__target(*self.__args, **self.__kwargs) | ||
2374 | 34 | except: | ||
2375 | 35 | if sys: | ||
2376 | 36 | self._exception = sys.exc_info() | ||
2377 | 37 | |||
2378 | 38 | def return_value(self): | ||
2379 | 39 | '''Return value from target function. | ||
2380 | 40 | |||
2381 | 41 | This can only be called after the thread has finished, i. e. when | ||
2382 | 42 | isAlive() is False and did not terminate with an exception. | ||
2383 | 43 | ''' | ||
2384 | 44 | assert not self.isAlive() | ||
2385 | 45 | assert not self._exception | ||
2386 | 46 | return self._retval | ||
2387 | 47 | |||
2388 | 48 | def exc_info(self): | ||
2389 | 49 | '''Return (type, value, traceback) of the exception caught in run().''' | ||
2390 | 50 | |||
2391 | 51 | return self._exception | ||
2392 | 52 | |||
2393 | 53 | def exc_raise(self): | ||
2394 | 54 | '''Raise the exception caught in the thread. | ||
2395 | 55 | |||
2396 | 56 | Do nothing if no exception was caught. | ||
2397 | 57 | ''' | ||
2398 | 58 | if self._exception: | ||
2399 | 59 | # there is no syntax which both Python 2 and 3 parse, so we need a | ||
2400 | 60 | # hack using exec() here | ||
2401 | 61 | # Python 3: | ||
2402 | 62 | if sys.version > '3': | ||
2403 | 63 | raise self._exception[1].with_traceback(self._exception[2]) | ||
2404 | 64 | else: | ||
2405 | 65 | exec('raise self._exception[0], self._exception[1], self._exception[2]') | ||
2406 | 0 | 66 | ||
2407 | === added file 'apport/__init__.py' | |||
2408 | --- apport/__init__.py 1970-01-01 00:00:00 +0000 | |||
2409 | +++ apport/__init__.py 2013-03-13 18:57:21 +0000 | |||
2410 | @@ -0,0 +1,73 @@ | |||
2411 | 1 | import sys | ||
2412 | 2 | import os | ||
2413 | 3 | import time | ||
2414 | 4 | |||
2415 | 5 | from apport.report import Report | ||
2416 | 6 | |||
2417 | 7 | from apport.packaging_impl import impl as packaging | ||
2418 | 8 | |||
2419 | 9 | Report # pyflakes | ||
2420 | 10 | packaging # pyflakes | ||
2421 | 11 | |||
2422 | 12 | # fix gettext to output proper unicode strings | ||
2423 | 13 | import gettext | ||
2424 | 14 | |||
2425 | 15 | |||
2426 | 16 | def unicode_gettext(str): | ||
2427 | 17 | trans = gettext.gettext(str) | ||
2428 | 18 | if type(trans) == type(b''): | ||
2429 | 19 | return trans.decode('UTF-8') | ||
2430 | 20 | else: | ||
2431 | 21 | return trans | ||
2432 | 22 | |||
2433 | 23 | |||
2434 | 24 | def log(message, timestamp=False): | ||
2435 | 25 | '''Log the given string to stdout. Prepend timestamp if requested''' | ||
2436 | 26 | |||
2437 | 27 | if timestamp: | ||
2438 | 28 | sys.stdout.write('%s: ' % time.strftime('%x %X')) | ||
2439 | 29 | print(message) | ||
2440 | 30 | |||
2441 | 31 | |||
2442 | 32 | def fatal(msg, *args): | ||
2443 | 33 | '''Print out an error message and exit the program.''' | ||
2444 | 34 | |||
2445 | 35 | error(msg, *args) | ||
2446 | 36 | sys.exit(1) | ||
2447 | 37 | |||
2448 | 38 | |||
2449 | 39 | def error(msg, *args): | ||
2450 | 40 | '''Print out an error message.''' | ||
2451 | 41 | |||
2452 | 42 | if sys.stderr: | ||
2453 | 43 | sys.stderr.write('ERROR: ') | ||
2454 | 44 | sys.stderr.write(msg % args) | ||
2455 | 45 | sys.stderr.write('\n') | ||
2456 | 46 | |||
2457 | 47 | |||
2458 | 48 | def warning(msg, *args): | ||
2459 | 49 | '''Print out an warning message.''' | ||
2460 | 50 | |||
2461 | 51 | if sys.stderr: | ||
2462 | 52 | sys.stderr.write('WARNING: ') | ||
2463 | 53 | sys.stderr.write(msg % args) | ||
2464 | 54 | sys.stderr.write('\n') | ||
2465 | 55 | |||
2466 | 56 | |||
2467 | 57 | def memdbg(checkpoint): | ||
2468 | 58 | '''Print current memory usage. | ||
2469 | 59 | |||
2470 | 60 | This is only done if $APPORT_MEMDEBUG is set. | ||
2471 | 61 | ''' | ||
2472 | 62 | if not 'APPORT_MEMDEBUG' in os.environ or not sys.stderr: | ||
2473 | 63 | return | ||
2474 | 64 | |||
2475 | 65 | memstat = {} | ||
2476 | 66 | with open('/proc/self/status') as f: | ||
2477 | 67 | for l in f: | ||
2478 | 68 | if l.startswith('Vm'): | ||
2479 | 69 | (field, size, unit) = l.split() | ||
2480 | 70 | memstat[field[:-1]] = int(size) / 1024. | ||
2481 | 71 | |||
2482 | 72 | sys.stderr.write('Size: %.1f MB, RSS: %.1f MB, Stk: %.1f MB @ %s\n' % | ||
2483 | 73 | (memstat['VmSize'], memstat['VmRSS'], memstat['VmStk'], checkpoint)) | ||
2484 | 0 | 74 | ||
2485 | === added file 'apport/com.ubuntu.apport.policy.in' | |||
2486 | --- apport/com.ubuntu.apport.policy.in 1970-01-01 00:00:00 +0000 | |||
2487 | +++ apport/com.ubuntu.apport.policy.in 2013-03-13 18:57:21 +0000 | |||
2488 | @@ -0,0 +1,34 @@ | |||
2489 | 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
2490 | 2 | <!DOCTYPE policyconfig PUBLIC | ||
2491 | 3 | "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" | ||
2492 | 4 | "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd"> | ||
2493 | 5 | <policyconfig> | ||
2494 | 6 | <vendor>Apport</vendor> | ||
2495 | 7 | <vendor_url>https://wiki.ubuntu.com/Apport</vendor_url> | ||
2496 | 8 | <icon_name>apport</icon_name> | ||
2497 | 9 | |||
2498 | 10 | <action id="com.ubuntu.apport.root-info"> | ||
2499 | 11 | <_description>Collect system information</_description> | ||
2500 | 12 | <_message>Authentication is required to collect system information for this problem report</_message> | ||
2501 | 13 | <annotate key="org.freedesktop.policykit.exec.path">/usr/share/apport/root_info_wrapper</annotate> | ||
2502 | 14 | <!-- <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate> --> | ||
2503 | 15 | <defaults> | ||
2504 | 16 | <allow_any>auth_admin</allow_any> | ||
2505 | 17 | <allow_inactive>auth_admin</allow_inactive> | ||
2506 | 18 | <allow_active>auth_admin</allow_active> | ||
2507 | 19 | </defaults> | ||
2508 | 20 | </action> | ||
2509 | 21 | |||
2510 | 22 | <action id="com.ubuntu.apport.apport-gtk-root"> | ||
2511 | 23 | <_description>System problem reports</_description> | ||
2512 | 24 | <_message>Please enter your password to access problem reports of system programs</_message> | ||
2513 | 25 | <annotate key="org.freedesktop.policykit.exec.path">/usr/share/apport/apport-gtk</annotate> | ||
2514 | 26 | <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate> | ||
2515 | 27 | <defaults> | ||
2516 | 28 | <allow_any>auth_admin</allow_any> | ||
2517 | 29 | <allow_inactive>auth_admin</allow_inactive> | ||
2518 | 30 | <allow_active>auth_admin</allow_active> | ||
2519 | 31 | </defaults> | ||
2520 | 32 | </action> | ||
2521 | 33 | |||
2522 | 34 | </policyconfig> | ||
2523 | 0 | 35 | ||
2524 | === added file 'apport/crashdb.py' | |||
2525 | --- apport/crashdb.py 1970-01-01 00:00:00 +0000 | |||
2526 | +++ apport/crashdb.py 2013-03-13 18:57:21 +0000 | |||
2527 | @@ -0,0 +1,856 @@ | |||
2528 | 1 | '''Abstract crash database interface.''' | ||
2529 | 2 | |||
2530 | 3 | # Copyright (C) 2007 - 2009 Canonical Ltd. | ||
2531 | 4 | # Author: Martin Pitt <martin.pitt@ubuntu.com> | ||
2532 | 5 | # | ||
2533 | 6 | # This program is free software; you can redistribute it and/or modify it | ||
2534 | 7 | # under the terms of the GNU General Public License as published by the | ||
2535 | 8 | # Free Software Foundation; either version 2 of the License, or (at your | ||
2536 | 9 | # option) any later version. See http://www.gnu.org/copyleft/gpl.html for | ||
2537 | 10 | # the full text of the license. | ||
2538 | 11 | |||
2539 | 12 | import os, os.path, sys, shutil | ||
2540 | 13 | |||
2541 | 14 | try: | ||
2542 | 15 | from exceptions import Exception | ||
2543 | 16 | from urllib import quote_plus, urlopen | ||
2544 | 17 | URLError = IOError | ||
2545 | 18 | (quote_plus, urlopen) # pyflakes | ||
2546 | 19 | except ImportError: | ||
2547 | 20 | # python 3 | ||
2548 | 21 | from functools import cmp_to_key | ||
2549 | 22 | from urllib.parse import quote_plus | ||
2550 | 23 | from urllib.request import urlopen | ||
2551 | 24 | from urllib.error import URLError | ||
2552 | 25 | |||
2553 | 26 | import apport | ||
2554 | 27 | |||
2555 | 28 | |||
2556 | 29 | def _u(str): | ||
2557 | 30 | '''Convert str to an unicode if it isn't already.''' | ||
2558 | 31 | |||
2559 | 32 | if type(str) == type(b''): | ||
2560 | 33 | return str.decode('UTF-8', 'ignore') | ||
2561 | 34 | return str | ||
2562 | 35 | |||
2563 | 36 | |||
2564 | 37 | class CrashDatabase: | ||
2565 | 38 | def __init__(self, auth_file, options): | ||
2566 | 39 | '''Initialize crash database connection. | ||
2567 | 40 | |||
2568 | 41 | You need to specify an implementation specific file with the | ||
2569 | 42 | authentication credentials for retracing access for download() and | ||
2570 | 43 | update(). For upload() and get_comment_url() you can use None. | ||
2571 | 44 | |||
2572 | 45 | options is a dictionary with additional settings from crashdb.conf; see | ||
2573 | 46 | get_crashdb() for details. | ||
2574 | 47 | ''' | ||
2575 | 48 | self.auth_file = auth_file | ||
2576 | 49 | self.options = options | ||
2577 | 50 | self.duplicate_db = None | ||
2578 | 51 | |||
2579 | 52 | def get_bugpattern_baseurl(self): | ||
2580 | 53 | '''Return the base URL for bug patterns. | ||
2581 | 54 | |||
2582 | 55 | See apport.report.Report.search_bug_patterns() for details. If this | ||
2583 | 56 | function returns None, bug patterns are disabled. | ||
2584 | 57 | ''' | ||
2585 | 58 | return self.options.get('bug_pattern_url') | ||
2586 | 59 | |||
2587 | 60 | def accepts(self, report): | ||
2588 | 61 | '''Check if this report can be uploaded to this database. | ||
2589 | 62 | |||
2590 | 63 | Crash databases might limit the types of reports they get with e. g. | ||
2591 | 64 | the "problem_types" option. | ||
2592 | 65 | ''' | ||
2593 | 66 | if 'problem_types' in self.options: | ||
2594 | 67 | return report.get('ProblemType') in self.options['problem_types'] | ||
2595 | 68 | |||
2596 | 69 | return True | ||
2597 | 70 | |||
2598 | 71 | # | ||
2599 | 72 | # API for duplicate detection | ||
2600 | 73 | # | ||
2601 | 74 | # Tests are in apport/crashdb_impl/memory.py. | ||
2602 | 75 | |||
2603 | 76 | def init_duplicate_db(self, path): | ||
2604 | 77 | '''Initialize duplicate database. | ||
2605 | 78 | |||
2606 | 79 | path specifies an SQLite database. It will be created if it does not | ||
2607 | 80 | exist yet. | ||
2608 | 81 | ''' | ||
2609 | 82 | import sqlite3 as dbapi2 | ||
2610 | 83 | |||
2611 | 84 | assert dbapi2.paramstyle == 'qmark', \ | ||
2612 | 85 | 'this module assumes qmark dbapi parameter style' | ||
2613 | 86 | |||
2614 | 87 | self.format_version = 3 | ||
2615 | 88 | |||
2616 | 89 | init = not os.path.exists(path) or path == ':memory:' or \ | ||
2617 | 90 | os.path.getsize(path) == 0 | ||
2618 | 91 | self.duplicate_db = dbapi2.connect(path, timeout=7200) | ||
2619 | 92 | |||
2620 | 93 | if init: | ||
2621 | 94 | cur = self.duplicate_db.cursor() | ||
2622 | 95 | cur.execute('CREATE TABLE version (format INTEGER NOT NULL)') | ||
2623 | 96 | cur.execute('INSERT INTO version VALUES (?)', [self.format_version]) | ||
2624 | 97 | |||
2625 | 98 | cur.execute('''CREATE TABLE crashes ( | ||
2626 | 99 | signature VARCHAR(255) NOT NULL, | ||
2627 | 100 | crash_id INTEGER NOT NULL, | ||
2628 | 101 | fixed_version VARCHAR(50), | ||
2629 | 102 | last_change TIMESTAMP, | ||
2630 | 103 | CONSTRAINT crashes_pk PRIMARY KEY (crash_id))''') | ||
2631 | 104 | |||
2632 | 105 | cur.execute('''CREATE TABLE address_signatures ( | ||
2633 | 106 | signature VARCHAR(1000) NOT NULL, | ||
2634 | 107 | crash_id INTEGER NOT NULL, | ||
2635 | 108 | CONSTRAINT address_signatures_pk PRIMARY KEY (signature))''') | ||
2636 | 109 | |||
2637 | 110 | self.duplicate_db.commit() | ||
2638 | 111 | |||
2639 | 112 | # verify integrity | ||
2640 | 113 | cur = self.duplicate_db.cursor() | ||
2641 | 114 | cur.execute('PRAGMA integrity_check') | ||
2642 | 115 | result = cur.fetchall() | ||
2643 | 116 | if result != [('ok',)]: | ||
2644 | 117 | raise SystemError('Corrupt duplicate db:' + str(result)) | ||
2645 | 118 | |||
2646 | 119 | try: | ||
2647 | 120 | cur.execute('SELECT format FROM version') | ||
2648 | 121 | result = cur.fetchone() | ||
2649 | 122 | except self.duplicate_db.OperationalError as e: | ||
2650 | 123 | if 'no such table' in str(e): | ||
2651 | 124 | # first db format did not have version table yet | ||
2652 | 125 | result = [0] | ||
2653 | 126 | if result[0] > self.format_version: | ||
2654 | 127 | raise SystemError('duplicate DB has unknown format %i' % result[0]) | ||
2655 | 128 | if result[0] < self.format_version: | ||
2656 | 129 | print('duplicate db has format %i, upgrading to %i' % | ||
2657 | 130 | (result[0], self.format_version)) | ||
2658 | 131 | self._duplicate_db_upgrade(result[0]) | ||
2659 | 132 | |||
2660 | 133 | def check_duplicate(self, id, report=None): | ||
2661 | 134 | '''Check whether a crash is already known. | ||
2662 | 135 | |||
2663 | 136 | If the crash is new, it will be added to the duplicate database and the | ||
2664 | 137 | function returns None. If the crash is already known, the function | ||
2665 | 138 | returns a pair (crash_id, fixed_version), where fixed_version might be | ||
2666 | 139 | None if the crash is not fixed in the latest version yet. Depending on | ||
2667 | 140 | whether the version in report is smaller than/equal to the fixed | ||
2668 | 141 | version or larger, this calls close_duplicate() or mark_regression(). | ||
2669 | 142 | |||
2670 | 143 | If the report does not have a valid crash signature, this function does | ||
2671 | 144 | nothing and just returns None. | ||
2672 | 145 | |||
2673 | 146 | By default, the report gets download()ed, but for performance reasons | ||
2674 | 147 | it can be explicitly passed to this function if it is already available. | ||
2675 | 148 | ''' | ||
2676 | 149 | assert self.duplicate_db, 'init_duplicate_db() needs to be called before' | ||
2677 | 150 | |||
2678 | 151 | if not report: | ||
2679 | 152 | report = self.download(id) | ||
2680 | 153 | |||
2681 | 154 | self._mark_dup_checked(id, report) | ||
2682 | 155 | |||
2683 | 156 | if 'DuplicateSignature' in report: | ||
2684 | 157 | sig = report['DuplicateSignature'] | ||
2685 | 158 | else: | ||
2686 | 159 | sig = report.crash_signature() | ||
2687 | 160 | existing = [] | ||
2688 | 161 | if sig: | ||
2689 | 162 | # use real duplicate signature | ||
2690 | 163 | existing = self._duplicate_search_signature(sig, id) | ||
2691 | 164 | |||
2692 | 165 | if existing: | ||
2693 | 166 | # update status of existing master bugs | ||
2694 | 167 | for (ex_id, _) in existing: | ||
2695 | 168 | self._duplicate_db_sync_status(ex_id) | ||
2696 | 169 | existing = self._duplicate_search_signature(sig, id) | ||
2697 | 170 | |||
2698 | 171 | try: | ||
2699 | 172 | report_package_version = report['Package'].split()[1] | ||
2700 | 173 | except (KeyError, IndexError): | ||
2701 | 174 | report_package_version = None | ||
2702 | 175 | |||
2703 | 176 | # check the existing IDs whether there is one that is unfixed or not | ||
2704 | 177 | # older than the report's package version; if so, we have a duplicate. | ||
2705 | 178 | master_id = None | ||
2706 | 179 | master_ver = None | ||
2707 | 180 | for (ex_id, ex_ver) in existing: | ||
2708 | 181 | if not ex_ver or not report_package_version or apport.packaging.compare_versions(report_package_version, ex_ver) < 0: | ||
2709 | 182 | master_id = ex_id | ||
2710 | 183 | master_ver = ex_ver | ||
2711 | 184 | break | ||
2712 | 185 | else: | ||
2713 | 186 | # if we did not find a new enough open master report, | ||
2714 | 187 | # we have a regression of the latest fix. Mark it so, and create a | ||
2715 | 188 | # new unfixed ID for it later on | ||
2716 | 189 | if existing: | ||
2717 | 190 | self.mark_regression(id, existing[-1][0]) | ||
2718 | 191 | |||
2719 | 192 | # now query address signatures, they might turn up another duplicate | ||
2720 | 193 | # (not necessarily the same, due to Stacktraces sometimes being | ||
2721 | 194 | # slightly different) | ||
2722 | 195 | addr_sig = report.crash_signature_addresses() | ||
2723 | 196 | if addr_sig: | ||
2724 | 197 | addr_match = self._duplicate_search_address_signature(addr_sig) | ||
2725 | 198 | if addr_match and addr_match != master_id: | ||
2726 | 199 | if master_id is None: | ||
2727 | 200 | # we have a duplicate only identified by address sig, close it | ||
2728 | 201 | master_id = addr_match | ||
2729 | 202 | else: | ||
2730 | 203 | # our bug is a dupe of two different masters, one from | ||
2731 | 204 | # symbolic, the other from addr matching (see LP#943117); | ||
2732 | 205 | # make them all duplicates of each other, using the lower | ||
2733 | 206 | # number as master | ||
2734 | 207 | if master_id < addr_match: | ||
2735 | 208 | self.close_duplicate(report, addr_match, master_id) | ||
2736 | 209 | self._duplicate_db_merge_id(addr_match, master_id) | ||
2737 | 210 | else: | ||
2738 | 211 | self.close_duplicate(report, master_id, addr_match) | ||
2739 | 212 | self._duplicate_db_merge_id(master_id, addr_match) | ||
2740 | 213 | master_id = addr_match | ||
2741 | 214 | master_ver = None # no version tracking for address signatures yet | ||
2742 | 215 | |||
2743 | 216 | if master_id is not None and master_id != id: | ||
2744 | 217 | if addr_sig: | ||
2745 | 218 | self._duplicate_db_add_address_signature(addr_sig, master_id) | ||
2746 | 219 | self.close_duplicate(report, id, master_id) | ||
2747 | 220 | return (master_id, master_ver) | ||
2748 | 221 | |||
2749 | 222 | # no duplicate detected; create a new record for the ID if we don't have one already | ||
2750 | 223 | if sig: | ||
2751 | 224 | cur = self.duplicate_db.cursor() | ||
2752 | 225 | cur.execute('SELECT count(*) FROM crashes WHERE crash_id == ?', [id]) | ||
2753 | 226 | count_id = cur.fetchone()[0] | ||
2754 | 227 | if count_id == 0: | ||
2755 | 228 | cur.execute('INSERT INTO crashes VALUES (?, ?, ?, CURRENT_TIMESTAMP)', (_u(sig), id, None)) | ||
2756 | 229 | self.duplicate_db.commit() | ||
2757 | 230 | if addr_sig: | ||
2758 | 231 | self._duplicate_db_add_address_signature(addr_sig, id) | ||
2759 | 232 | |||
2760 | 233 | return None | ||
2761 | 234 | |||
2762 | 235 | def known(self, report): | ||
2763 | 236 | '''Check if the crash db already knows about the crash signature. | ||
2764 | 237 | |||
2765 | 238 | Check if the report has a DuplicateSignature, crash_signature(), or | ||
2766 | 239 | StacktraceAddressSignature, and ask the database whether the problem is | ||
2767 | 240 | already known. If so, return an URL where the user can check the status | ||
2768 | 241 | or subscribe (if available), or just return True if the report is known | ||
2769 | 242 | but there is no public URL. In that case the report will not be | ||
2770 | 243 | uploaded (i. e. upload() will not be called). | ||
2771 | 244 | |||
2772 | 245 | Return None if the report does not have any signature or the crash | ||
2773 | 246 | database does not support checking for duplicates on the client side. | ||
2774 | 247 | |||
2775 | 248 | The default implementation uses a text file format generated by | ||
2776 | 249 | duplicate_db_publish() at an URL specified by the "dupdb_url" option. | ||
2777 | 250 | Subclasses are free to override this with a custom implementation, such | ||
2778 | 251 | as a real database lookup. | ||
2779 | 252 | ''' | ||
2780 | 253 | if not self.options.get('dupdb_url'): | ||
2781 | 254 | return None | ||
2782 | 255 | |||
2783 | 256 | for kind in ('sig', 'address'): | ||
2784 | 257 | # get signature | ||
2785 | 258 | if kind == 'sig': | ||
2786 | 259 | if 'DuplicateSignature' in report: | ||
2787 | 260 | sig = report['DuplicateSignature'] | ||
2788 | 261 | else: | ||
2789 | 262 | sig = report.crash_signature() | ||
2790 | 263 | else: | ||
2791 | 264 | sig = report.crash_signature_addresses() | ||
2792 | 265 | |||
2793 | 266 | if not sig: | ||
2794 | 267 | continue | ||
2795 | 268 | |||
2796 | 269 | # build URL where the data should be | ||
2797 | 270 | h = self.duplicate_sig_hash(sig) | ||
2798 | 271 | if not h: | ||
2799 | 272 | return None | ||
2800 | 273 | |||
2801 | 274 | # the hash is already quoted, but we really want to open the quoted | ||
2802 | 275 | # file names; as urlopen() unquotes, we need to double-quote here | ||
2803 | 276 | # again so that urlopen() sees the single-quoted file names | ||
2804 | 277 | url = os.path.join(self.options['dupdb_url'], kind, quote_plus(h)) | ||
2805 | 278 | |||
2806 | 279 | # read data file | ||
2807 | 280 | try: | ||
2808 | 281 | f = urlopen(url) | ||
2809 | 282 | contents = f.read().decode('UTF-8') | ||
2810 | 283 | f.close() | ||
2811 | 284 | if '<title>404 Not Found' in contents: | ||
2812 | 285 | continue | ||
2813 | 286 | except (IOError, URLError): | ||
2814 | 287 | # does not exist, failed to load, etc. | ||
2815 | 288 | continue | ||
2816 | 289 | |||
2817 | 290 | # now check if we find our signature | ||
2818 | 291 | for line in contents.splitlines(): | ||
2819 | 292 | try: | ||
2820 | 293 | id, s = line.split(None, 1) | ||
2821 | 294 | id = int(id) | ||
2822 | 295 | except ValueError: | ||
2823 | 296 | continue | ||
2824 | 297 | if s == sig: | ||
2825 | 298 | result = self.get_id_url(report, id) | ||
2826 | 299 | if not result: | ||
2827 | 300 | # if we can't have an URL, just report as "known" | ||
2828 | 301 | result = '1' | ||
2829 | 302 | return result | ||
2830 | 303 | |||
2831 | 304 | return None | ||
2832 | 305 | |||
2833 | 306 | def duplicate_db_fixed(self, id, version): | ||
2834 | 307 | '''Mark given crash ID as fixed in the duplicate database. | ||
2835 | 308 | |||
2836 | 309 | version specifies the package version the crash was fixed in (None for | ||
2837 | 310 | 'still unfixed'). | ||
2838 | 311 | ''' | ||
2839 | 312 | assert self.duplicate_db, 'init_duplicate_db() needs to be called before' | ||
2840 | 313 | |||
2841 | 314 | cur = self.duplicate_db.cursor() | ||
2842 | 315 | n = cur.execute('UPDATE crashes SET fixed_version = ?, last_change = CURRENT_TIMESTAMP WHERE crash_id = ?', | ||
2843 | 316 | (version, id)) | ||
2844 | 317 | assert n.rowcount == 1 | ||
2845 | 318 | self.duplicate_db.commit() | ||
2846 | 319 | |||
2847 | 320 | def duplicate_db_remove(self, id): | ||
2848 | 321 | '''Remove crash from the duplicate database. | ||
2849 | 322 | |||
2850 | 323 | This happens when a report got rejected or manually duplicated. | ||
2851 | 324 | ''' | ||
2852 | 325 | assert self.duplicate_db, 'init_duplicate_db() needs to be called before' | ||
2853 | 326 | |||
2854 | 327 | cur = self.duplicate_db.cursor() | ||
2855 | 328 | cur.execute('DELETE FROM crashes WHERE crash_id = ?', [id]) | ||
2856 | 329 | cur.execute('DELETE FROM address_signatures WHERE crash_id = ?', [id]) | ||
2857 | 330 | self.duplicate_db.commit() | ||
2858 | 331 | |||
2859 | 332 | def duplicate_db_change_master_id(self, old_id, new_id): | ||
2860 | 333 | '''Change a crash ID.''' | ||
2861 | 334 | |||
2862 | 335 | assert self.duplicate_db, 'init_duplicate_db() needs to be called before' | ||
2863 | 336 | |||
2864 | 337 | cur = self.duplicate_db.cursor() | ||
2865 | 338 | cur.execute('UPDATE crashes SET crash_id = ?, last_change = CURRENT_TIMESTAMP WHERE crash_id = ?', | ||
2866 | 339 | [new_id, old_id]) | ||
2867 | 340 | cur.execute('UPDATE address_signatures SET crash_id = ? WHERE crash_id = ?', | ||
2868 | 341 | [new_id, old_id]) | ||
2869 | 342 | self.duplicate_db.commit() | ||
2870 | 343 | |||
2871 | 344 | def duplicate_db_publish(self, dir): | ||
2872 | 345 | '''Create text files suitable for www publishing. | ||
2873 | 346 | |||
2874 | 347 | Create a number of text files in the given directory which Apport | ||
2875 | 348 | clients can use to determine whether a problem is already reported to | ||
2876 | 349 | the database, through the known() method. This directory is suitable | ||
2877 | 350 | for publishing to the web. | ||
2878 | 351 | |||
2879 | 352 | The database is indexed by the first two fields of the duplicate or | ||
2880 | 353 | crash signature, to avoid having to download the entire database every | ||
2881 | 354 | time. | ||
2882 | 355 | |||
2883 | 356 | If the directory already exists, it will be updated. The new content is | ||
2884 | 357 | built in a new directory which is the given one with ".new" appended, | ||
2885 | 358 | then moved to the given name in an almost atomic way. | ||
2886 | 359 | ''' | ||
2887 | 360 | assert self.duplicate_db, 'init_duplicate_db() needs to be called before' | ||
2888 | 361 | |||
2889 | 362 | # first create the temporary new dir; if that fails, nothing has been | ||
2890 | 363 | # changed and we fail early | ||
2891 | 364 | out = dir + '.new' | ||
2892 | 365 | os.mkdir(out) | ||
2893 | 366 | |||
2894 | 367 | # crash addresses | ||
2895 | 368 | addr_base = os.path.join(out, 'address') | ||
2896 | 369 | os.mkdir(addr_base) | ||
2897 | 370 | cur_hash = None | ||
2898 | 371 | cur_file = None | ||
2899 | 372 | |||
2900 | 373 | cur = self.duplicate_db.cursor() | ||
2901 | 374 | |||
2902 | 375 | cur.execute('SELECT * from address_signatures ORDER BY signature') | ||
2903 | 376 | for (sig, id) in cur.fetchall(): | ||
2904 | 377 | h = self.duplicate_sig_hash(sig) | ||
2905 | 378 | if h is None: | ||
2906 | 379 | # some entries can't be represented in a single line | ||
2907 | 380 | continue | ||
2908 | 381 | if h != cur_hash: | ||
2909 | 382 | cur_hash = h | ||
2910 | 383 | if cur_file: | ||
2911 | 384 | cur_file.close() | ||
2912 | 385 | cur_file = open(os.path.join(addr_base, cur_hash), 'w') | ||
2913 | 386 | |||
2914 | 387 | cur_file.write('%i %s\n' % (id, sig)) | ||
2915 | 388 | |||
2916 | 389 | if cur_file: | ||
2917 | 390 | cur_file.close() | ||
2918 | 391 | |||
2919 | 392 | # duplicate signatures | ||
2920 | 393 | sig_base = os.path.join(out, 'sig') | ||
2921 | 394 | os.mkdir(sig_base) | ||
2922 | 395 | cur_hash = None | ||
2923 | 396 | cur_file = None | ||
2924 | 397 | |||
2925 | 398 | cur.execute('SELECT signature, crash_id from crashes ORDER BY signature') | ||
2926 | 399 | for (sig, id) in cur.fetchall(): | ||
2927 | 400 | h = self.duplicate_sig_hash(sig) | ||
2928 | 401 | if h is None: | ||
2929 | 402 | # some entries can't be represented in a single line | ||
2930 | 403 | continue | ||
2931 | 404 | if h != cur_hash: | ||
2932 | 405 | cur_hash = h | ||
2933 | 406 | if cur_file: | ||
2934 | 407 | cur_file.close() | ||
2935 | 408 | cur_file = open(os.path.join(sig_base, cur_hash), 'wb') | ||
2936 | 409 | |||
2937 | 410 | cur_file.write(('%i %s\n' % (id, sig)).encode('UTF-8')) | ||
2938 | 411 | |||
2939 | 412 | if cur_file: | ||
2940 | 413 | cur_file.close() | ||
2941 | 414 | |||
2942 | 415 | # switch over tree; this is as atomic as we can be with directories | ||
2943 | 416 | if os.path.exists(dir): | ||
2944 | 417 | os.rename(dir, dir + '.old') | ||
2945 | 418 | os.rename(out, dir) | ||
2946 | 419 | if os.path.exists(dir + '.old'): | ||
2947 | 420 | shutil.rmtree(dir + '.old') | ||
2948 | 421 | |||
2949 | 422 | def _duplicate_db_upgrade(self, cur_format): | ||
2950 | 423 | '''Upgrade database to current format''' | ||
2951 | 424 | |||
2952 | 425 | # Format 3 added a primary key which can't be done as an upgrade in | ||
2953 | 426 | # SQLite | ||
2954 | 427 | if cur_format < 3: | ||
2955 | 428 | raise SystemError('Cannot upgrade database from format earlier than 3') | ||
2956 | 429 | |||
2957 | 430 | cur = self.duplicate_db.cursor() | ||
2958 | 431 | |||
2959 | 432 | cur.execute('UPDATE version SET format = ?', (cur_format,)) | ||
2960 | 433 | self.duplicate_db.commit() | ||
2961 | 434 | |||
2962 | 435 | assert cur_format == self.format_version | ||
2963 | 436 | |||
2964 | 437 | def _duplicate_search_signature(self, sig, id): | ||
2965 | 438 | '''Look up signature in the duplicate db. | ||
2966 | 439 | |||
2967 | 440 | Return [(id, fixed_version)] tuple list. | ||
2968 | 441 | |||
2969 | 442 | There might be several matches if a crash has been reintroduced in a | ||
2970 | 443 | later version. The results are sorted so that the highest fixed version | ||
2971 | 444 | comes first, and "unfixed" being the last result. | ||
2972 | 445 | |||
2973 | 446 | id is the bug we are looking to find a duplicate for. The result will | ||
2974 | 447 | never contain id, to avoid marking a bug as a duplicate of itself if a | ||
2975 | 448 | bug is reprocessed more than once. | ||
2976 | 449 | ''' | ||
2977 | 450 | cur = self.duplicate_db.cursor() | ||
2978 | 451 | cur.execute('SELECT crash_id, fixed_version FROM crashes WHERE signature = ? AND crash_id <> ?', [_u(sig), id]) | ||
2979 | 452 | existing = cur.fetchall() | ||
2980 | 453 | |||
2981 | 454 | def cmp(x, y): | ||
2982 | 455 | x = x[1] | ||
2983 | 456 | y = y[1] | ||
2984 | 457 | if x == y: | ||
2985 | 458 | return 0 | ||
2986 | 459 | if x == '': | ||
2987 | 460 | if y is None: | ||
2988 | 461 | return -1 | ||
2989 | 462 | else: | ||
2990 | 463 | return 1 | ||
2991 | 464 | if y == '': | ||
2992 | 465 | if x is None: | ||
2993 | 466 | return 1 | ||
2994 | 467 | else: | ||
2995 | 468 | return -1 | ||
2996 | 469 | if x is None: | ||
2997 | 470 | return 1 | ||
2998 | 471 | if y is None: | ||
2999 | 472 | return -1 | ||
3000 | 473 | return apport.packaging.compare_versions(x, y) | ||
3001 | 474 | |||
3002 | 475 | if sys.version[0] >= '3': | ||
3003 | 476 | existing.sort(key=cmp_to_key(cmp)) | ||
3004 | 477 | else: | ||
3005 | 478 | existing.sort(cmp=cmp) | ||
3006 | 479 | |||
3007 | 480 | return existing | ||
3008 | 481 | |||
3009 | 482 | def _duplicate_search_address_signature(self, sig): | ||
3010 | 483 | '''Return ID for crash address signature. | ||
3011 | 484 | |||
3012 | 485 | Return None if signature is unknown. | ||
3013 | 486 | ''' | ||
3014 | 487 | if not sig: | ||
3015 | 488 | return None | ||
3016 | 489 | |||
3017 | 490 | cur = self.duplicate_db.cursor() | ||
3018 | 491 | |||
3019 | 492 | cur.execute('SELECT crash_id FROM address_signatures WHERE signature == ?', [sig]) | ||
3020 | 493 | existing_ids = cur.fetchall() | ||
3021 | 494 | assert len(existing_ids) <= 1 | ||
3022 | 495 | if existing_ids: | ||
3023 | 496 | return existing_ids[0][0] | ||
3024 | 497 | else: | ||
3025 | 498 | return None | ||
3026 | 499 | |||
3027 | 500 | def _duplicate_db_dump(self, with_timestamps=False): | ||
3028 | 501 | '''Return the entire duplicate database as a dictionary. | ||
3029 | 502 | |||
3030 | 503 | The returned dictionary maps "signature" to (crash_id, fixed_version) | ||
3031 | 504 | pairs. | ||
3032 | 505 | |||
3033 | 506 | If with_timestamps is True, then the map will contain triples | ||
3034 | 507 | (crash_id, fixed_version, last_change) instead. | ||
3035 | 508 | |||
3036 | 509 | This is mainly useful for debugging and test suites. | ||
3037 | 510 | ''' | ||
3038 | 511 | assert self.duplicate_db, 'init_duplicate_db() needs to be called before' | ||
3039 | 512 | |||
3040 | 513 | dump = {} | ||
3041 | 514 | cur = self.duplicate_db.cursor() | ||
3042 | 515 | cur.execute('SELECT * FROM crashes') | ||
3043 | 516 | for (sig, id, ver, last_change) in cur: | ||
3044 | 517 | if with_timestamps: | ||
3045 | 518 | dump[sig] = (id, ver, last_change) | ||
3046 | 519 | else: | ||
3047 | 520 | dump[sig] = (id, ver) | ||
3048 | 521 | return dump | ||
3049 | 522 | |||
3050 | 523 | def _duplicate_db_sync_status(self, id): | ||
3051 | 524 | '''Update the duplicate db to the reality of the report in the crash db. | ||
3052 | 525 | |||
3053 | 526 | This uses get_fixed_version() to get the status of the given crash. | ||
3054 | 527 | An invalid ID gets removed from the duplicate db, and a crash which got | ||
3055 | 528 | fixed is marked as such in the database. | ||
3056 | 529 | ''' | ||
3057 | 530 | assert self.duplicate_db, 'init_duplicate_db() needs to be called before' | ||
3058 | 531 | |||
3059 | 532 | cur = self.duplicate_db.cursor() | ||
3060 | 533 | cur.execute('SELECT fixed_version FROM crashes WHERE crash_id = ?', [id]) | ||
3061 | 534 | db_fixed_version = cur.fetchone() | ||
3062 | 535 | if not db_fixed_version: | ||
3063 | 536 | return | ||
3064 | 537 | db_fixed_version = db_fixed_version[0] | ||
3065 | 538 | |||
3066 | 539 | real_fixed_version = self.get_fixed_version(id) | ||
3067 | 540 | |||
3068 | 541 | # crash got rejected | ||
3069 | 542 | if real_fixed_version == 'invalid': | ||
3070 | 543 | print('DEBUG: bug %i was invalidated, removing from database' % id) | ||
3071 | 544 | self.duplicate_db_remove(id) | ||
3072 | 545 | return | ||
3073 | 546 | |||
3074 | 547 | # crash got fixed | ||
3075 | 548 | if not db_fixed_version and real_fixed_version: | ||
3076 | 549 | print('DEBUG: bug %i got fixed in version %s, updating database' % (id, real_fixed_version)) | ||
3077 | 550 | self.duplicate_db_fixed(id, real_fixed_version) | ||
3078 | 551 | return | ||
3079 | 552 | |||
3080 | 553 | # crash got reopened | ||
3081 | 554 | if db_fixed_version and not real_fixed_version: | ||
3082 | 555 | print('DEBUG: bug %i got reopened, dropping fixed version %s from database' % (id, db_fixed_version)) | ||
3083 | 556 | self.duplicate_db_fixed(id, real_fixed_version) | ||
3084 | 557 | return | ||
3085 | 558 | |||
3086 | 559 | def _duplicate_db_add_address_signature(self, sig, id): | ||
3087 | 560 | # sanity check | ||
3088 | 561 | existing = self._duplicate_search_address_signature(sig) | ||
3089 | 562 | if existing: | ||
3090 | 563 | if existing != id: | ||
3091 | 564 | raise SystemError('ID %i has signature %s, but database already has that signature for ID %i' % ( | ||
3092 | 565 | id, sig, existing)) | ||
3093 | 566 | else: | ||
3094 | 567 | cur = self.duplicate_db.cursor() | ||
3095 | 568 | cur.execute('INSERT INTO address_signatures VALUES (?, ?)', (_u(sig), id)) | ||
3096 | 569 | self.duplicate_db.commit() | ||
3097 | 570 | |||
3098 | 571 | def _duplicate_db_merge_id(self, dup, master): | ||
3099 | 572 | '''Merge two crash IDs. | ||
3100 | 573 | |||
3101 | 574 | This is necessary when having to mark a bug as a duplicate if it | ||
3102 | 575 | already is in the duplicate DB. | ||
3103 | 576 | ''' | ||
3104 | 577 | assert self.duplicate_db, 'init_duplicate_db() needs to be called before' | ||
3105 | 578 | |||
3106 | 579 | cur = self.duplicate_db.cursor() | ||
3107 | 580 | cur.execute('DELETE FROM crashes WHERE crash_id = ?', [dup]) | ||
3108 | 581 | cur.execute('UPDATE address_signatures SET crash_id = ? WHERE crash_id = ?', | ||
3109 | 582 | [master, dup]) | ||
3110 | 583 | self.duplicate_db.commit() | ||
3111 | 584 | |||
3112 | 585 | @classmethod | ||
3113 | 586 | def duplicate_sig_hash(klass, sig): | ||
3114 | 587 | '''Create a www/URL proof hash for a duplicate signature''' | ||
3115 | 588 | |||
3116 | 589 | # cannot hash multi-line custom duplicate signatures | ||
3117 | 590 | if '\n' in sig: | ||
3118 | 591 | return None | ||
3119 | 592 | |||
3120 | 593 | # custom DuplicateSignatures have a free format, split off first word | ||
3121 | 594 | i = sig.split(' ', 1)[0] | ||
3122 | 595 | # standard crash/address signatures use ':' as field separator, usually | ||
3123 | 596 | # for ExecutableName:Signal | ||
3124 | 597 | i = '_'.join(i.split(':', 2)[:2]) | ||
3125 | 598 | # we manually quote '/' to make them nicer to read | ||
3126 | 599 | i = i.replace('/', '_') | ||
3127 | 600 | i = quote_plus(i.encode('UTF-8')) | ||
3128 | 601 | # avoid too long file names | ||
3129 | 602 | i = i[:200] | ||
3130 | 603 | return i | ||
3131 | 604 | |||
3132 | 605 | # | ||
3133 | 606 | # Abstract functions that need to be implemented by subclasses | ||
3134 | 607 | # | ||
3135 | 608 | |||
3136 | 609 | def upload(self, report, progress_callback=None): | ||
3137 | 610 | '''Upload given problem report return a handle for it. | ||
3138 | 611 | |||
3139 | 612 | This should happen noninteractively. | ||
3140 | 613 | |||
3141 | 614 | If the implementation supports it, and a function progress_callback is | ||
3142 | 615 | passed, that is called repeatedly with two arguments: the number of | ||
3143 | 616 | bytes already sent, and the total number of bytes to send. This can be | ||
3144 | 617 | used to provide a proper upload progress indication on frontends. | ||
3145 | 618 | |||
3146 | 619 | Implementations ought to "assert self.accepts(report)". The UI logic | ||
3147 | 620 | already prevents uploading a report to a database which does not accept | ||
3148 | 621 | it, but for third-party users of the API this should still be checked. | ||
3149 | 622 | |||
3150 | 623 | This method can raise a NeedsCredentials exception in case of failure. | ||
3151 | 624 | ''' | ||
3152 | 625 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3153 | 626 | |||
3154 | 627 | def get_comment_url(self, report, handle): | ||
3155 | 628 | '''Return an URL that should be opened after report has been uploaded | ||
3156 | 629 | and upload() returned handle. | ||
3157 | 630 | |||
3158 | 631 | Should return None if no URL should be opened (anonymous filing without | ||
3159 | 632 | user comments); in that case this function should do whichever | ||
3160 | 633 | interactive steps it wants to perform. | ||
3161 | 634 | ''' | ||
3162 | 635 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3163 | 636 | |||
3164 | 637 | def get_id_url(self, report, id): | ||
3165 | 638 | '''Return URL for a given report ID. | ||
3166 | 639 | |||
3167 | 640 | The report is passed in case building the URL needs additional | ||
3168 | 641 | information from it, such as the SourcePackage name. | ||
3169 | 642 | |||
3170 | 643 | Return None if URL is not available or cannot be determined. | ||
3171 | 644 | ''' | ||
3172 | 645 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3173 | 646 | |||
3174 | 647 | def download(self, id): | ||
3175 | 648 | '''Download the problem report from given ID and return a Report.''' | ||
3176 | 649 | |||
3177 | 650 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3178 | 651 | |||
3179 | 652 | def update(self, id, report, comment, change_description=False, | ||
3180 | 653 | attachment_comment=None, key_filter=None): | ||
3181 | 654 | '''Update the given report ID with all data from report. | ||
3182 | 655 | |||
3183 | 656 | This creates a text comment with the "short" data (see | ||
3184 | 657 | ProblemReport.write_mime()), and creates attachments for all the | ||
3185 | 658 | bulk/binary data. | ||
3186 | 659 | |||
3187 | 660 | If change_description is True, and the crash db implementation supports | ||
3188 | 661 | it, the short data will be put into the description instead (like in a | ||
3189 | 662 | new bug). | ||
3190 | 663 | |||
3191 | 664 | comment will be added to the "short" data. If attachment_comment is | ||
3192 | 665 | given, it will be added to the attachment uploads. | ||
3193 | 666 | |||
3194 | 667 | If key_filter is a list or set, then only those keys will be added. | ||
3195 | 668 | ''' | ||
3196 | 669 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3197 | 670 | |||
3198 | 671 | def update_traces(self, id, report, comment=''): | ||
3199 | 672 | '''Update the given report ID for retracing results. | ||
3200 | 673 | |||
3201 | 674 | This updates Stacktrace, ThreadStacktrace, StacktraceTop, | ||
3202 | 675 | and StacktraceSource. You can also supply an additional comment. | ||
3203 | 676 | ''' | ||
3204 | 677 | self.update(id, report, comment, key_filter=[ | ||
3205 | 678 | 'Stacktrace', 'ThreadStacktrace', 'StacktraceSource', 'StacktraceTop']) | ||
3206 | 679 | |||
3207 | 680 | def set_credentials(self, username, password): | ||
3208 | 681 | '''Set username and password.''' | ||
3209 | 682 | |||
3210 | 683 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3211 | 684 | |||
3212 | 685 | def get_distro_release(self, id): | ||
3213 | 686 | '''Get 'DistroRelease: <release>' from the report ID.''' | ||
3214 | 687 | |||
3215 | 688 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3216 | 689 | |||
3217 | 690 | def get_unretraced(self): | ||
3218 | 691 | '''Return set of crash IDs which have not been retraced yet. | ||
3219 | 692 | |||
3220 | 693 | This should only include crashes which match the current host | ||
3221 | 694 | architecture. | ||
3222 | 695 | ''' | ||
3223 | 696 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3224 | 697 | |||
3225 | 698 | def get_dup_unchecked(self): | ||
3226 | 699 | '''Return set of crash IDs which need duplicate checking. | ||
3227 | 700 | |||
3228 | 701 | This is mainly useful for crashes of scripting languages such as | ||
3229 | 702 | Python, since they do not need to be retraced. It should not return | ||
3230 | 703 | bugs that are covered by get_unretraced(). | ||
3231 | 704 | ''' | ||
3232 | 705 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3233 | 706 | |||
3234 | 707 | def get_unfixed(self): | ||
3235 | 708 | '''Return an ID set of all crashes which are not yet fixed. | ||
3236 | 709 | |||
3237 | 710 | The list must not contain bugs which were rejected or duplicate. | ||
3238 | 711 | |||
3239 | 712 | This function should make sure that the returned list is correct. If | ||
3240 | 713 | there are any errors with connecting to the crash database, it should | ||
3241 | 714 | raise an exception (preferably IOError). | ||
3242 | 715 | ''' | ||
3243 | 716 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3244 | 717 | |||
3245 | 718 | def get_fixed_version(self, id): | ||
3246 | 719 | '''Return the package version that fixes a given crash. | ||
3247 | 720 | |||
3248 | 721 | Return None if the crash is not yet fixed, or an empty string if the | ||
3249 | 722 | crash is fixed, but it cannot be determined by which version. Return | ||
3250 | 723 | 'invalid' if the crash report got invalidated, such as closed a | ||
3251 | 724 | duplicate or rejected. | ||
3252 | 725 | |||
3253 | 726 | This function should make sure that the returned result is correct. If | ||
3254 | 727 | there are any errors with connecting to the crash database, it should | ||
3255 | 728 | raise an exception (preferably IOError). | ||
3256 | 729 | ''' | ||
3257 | 730 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3258 | 731 | |||
3259 | 732 | def get_affected_packages(self, id): | ||
3260 | 733 | '''Return list of affected source packages for given ID.''' | ||
3261 | 734 | |||
3262 | 735 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3263 | 736 | |||
3264 | 737 | def is_reporter(self, id): | ||
3265 | 738 | '''Check whether the user is the reporter of given ID.''' | ||
3266 | 739 | |||
3267 | 740 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3268 | 741 | |||
3269 | 742 | def can_update(self, id): | ||
3270 | 743 | '''Check whether the user is eligible to update a report. | ||
3271 | 744 | |||
3272 | 745 | A user should add additional information to an existing ID if (s)he is | ||
3273 | 746 | the reporter or subscribed, the bug is open, not a duplicate, etc. The | ||
3274 | 747 | exact policy and checks should be done according to the particular | ||
3275 | 748 | implementation. | ||
3276 | 749 | ''' | ||
3277 | 750 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3278 | 751 | |||
3279 | 752 | def duplicate_of(self, id): | ||
3280 | 753 | '''Return master ID for a duplicate bug. | ||
3281 | 754 | |||
3282 | 755 | If the bug is not a duplicate, return None. | ||
3283 | 756 | ''' | ||
3284 | 757 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3285 | 758 | |||
3286 | 759 | def close_duplicate(self, report, id, master): | ||
3287 | 760 | '''Mark a crash id as duplicate of given master ID. | ||
3288 | 761 | |||
3289 | 762 | If master is None, id gets un-duplicated. | ||
3290 | 763 | ''' | ||
3291 | 764 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3292 | 765 | |||
3293 | 766 | def mark_regression(self, id, master): | ||
3294 | 767 | '''Mark a crash id as reintroducing an earlier crash which is | ||
3295 | 768 | already marked as fixed (having ID 'master').''' | ||
3296 | 769 | |||
3297 | 770 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3298 | 771 | |||
3299 | 772 | def mark_retraced(self, id): | ||
3300 | 773 | '''Mark crash id as retraced.''' | ||
3301 | 774 | |||
3302 | 775 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3303 | 776 | |||
3304 | 777 | def mark_retrace_failed(self, id, invalid_msg=None): | ||
3305 | 778 | '''Mark crash id as 'failed to retrace'. | ||
3306 | 779 | |||
3307 | 780 | If invalid_msg is given, the bug should be closed as invalid with given | ||
3308 | 781 | message, otherwise just marked as a failed retrace. | ||
3309 | 782 | |||
3310 | 783 | This can be a no-op if you are not interested in this. | ||
3311 | 784 | ''' | ||
3312 | 785 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3313 | 786 | |||
3314 | 787 | def _mark_dup_checked(self, id, report): | ||
3315 | 788 | '''Mark crash id as checked for being a duplicate | ||
3316 | 789 | |||
3317 | 790 | This is an internal method that should not be called from outside. | ||
3318 | 791 | ''' | ||
3319 | 792 | raise NotImplementedError('this method must be implemented by a concrete subclass') | ||
3320 | 793 | |||
3321 | 794 | # | ||
3322 | 795 | # factory | ||
3323 | 796 | # | ||
3324 | 797 | |||
3325 | 798 | |||
3326 | 799 | def get_crashdb(auth_file, name=None, conf=None): | ||
3327 | 800 | '''Return a CrashDatabase object for the given crash db name. | ||
3328 | 801 | |||
3329 | 802 | This reads the configuration file 'conf'. | ||
3330 | 803 | |||
3331 | 804 | If name is None, it defaults to the 'default' value in conf. | ||
3332 | 805 | |||
3333 | 806 | If conf is None, it defaults to the environment variable | ||
3334 | 807 | APPORT_CRASHDB_CONF; if that does not exist, the hardcoded default is | ||
3335 | 808 | /etc/apport/crashdb.conf. This Python syntax file needs to specify: | ||
3336 | 809 | |||
3337 | 810 | - A string variable 'default', giving a default value for 'name' if that is | ||
3338 | 811 | None. | ||
3339 | 812 | |||
3340 | 813 | - A dictionary 'databases' which maps names to crash db configuration | ||
3341 | 814 | dictionaries. These need to have at least the key 'impl' (Python module | ||
3342 | 815 | in apport.crashdb_impl which contains a concrete 'CrashDatabase' class | ||
3343 | 816 | implementation for that crash db type). Other generally known options are | ||
3344 | 817 | 'bug_pattern_url', 'dupdb_url', and 'problem_types'. | ||
3345 | 818 | ''' | ||
3346 | 819 | if not conf: | ||
3347 | 820 | conf = os.environ.get('APPORT_CRASHDB_CONF', '/etc/apport/crashdb.conf') | ||
3348 | 821 | settings = {} | ||
3349 | 822 | with open(conf) as f: | ||
3350 | 823 | exec(compile(f.read(), conf, 'exec'), settings) | ||
3351 | 824 | |||
3352 | 825 | # Load third parties crashdb.conf | ||
3353 | 826 | confdDir = conf + '.d' | ||
3354 | 827 | if os.path.isdir(confdDir): | ||
3355 | 828 | for cf in os.listdir(confdDir): | ||
3356 | 829 | cfpath = os.path.join(confdDir, cf) | ||
3357 | 830 | if os.path.isfile(cfpath) and cf.endswith('.conf'): | ||
3358 | 831 | try: | ||
3359 | 832 | with open(cfpath) as f: | ||
3360 | 833 | exec(compile(f.read(), cfpath, 'exec'), settings['databases']) | ||
3361 | 834 | except Exception as e: | ||
3362 | 835 | # ignore broken files | ||
3363 | 836 | sys.stderr.write('Invalid file %s: %s\n' % (cfpath, str(e))) | ||
3364 | 837 | pass | ||
3365 | 838 | |||
3366 | 839 | if not name: | ||
3367 | 840 | name = settings['default'] | ||
3368 | 841 | |||
3369 | 842 | return load_crashdb(auth_file, settings['databases'][name]) | ||
3370 | 843 | |||
3371 | 844 | |||
3372 | 845 | def load_crashdb(auth_file, spec): | ||
3373 | 846 | '''Return a CrashDatabase object for a given DB specification. | ||
3374 | 847 | |||
3375 | 848 | spec is a crash db configuration dictionary as described in get_crashdb(). | ||
3376 | 849 | ''' | ||
3377 | 850 | m = __import__('apport.crashdb_impl.' + spec['impl'], globals(), locals(), ['CrashDatabase']) | ||
3378 | 851 | return m.CrashDatabase(auth_file, spec) | ||
3379 | 852 | |||
3380 | 853 | |||
3381 | 854 | class NeedsCredentials(Exception): | ||
3382 | 855 | '''This may be raised when unable to log in to the crashdb.''' | ||
3383 | 856 | pass | ||
3384 | 0 | 857 | ||
3385 | === added directory 'apport/crashdb_impl' | |||
3386 | === added file 'apport/crashdb_impl/__init__.py' | |||
3387 | === added file 'apport/crashdb_impl/debian.py' | |||
3388 | --- apport/crashdb_impl/debian.py 1970-01-01 00:00:00 +0000 | |||
3389 | +++ apport/crashdb_impl/debian.py 2013-03-13 18:57:21 +0000 | |||
3390 | @@ -0,0 +1,113 @@ | |||
3391 | 1 | '''Debian crash database interface.''' | ||
3392 | 2 | |||
3393 | 3 | # Debian adaptation Copyright (C) 2012 Ritesh Raj Sarraf <rrs@debian.org> | ||
3394 | 4 | # | ||
3395 | 5 | # This program is free software; you can redistribute it and/or modify it | ||
3396 | 6 | # under the terms of the GNU General Public License as published by the | ||
3397 | 7 | # Free Software Foundation; either version 2 of the License, or (at your | ||
3398 | 8 | # option) any later version. See http://www.gnu.org/copyleft/gpl.html for | ||
3399 | 9 | # the full text of the license. | ||
3400 | 10 | |||
3401 | 11 | |||
3402 | 12 | import smtplib, tempfile | ||
3403 | 13 | from email.mime.text import MIMEText | ||
3404 | 14 | |||
3405 | 15 | import apport | ||
3406 | 16 | import apport.crashdb | ||
3407 | 17 | |||
3408 | 18 | |||
3409 | 19 | class CrashDatabase(apport.crashdb.CrashDatabase): | ||
3410 | 20 | ''' | ||
3411 | 21 | Debian crash database | ||
3412 | 22 | This is a Apport CrashDB implementation for interacting with Debian BTS | ||
3413 | 23 | ''' | ||
3414 | 24 | def __init__(self, auth_file, options): | ||
3415 | 25 | ''' | ||
3416 | 26 | Initialize crash database connection. | ||
3417 | 27 | |||
3418 | 28 | Debian implementation is pretty basic as most of its bug management | ||
3419 | 29 | processes revolve around the email interface | ||
3420 | 30 | ''' | ||
3421 | 31 | apport.crashdb.CrashDatabase.__init__(self, auth_file, options) | ||
3422 | 32 | self.options = options | ||
3423 | 33 | |||
3424 | 34 | if not self.options.get('smtphost'): | ||
3425 | 35 | self.options['smtphost'] = 'reportbug.debian.org' | ||
3426 | 36 | |||
3427 | 37 | if not self.options.get('recipient'): | ||
3428 | 38 | self.options['recipient'] = 'submit@bugs.debian.org' | ||
3429 | 39 | |||
3430 | 40 | def accepts(self, report): | ||
3431 | 41 | ''' | ||
3432 | 42 | Check if this report can be uploaded to this database. | ||
3433 | 43 | Checks for the proper settings of apport. | ||
3434 | 44 | ''' | ||
3435 | 45 | if not self.options.get('sender') and 'UnreportableReason' not in report: | ||
3436 | 46 | report['UnreportableReason'] = 'Please configure sender settings in /etc/apport/crashdb.conf' | ||
3437 | 47 | |||
3438 | 48 | # At this time, we are not ready to take CrashDumps | ||
3439 | 49 | if 'Stacktrace' in report and not report.has_useful_stacktrace(): | ||
3440 | 50 | report['UnreportableReason'] = 'Incomplete backtrace. Please install the debug symbol packages' | ||
3441 | 51 | |||
3442 | 52 | return apport.crashdb.CrashDatabase.accepts(self, report) | ||
3443 | 53 | |||
3444 | 54 | def upload(self, report, progress_callback=None): | ||
3445 | 55 | '''Upload given problem report return a handle for it. | ||
3446 | 56 | |||
3447 | 57 | In Debian, we use BTS, which is heavily email oriented | ||
3448 | 58 | This method crafts the bug into an email report understood by Debian BTS | ||
3449 | 59 | ''' | ||
3450 | 60 | # first and foremost, let's check if the apport bug filing settings are set correct | ||
3451 | 61 | assert self.accepts(report) | ||
3452 | 62 | |||
3453 | 63 | # Frame the report in the format the BTS understands | ||
3454 | 64 | try: | ||
3455 | 65 | (buggyPackage, buggyVersion) = report['Package'].split(' ') | ||
3456 | 66 | except (KeyError, ValueError): | ||
3457 | 67 | return False | ||
3458 | 68 | |||
3459 | 69 | temp = tempfile.NamedTemporaryFile() | ||
3460 | 70 | |||
3461 | 71 | temp.file.write(('Package: ' + buggyPackage + '\n').encode('UTF-8')) | ||
3462 | 72 | temp.file.write(('Version: ' + buggyVersion + '\n\n\n').encode('UTF-8')) | ||
3463 | 73 | temp.file.write(('=============================\n\n').encode('UTF-8')) | ||
3464 | 74 | |||
3465 | 75 | # Let's remove the CoreDump first | ||
3466 | 76 | |||
3467 | 77 | # Even if we have a valid backtrace, we already are reporting it as text | ||
3468 | 78 | # We don't want to send very large emails to the BTS. | ||
3469 | 79 | # OTOH, if the backtrace is invalid, has_useful_backtrace() will already | ||
3470 | 80 | # deny reporting of the bug report. | ||
3471 | 81 | try: | ||
3472 | 82 | del report['CoreDump'] | ||
3473 | 83 | except KeyError: | ||
3474 | 84 | pass | ||
3475 | 85 | |||
3476 | 86 | # Now write the apport bug report | ||
3477 | 87 | report.write(temp) | ||
3478 | 88 | |||
3479 | 89 | temp.file.seek(0) | ||
3480 | 90 | |||
3481 | 91 | msg = MIMEText(temp.file.read().decode('UTF-8')) | ||
3482 | 92 | msg['Subject'] = report['Title'] | ||
3483 | 93 | msg['From'] = self.options['sender'] | ||
3484 | 94 | msg['To'] = self.options['recipient'] | ||
3485 | 95 | |||
3486 | 96 | # Subscribe the submitted to the bug report | ||
3487 | 97 | msg.add_header('X-Debbugs-CC', self.options['sender']) | ||
3488 | 98 | msg.add_header('Usertag', 'apport-%s' % report['ProblemType'].lower()) | ||
3489 | 99 | |||
3490 | 100 | s = smtplib.SMTP(self.options['smtphost']) | ||
3491 | 101 | s.sendmail(self.options['sender'], self.options['recipient'], msg.as_string().encode('UTF-8')) | ||
3492 | 102 | s.quit() | ||
3493 | 103 | |||
3494 | 104 | def get_comment_url(self, report, handle): | ||
3495 | 105 | ''' | ||
3496 | 106 | Return an URL that should be opened after report has been uploaded | ||
3497 | 107 | and upload() returned handle. | ||
3498 | 108 | |||
3499 | 109 | Should return None if no URL should be opened (anonymous filing without | ||
3500 | 110 | user comments); in that case this function should do whichever | ||
3501 | 111 | interactive steps it wants to perform. | ||
3502 | 112 | ''' | ||
3503 | 113 | return None | ||
3504 | 0 | 114 | ||
3505 | === added file 'apport/crashdb_impl/launchpad.py' | |||
3506 | --- apport/crashdb_impl/launchpad.py 1970-01-01 00:00:00 +0000 | |||
3507 | +++ apport/crashdb_impl/launchpad.py 2013-03-13 18:57:21 +0000 | |||
3508 | @@ -0,0 +1,1993 @@ | |||
3509 | 1 | # vim: set fileencoding=UTF-8 : | ||
3510 | 2 | '''Crash database implementation for Launchpad.''' | ||
3511 | 3 | |||
3512 | 4 | # Copyright (C) 2007 - 2009 Canonical Ltd. | ||
3513 | 5 | # Authors: Martin Pitt <martin.pitt@ubuntu.com> and Markus Korn <thekorn@gmx.de> | ||
3514 | 6 | # | ||
3515 | 7 | # This program is free software; you can redistribute it and/or modify it | ||
3516 | 8 | # under the terms of the GNU General Public License as published by the | ||
3517 | 9 | # Free Software Foundation; either version 2 of the License, or (at your | ||
3518 | 10 | # option) any later version. See http://www.gnu.org/copyleft/gpl.html for | ||
3519 | 11 | # the full text of the license. | ||
3520 | 12 | |||
3521 | 13 | import tempfile, os.path, re, gzip, sys, email, time | ||
3522 | 14 | |||
3523 | 15 | from io import BytesIO | ||
3524 | 16 | |||
3525 | 17 | if sys.version_info.major == 2: | ||
3526 | 18 | from urllib2 import HTTPSHandler, Request, build_opener | ||
3527 | 19 | from httplib import HTTPSConnection | ||
3528 | 20 | from urllib import urlencode, urlopen | ||
3529 | 21 | (HTTPSHandler, Request, build_opener, HTTPSConnection, urlencode, urlopen) # pyflakes | ||
3530 | 22 | _python2 = True | ||
3531 | 23 | else: | ||
3532 | 24 | from urllib.request import HTTPSHandler, Request, build_opener, urlopen | ||
3533 | 25 | from urllib.parse import urlencode | ||
3534 | 26 | from http.client import HTTPSConnection | ||
3535 | 27 | _python2 = False | ||
3536 | 28 | |||
3537 | 29 | try: | ||
3538 | 30 | from launchpadlib.errors import HTTPError | ||
3539 | 31 | from launchpadlib.launchpad import Launchpad | ||
3540 | 32 | Launchpad # pyflakes | ||
3541 | 33 | except ImportError: | ||
3542 | 34 | # if launchpadlib is not available, only client-side reporting will work | ||
3543 | 35 | Launchpad = None | ||
3544 | 36 | |||
3545 | 37 | import apport.crashdb | ||
3546 | 38 | import apport | ||
3547 | 39 | |||
3548 | 40 | default_credentials_path = os.path.expanduser('~/.cache/apport/launchpad.credentials') | ||
3549 | 41 | |||
3550 | 42 | |||
3551 | 43 | def filter_filename(attachments): | ||
3552 | 44 | for attachment in attachments: | ||
3553 | 45 | try: | ||
3554 | 46 | f = attachment.data.open() | ||
3555 | 47 | except HTTPError: | ||
3556 | 48 | apport.error('Broken attachment on bug, ignoring') | ||
3557 | 49 | continue | ||
3558 | 50 | name = f.filename | ||
3559 | 51 | if name.endswith('.txt') or name.endswith('.gz'): | ||
3560 | 52 | yield f | ||
3561 | 53 | |||
3562 | 54 | |||
3563 | 55 | def id_set(tasks): | ||
3564 | 56 | # same as set(int(i.bug.id) for i in tasks) but faster | ||
3565 | 57 | return set(int(i.self_link.split('/').pop()) for i in tasks) | ||
3566 | 58 | |||
3567 | 59 | |||
3568 | 60 | class CrashDatabase(apport.crashdb.CrashDatabase): | ||
3569 | 61 | '''Launchpad implementation of crash database interface.''' | ||
3570 | 62 | |||
3571 | 63 | def __init__(self, auth, options): | ||
3572 | 64 | '''Initialize Launchpad crash database. | ||
3573 | 65 | |||
3574 | 66 | You need to specify a launchpadlib-style credentials file to | ||
3575 | 67 | access launchpad. If you supply None, it will use | ||
3576 | 68 | default_credentials_path (~/.cache/apport/launchpad.credentials). | ||
3577 | 69 | |||
3578 | 70 | Recognized options are: | ||
3579 | 71 | - distro: Name of the distribution in Launchpad | ||
3580 | 72 | - project: Name of the project in Launchpad | ||
3581 | 73 | (Note that exactly one of "distro" or "project" must be given.) | ||
3582 | 74 | - launchpad_instance: If set, this uses the given launchpad instance | ||
3583 | 75 | instead of production (optional). This can be overriden or set by | ||
3584 | 76 | $APPORT_LAUNCHPAD_INSTANCE environment. | ||
3585 | 77 | - cache_dir: Path to a permanent cache directory; by default it uses a | ||
3586 | 78 | temporary one. (optional). This can be overridden or set by | ||
3587 | 79 | $APPORT_LAUNCHPAD_CACHE environment. | ||
3588 | 80 | - escalation_subscription: This subscribes the given person or team to | ||
3589 | 81 | a bug once it gets the 10th duplicate. | ||
3590 | 82 | - escalation_tag: This adds the given tag to a bug once it gets more | ||
3591 | 83 | than 10 duplicates. | ||
3592 | 84 | - initial_subscriber: The Launchpad user which gets subscribed to newly | ||
3593 | 85 | filed bugs (default: "apport"). It should be a bot user which the | ||
3594 | 86 | crash-digger instance runs as, as this will get to see all bug | ||
3595 | 87 | details immediately. | ||
3596 | 88 | - triaging_team: The Launchpad user/team which gets subscribed after | ||
3597 | 89 | updating a crash report bug by the retracer (default: | ||
3598 | 90 | "ubuntu-crashes-universe") | ||
3599 | 91 | - architecture: If set, this sets and watches out for needs-*-retrace | ||
3600 | 92 | tags of this architecture. This is useful when being used with | ||
3601 | 93 | apport-retrace and crash-digger to process crash reports of foreign | ||
3602 | 94 | architectures. Defaults to system architecture. | ||
3603 | 95 | ''' | ||
3604 | 96 | if os.getenv('APPORT_LAUNCHPAD_INSTANCE'): | ||
3605 | 97 | options['launchpad_instance'] = os.getenv( | ||
3606 | 98 | 'APPORT_LAUNCHPAD_INSTANCE') | ||
3607 | 99 | if not auth: | ||
3608 | 100 | lp_instance = options.get('launchpad_instance') | ||
3609 | 101 | if lp_instance: | ||
3610 | 102 | auth = default_credentials_path + '.' + lp_instance.split('://', 1)[-1] | ||
3611 | 103 | else: | ||
3612 | 104 | auth = default_credentials_path | ||
3613 | 105 | apport.crashdb.CrashDatabase.__init__(self, auth, options) | ||
3614 | 106 | |||
3615 | 107 | self.distro = options.get('distro') | ||
3616 | 108 | if self.distro: | ||
3617 | 109 | assert 'project' not in options, 'Must not set both "project" and "distro" option' | ||
3618 | 110 | else: | ||
3619 | 111 | assert 'project' in options, 'Need to have either "project" or "distro" option' | ||
3620 | 112 | |||
3621 | 113 | if 'architecture' in options: | ||
3622 | 114 | self.arch_tag = 'need-%s-retrace' % options['architecture'] | ||
3623 | 115 | else: | ||
3624 | 116 | self.arch_tag = 'need-%s-retrace' % apport.packaging.get_system_architecture() | ||
3625 | 117 | self.options = options | ||
3626 | 118 | self.auth = auth | ||
3627 | 119 | assert self.auth | ||
3628 | 120 | |||
3629 | 121 | self.__launchpad = None | ||
3630 | 122 | self.__lp_distro = None | ||
3631 | 123 | self.__lpcache = os.getenv('APPORT_LAUNCHPAD_CACHE', options.get('cache_dir')) | ||
3632 | 124 | |||
3633 | 125 | @property | ||
3634 | 126 | def launchpad(self): | ||
3635 | 127 | '''Return Launchpad instance.''' | ||
3636 | 128 | |||
3637 | 129 | if self.__launchpad: | ||
3638 | 130 | return self.__launchpad | ||
3639 | 131 | |||
3640 | 132 | if Launchpad is None: | ||
3641 | 133 | sys.stderr.write('ERROR: The launchpadlib Python module is not installed. This functionality is not available.\n') | ||
3642 | 134 | sys.exit(1) | ||
3643 | 135 | |||
3644 | 136 | if self.options.get('launchpad_instance'): | ||
3645 | 137 | launchpad_instance = self.options.get('launchpad_instance') | ||
3646 | 138 | else: | ||
3647 | 139 | launchpad_instance = 'production' | ||
3648 | 140 | |||
3649 | 141 | auth_dir = os.path.dirname(self.auth) | ||
3650 | 142 | if auth_dir and not os.path.isdir(auth_dir): | ||
3651 | 143 | os.makedirs(auth_dir) | ||
3652 | 144 | |||
3653 | 145 | try: | ||
3654 | 146 | self.__launchpad = Launchpad.login_with('apport-collect', | ||
3655 | 147 | launchpad_instance, | ||
3656 | 148 | launchpadlib_dir=self.__lpcache, | ||
3657 | 149 | allow_access_levels=['WRITE_PRIVATE'], | ||
3658 | 150 | credentials_file=self.auth, | ||
3659 | 151 | version='1.0') | ||
3660 | 152 | except Exception as e: | ||
3661 | 153 | if hasattr(e, 'content'): | ||
3662 | 154 | msg = e.content | ||
3663 | 155 | else: | ||
3664 | 156 | msg = str(e) | ||
3665 | 157 | apport.error('connecting to Launchpad failed: %s\nYou can reset the credentials by removing the file "%s"', msg, self.auth) | ||
3666 | 158 | sys.exit(99) # transient error | ||
3667 | 159 | |||
3668 | 160 | return self.__launchpad | ||
3669 | 161 | |||
3670 | 162 | def _get_distro_tasks(self, tasks): | ||
3671 | 163 | if not self.distro: | ||
3672 | 164 | raise StopIteration | ||
3673 | 165 | |||
3674 | 166 | for t in tasks: | ||
3675 | 167 | if t.bug_target_name.lower() == self.distro or \ | ||
3676 | 168 | re.match('^.+\(%s.*\)$' % self.distro, t.bug_target_name.lower()): | ||
3677 | 169 | yield t | ||
3678 | 170 | |||
3679 | 171 | @property | ||
3680 | 172 | def lp_distro(self): | ||
3681 | 173 | if self.__lp_distro is None: | ||
3682 | 174 | if self.distro: | ||
3683 | 175 | self.__lp_distro = self.launchpad.distributions[self.distro] | ||
3684 | 176 | elif 'project' in self.options: | ||
3685 | 177 | self.__lp_distro = self.launchpad.projects[self.options['project']] | ||
3686 | 178 | else: | ||
3687 | 179 | raise SystemError('distro or project needs to be specified in crashdb options') | ||
3688 | 180 | |||
3689 | 181 | return self.__lp_distro | ||
3690 | 182 | |||
3691 | 183 | def upload(self, report, progress_callback=None): | ||
3692 | 184 | '''Upload given problem report return a handle for it. | ||
3693 | 185 | |||
3694 | 186 | This should happen noninteractively. | ||
3695 | 187 | |||
3696 | 188 | If the implementation supports it, and a function progress_callback is | ||
3697 | 189 | passed, that is called repeatedly with two arguments: the number of | ||
3698 | 190 | bytes already sent, and the total number of bytes to send. This can be | ||
3699 | 191 | used to provide a proper upload progress indication on frontends. | ||
3700 | 192 | ''' | ||
3701 | 193 | assert self.accepts(report) | ||
3702 | 194 | |||
3703 | 195 | blob_file = self._generate_upload_blob(report) | ||
3704 | 196 | ticket = upload_blob(blob_file, progress_callback, hostname=self.get_hostname()) | ||
3705 | 197 | blob_file.close() | ||
3706 | 198 | assert ticket | ||
3707 | 199 | return ticket | ||
3708 | 200 | |||
3709 | 201 | def get_hostname(self): | ||
3710 | 202 | '''Return the hostname for the Launchpad instance.''' | ||
3711 | 203 | |||
3712 | 204 | launchpad_instance = self.options.get('launchpad_instance') | ||
3713 | 205 | if launchpad_instance: | ||
3714 | 206 | if launchpad_instance == 'staging': | ||
3715 | 207 | hostname = 'staging.launchpad.net' | ||
3716 | 208 | else: | ||
3717 | 209 | hostname = 'launchpad.dev' | ||
3718 | 210 | else: | ||
3719 | 211 | hostname = 'launchpad.net' | ||
3720 | 212 | return hostname | ||
3721 | 213 | |||
3722 | 214 | def get_comment_url(self, report, handle): | ||
3723 | 215 | '''Return an URL that should be opened after report has been uploaded | ||
3724 | 216 | and upload() returned handle. | ||
3725 | 217 | |||
3726 | 218 | Should return None if no URL should be opened (anonymous filing without | ||
3727 | 219 | user comments); in that case this function should do whichever | ||
3728 | 220 | interactive steps it wants to perform.''' | ||
3729 | 221 | |||
3730 | 222 | args = {} | ||
3731 | 223 | title = report.get('Title', report.standard_title()) | ||
3732 | 224 | if title: | ||
3733 | 225 | # always use UTF-8 encoding, urlencode() blows up otherwise in | ||
3734 | 226 | # python 2.7 | ||
3735 | 227 | if type(title) != type(b''): | ||
3736 | 228 | title = title.encode('UTF-8') | ||
3737 | 229 | args['field.title'] = title | ||
3738 | 230 | |||
3739 | 231 | hostname = self.get_hostname() | ||
3740 | 232 | |||
3741 | 233 | project = self.options.get('project') | ||
3742 | 234 | |||
3743 | 235 | if not project: | ||
3744 | 236 | if 'SourcePackage' in report: | ||
3745 | 237 | return 'https://bugs.%s/%s/+source/%s/+filebug/%s?%s' % ( | ||
3746 | 238 | hostname, self.distro, report['SourcePackage'], handle, urlencode(args)) | ||
3747 | 239 | else: | ||
3748 | 240 | return 'https://bugs.%s/%s/+filebug/%s?%s' % ( | ||
3749 | 241 | hostname, self.distro, handle, urlencode(args)) | ||
3750 | 242 | else: | ||
3751 | 243 | return 'https://bugs.%s/%s/+filebug/%s?%s' % ( | ||
3752 | 244 | hostname, project, handle, urlencode(args)) | ||
3753 | 245 | |||
3754 | 246 | def get_id_url(self, report, id): | ||
3755 | 247 | '''Return URL for a given report ID. | ||
3756 | 248 | |||
3757 | 249 | The report is passed in case building the URL needs additional | ||
3758 | 250 | information from it, such as the SourcePackage name. | ||
3759 | 251 | |||
3760 | 252 | Return None if URL is not available or cannot be determined. | ||
3761 | 253 | ''' | ||
3762 | 254 | return 'https://bugs.launchpad.net/bugs/' + str(id) | ||
3763 | 255 | |||
3764 | 256 | def download(self, id): | ||
3765 | 257 | '''Download the problem report from given ID and return a Report.''' | ||
3766 | 258 | |||
3767 | 259 | report = apport.Report() | ||
3768 | 260 | b = self.launchpad.bugs[id] | ||
3769 | 261 | |||
3770 | 262 | # parse out fields from summary | ||
3771 | 263 | m = re.search(r'(ProblemType:.*)$', b.description, re.S) | ||
3772 | 264 | if not m: | ||
3773 | 265 | m = re.search(r'^--- \r?$[\r\n]*(.*)', b.description, re.M | re.S) | ||
3774 | 266 | assert m, 'bug description must contain standard apport format data' | ||
3775 | 267 | |||
3776 | 268 | description = m.group(1).encode('UTF-8').replace('\xc2\xa0', ' ').replace('\r\n', '\n') | ||
3777 | 269 | |||
3778 | 270 | if '\n\n' in description: | ||
3779 | 271 | # this often happens, remove all empty lines between top and | ||
3780 | 272 | # 'Uname' | ||
3781 | 273 | if 'Uname:' in description: | ||
3782 | 274 | # this will take care of bugs like LP #315728 where stuff | ||
3783 | 275 | # is added after the apport data | ||
3784 | 276 | (part1, part2) = description.split('Uname:', 1) | ||
3785 | 277 | description = part1.replace('\n\n', '\n') + 'Uname:' \ | ||
3786 | 278 | + part2.split('\n\n', 1)[0] | ||
3787 | 279 | else: | ||
3788 | 280 | # just parse out the Apport block; e. g. LP #269539 | ||
3789 | 281 | description = description.split('\n\n', 1)[0] | ||
3790 | 282 | |||
3791 | 283 | report.load(BytesIO(description)) | ||
3792 | 284 | |||
3793 | 285 | if 'Date' not in report: | ||
3794 | 286 | # We had not submitted this field for a while, claiming it | ||
3795 | 287 | # redundant. But it is indeed required for up-to-the-minute | ||
3796 | 288 | # comparison with log files, etc. For backwards compatibility with | ||
3797 | 289 | # those reported bugs, read the creation date | ||
3798 | 290 | try: | ||
3799 | 291 | report['Date'] = b.date_created.ctime() | ||
3800 | 292 | except AttributeError: | ||
3801 | 293 | # support older wadllib API which returned strings | ||
3802 | 294 | report['Date'] = b.date_created | ||
3803 | 295 | if 'ProblemType' not in report: | ||
3804 | 296 | if 'apport-bug' in b.tags: | ||
3805 | 297 | report['ProblemType'] = 'Bug' | ||
3806 | 298 | elif 'apport-crash' in b.tags: | ||
3807 | 299 | report['ProblemType'] = 'Crash' | ||
3808 | 300 | elif 'apport-kernelcrash' in b.tags: | ||
3809 | 301 | report['ProblemType'] = 'KernelCrash' | ||
3810 | 302 | elif 'apport-package' in b.tags: | ||
3811 | 303 | report['ProblemType'] = 'Package' | ||
3812 | 304 | else: | ||
3813 | 305 | raise ValueError('cannot determine ProblemType from tags: ' + str(b.tags)) | ||
3814 | 306 | |||
3815 | 307 | report['Tags'] = ' '.join(b.tags) | ||
3816 | 308 | |||
3817 | 309 | if 'Title' in report: | ||
3818 | 310 | report['OriginalTitle'] = report['Title'] | ||
3819 | 311 | |||
3820 | 312 | report['Title'] = b.title | ||
3821 | 313 | |||
3822 | 314 | for attachment in filter_filename(b.attachments): | ||
3823 | 315 | key, ext = os.path.splitext(attachment.filename) | ||
3824 | 316 | # ignore attachments with invalid keys | ||
3825 | 317 | try: | ||
3826 | 318 | report[key] = '' | ||
3827 | 319 | except: | ||
3828 | 320 | continue | ||
3829 | 321 | if ext == '.txt': | ||
3830 | 322 | report[key] = attachment.read() | ||
3831 | 323 | elif ext == '.gz': | ||
3832 | 324 | try: | ||
3833 | 325 | report[key] = gzip.GzipFile(fileobj=attachment).read() | ||
3834 | 326 | except IOError as e: | ||
3835 | 327 | # some attachments are only called .gz, but are | ||
3836 | 328 | # uncompressed (LP #574360) | ||
3837 | 329 | if 'Not a gzip' not in str(e): | ||
3838 | 330 | raise | ||
3839 | 331 | attachment.seek(0) | ||
3840 | 332 | report[key] = attachment.read() | ||
3841 | 333 | else: | ||
3842 | 334 | raise Exception('Unknown attachment type: ' + attachment.filename) | ||
3843 | 335 | return report | ||
3844 | 336 | |||
3845 | 337 | def update(self, id, report, comment, change_description=False, | ||
3846 | 338 | attachment_comment=None, key_filter=None): | ||
3847 | 339 | '''Update the given report ID with all data from report. | ||
3848 | 340 | |||
3849 | 341 | This creates a text comment with the "short" data (see | ||
3850 | 342 | ProblemReport.write_mime()), and creates attachments for all the | ||
3851 | 343 | bulk/binary data. | ||
3852 | 344 | |||
3853 | 345 | If change_description is True, and the crash db implementation supports | ||
3854 | 346 | it, the short data will be put into the description instead (like in a | ||
3855 | 347 | new bug). | ||
3856 | 348 | |||
3857 | 349 | comment will be added to the "short" data. If attachment_comment is | ||
3858 | 350 | given, it will be added to the attachment uploads. | ||
3859 | 351 | |||
3860 | 352 | If key_filter is a list or set, then only those keys will be added. | ||
3861 | 353 | ''' | ||
3862 | 354 | bug = self.launchpad.bugs[id] | ||
3863 | 355 | |||
3864 | 356 | if key_filter: | ||
3865 | 357 | skip_keys = set(report.keys()) - set(key_filter) | ||
3866 | 358 | else: | ||
3867 | 359 | skip_keys = None | ||
3868 | 360 | |||
3869 | 361 | # we want to reuse the knowledge of write_mime() with all its different input | ||
3870 | 362 | # types and output formatting; however, we have to dissect the mime ourselves, | ||
3871 | 363 | # since we can't just upload it as a blob | ||
3872 | 364 | mime = tempfile.TemporaryFile() | ||
3873 | 365 | report.write_mime(mime, skip_keys=skip_keys) | ||
3874 | 366 | mime.flush() | ||
3875 | 367 | mime.seek(0) | ||
3876 | 368 | msg = email.message_from_file(mime) | ||
3877 | 369 | msg_iter = msg.walk() | ||
3878 | 370 | |||
3879 | 371 | # first part is the multipart container | ||
3880 | 372 | part = msg_iter.next() | ||
3881 | 373 | assert part.is_multipart() | ||
3882 | 374 | |||
3883 | 375 | # second part should be an inline text/plain attachments with all short | ||
3884 | 376 | # fields | ||
3885 | 377 | part = msg_iter.next() | ||
3886 | 378 | assert not part.is_multipart() | ||
3887 | 379 | assert part.get_content_type() == 'text/plain' | ||
3888 | 380 | |||
3889 | 381 | if not key_filter: | ||
3890 | 382 | # when we update a complete report, we are updating an existing bug | ||
3891 | 383 | # with apport-collect | ||
3892 | 384 | x = bug.tags[:] # LP#254901 workaround | ||
3893 | 385 | x.append('apport-collected') | ||
3894 | 386 | # add any tags (like the release) to the bug | ||
3895 | 387 | if 'Tags' in report: | ||
3896 | 388 | x += self._filter_tag_names(report['Tags']).split() | ||
3897 | 389 | bug.tags = x | ||
3898 | 390 | bug.lp_save() | ||
3899 | 391 | bug = self.launchpad.bugs[id] # fresh bug object, LP#336866 workaround | ||
3900 | 392 | |||
3901 | 393 | # short text data | ||
3902 | 394 | if change_description: | ||
3903 | 395 | bug.description = bug.description + '\n--- \n' + part.get_payload(decode=True).decode('UTF-8', 'replace') | ||
3904 | 396 | bug.lp_save() | ||
3905 | 397 | else: | ||
3906 | 398 | bug.newMessage(content=part.get_payload(decode=True), subject=comment) | ||
3907 | 399 | |||
3908 | 400 | # other parts are the attachments: | ||
3909 | 401 | for part in msg_iter: | ||
3910 | 402 | # print ' attachment: %s...' % part.get_filename() | ||
3911 | 403 | bug.addAttachment(comment=attachment_comment or '', | ||
3912 | 404 | description=part.get_filename(), | ||
3913 | 405 | content_type=None, | ||
3914 | 406 | data=part.get_payload(decode=True), | ||
3915 | 407 | filename=part.get_filename(), is_patch=False) | ||
3916 | 408 | |||
3917 | 409 | def update_traces(self, id, report, comment=''): | ||
3918 | 410 | '''Update the given report ID for retracing results. | ||
3919 | 411 | |||
3920 | 412 | This updates Stacktrace, ThreadStacktrace, StacktraceTop, | ||
3921 | 413 | and StacktraceSource. You can also supply an additional comment. | ||
3922 | 414 | ''' | ||
3923 | 415 | apport.crashdb.CrashDatabase.update_traces(self, id, report, comment) | ||
3924 | 416 | |||
3925 | 417 | bug = self.launchpad.bugs[id] | ||
3926 | 418 | # ensure it's assigned to a package | ||
3927 | 419 | if 'SourcePackage' in report: | ||
3928 | 420 | for task in bug.bug_tasks: | ||
3929 | 421 | if task.target.resource_type_link.endswith('#distribution'): | ||
3930 | 422 | task.target = self.lp_distro.getSourcePackage(name=report['SourcePackage']) | ||
3931 | 423 | task.lp_save() | ||
3932 | 424 | bug = self.launchpad.bugs[id] | ||
3933 | 425 | break | ||
3934 | 426 | |||
3935 | 427 | # remove core dump if stack trace is usable | ||
3936 | 428 | if report.has_useful_stacktrace(): | ||
3937 | 429 | for a in bug.attachments: | ||
3938 | 430 | if a.title == 'CoreDump.gz': | ||
3939 | 431 | try: | ||
3940 | 432 | a.removeFromBug() | ||
3941 | 433 | except HTTPError: | ||
3942 | 434 | pass # LP#249950 workaround | ||
3943 | 435 | try: | ||
3944 | 436 | task = self._get_distro_tasks(bug.bug_tasks).next() | ||
3945 | 437 | if task.importance == 'Undecided': | ||
3946 | 438 | task.importance = 'Medium' | ||
3947 | 439 | task.lp_save() | ||
3948 | 440 | except StopIteration: | ||
3949 | 441 | pass # no distro tasks | ||
3950 | 442 | |||
3951 | 443 | # update bug title with retraced function name | ||
3952 | 444 | fn = report.stacktrace_top_function() | ||
3953 | 445 | if fn: | ||
3954 | 446 | m = re.match('^(.*crashed with SIG.* in )([^( ]+)(\(\).*$)', bug.title) | ||
3955 | 447 | if m and m.group(2) != fn: | ||
3956 | 448 | bug.title = m.group(1) + fn + m.group(3) | ||
3957 | 449 | try: | ||
3958 | 450 | bug.lp_save() | ||
3959 | 451 | except HTTPError: | ||
3960 | 452 | pass # LP#336866 workaround | ||
3961 | 453 | bug = self.launchpad.bugs[id] | ||
3962 | 454 | |||
3963 | 455 | self._subscribe_triaging_team(bug, report) | ||
3964 | 456 | |||
3965 | 457 | def get_distro_release(self, id): | ||
3966 | 458 | '''Get 'DistroRelease: <release>' from the given report ID and return | ||
3967 | 459 | it.''' | ||
3968 | 460 | bug = self.launchpad.bugs[id] | ||
3969 | 461 | m = re.search('DistroRelease: ([-a-zA-Z0-9.+/ ]+)', bug.description) | ||
3970 | 462 | if m: | ||
3971 | 463 | return m.group(1) | ||
3972 | 464 | raise ValueError('URL does not contain DistroRelease: field') | ||
3973 | 465 | |||
3974 | 466 | def get_affected_packages(self, id): | ||
3975 | 467 | '''Return list of affected source packages for given ID.''' | ||
3976 | 468 | |||
3977 | 469 | bug_target_re = re.compile( | ||
3978 | 470 | r'/%s/(?:(?P<suite>[^/]+)/)?\+source/(?P<source>[^/]+)$' % self.distro) | ||
3979 | 471 | |||
3980 | 472 | bug = self.launchpad.bugs[id] | ||
3981 | 473 | result = [] | ||
3982 | 474 | |||
3983 | 475 | for task in bug.bug_tasks: | ||
3984 | 476 | match = bug_target_re.search(task.target.self_link) | ||
3985 | 477 | if not match: | ||
3986 | 478 | continue | ||
3987 | 479 | if task.status in ('Invalid', "Won't Fix", 'Fix Released'): | ||
3988 | 480 | continue | ||
3989 | 481 | result.append(match.group('source')) | ||
3990 | 482 | return result | ||
3991 | 483 | |||
3992 | 484 | def is_reporter(self, id): | ||
3993 | 485 | '''Check whether the user is the reporter of given ID.''' | ||
3994 | 486 | |||
3995 | 487 | bug = self.launchpad.bugs[id] | ||
3996 | 488 | return bug.owner.name == self.launchpad.me.name | ||
3997 | 489 | |||
3998 | 490 | def can_update(self, id): | ||
3999 | 491 | '''Check whether the user is eligible to update a report. | ||
4000 | 492 | |||
4001 | 493 | A user should add additional information to an existing ID if (s)he is | ||
4002 | 494 | the reporter or subscribed, the bug is open, not a duplicate, etc. The | ||
4003 | 495 | exact policy and checks should be done according to the particular | ||
4004 | 496 | implementation. | ||
4005 | 497 | ''' | ||
4006 | 498 | bug = self.launchpad.bugs[id] | ||
4007 | 499 | if bug.duplicate_of: | ||
4008 | 500 | return False | ||
4009 | 501 | |||
4010 | 502 | if bug.owner.name == self.launchpad.me.name: | ||
4011 | 503 | return True | ||
4012 | 504 | |||
4013 | 505 | # check subscription | ||
4014 | 506 | me = self.launchpad.me.self_link | ||
4015 | 507 | for sub in bug.subscriptions.entries: | ||
4016 | 508 | if sub['person_link'] == me: | ||
4017 | 509 | return True | ||
4018 | 510 | |||
4019 | 511 | return False | ||
4020 | 512 | |||
4021 | 513 | def get_unretraced(self): | ||
4022 | 514 | '''Return an ID set of all crashes which have not been retraced yet and | ||
4023 | 515 | which happened on the current host architecture.''' | ||
4024 | 516 | try: | ||
4025 | 517 | bugs = self.lp_distro.searchTasks(tags=self.arch_tag, created_since='2011-08-01') | ||
4026 | 518 | return id_set(bugs) | ||
4027 | 519 | except Exception as e: | ||
4028 | 520 | apport.error('connecting to Launchpad failed: %s', str(e)) | ||
4029 | 521 | sys.exit(99) # transient error | ||
4030 | 522 | |||
4031 | 523 | def get_dup_unchecked(self): | ||
4032 | 524 | '''Return an ID set of all crashes which have not been checked for | ||
4033 | 525 | being a duplicate. | ||
4034 | 526 | |||
4035 | 527 | This is mainly useful for crashes of scripting languages such as | ||
4036 | 528 | Python, since they do not need to be retraced. It should not return | ||
4037 | 529 | bugs that are covered by get_unretraced().''' | ||
4038 | 530 | |||
4039 | 531 | try: | ||
4040 | 532 | bugs = self.lp_distro.searchTasks(tags='need-duplicate-check', created_since='2011-08-01') | ||
4041 | 533 | return id_set(bugs) | ||
4042 | 534 | except Exception as e: | ||
4043 | 535 | apport.error('connecting to Launchpad failed: %s', str(e)) | ||
4044 | 536 | sys.exit(99) # transient error | ||
4045 | 537 | |||
4046 | 538 | def get_unfixed(self): | ||
4047 | 539 | '''Return an ID set of all crashes which are not yet fixed. | ||
4048 | 540 | |||
4049 | 541 | The list must not contain bugs which were rejected or duplicate. | ||
4050 | 542 | |||
4051 | 543 | This function should make sure that the returned list is correct. If | ||
4052 | 544 | there are any errors with connecting to the crash database, it should | ||
4053 | 545 | raise an exception (preferably IOError).''' | ||
4054 | 546 | |||
4055 | 547 | bugs = self.lp_distro.searchTasks(tags='apport-crash') | ||
4056 | 548 | return id_set(bugs) | ||
4057 | 549 | |||
4058 | 550 | def _get_source_version(self, package): | ||
4059 | 551 | '''Return the version of given source package in the latest release of | ||
4060 | 552 | given distribution. | ||
4061 | 553 | |||
4062 | 554 | If 'distro' is None, we will look for a launchpad project . | ||
4063 | 555 | ''' | ||
4064 | 556 | sources = self.lp_distro.main_archive.getPublishedSources( | ||
4065 | 557 | exact_match=True, | ||
4066 | 558 | source_name=package, | ||
4067 | 559 | distro_series=self.lp_distro.current_series | ||
4068 | 560 | ) | ||
4069 | 561 | # first element is the latest one | ||
4070 | 562 | return sources[0].source_package_version | ||
4071 | 563 | |||
4072 | 564 | def get_fixed_version(self, id): | ||
4073 | 565 | '''Return the package version that fixes a given crash. | ||
4074 | 566 | |||
4075 | 567 | Return None if the crash is not yet fixed, or an empty string if the | ||
4076 | 568 | crash is fixed, but it cannot be determined by which version. Return | ||
4077 | 569 | 'invalid' if the crash report got invalidated, such as closed a | ||
4078 | 570 | duplicate or rejected. | ||
4079 | 571 | |||
4080 | 572 | This function should make sure that the returned result is correct. If | ||
4081 | 573 | there are any errors with connecting to the crash database, it should | ||
4082 | 574 | raise an exception (preferably IOError). | ||
4083 | 575 | ''' | ||
4084 | 576 | # do not do version tracking yet; for that, we need to get the current | ||
4085 | 577 | # distrorelease and the current package version in that distrorelease | ||
4086 | 578 | # (or, of course, proper version tracking in Launchpad itself) | ||
4087 | 579 | |||
4088 | 580 | try: | ||
4089 | 581 | b = self.launchpad.bugs[id] | ||
4090 | 582 | except KeyError: | ||
4091 | 583 | return 'invalid' | ||
4092 | 584 | |||
4093 | 585 | if b.duplicate_of: | ||
4094 | 586 | return 'invalid' | ||
4095 | 587 | |||
4096 | 588 | tasks = list(b.bug_tasks) # just fetch it once | ||
4097 | 589 | |||
4098 | 590 | if self.distro: | ||
4099 | 591 | distro_identifier = '(%s)' % self.distro.lower() | ||
4100 | 592 | fixed_tasks = filter(lambda task: task.status == 'Fix Released' and | ||
4101 | 593 | distro_identifier in task.bug_target_display_name.lower(), tasks) | ||
4102 | 594 | |||
4103 | 595 | if not fixed_tasks: | ||
4104 | 596 | fixed_distro = filter(lambda task: task.status == 'Fix Released' and | ||
4105 | 597 | task.bug_target_name.lower() == self.distro.lower(), tasks) | ||
4106 | 598 | if fixed_distro: | ||
4107 | 599 | # fixed in distro inself (without source package) | ||
4108 | 600 | return '' | ||
4109 | 601 | |||
4110 | 602 | if len(fixed_tasks) > 1: | ||
4111 | 603 | apport.warning('There is more than one task fixed in %s %s, using first one to determine fixed version', self.distro, id) | ||
4112 | 604 | return '' | ||
4113 | 605 | |||
4114 | 606 | if fixed_tasks: | ||
4115 | 607 | task = fixed_tasks.pop() | ||
4116 | 608 | try: | ||
4117 | 609 | return self._get_source_version(task.bug_target_display_name.split()[0]) | ||
4118 | 610 | except IndexError: | ||
4119 | 611 | # source does not exist any more | ||
4120 | 612 | return 'invalid' | ||
4121 | 613 | else: | ||
4122 | 614 | # check if there only invalid ones | ||
4123 | 615 | invalid_tasks = filter(lambda task: task.status in ('Invalid', "Won't Fix", 'Expired') and | ||
4124 | 616 | distro_identifier in task.bug_target_display_name.lower(), tasks) | ||
4125 | 617 | if invalid_tasks: | ||
4126 | 618 | non_invalid_tasks = filter( | ||
4127 | 619 | lambda task: task.status not in ('Invalid', "Won't Fix", 'Expired') and | ||
4128 | 620 | distro_identifier in task.bug_target_display_name.lower(), tasks) | ||
4129 | 621 | if not non_invalid_tasks: | ||
4130 | 622 | return 'invalid' | ||
4131 | 623 | else: | ||
4132 | 624 | fixed_tasks = filter(lambda task: task.status == 'Fix Released', tasks) | ||
4133 | 625 | if fixed_tasks: | ||
4134 | 626 | # TODO: look for current series | ||
4135 | 627 | return '' | ||
4136 | 628 | # check if there any invalid ones | ||
4137 | 629 | if filter(lambda task: task.status == 'Invalid', tasks): | ||
4138 | 630 | return 'invalid' | ||
4139 | 631 | |||
4140 | 632 | return None | ||
4141 | 633 | |||
4142 | 634 | def duplicate_of(self, id): | ||
4143 | 635 | '''Return master ID for a duplicate bug. | ||
4144 | 636 | |||
4145 | 637 | If the bug is not a duplicate, return None. | ||
4146 | 638 | ''' | ||
4147 | 639 | b = self.launchpad.bugs[id].duplicate_of | ||
4148 | 640 | if b: | ||
4149 | 641 | return b.id | ||
4150 | 642 | else: | ||
4151 | 643 | return None | ||
4152 | 644 | |||
4153 | 645 | def close_duplicate(self, report, id, master_id): | ||
4154 | 646 | '''Mark a crash id as duplicate of given master ID. | ||
4155 | 647 | |||
4156 | 648 | If master is None, id gets un-duplicated. | ||
4157 | 649 | ''' | ||
4158 | 650 | bug = self.launchpad.bugs[id] | ||
4159 | 651 | |||
4160 | 652 | if master_id: | ||
4161 | 653 | assert id != master_id, 'cannot mark bug %s as a duplicate of itself' % str(id) | ||
4162 | 654 | |||
4163 | 655 | # check whether the master itself is a dup | ||
4164 | 656 | master = self.launchpad.bugs[master_id] | ||
4165 | 657 | if master.duplicate_of: | ||
4166 | 658 | master = master.duplicate_of | ||
4167 | 659 | master_id = master.id | ||
4168 | 660 | if master.id == id: | ||
4169 | 661 | # this happens if the bug was manually duped to a newer one | ||
4170 | 662 | apport.warning('Bug %i was manually marked as a dupe of newer bug %i, not closing as duplicate', | ||
4171 | 663 | id, master_id) | ||
4172 | 664 | return | ||
4173 | 665 | |||
4174 | 666 | for a in bug.attachments: | ||
4175 | 667 | if a.title in ('CoreDump.gz', 'Stacktrace.txt', | ||
4176 | 668 | 'ThreadStacktrace.txt', 'ProcMaps.txt', | ||
4177 | 669 | 'ProcStatus.txt', 'Registers.txt', | ||
4178 | 670 | 'Disassembly.txt'): | ||
4179 | 671 | try: | ||
4180 | 672 | a.removeFromBug() | ||
4181 | 673 | except HTTPError: | ||
4182 | 674 | pass # LP#249950 workaround | ||
4183 | 675 | |||
4184 | 676 | bug = self.launchpad.bugs[id] # fresh bug object, LP#336866 workaround | ||
4185 | 677 | bug.newMessage(content='Thank you for taking the time to report this crash and helping \ | ||
4186 | 678 | to make this software better. This particular crash has already been reported and \ | ||
4187 | 679 | is a duplicate of bug #%i, so is being marked as such. Please look at the \ | ||
4188 | 680 | other bug report to see if there is any missing information that you can \ | ||
4189 | 681 | provide, or to see if there is a workaround for the bug. Additionally, any \ | ||
4190 | 682 | further discussion regarding the bug should occur in the other report. \ | ||
4191 | 683 | Please continue to report any other bugs you may find.' % master_id, | ||
4192 | 684 | subject='This bug is a duplicate') | ||
4193 | 685 | |||
4194 | 686 | bug = self.launchpad.bugs[id] # refresh, LP#336866 workaround | ||
4195 | 687 | if bug.private: | ||
4196 | 688 | bug.private = False | ||
4197 | 689 | |||
4198 | 690 | # set duplicate last, since we cannot modify already dup'ed bugs | ||
4199 | 691 | if not bug.duplicate_of: | ||
4200 | 692 | bug.duplicate_of = master | ||
4201 | 693 | |||
4202 | 694 | # cache tags of master bug report instead of performing multiple | ||
4203 | 695 | # queries | ||
4204 | 696 | master_tags = master.tags | ||
4205 | 697 | |||
4206 | 698 | if len(master.duplicates) == 10: | ||
4207 | 699 | if 'escalation_tag' in self.options and self.options['escalation_tag'] not in master_tags and self.options.get('escalated_tag', ' invalid ') not in master_tags: | ||
4208 | 700 | master.tags = master_tags + [self.options['escalation_tag']] # LP#254901 workaround | ||
4209 | 701 | master.lp_save() | ||
4210 | 702 | |||
4211 | 703 | if 'escalation_subscription' in self.options and self.options.get('escalated_tag', ' invalid ') not in master_tags: | ||
4212 | 704 | p = self.launchpad.people[self.options['escalation_subscription']] | ||
4213 | 705 | master.subscribe(person=p) | ||
4214 | 706 | |||
4215 | 707 | # requesting updated stack trace? | ||
4216 | 708 | if report.has_useful_stacktrace() and ('apport-request-retrace' in master_tags | ||
4217 | 709 | or 'apport-failed-retrace' in master_tags): | ||
4218 | 710 | self.update(master_id, report, 'Updated stack trace from duplicate bug %i' % id, | ||
4219 | 711 | key_filter=['Stacktrace', 'ThreadStacktrace', | ||
4220 | 712 | 'Package', 'Dependencies', 'ProcMaps', 'ProcCmdline']) | ||
4221 | 713 | |||
4222 | 714 | master = self.launchpad.bugs[master_id] | ||
4223 | 715 | x = master.tags[:] # LP#254901 workaround | ||
4224 | 716 | try: | ||
4225 | 717 | x.remove('apport-failed-retrace') | ||
4226 | 718 | except ValueError: | ||
4227 | 719 | pass | ||
4228 | 720 | try: | ||
4229 | 721 | x.remove('apport-request-retrace') | ||
4230 | 722 | except ValueError: | ||
4231 | 723 | pass | ||
4232 | 724 | master.tags = x | ||
4233 | 725 | try: | ||
4234 | 726 | master.lp_save() | ||
4235 | 727 | except HTTPError: | ||
4236 | 728 | pass # LP#336866 workaround | ||
4237 | 729 | |||
4238 | 730 | # white list of tags to copy from duplicates bugs to the master | ||
4239 | 731 | tags_to_copy = ['bugpattern-needed'] | ||
4240 | 732 | for series in self.lp_distro.series: | ||
4241 | 733 | if series.status not in ['Active Development', | ||
4242 | 734 | 'Current Stable Release', | ||
4243 | 735 | 'Supported', 'Pre-release Freeze']: | ||
4244 | 736 | continue | ||
4245 | 737 | tags_to_copy.append(series.name) | ||
4246 | 738 | # copy tags over from the duplicate bug to the master bug | ||
4247 | 739 | dupe_tags = set(bug.tags) | ||
4248 | 740 | # reload master tags as they may have changed | ||
4249 | 741 | master_tags = master.tags | ||
4250 | 742 | missing_tags = dupe_tags.difference(master_tags) | ||
4251 | 743 | |||
4252 | 744 | for tag in missing_tags: | ||
4253 | 745 | if tag in tags_to_copy: | ||
4254 | 746 | master_tags.append(tag) | ||
4255 | 747 | |||
4256 | 748 | master.tags = master_tags | ||
4257 | 749 | master.lp_save() | ||
4258 | 750 | |||
4259 | 751 | else: | ||
4260 | 752 | if bug.duplicate_of: | ||
4261 | 753 | bug.duplicate_of = None | ||
4262 | 754 | |||
4263 | 755 | if bug._dirty_attributes: # LP#336866 workaround | ||
4264 | 756 | bug.lp_save() | ||
4265 | 757 | |||
4266 | 758 | def mark_regression(self, id, master): | ||
4267 | 759 | '''Mark a crash id as reintroducing an earlier crash which is | ||
4268 | 760 | already marked as fixed (having ID 'master').''' | ||
4269 | 761 | |||
4270 | 762 | bug = self.launchpad.bugs[id] | ||
4271 | 763 | bug.newMessage(content='This crash has the same stack trace characteristics as bug #%i. \ | ||
4272 | 764 | However, the latter was already fixed in an earlier package version than the \ | ||
4273 | 765 | one in this report. This might be a regression or because the problem is \ | ||
4274 | 766 | in a dependent package.' % master, | ||
4275 | 767 | subject='Possible regression detected') | ||
4276 | 768 | bug = self.launchpad.bugs[id] # fresh bug object, LP#336866 workaround | ||
4277 | 769 | bug.tags = bug.tags + ['regression-retracer'] # LP#254901 workaround | ||
4278 | 770 | bug.lp_save() | ||
4279 | 771 | |||
4280 | 772 | def mark_retraced(self, id): | ||
4281 | 773 | '''Mark crash id as retraced.''' | ||
4282 | 774 | |||
4283 | 775 | bug = self.launchpad.bugs[id] | ||
4284 | 776 | if self.arch_tag in bug.tags: | ||
4285 | 777 | x = bug.tags[:] # LP#254901 workaround | ||
4286 | 778 | x.remove(self.arch_tag) | ||
4287 | 779 | bug.tags = x | ||
4288 | 780 | try: | ||
4289 | 781 | bug.lp_save() | ||
4290 | 782 | except HTTPError: | ||
4291 | 783 | pass # LP#336866 workaround | ||
4292 | 784 | |||
4293 | 785 | def mark_retrace_failed(self, id, invalid_msg=None): | ||
4294 | 786 | '''Mark crash id as 'failed to retrace'.''' | ||
4295 | 787 | |||
4296 | 788 | bug = self.launchpad.bugs[id] | ||
4297 | 789 | if invalid_msg: | ||
4298 | 790 | try: | ||
4299 | 791 | task = self._get_distro_tasks(bug.bug_tasks).next() | ||
4300 | 792 | except StopIteration: | ||
4301 | 793 | # no distro task, just use the first one | ||
4302 | 794 | task = bug.bug_tasks[0] | ||
4303 | 795 | task.status = 'Invalid' | ||
4304 | 796 | task.lp_save() | ||
4305 | 797 | bug.newMessage(content=invalid_msg, | ||
4306 | 798 | subject='Crash report cannot be processed') | ||
4307 | 799 | |||
4308 | 800 | for a in bug.attachments: | ||
4309 | 801 | if a.title == 'CoreDump.gz': | ||
4310 | 802 | try: | ||
4311 | 803 | a.removeFromBug() | ||
4312 | 804 | except HTTPError: | ||
4313 | 805 | pass # LP#249950 workaround | ||
4314 | 806 | else: | ||
4315 | 807 | if 'apport-failed-retrace' not in bug.tags: | ||
4316 | 808 | bug.tags = bug.tags + ['apport-failed-retrace'] # LP#254901 workaround | ||
4317 | 809 | bug.lp_save() | ||
4318 | 810 | |||
4319 | 811 | def _mark_dup_checked(self, id, report): | ||
4320 | 812 | '''Mark crash id as checked for being a duplicate.''' | ||
4321 | 813 | |||
4322 | 814 | bug = self.launchpad.bugs[id] | ||
4323 | 815 | |||
4324 | 816 | # if we have a distro task without a package, fix it | ||
4325 | 817 | if 'SourcePackage' in report: | ||
4326 | 818 | for task in bug.bug_tasks: | ||
4327 | 819 | if task.target.resource_type_link.endswith('#distribution'): | ||
4328 | 820 | task.target = self.lp_distro.getSourcePackage( | ||
4329 | 821 | name=report['SourcePackage']) | ||
4330 | 822 | task.lp_save() | ||
4331 | 823 | bug = self.launchpad.bugs[id] | ||
4332 | 824 | break | ||
4333 | 825 | |||
4334 | 826 | if 'need-duplicate-check' in bug.tags: | ||
4335 | 827 | x = bug.tags[:] # LP#254901 workaround | ||
4336 | 828 | x.remove('need-duplicate-check') | ||
4337 | 829 | bug.tags = x | ||
4338 | 830 | bug.lp_save() | ||
4339 | 831 | if 'Traceback' in report: | ||
4340 | 832 | for task in bug.bug_tasks: | ||
4341 | 833 | if '#distribution' in task.target.resource_type_link: | ||
4342 | 834 | if task.importance == 'Undecided': | ||
4343 | 835 | task.importance = 'Medium' | ||
4344 | 836 | task.lp_save() | ||
4345 | 837 | self._subscribe_triaging_team(bug, report) | ||
4346 | 838 | |||
4347 | 839 | def known(self, report): | ||
4348 | 840 | '''Check if the crash db already knows about the crash signature. | ||
4349 | 841 | |||
4350 | 842 | Check if the report has a DuplicateSignature, crash_signature(), or | ||
4351 | 843 | StacktraceAddressSignature, and ask the database whether the problem is | ||
4352 | 844 | already known. If so, return an URL where the user can check the status | ||
4353 | 845 | or subscribe (if available), or just return True if the report is known | ||
4354 | 846 | but there is no public URL. In that case the report will not be | ||
4355 | 847 | uploaded (i. e. upload() will not be called). | ||
4356 | 848 | |||
4357 | 849 | Return None if the report does not have any signature or the crash | ||
4358 | 850 | database does not support checking for duplicates on the client side. | ||
4359 | 851 | |||
4360 | 852 | The default implementation uses a text file format generated by | ||
4361 | 853 | duplicate_db_publish() at an URL specified by the "dupdb_url" option. | ||
4362 | 854 | Subclasses are free to override this with a custom implementation, such | ||
4363 | 855 | as a real database lookup. | ||
4364 | 856 | ''' | ||
4365 | 857 | # we override the method here to check if the user actually has access | ||
4366 | 858 | # to the bug, and if the bug requests more retraces; in either case we | ||
4367 | 859 | # should file it. | ||
4368 | 860 | url = apport.crashdb.CrashDatabase.known(self, report) | ||
4369 | 861 | |||
4370 | 862 | if not url: | ||
4371 | 863 | return url | ||
4372 | 864 | |||
4373 | 865 | # record the fact that it is a duplicate, for triagers | ||
4374 | 866 | report['DuplicateOf'] = url | ||
4375 | 867 | |||
4376 | 868 | try: | ||
4377 | 869 | f = urlopen(url + '/+text') | ||
4378 | 870 | except IOError: | ||
4379 | 871 | # if we are offline, or LP is down, upload will fail anyway, so we | ||
4380 | 872 | # can just as well avoid the upload | ||
4381 | 873 | return url | ||
4382 | 874 | |||
4383 | 875 | line = f.readline() | ||
4384 | 876 | if not line.startswith(b'bug:'): | ||
4385 | 877 | # presumably a 404 etc. page, which happens for private bugs | ||
4386 | 878 | return True | ||
4387 | 879 | |||
4388 | 880 | # check tags | ||
4389 | 881 | for line in f: | ||
4390 | 882 | if line.startswith(b'tags:'): | ||
4391 | 883 | if b'apport-failed-retrace' in line or b'apport-request-retrace' in line: | ||
4392 | 884 | return None | ||
4393 | 885 | else: | ||
4394 | 886 | break | ||
4395 | 887 | |||
4396 | 888 | # stop at the first task, tags are in the first block | ||
4397 | 889 | if not line.strip(): | ||
4398 | 890 | break | ||
4399 | 891 | |||
4400 | 892 | return url | ||
4401 | 893 | |||
4402 | 894 | def _subscribe_triaging_team(self, bug, report): | ||
4403 | 895 | '''Subscribe the right triaging team to the bug.''' | ||
4404 | 896 | |||
4405 | 897 | #FIXME: this entire function is an ugly Ubuntu specific hack until LP | ||
4406 | 898 | #gets a real crash db; see https://wiki.ubuntu.com/CrashReporting | ||
4407 | 899 | |||
4408 | 900 | if 'DistroRelease' in report and report['DistroRelease'].split()[0] != 'Ubuntu': | ||
4409 | 901 | return # only Ubuntu bugs are filed private | ||
4410 | 902 | |||
4411 | 903 | #use a url hack here, it is faster | ||
4412 | 904 | person = '%s~%s' % (self.launchpad._root_uri, | ||
4413 | 905 | self.options.get('triaging_team', 'ubuntu-crashes-universe')) | ||
4414 | 906 | bug.subscribe(person=person) | ||
4415 | 907 | |||
4416 | 908 | def _generate_upload_blob(self, report): | ||
4417 | 909 | '''Generate a multipart/MIME temporary file for uploading. | ||
4418 | 910 | |||
4419 | 911 | You have to close the returned file object after you are done with it. | ||
4420 | 912 | ''' | ||
4421 | 913 | # set reprocessing tags | ||
4422 | 914 | hdr = {} | ||
4423 | 915 | hdr['Tags'] = 'apport-%s' % report['ProblemType'].lower() | ||
4424 | 916 | a = report.get('PackageArchitecture') | ||
4425 | 917 | if not a or a == 'all': | ||
4426 | 918 | a = report.get('Architecture') | ||
4427 | 919 | if a: | ||
4428 | 920 | hdr['Tags'] += ' ' + a | ||
4429 | 921 | if 'Tags' in report: | ||
4430 | 922 | hdr['Tags'] += ' ' + self._filter_tag_names(report['Tags']) | ||
4431 | 923 | |||
4432 | 924 | # privacy/retracing for distro reports | ||
4433 | 925 | # FIXME: ugly hack until LP has a real crash db | ||
4434 | 926 | if 'DistroRelease' in report: | ||
4435 | 927 | if a and ('VmCore' in report or 'CoreDump' in report or 'LaunchpadPrivate' in report): | ||
4436 | 928 | hdr['Private'] = 'yes' | ||
4437 | 929 | hdr['Subscribers'] = report.get('LaunchpadSubscribe', | ||
4438 | 930 | self.options.get('initial_subscriber', 'apport')) | ||
4439 | 931 | hdr['Tags'] += ' need-%s-retrace' % a | ||
4440 | 932 | elif 'Traceback' in report: | ||
4441 | 933 | hdr['Private'] = 'yes' | ||
4442 | 934 | hdr['Subscribers'] = 'apport' | ||
4443 | 935 | hdr['Tags'] += ' need-duplicate-check' | ||
4444 | 936 | if 'DuplicateSignature' in report and 'need-duplicate-check' not in hdr['Tags']: | ||
4445 | 937 | hdr['Tags'] += ' need-duplicate-check' | ||
4446 | 938 | |||
4447 | 939 | # if we have checkbox submission key, link it to the bug; keep text | ||
4448 | 940 | # reference until the link is shown in Launchpad's UI | ||
4449 | 941 | if 'CheckboxSubmission' in report: | ||
4450 | 942 | hdr['HWDB-Submission'] = report['CheckboxSubmission'] | ||
4451 | 943 | |||
4452 | 944 | # order in which keys should appear in the temporary file | ||
4453 | 945 | order = ['ProblemType', 'DistroRelease', 'Package', 'Regression', 'Reproducible', | ||
4454 | 946 | 'TestedUpstream', 'ProcVersionSignature', 'Uname', 'NonfreeKernelModules'] | ||
4455 | 947 | |||
4456 | 948 | # write MIME/Multipart version into temporary file | ||
4457 | 949 | mime = tempfile.TemporaryFile() | ||
4458 | 950 | report.write_mime(mime, extra_headers=hdr, | ||
4459 | 951 | skip_keys=['Tags', 'LaunchpadPrivate', 'LaunchpadSubscribe'], | ||
4460 | 952 | priority_fields=order) | ||
4461 | 953 | mime.flush() | ||
4462 | 954 | mime.seek(0) | ||
4463 | 955 | |||
4464 | 956 | return mime | ||
4465 | 957 | |||
4466 | 958 | @classmethod | ||
4467 | 959 | def _filter_tag_names(klass, tags): | ||
4468 | 960 | '''Replace characters from tags which are not palatable to Launchpad''' | ||
4469 | 961 | |||
4470 | 962 | res = '' | ||
4471 | 963 | for ch in tags.lower().encode('ASCII', errors='ignore'): | ||
4472 | 964 | if ch in b'abcdefghijklmnopqrstuvwxyz0123456789 ' or (len(res) > 0 and ch in b'+-.'): | ||
4473 | 965 | if _python2: | ||
4474 | 966 | res += ch | ||
4475 | 967 | else: | ||
4476 | 968 | res += chr(ch) | ||
4477 | 969 | else: | ||
4478 | 970 | res += '.' | ||
4479 | 971 | |||
4480 | 972 | return res | ||
4481 | 973 | |||
4482 | 974 | # | ||
4483 | 975 | # Launchpad storeblob API (should go into launchpadlib, see LP #315358) | ||
4484 | 976 | # | ||
4485 | 977 | |||
4486 | 978 | _https_upload_callback = None | ||
4487 | 979 | |||
4488 | 980 | |||
4489 | 981 | # | ||
4490 | 982 | # This progress code is based on KodakLoader by Jason Hildebrand | ||
4491 | 983 | # <jason@opensky.ca>. See http://www.opensky.ca/~jdhildeb/software/kodakloader/ | ||
4492 | 984 | # for details. | ||
4493 | 985 | class HTTPSProgressConnection(HTTPSConnection): | ||
4494 | 986 | '''Implement a HTTPSConnection with an optional callback function for | ||
4495 | 987 | upload progress.''' | ||
4496 | 988 | |||
4497 | 989 | def send(self, data): | ||
4498 | 990 | global _https_upload_callback | ||
4499 | 991 | |||
4500 | 992 | # if callback has not been set, call the old method | ||
4501 | 993 | if not _https_upload_callback: | ||
4502 | 994 | HTTPSConnection.send(self, data) | ||
4503 | 995 | return | ||
4504 | 996 | |||
4505 | 997 | sent = 0 | ||
4506 | 998 | total = len(data) | ||
4507 | 999 | chunksize = 1024 | ||
4508 | 1000 | while sent < total: | ||
4509 | 1001 | _https_upload_callback(sent, total) | ||
4510 | 1002 | t1 = time.time() | ||
4511 | 1003 | HTTPSConnection.send(self, data[sent:(sent + chunksize)]) | ||
4512 | 1004 | sent += chunksize | ||
4513 | 1005 | t2 = time.time() | ||
4514 | 1006 | |||
4515 | 1007 | # adjust chunksize so that it takes between .5 and 2 | ||
4516 | 1008 | # seconds to send a chunk | ||
4517 | 1009 | if chunksize > 1024: | ||
4518 | 1010 | if t2 - t1 < .5: | ||
4519 | 1011 | chunksize <<= 1 | ||
4520 | 1012 | elif t2 - t1 > 2: | ||
4521 | 1013 | chunksize >>= 1 | ||
4522 | 1014 | |||
4523 | 1015 | |||
4524 | 1016 | class HTTPSProgressHandler(HTTPSHandler): | ||
4525 | 1017 | |||
4526 | 1018 | def https_open(self, req): | ||
4527 | 1019 | return self.do_open(HTTPSProgressConnection, req) | ||
4528 | 1020 | |||
4529 | 1021 | |||
4530 | 1022 | def upload_blob(blob, progress_callback=None, hostname='launchpad.net'): | ||
4531 | 1023 | '''Upload blob (file-like object) to Launchpad. | ||
4532 | 1024 | |||
4533 | 1025 | progress_callback can be set to a function(sent, total) which is regularly | ||
4534 | 1026 | called with the number of bytes already sent and total number of bytes to | ||
4535 | 1027 | send. It is called every 0.5 to 2 seconds (dynamically adapted to upload | ||
4536 | 1028 | bandwidth). | ||
4537 | 1029 | |||
4538 | 1030 | Return None on error, or the ticket number on success. | ||
4539 | 1031 | |||
4540 | 1032 | By default this uses the production Launchpad hostname. Set | ||
4541 | 1033 | hostname to 'launchpad.dev' or 'staging.launchpad.net' to use another | ||
4542 | 1034 | instance for testing. | ||
4543 | 1035 | ''' | ||
4544 | 1036 | ticket = None | ||
4545 | 1037 | url = 'https://%s/+storeblob' % hostname | ||
4546 | 1038 | |||
4547 | 1039 | global _https_upload_callback | ||
4548 | 1040 | _https_upload_callback = progress_callback | ||
4549 | 1041 | |||
4550 | 1042 | # build the form-data multipart/MIME request | ||
4551 | 1043 | data = email.mime.multipart.MIMEMultipart() | ||
4552 | 1044 | |||
4553 | 1045 | submit = email.mime.text.MIMEText('1') | ||
4554 | 1046 | submit.add_header('Content-Disposition', 'form-data; name="FORM_SUBMIT"') | ||
4555 | 1047 | data.attach(submit) | ||
4556 | 1048 | |||
4557 | 1049 | form_blob = email.mime.base.MIMEBase('application', 'octet-stream') | ||
4558 | 1050 | form_blob.add_header('Content-Disposition', 'form-data; name="field.blob"; filename="x"') | ||
4559 | 1051 | form_blob.set_payload(blob.read().decode('ascii')) | ||
4560 | 1052 | data.attach(form_blob) | ||
4561 | 1053 | |||
4562 | 1054 | data_flat = BytesIO() | ||
4563 | 1055 | if sys.version_info.major == 2: | ||
4564 | 1056 | gen = email.generator.Generator(data_flat, mangle_from_=False) | ||
4565 | 1057 | else: | ||
4566 | 1058 | gen = email.generator.BytesGenerator(data_flat, mangle_from_=False) | ||
4567 | 1059 | gen.flatten(data) | ||
4568 | 1060 | |||
4569 | 1061 | # do the request; we need to explicitly set the content type here, as it | ||
4570 | 1062 | # defaults to x-www-form-urlencoded | ||
4571 | 1063 | req = Request(url, data_flat.getvalue()) | ||
4572 | 1064 | req.add_header('Content-Type', 'multipart/form-data; boundary=' + data.get_boundary()) | ||
4573 | 1065 | opener = build_opener(HTTPSProgressHandler) | ||
4574 | 1066 | result = opener.open(req) | ||
4575 | 1067 | ticket = result.info().get('X-Launchpad-Blob-Token') | ||
4576 | 1068 | |||
4577 | 1069 | assert ticket | ||
4578 | 1070 | return ticket | ||
4579 | 1071 | |||
4580 | 1072 | # | ||
4581 | 1073 | # Unit tests | ||
4582 | 1074 | # | ||
4583 | 1075 | |||
4584 | 1076 | if __name__ == '__main__': | ||
4585 | 1077 | import unittest, atexit, shutil, subprocess | ||
4586 | 1078 | import mock | ||
4587 | 1079 | |||
4588 | 1080 | crashdb = None | ||
4589 | 1081 | _segv_report = None | ||
4590 | 1082 | _python_report = None | ||
4591 | 1083 | _uncommon_description_report = None | ||
4592 | 1084 | |||
4593 | 1085 | class _T(unittest.TestCase): | ||
4594 | 1086 | # this assumes that a source package 'coreutils' exists and builds a | ||
4595 | 1087 | # binary package 'coreutils' | ||
4596 | 1088 | test_package = 'coreutils' | ||
4597 | 1089 | test_srcpackage = 'coreutils' | ||
4598 | 1090 | |||
4599 | 1091 | # | ||
4600 | 1092 | # Generic tests, should work for all CrashDB implementations | ||
4601 | 1093 | # | ||
4602 | 1094 | |||
4603 | 1095 | def setUp(self): | ||
4604 | 1096 | global crashdb | ||
4605 | 1097 | if not crashdb: | ||
4606 | 1098 | crashdb = self._get_instance() | ||
4607 | 1099 | self.crashdb = crashdb | ||
4608 | 1100 | |||
4609 | 1101 | # create a local reference report so that we can compare | ||
4610 | 1102 | # DistroRelease, Architecture, etc. | ||
4611 | 1103 | self.ref_report = apport.Report() | ||
4612 | 1104 | self.ref_report.add_os_info() | ||
4613 | 1105 | self.ref_report.add_user_info() | ||
4614 | 1106 | self.ref_report['SourcePackage'] = 'coreutils' | ||
4615 | 1107 | |||
4616 | 1108 | # Objects tests rely on. | ||
4617 | 1109 | self._create_project('langpack-o-matic') | ||
4618 | 1110 | |||
4619 | 1111 | def _create_project(self, name): | ||
4620 | 1112 | '''Create a project using launchpadlib to be used by tests.''' | ||
4621 | 1113 | |||
4622 | 1114 | project = self.crashdb.launchpad.projects[name] | ||
4623 | 1115 | if not project: | ||
4624 | 1116 | self.crashdb.launchpad.projects.new_project( | ||
4625 | 1117 | description=name + 'description', | ||
4626 | 1118 | display_name=name, | ||
4627 | 1119 | name=name, | ||
4628 | 1120 | summary=name + 'summary', | ||
4629 | 1121 | title=name + 'title') | ||
4630 | 1122 | |||
4631 | 1123 | @property | ||
4632 | 1124 | def hostname(self): | ||
4633 | 1125 | '''Get the Launchpad hostname for the given crashdb.''' | ||
4634 | 1126 | |||
4635 | 1127 | return self.crashdb.get_hostname() | ||
4636 | 1128 | |||
4637 | 1129 | def get_segv_report(self, force_fresh=False): | ||
4638 | 1130 | '''Generate SEGV crash report. | ||
4639 | 1131 | |||
4640 | 1132 | This is only done once, subsequent calls will return the already | ||
4641 | 1133 | existing ID, unless force_fresh is True. | ||
4642 | 1134 | |||
4643 | 1135 | Return the ID. | ||
4644 | 1136 | ''' | ||
4645 | 1137 | global _segv_report | ||
4646 | 1138 | if not force_fresh and _segv_report is not None: | ||
4647 | 1139 | return _segv_report | ||
4648 | 1140 | |||
4649 | 1141 | r = self._generate_sigsegv_report() | ||
4650 | 1142 | r.add_package_info(self.test_package) | ||
4651 | 1143 | r.add_os_info() | ||
4652 | 1144 | r.add_gdb_info() | ||
4653 | 1145 | r.add_user_info() | ||
4654 | 1146 | self.assertEqual(r.standard_title(), 'crash crashed with SIGSEGV in f()') | ||
4655 | 1147 | |||
4656 | 1148 | # add some binary gibberish which isn't UTF-8 | ||
4657 | 1149 | r['ShortGibberish'] = ' "]\xb6"\n' | ||
4658 | 1150 | r['LongGibberish'] = 'a\nb\nc\nd\ne\n\xff\xff\xff\n\f' | ||
4659 | 1151 | |||
4660 | 1152 | # create a bug for the report | ||
4661 | 1153 | bug_target = self._get_bug_target(self.crashdb, r) | ||
4662 | 1154 | self.assertTrue(bug_target) | ||
4663 | 1155 | |||
4664 | 1156 | id = self._file_bug(bug_target, r) | ||
4665 | 1157 | self.assertTrue(id > 0) | ||
4666 | 1158 | |||
4667 | 1159 | sys.stderr.write('(Created SEGV report: https://%s/bugs/%i) ' % (self.hostname, id)) | ||
4668 | 1160 | if not force_fresh: | ||
4669 | 1161 | _segv_report = id | ||
4670 | 1162 | return id | ||
4671 | 1163 | |||
4672 | 1164 | def get_python_report(self): | ||
4673 | 1165 | '''Generate Python crash report. | ||
4674 | 1166 | |||
4675 | 1167 | Return the ID. | ||
4676 | 1168 | ''' | ||
4677 | 1169 | global _python_report | ||
4678 | 1170 | if _python_report is not None: | ||
4679 | 1171 | return _python_report | ||
4680 | 1172 | |||
4681 | 1173 | r = apport.Report('Crash') | ||
4682 | 1174 | r['ExecutablePath'] = '/bin/foo' | ||
4683 | 1175 | r['Traceback'] = '''Traceback (most recent call last): | ||
4684 | 1176 | File "/bin/foo", line 67, in fuzz | ||
4685 | 1177 | print(weird) | ||
4686 | 1178 | NameError: global name 'weird' is not defined''' | ||
4687 | 1179 | r['Tags'] = 'boogus pybogus' | ||
4688 | 1180 | r.add_package_info(self.test_package) | ||
4689 | 1181 | r.add_os_info() | ||
4690 | 1182 | r.add_user_info() | ||
4691 | 1183 | self.assertEqual(r.standard_title(), | ||
4692 | 1184 | "foo crashed with NameError in fuzz(): global name 'weird' is not defined") | ||
4693 | 1185 | |||
4694 | 1186 | bug_target = self._get_bug_target(self.crashdb, r) | ||
4695 | 1187 | self.assertTrue(bug_target) | ||
4696 | 1188 | |||
4697 | 1189 | id = self._file_bug(bug_target, r) | ||
4698 | 1190 | self.assertTrue(id > 0) | ||
4699 | 1191 | sys.stderr.write('(Created Python report: https://%s/bugs/%i) ' % (self.hostname, id)) | ||
4700 | 1192 | _python_report = id | ||
4701 | 1193 | return id | ||
4702 | 1194 | |||
4703 | 1195 | def get_uncommon_description_report(self, force_fresh=False): | ||
4704 | 1196 | '''File a bug report with an uncommon description. | ||
4705 | 1197 | |||
4706 | 1198 | This is only done once, subsequent calls will return the already | ||
4707 | 1199 | existing ID, unless force_fresh is True. | ||
4708 | 1200 | |||
4709 | 1201 | Example taken from real LP bug 269539. It contains only | ||
4710 | 1202 | ProblemType/Architecture/DistroRelease in the description, and has | ||
4711 | 1203 | free-form description text after the Apport data. | ||
4712 | 1204 | |||
4713 | 1205 | Return the ID. | ||
4714 | 1206 | ''' | ||
4715 | 1207 | global _uncommon_description_report | ||
4716 | 1208 | if not force_fresh and _uncommon_description_report is not None: | ||
4717 | 1209 | return _uncommon_description_report | ||
4718 | 1210 | |||
4719 | 1211 | desc = '''problem | ||
4720 | 1212 | |||
4721 | 1213 | ProblemType: Package | ||
4722 | 1214 | Architecture: amd64 | ||
4723 | 1215 | DistroRelease: Ubuntu 8.10 | ||
4724 | 1216 | |||
4725 | 1217 | more text | ||
4726 | 1218 | |||
4727 | 1219 | and more | ||
4728 | 1220 | ''' | ||
4729 | 1221 | bug = self.crashdb.launchpad.bugs.createBug( | ||
4730 | 1222 | title=b'mixed description bug'.encode(), | ||
4731 | 1223 | description=desc, | ||
4732 | 1224 | target=self.crashdb.lp_distro) | ||
4733 | 1225 | sys.stderr.write('(Created uncommon description: https://%s/bugs/%i) ' % (self.hostname, bug.id)) | ||
4734 | 1226 | |||
4735 | 1227 | if not force_fresh: | ||
4736 | 1228 | _uncommon_description_report = bug.id | ||
4737 | 1229 | return bug.id | ||
4738 | 1230 | |||
4739 | 1231 | def test_1_download(self): | ||
4740 | 1232 | '''download()''' | ||
4741 | 1233 | |||
4742 | 1234 | r = self.crashdb.download(self.get_segv_report()) | ||
4743 | 1235 | self.assertEqual(r['ProblemType'], 'Crash') | ||
4744 | 1236 | self.assertEqual(r['Title'], 'crash crashed with SIGSEGV in f()') | ||
4745 | 1237 | self.assertEqual(r['DistroRelease'], self.ref_report['DistroRelease']) | ||
4746 | 1238 | self.assertEqual(r['Architecture'], self.ref_report['Architecture']) | ||
4747 | 1239 | self.assertEqual(r['Uname'], self.ref_report['Uname']) | ||
4748 | 1240 | self.assertEqual(r.get('NonfreeKernelModules'), | ||
4749 | 1241 | self.ref_report.get('NonfreeKernelModules')) | ||
4750 | 1242 | self.assertEqual(r.get('UserGroups'), self.ref_report.get('UserGroups')) | ||
4751 | 1243 | tags = set(r['Tags'].split()) | ||
4752 | 1244 | self.assertEqual(tags, set([self.crashdb.arch_tag, 'apport-crash', | ||
4753 | 1245 | apport.packaging.get_system_architecture()])) | ||
4754 | 1246 | |||
4755 | 1247 | self.assertEqual(r['Signal'], '11') | ||
4756 | 1248 | self.assertTrue(r['ExecutablePath'].endswith('/crash')) | ||
4757 | 1249 | self.assertEqual(r['SourcePackage'], self.test_srcpackage) | ||
4758 | 1250 | self.assertTrue(r['Package'].startswith(self.test_package + ' ')) | ||
4759 | 1251 | self.assertTrue('f (x=42)' in r['Stacktrace']) | ||
4760 | 1252 | self.assertTrue('f (x=42)' in r['StacktraceTop']) | ||
4761 | 1253 | self.assertTrue('f (x=42)' in r['ThreadStacktrace']) | ||
4762 | 1254 | self.assertTrue(len(r['CoreDump']) > 1000) | ||
4763 | 1255 | self.assertTrue('Dependencies' in r) | ||
4764 | 1256 | self.assertTrue('Disassembly' in r) | ||
4765 | 1257 | self.assertTrue('Registers' in r) | ||
4766 | 1258 | |||
4767 | 1259 | # check tags | ||
4768 | 1260 | r = self.crashdb.download(self.get_python_report()) | ||
4769 | 1261 | tags = set(r['Tags'].split()) | ||
4770 | 1262 | self.assertEqual(tags, set(['apport-crash', 'boogus', 'pybogus', | ||
4771 | 1263 | 'need-duplicate-check', apport.packaging.get_system_architecture()])) | ||
4772 | 1264 | |||
4773 | 1265 | def test_2_update_traces(self): | ||
4774 | 1266 | '''update_traces()''' | ||
4775 | 1267 | |||
4776 | 1268 | r = self.crashdb.download(self.get_segv_report()) | ||
4777 | 1269 | self.assertTrue('CoreDump' in r) | ||
4778 | 1270 | self.assertTrue('Dependencies' in r) | ||
4779 | 1271 | self.assertTrue('Disassembly' in r) | ||
4780 | 1272 | self.assertTrue('Registers' in r) | ||
4781 | 1273 | self.assertTrue('Stacktrace' in r) | ||
4782 | 1274 | self.assertTrue('ThreadStacktrace' in r) | ||
4783 | 1275 | self.assertEqual(r['Title'], 'crash crashed with SIGSEGV in f()') | ||
4784 | 1276 | |||
4785 | 1277 | # updating with a useless stack trace retains core dump | ||
4786 | 1278 | r['StacktraceTop'] = '?? ()' | ||
4787 | 1279 | r['Stacktrace'] = 'long\ntrace' | ||
4788 | 1280 | r['ThreadStacktrace'] = 'thread\neven longer\ntrace' | ||
4789 | 1281 | r['FooBar'] = 'bogus' | ||
4790 | 1282 | self.crashdb.update_traces(self.get_segv_report(), r, 'I can has a better retrace?') | ||
4791 | 1283 | r = self.crashdb.download(self.get_segv_report()) | ||
4792 | 1284 | self.assertTrue('CoreDump' in r) | ||
4793 | 1285 | self.assertTrue('Dependencies' in r) | ||
4794 | 1286 | self.assertTrue('Disassembly' in r) | ||
4795 | 1287 | self.assertTrue('Registers' in r) | ||
4796 | 1288 | self.assertTrue('Stacktrace' in r) # TODO: ascertain that it's the updated one | ||
4797 | 1289 | self.assertTrue('ThreadStacktrace' in r) | ||
4798 | 1290 | self.assertFalse('FooBar' in r) | ||
4799 | 1291 | self.assertEqual(r['Title'], 'crash crashed with SIGSEGV in f()') | ||
4800 | 1292 | |||
4801 | 1293 | tags = self.crashdb.launchpad.bugs[self.get_segv_report()].tags | ||
4802 | 1294 | self.assertTrue('apport-crash' in tags) | ||
4803 | 1295 | self.assertFalse('apport-collected' in tags) | ||
4804 | 1296 | |||
4805 | 1297 | # updating with a useful stack trace removes core dump | ||
4806 | 1298 | r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so' | ||
4807 | 1299 | r['Stacktrace'] = 'long\ntrace' | ||
4808 | 1300 | r['ThreadStacktrace'] = 'thread\neven longer\ntrace' | ||
4809 | 1301 | self.crashdb.update_traces(self.get_segv_report(), r, 'good retrace!') | ||
4810 | 1302 | r = self.crashdb.download(self.get_segv_report()) | ||
4811 | 1303 | self.assertFalse('CoreDump' in r) | ||
4812 | 1304 | self.assertTrue('Dependencies' in r) | ||
4813 | 1305 | self.assertTrue('Disassembly' in r) | ||
4814 | 1306 | self.assertTrue('Registers' in r) | ||
4815 | 1307 | self.assertTrue('Stacktrace' in r) | ||
4816 | 1308 | self.assertTrue('ThreadStacktrace' in r) | ||
4817 | 1309 | self.assertFalse('FooBar' in r) | ||
4818 | 1310 | |||
4819 | 1311 | # as previous title had standard form, the top function gets | ||
4820 | 1312 | # updated | ||
4821 | 1313 | self.assertEqual(r['Title'], 'crash crashed with SIGSEGV in read()') | ||
4822 | 1314 | |||
4823 | 1315 | # respects title amendments | ||
4824 | 1316 | bug = self.crashdb.launchpad.bugs[self.get_segv_report()] | ||
4825 | 1317 | bug.title = 'crash crashed with SIGSEGV in f() on exit' | ||
4826 | 1318 | try: | ||
4827 | 1319 | bug.lp_save() | ||
4828 | 1320 | except HTTPError: | ||
4829 | 1321 | pass # LP#336866 workaround | ||
4830 | 1322 | r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so' | ||
4831 | 1323 | self.crashdb.update_traces(self.get_segv_report(), r, 'good retrace with title amendment') | ||
4832 | 1324 | r = self.crashdb.download(self.get_segv_report()) | ||
4833 | 1325 | self.assertEqual(r['Title'], 'crash crashed with SIGSEGV in read() on exit') | ||
4834 | 1326 | |||
4835 | 1327 | # does not destroy custom titles | ||
4836 | 1328 | bug = self.crashdb.launchpad.bugs[self.get_segv_report()] | ||
4837 | 1329 | bug.title = 'crash is crashy' | ||
4838 | 1330 | try: | ||
4839 | 1331 | bug.lp_save() | ||
4840 | 1332 | except HTTPError: | ||
4841 | 1333 | pass # LP#336866 workaround | ||
4842 | 1334 | |||
4843 | 1335 | r['StacktraceTop'] = 'read () from /lib/libc.6.so\nfoo (i=1) from /usr/lib/libfoo.so' | ||
4844 | 1336 | self.crashdb.update_traces(self.get_segv_report(), r, 'good retrace with custom title') | ||
4845 | 1337 | r = self.crashdb.download(self.get_segv_report()) | ||
4846 | 1338 | self.assertEqual(r['Title'], 'crash is crashy') | ||
4847 | 1339 | |||
4848 | 1340 | # test various situations which caused crashes | ||
4849 | 1341 | r['Stacktrace'] = '' # empty file | ||
4850 | 1342 | r['ThreadStacktrace'] = '"]\xb6"\n' # not interpretable as UTF-8, LP #353805 | ||
4851 | 1343 | r['StacktraceSource'] = 'a\nb\nc\nd\ne\n\xff\xff\xff\n\f' | ||
4852 | 1344 | self.crashdb.update_traces(self.get_segv_report(), r, 'tests') | ||
4853 | 1345 | |||
4854 | 1346 | def test_get_comment_url(self): | ||
4855 | 1347 | '''get_comment_url() for non-ASCII titles''' | ||
4856 | 1348 | |||
4857 | 1349 | # UTF-8 bytestring, works in both python 2.7 and 3 | ||
4858 | 1350 | title = b'1\xc3\xa4\xe2\x99\xa52' | ||
4859 | 1351 | |||
4860 | 1352 | # distro, UTF-8 bytestring | ||
4861 | 1353 | r = apport.Report('Bug') | ||
4862 | 1354 | r['Title'] = title | ||
4863 | 1355 | url = self.crashdb.get_comment_url(r, 42) | ||
4864 | 1356 | self.assertTrue(url.endswith('/ubuntu/+filebug/42?field.title=1%C3%A4%E2%99%A52')) | ||
4865 | 1357 | |||
4866 | 1358 | # distro, unicode | ||
4867 | 1359 | r['Title'] = title.decode('UTF-8') | ||
4868 | 1360 | url = self.crashdb.get_comment_url(r, 42) | ||
4869 | 1361 | self.assertTrue(url.endswith('/ubuntu/+filebug/42?field.title=1%C3%A4%E2%99%A52')) | ||
4870 | 1362 | |||
4871 | 1363 | # package, unicode | ||
4872 | 1364 | r['SourcePackage'] = 'coreutils' | ||
4873 | 1365 | url = self.crashdb.get_comment_url(r, 42) | ||
4874 | 1366 | self.assertTrue(url.endswith('/ubuntu/+source/coreutils/+filebug/42?field.title=1%C3%A4%E2%99%A52')) | ||
4875 | 1367 | |||
4876 | 1368 | def test_update_description(self): | ||
4877 | 1369 | '''update() with changing description''' | ||
4878 | 1370 | |||
4879 | 1371 | bug_target = self.crashdb.lp_distro.getSourcePackage(name='bash') | ||
4880 | 1372 | bug = self.crashdb.launchpad.bugs.createBug( | ||
4881 | 1373 | description='test description for test bug.', | ||
4882 | 1374 | target=bug_target, | ||
4883 | 1375 | title='testbug') | ||
4884 | 1376 | id = bug.id | ||
4885 | 1377 | self.assertTrue(id > 0) | ||
4886 | 1378 | sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id)) | ||
4887 | 1379 | |||
4888 | 1380 | r = apport.Report('Bug') | ||
4889 | 1381 | |||
4890 | 1382 | r['OneLiner'] = b'bogus\xe2\x86\x92'.decode('UTF-8') | ||
4891 | 1383 | r['StacktraceTop'] = 'f()\ng()\nh(1)' | ||
4892 | 1384 | r['ShortGoo'] = 'lineone\nlinetwo' | ||
4893 | 1385 | r['DpkgTerminalLog'] = 'one\ntwo\nthree\nfour\nfive\nsix' | ||
4894 | 1386 | r['VarLogDistupgradeBinGoo'] = b'\x01' * 1024 | ||
4895 | 1387 | |||
4896 | 1388 | self.crashdb.update(id, r, 'NotMe', change_description=True) | ||
4897 | 1389 | |||
4898 | 1390 | r = self.crashdb.download(id) | ||
4899 | 1391 | |||
4900 | 1392 | self.assertEqual(r['OneLiner'], b'bogus\xe2\x86\x92'.decode('UTF-8')) | ||
4901 | 1393 | self.assertEqual(r['ShortGoo'], 'lineone\nlinetwo') | ||
4902 | 1394 | self.assertEqual(r['DpkgTerminalLog'], 'one\ntwo\nthree\nfour\nfive\nsix') | ||
4903 | 1395 | self.assertEqual(r['VarLogDistupgradeBinGoo'], b'\x01' * 1024) | ||
4904 | 1396 | |||
4905 | 1397 | self.assertEqual(self.crashdb.launchpad.bugs[id].tags, | ||
4906 | 1398 | ['apport-collected']) | ||
4907 | 1399 | |||
4908 | 1400 | def test_update_comment(self): | ||
4909 | 1401 | '''update() with appending comment''' | ||
4910 | 1402 | |||
4911 | 1403 | bug_target = self.crashdb.lp_distro.getSourcePackage(name='bash') | ||
4912 | 1404 | # we need to fake an apport description separator here, since we | ||
4913 | 1405 | # want to be lazy and use download() for checking the result | ||
4914 | 1406 | bug = self.crashdb.launchpad.bugs.createBug( | ||
4915 | 1407 | description='Pr0blem\n\n--- \nProblemType: Bug', | ||
4916 | 1408 | target=bug_target, | ||
4917 | 1409 | title='testbug') | ||
4918 | 1410 | id = bug.id | ||
4919 | 1411 | self.assertTrue(id > 0) | ||
4920 | 1412 | sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id)) | ||
4921 | 1413 | |||
4922 | 1414 | r = apport.Report('Bug') | ||
4923 | 1415 | |||
4924 | 1416 | r['OneLiner'] = 'bogus→' | ||
4925 | 1417 | r['StacktraceTop'] = 'f()\ng()\nh(1)' | ||
4926 | 1418 | r['ShortGoo'] = 'lineone\nlinetwo' | ||
4927 | 1419 | r['DpkgTerminalLog'] = 'one\ntwo\nthree\nfour\nfive\nsix' | ||
4928 | 1420 | r['VarLogDistupgradeBinGoo'] = '\x01' * 1024 | ||
4929 | 1421 | |||
4930 | 1422 | self.crashdb.update(id, r, 'meow', change_description=False) | ||
4931 | 1423 | |||
4932 | 1424 | r = self.crashdb.download(id) | ||
4933 | 1425 | |||
4934 | 1426 | self.assertFalse('OneLiner' in r) | ||
4935 | 1427 | self.assertFalse('ShortGoo' in r) | ||
4936 | 1428 | self.assertEqual(r['ProblemType'], 'Bug') | ||
4937 | 1429 | self.assertEqual(r['DpkgTerminalLog'], 'one\ntwo\nthree\nfour\nfive\nsix') | ||
4938 | 1430 | self.assertEqual(r['VarLogDistupgradeBinGoo'], '\x01' * 1024) | ||
4939 | 1431 | |||
4940 | 1432 | self.assertEqual(self.crashdb.launchpad.bugs[id].tags, | ||
4941 | 1433 | ['apport-collected']) | ||
4942 | 1434 | |||
4943 | 1435 | def test_update_filter(self): | ||
4944 | 1436 | '''update() with a key filter''' | ||
4945 | 1437 | |||
4946 | 1438 | bug_target = self.crashdb.lp_distro.getSourcePackage(name='bash') | ||
4947 | 1439 | bug = self.crashdb.launchpad.bugs.createBug( | ||
4948 | 1440 | description='test description for test bug', | ||
4949 | 1441 | target=bug_target, | ||
4950 | 1442 | title='testbug') | ||
4951 | 1443 | id = bug.id | ||
4952 | 1444 | self.assertTrue(id > 0) | ||
4953 | 1445 | sys.stderr.write('(https://%s/bugs/%i) ' % (self.hostname, id)) | ||
4954 | 1446 | |||
4955 | 1447 | r = apport.Report('Bug') | ||
4956 | 1448 | |||
4957 | 1449 | r['OneLiner'] = 'bogus→' | ||
4958 | 1450 | r['StacktraceTop'] = 'f()\ng()\nh(1)' | ||
4959 | 1451 | r['ShortGoo'] = 'lineone\nlinetwo' | ||
4960 | 1452 | r['DpkgTerminalLog'] = 'one\ntwo\nthree\nfour\nfive\nsix' | ||
4961 | 1453 | r['VarLogDistupgradeBinGoo'] = '\x01' * 1024 | ||
4962 | 1454 | |||
4963 | 1455 | self.crashdb.update(id, r, 'NotMe', change_description=True, | ||
4964 | 1456 | key_filter=['ProblemType', 'ShortGoo', 'DpkgTerminalLog']) | ||
4965 | 1457 | |||
4966 | 1458 | r = self.crashdb.download(id) | ||
4967 | 1459 | |||
4968 | 1460 | self.assertFalse('OneLiner' in r) | ||
4969 | 1461 | self.assertEqual(r['ShortGoo'], 'lineone\nlinetwo') | ||
4970 | 1462 | self.assertEqual(r['ProblemType'], 'Bug') | ||
4971 | 1463 | self.assertEqual(r['DpkgTerminalLog'], 'one\ntwo\nthree\nfour\nfive\nsix') | ||
4972 | 1464 | self.assertFalse('VarLogDistupgradeBinGoo' in r) | ||
4973 | 1465 | |||
4974 | 1466 | self.assertEqual(self.crashdb.launchpad.bugs[id].tags, []) | ||
4975 | 1467 | |||
4976 | 1468 | def test_get_distro_release(self): | ||
4977 | 1469 | '''get_distro_release()''' | ||
4978 | 1470 | |||
4979 | 1471 | self.assertEqual(self.crashdb.get_distro_release(self.get_segv_report()), | ||
4980 | 1472 | self.ref_report['DistroRelease']) | ||
4981 | 1473 | |||
4982 | 1474 | def test_get_affected_packages(self): | ||
4983 | 1475 | '''get_affected_packages()''' | ||
4984 | 1476 | |||
4985 | 1477 | self.assertEqual(self.crashdb.get_affected_packages(self.get_segv_report()), | ||
4986 | 1478 | [self.ref_report['SourcePackage']]) | ||
4987 | 1479 | |||
4988 | 1480 | def test_is_reporter(self): | ||
4989 | 1481 | '''is_reporter()''' | ||
4990 | 1482 | |||
4991 | 1483 | self.assertTrue(self.crashdb.is_reporter(self.get_segv_report())) | ||
4992 | 1484 | self.assertFalse(self.crashdb.is_reporter(1)) | ||
4993 | 1485 | |||
4994 | 1486 | def test_can_update(self): | ||
4995 | 1487 | '''can_update()''' | ||
4996 | 1488 | |||
4997 | 1489 | self.assertTrue(self.crashdb.can_update(self.get_segv_report())) | ||
4998 | 1490 | self.assertFalse(self.crashdb.can_update(1)) | ||
4999 | 1491 | |||
5000 | 1492 | def test_duplicates(self): |
The diff has been truncated for viewing.