[ABRT PATCH] abrt-gdb-exploitable: improve signal analysis by looking at siginfo.si_code
by Denys Vlasenko
In many cases, we can detect signals which are masquerading
as a segfault, but are really sent via kill(2) et al.
Signed-off-by: Denys Vlasenko <dvlasenk(a)redhat.com>
---
src/plugins/abrt-gdb-exploitable | 152 ++++++++++++++++++++++++++++++++++-----
1 file changed, 133 insertions(+), 19 deletions(-)
diff --git a/src/plugins/abrt-gdb-exploitable b/src/plugins/abrt-gdb-exploitable
index 51257e9..3bb80b9 100755
--- a/src/plugins/abrt-gdb-exploitable
+++ b/src/plugins/abrt-gdb-exploitable
@@ -451,16 +451,19 @@ class SignalAndInsn:
def get_signal(self):
self.signo = None
+ self.si_code = None
try:
# Requires new kernels which record complete siginfo
# in coredumps (Linux 3.9 still don't have it),
# and new gdb:
sig = gdb.parse_and_eval("$_siginfo.si_signo")
+ code = gdb.parse_and_eval("$_siginfo.si_code")
# Requires patched gdb:
#sig = gdb.parse_and_eval("$_signo")
#
# type(sig) = <type 'gdb.Value'>, convert to plain int:
self.signo = int(sig)
+ self.si_code = int(code)
except gdb.error:
# "Python Exception <class 'gdb.error'>
# Attempt to extract a component of a value that is not a structure"
@@ -495,8 +498,100 @@ class SignalAndInsn:
self.exploitable_rating = 3
self.exploitable_desc = ""
- if 0:
- pass
+ # siginfo.si_code:
+ # If <= 0, then it's not a crash:
+ # SI_ASYNCNL = -60 /* asynch name lookup completion */
+ # SI_TKILL = -6 /* tkill (and tgkill?) */
+ # SI_SIGIO = -5 /* queued SIGIO */
+ # SI_ASYNCIO = -4 /* AIO completion */
+ # SI_MESGQ = -3 /* real time mesq state change */
+ # SI_TIMER = -2 /* timer expiration (timer_create() with SIGEV_SIGNAL) */
+ # SI_QUEUE = -1 /* sigqueue */
+ # SI_USER = 0 /* kill, sigsend */
+ # Crashes have si_code > 0:
+ # testDivideByZero: SIGFPE si_code=FPE_INTDIV(1), si_addr=0x40054a
+ # x86-64 opcode 0x62: SIGILL si_code=ILL_ILLOPN(2), si_addr=0x40053f
+ # x86-64 priv.insn.: SIGSEGV si_code=SI_KERNEL(128), si_addr=0
+ # testExecuteInvalid: SIGSEGV si_code=SEGV_MAPERR(1), si_addr=0x1c2404000
+ # testStackBufferOverflow ("ret" to bad address):
+ # SIGSEGV si_code=SI_KERNEL(128), si_addr=0
+ # testStackRecursion: SIGSEGV si_code=SEGV_MAPERR(1), si_addr=0x7fff4c216d28
+ # testWriteRandom: SIGSEGV si_code=SEGV_MAPERR(1), si_addr=0x1eb004004
+ # However:
+ # Keyboard signals (^C INT, ^\ QUIT, ^Z TSTP) also have si_code=SI_KERNEL.
+ # SIGWINCH has si_code=SI_KERNEL.
+ # SIGALRM from alarm(N) has si_code=SI_KERNEL.
+ # Surprisingly, SIGPIPE has si_code=SI_USER!
+ if self.si_code is not None:
+ # Filter out user-generated signals:
+ if self.si_code == -6 or self.si_code == -1: # SI_TKILL/SI_QUEUE
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by userspace code")
+ return
+ if self.si_code < 0:
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by timer/IO/async event")
+ return
+ # Unfortunately, this isn't reliable to flag user-sent signals:
+ # not only SIGPIPE, but some other kernel signals have SI_USER
+ # (grep kernel sources for "send_sig(sig, ..., 0)").
+ # At least we know it's not a crash.
+ if self.si_code == 0: # SI_USER
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal has siginfo.si_code = SI_USER")
+ # Special case (a kernel buglet?)
+ if self.signo == signal.SIGPIPE:
+ self.exploitable_desc = _("Signal due to write to closed pipe")
+ return
+ # And kernel-generated ones:
+ if self.si_code == 0x80: # SI_KERNEL
+ if self.signo in (signal.SIGINT, signal.SIGQUIT, signal.SIGTSTP):
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by keyboard")
+ return
+ if self.signo in (signal.SIGTTIN, signal.SIGTTOU, signal.SIGHUP):
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Job control signal sent by kernel")
+ return
+ if self.signo == signal.SIGWINCH:
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by window resize")
+ return
+ if self.signo == signal.SIGALRM:
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by alarm(N) expiration")
+ return
+ # else: Can't rule out "crash" signal: may be FPE/ILL/BUS/SEGV.
+ # Fall through into signo/insn analysis.
+ # else: We are here if signal was not from user and not SI_KERNEL.
+ # Fall through into signo/insn analysis.
+ # else: We are here if si_code isn't known.
+ # Fall through into signo/insn analysis.
+
+ # Guessing here... it might be kill(2) as well.
+ # Should I add "Likely ..." to the descriptions?
+ if self.signo in (signal.SIGINT, signal.SIGQUIT, signal.SIGTSTP):
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by keyboard")
+ return
+ if self.signo in (signal.SIGTTIN, signal.SIGTTOU, signal.SIGHUP):
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Job control signal sent by kernel")
+ return
+ if self.signo == signal.SIGPIPE:
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal due to write to broken pipe")
+ return
+ if self.signo == signal.SIGWINCH:
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by window resize")
+ return
+ if self.signo == signal.SIGALRM:
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by alarm(N) expiration")
+ return
+
+ # Which signals can coredump?
# SIGABRT Abort signal from abort(3)
# SIGQUIT Quit from keyboard
# SIGXCPU CPU time limit exceeded
@@ -507,58 +602,77 @@ class SignalAndInsn:
# SIGILL Illegal Instruction
# SIGSEGV Invalid memory reference
# SIGBUS Bus error (bad memory access)
- elif self.signo == signal.SIGABRT:
+ if self.signo == signal.SIGABRT:
self.exploitable_rating = 0
self.exploitable_desc = _("ABRT signal (abort() was called?)")
- elif self.signo == signal.SIGQUIT:
+ return
+ if self.signo == signal.SIGQUIT:
self.exploitable_rating = 0
self.exploitable_desc = _("QUIT signal (Ctrl-\\ pressed?)")
- elif self.signo == signal.SIGXCPU:
+ return
+ if self.signo == signal.SIGXCPU:
self.exploitable_rating = 0
self.exploitable_desc = _("XCPU signal (over CPU time limit)")
- elif self.signo == signal.SIGXFSZ:
+ return
+ if self.signo == signal.SIGXFSZ:
self.exploitable_rating = 0
self.exploitable_desc = _("XFSZ signal (over file size limit)")
- elif self.signo == signal.SIGTRAP:
+ return
+ if self.signo == signal.SIGTRAP:
self.exploitable_rating = 0
self.exploitable_desc = _("TRAP signal (can be a bug in a debugger/tracer)")
+ return
elif self.signo == signal.SIGSYS:
self.exploitable_rating = 1
- self.exploitable_desc = _("SYS signal (unknown/masked syscall was called?)")
+ self.exploitable_desc = _("SYS signal (unknown syscall was called?)")
+ return
- elif self.signo == signal.SIGFPE:
+ if self.signo == signal.SIGFPE:
self.exploitable_rating = 1
self.exploitable_desc = _("Arithmetic exception")
- if self.instruction_is_division:
+ # 1 is FPE_INTDIV
+ if self.si_code == 1 or self.instruction_is_division:
self.exploitable_rating = 0
self.exploitable_desc = _("Division by zero")
- elif self.signo == signal.SIGILL:
+ return
+ if self.signo == signal.SIGILL:
self.exploitable_rating = 5
self.exploitable_desc = _("Illegal instruction (jump to a random address?)")
+ return
- # TODO: check that sig is SIGSEGV/SIGBUS?
+ if self.signo != signal.SIGSEGV and self.signo != signal.SIGBUS:
+ self.exploitable_rating = 2
+ # Pity that we can't give a more descriptive explanation
+ self.exploitable_desc = _("Non-crash related signal")
+ return
- elif self.instruction_is_pushing:
+ if self.instruction_is_pushing:
self.exploitable_rating = 4
self.exploitable_desc = _("Stack overflow")
- elif self.instruction_is_store:
+ return
+ if self.instruction_is_store:
self.exploitable_rating = 6
self.exploitable_desc = _("Write to an invalid address")
- elif self.instruction_is_return:
+ return
+ if self.instruction_is_return:
self.exploitable_rating = 7
self.exploitable_desc = _("Subroutine return to an invalid address (corrupted stack?)")
+ return
# Note: we check "ret" first, _then_ jumps.
# Corrupted stack is different from corrupted data.
- elif self.instruction_is_branch:
+ if self.instruction_is_branch:
self.exploitable_rating = 6
self.exploitable_desc = _("Jump to an invalid address")
- elif not self.current_instruction:
+ return
+ if not self.current_instruction:
self.exploitable_rating = 6
self.exploitable_desc = _("Jump to an invalid address")
- elif self.signo == signal.SIGBUS:
+ return
+ if self.signo == signal.SIGBUS:
self.exploitable_rating = 5
self.exploitable_desc = _("Access past the end of mapped file, invalid address, unaligned access, etc")
- #elif self.signo = signal.SIGfoo:
+ return
+ # default values remain (e.g. description is "")
class AbrtExploitable(gdb.Command):
"Analyze a crash to determine exploitability"
--
1.8.1.4
10 years, 10 months
[LIBREPORT PATCH] resize the config window upon collapsing Advanced section
by Jakub Filak
Closes rhbz#965963
Signed-off-by: Jakub Filak <jfilak(a)redhat.com>
---
src/gtk-helpers/event_config_dialog.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/gtk-helpers/event_config_dialog.c b/src/gtk-helpers/event_config_dialog.c
index 238c84b..7ea5b29 100644
--- a/src/gtk-helpers/event_config_dialog.c
+++ b/src/gtk-helpers/event_config_dialog.c
@@ -219,6 +219,8 @@ config_dialog_t *create_event_config_dialog_content(event_config_t *event, GtkWi
g_object_set_data(G_OBJECT(adv_option_table), "n-rows", (gpointer)-1);
GtkWidget *adv_expander = gtk_expander_new(_("Advanced"));
+ /* resize the toplevel widget containing the expander upon resizing and collapsing. */
+ gtk_expander_set_resize_toplevel(GTK_EXPANDER(adv_expander), TRUE);
gtk_container_add(GTK_CONTAINER(adv_expander), adv_option_table);
g_object_set_data(G_OBJECT(option_table), "advanced-options", adv_option_table);
@@ -298,9 +300,6 @@ config_dialog_t *create_event_config_dialog(const char *event_name, GtkWindow *p
/* Allow resize?
* W/o resize, e.g. upload configuration hint looks awfully
* line wrapped.
- * With resize, there are some somewhat not nice effects:
- * for one, opening an expander will enlarge window,
- * but won't contract it back when expander is closed.
*/
gtk_window_set_resizable(GTK_WINDOW(dialog), true);
gtk_window_set_default_size(GTK_WINDOW(dialog), 450, -1);
--
1.8.2.1
10 years, 10 months
abrt-gdb-exploitable doc
by Denys Vlasenko
Per Jiri's suggestion, here's a draft documentation
for abrt-gdb-exploitable.
I plan to put it into the header comment, as a minimum.
Where else it needs to be, in your opinion?
================================================
abrt-gdb-exploitable is a gdb plugin written in python.
To load it, use this gdb command:
python execfile("[/path/to/]abrt-gdb-exploitable")
If it loads successfully, a new gdb command is made available:
abrt-exploitable [RATING] [OUTFILE]
It can be run on a live process or a core dump.
If process didn't receive any signal, the command will emit a
"Can't get signal no and do exploitability analysis"
message to stderr.
If it is unable to make a guess about the reason why the
signal was received, then it will emit a "Exploitability
analysis came up empty" message to stderr.
Otherwise, it will emit the following message to stdout:
Likely crash reason: STRING
Exploitable rating (0-9 scale): N
where STRING explains why the signal was sent, and N
shows how likely is this to be a result of an exploitable
bug in the process. 0 means that it doesn't look dangerous at all,
9 means that the tool is virtually certain it is an exploitable bug.
For example, stack corrupting bugs are much more likely to be exploitable
than division by zero.
If RATING is specified, the message is only emitted if N >= RATING.
If OUTFILE is specified, the message is written to the OUTFILE
instead of stdout.
Of course, the tool is not an AI. It uses a relatively simple
heuristics to decide what has happened.
In order to be able to analyze core dumps, the tool needs that
the kernel which produced the core dump, and the gdb should
be sufficiently recent to provide $_siginfo variable.
10 years, 10 months
[GNOME-ABRT PATCH 1/3] Improve data caching
by Jakub Filak
Average time required for loading 60 Kerneloopses()
Before: 6.8s
After : 2.2s
But this patch still doesn't make start up faster for the worst case
when all of the problems are different and none of them has a desktop
entry.
Related to rhbz#963054
Signed-off-by: Jakub Filak <jfilak(a)redhat.com>
---
src/gnome_abrt/application.py | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/src/gnome_abrt/application.py b/src/gnome_abrt/application.py
index 910e76a..8517eaa 100644
--- a/src/gnome_abrt/application.py
+++ b/src/gnome_abrt/application.py
@@ -121,6 +121,7 @@ def find_application(component, executable, cmdline):
(executable, compare_executable),
(component, compare_component)]
+ # explore the cache in the first step
for pred in lookupnames:
if not pred[0]:
continue
@@ -128,6 +129,11 @@ def find_application(component, executable, cmdline):
if pred[0] in __globa_app_cache__:
return __globa_app_cache__[pred[0]]
+ # no cache entry was found, try to find corresponding desktop entry
+ for pred in lookupnames:
+ if not pred[0]:
+ continue
+
theme = Gtk.IconTheme.get_default()
for dai in Gio.DesktopAppInfo.get_all():
if pred[1](pred[0], dai):
@@ -154,4 +160,11 @@ def find_application(component, executable, cmdline):
icon=icon)
return __globa_app_cache__[pred[0]]
- return Application(executable, name=component)
+ app = Application(executable, name=component)
+
+ # cache by cmdline because package and component can provide many
+ # applications but cmdline looks like pretty unique information
+ if cmdline:
+ __globa_app_cache__[cmdline] = app
+
+ return app
--
1.8.2.1
10 years, 10 months
[Bug 882955] SELinux is preventing /lib/systemd/systemd-logind from 'setattr' accesses on the blk_file sr0.
by Red Hat Bugzilla
https://bugzilla.redhat.com/show_bug.cgi?id=882955
--- Comment #1 from Fedora End Of Life <endoflife(a)fedoraproject.org> ---
This message is a reminder that Fedora 17 is nearing its end of life.
Approximately 4 (four) weeks from now Fedora will stop maintaining
and issuing updates for Fedora 17. It is Fedora's policy to close all
bug reports from releases that are no longer maintained. At that time
this bug will be closed as WONTFIX if it remains open with a Fedora
'version' of '17'.
Package Maintainer: If you wish for this bug to remain open because you
plan to fix it in a currently maintained version, simply change the 'version'
to a later Fedora version prior to Fedora 17's end of life.
Bug Reporter: Thank you for reporting this issue and we are sorry that
we may not be able to fix it before Fedora 17 is end of life. If you
would still like to see this bug fixed and are able to reproduce it
against a later version of Fedora, you are encouraged change the
'version' to a later Fedora version prior to Fedora 17's end of life.
Although we aim to fix as many bugs as possible during every release's
lifetime, sometimes those efforts are overtaken by events. Often a
more recent Fedora release includes newer upstream software that fixes
bugs or makes them obsolete.
--
You are receiving this mail because:
You reported the bug.
10 years, 10 months
[Bug 856276] [abrt] will-crash-0.2-1.fc17: main: i
by Red Hat Bugzilla
https://bugzilla.redhat.com/show_bug.cgi?id=856276
--- Comment #11 from Fedora End Of Life <endoflife(a)fedoraproject.org> ---
This message is a reminder that Fedora 17 is nearing its end of life.
Approximately 4 (four) weeks from now Fedora will stop maintaining
and issuing updates for Fedora 17. It is Fedora's policy to close all
bug reports from releases that are no longer maintained. At that time
this bug will be closed as WONTFIX if it remains open with a Fedora
'version' of '17'.
Package Maintainer: If you wish for this bug to remain open because you
plan to fix it in a currently maintained version, simply change the 'version'
to a later Fedora version prior to Fedora 17's end of life.
Bug Reporter: Thank you for reporting this issue and we are sorry that
we may not be able to fix it before Fedora 17 is end of life. If you
would still like to see this bug fixed and are able to reproduce it
against a later version of Fedora, you are encouraged change the
'version' to a later Fedora version prior to Fedora 17's end of life.
Although we aim to fix as many bugs as possible during every release's
lifetime, sometimes those efforts are overtaken by events. Often a
more recent Fedora release includes newer upstream software that fixes
bugs or makes them obsolete.
--
You are receiving this mail because:
You reported the bug.
10 years, 10 months
[Bug 851210] [abrt] will-crash-0.2-1.fc17: main: Process /usr/bin/will_abort was killed by signal 6 (SIGABRT)
by Red Hat Bugzilla
https://bugzilla.redhat.com/show_bug.cgi?id=851210
--- Comment #19 from Fedora End Of Life <endoflife(a)fedoraproject.org> ---
This message is a reminder that Fedora 17 is nearing its end of life.
Approximately 4 (four) weeks from now Fedora will stop maintaining
and issuing updates for Fedora 17. It is Fedora's policy to close all
bug reports from releases that are no longer maintained. At that time
this bug will be closed as WONTFIX if it remains open with a Fedora
'version' of '17'.
Package Maintainer: If you wish for this bug to remain open because you
plan to fix it in a currently maintained version, simply change the 'version'
to a later Fedora version prior to Fedora 17's end of life.
Bug Reporter: Thank you for reporting this issue and we are sorry that
we may not be able to fix it before Fedora 17 is end of life. If you
would still like to see this bug fixed and are able to reproduce it
against a later version of Fedora, you are encouraged change the
'version' to a later Fedora version prior to Fedora 17's end of life.
Although we aim to fix as many bugs as possible during every release's
lifetime, sometimes those efforts are overtaken by events. Often a
more recent Fedora release includes newer upstream software that fixes
bugs or makes them obsolete.
--
You are receiving this mail because:
You reported the bug.
10 years, 10 months
[ABRT PATCH 1/2 v2] abrt-gdb-exploitable: x86 factoring-out
by Denys Vlasenko
No substantial logic changes.
Mostly renaming stuff and moving blocks of code around.
v2: Python style fixes
Signed-off-by: Denys Vlasenko <dvlasenk(a)redhat.com>
---
src/plugins/abrt-gdb-exploitable | 228 ++++++++++++++++++++-------------------
1 file changed, 117 insertions(+), 111 deletions(-)
diff --git a/src/plugins/abrt-gdb-exploitable b/src/plugins/abrt-gdb-exploitable
index e3d637b..f33ec45 100755
--- a/src/plugins/abrt-gdb-exploitable
+++ b/src/plugins/abrt-gdb-exploitable
@@ -30,11 +30,10 @@ def init_gettext():
gettext.bindtextdomain(GETTEXT_PROGNAME, '/usr/share/locale')
gettext.textdomain(GETTEXT_PROGNAME)
-
_WRITES_ALWAYS = -1
_WRITES_IF_MEMREF = -2
-_writing_instr = {
+_x86_writing_instr = {
# insn:N, where N:
# -1: this insn always writes to memory
# -2: writes to memory if any operand is a memory operand
@@ -198,7 +197,7 @@ _writing_instr = {
# Note: stack-writing instructions are omitted
}
-_pushing_instr = (
+_x86_pushing_instr = (
"push",
"pusha",
"pushf",
@@ -207,9 +206,9 @@ _pushing_instr = (
"lcall"
)
-_intdiv_instr = ("div", "idiv")
+_x86_intdiv_instr = ("div", "idiv")
-_jumping_instr = (
+_x86_jumping_instr = (
"jmp", # indirect jumps/calls with garbage data
"call", # call: also possible that stack is exhausted (infinite recursion)
"ljmp",
@@ -233,7 +232,10 @@ _jumping_instr = (
)
# stack was smashed if we crash on one of these
-_return_instr = ("iret", "lret", "ret")
+_x86_return_instr = ("iret", "lret", "ret")
+
+_x86_mem_op1_regex = re.compile("^((-?0x)|[(])")
+_x86_mem_op2_regex = re.compile("[,:]((-?0x)|[(])")
def _fetch_insn_from_table(ins, table):
if not ins:
@@ -251,50 +253,68 @@ def _fetch_insn_from_table(ins, table):
return ins
return None
+
class SignalAndInsn:
- def get_signal(self):
- self.signo = None
- try:
- # Requires new kernels which record complete siginfo
- # in coredumps (Linux 3.9 still don't have it),
- # and new gdb:
- sig = gdb.parse_and_eval("$_siginfo.si_signo")
- # Requires patched gdb:
- #sig = gdb.parse_and_eval("$_signo")
- #
- # type(sig) = <type 'gdb.Value'>, convert to plain int:
- self.signo = int(sig)
- except gdb.error:
- # "Python Exception <class 'gdb.error'>
- # Attempt to extract a component of a value that is not a structure"
- # Possible reasons why $_siginfo doesn't exist:
- # program is still running, program exited normally,
- # we work with a coredump from an old kernel.
- #
- # Lets see whether we are running from the abrt and it
- # provided us with signal number. Horrible hack :(
- #
- try:
- self.signo = int(os.environ["SIGNO_OF_THE_COREDUMP"])
- except KeyError:
+
+ def x86_instruction_is_store(self):
+ operand = _fetch_insn_from_table(self.mnemonic, _x86_writing_instr)
+ if not operand:
+ if not self.mnemonic:
return False
- return True
+ # There are far too many SSE store instructions,
+ # don't want to pollute the table with them.
+ # Special-case the check for MOVxxx
+ # and its SIMD cousins VMOVxxx:
+ if self.mnemonic[:3] != "mov" and self.mnemonic[:4] != "vmov":
+ return False
+ operand = 2
- def get_instruction(self):
- self.current_instruction = None
- self.mnemonic = None
- self.operands = ""
+ if operand == _WRITES_ALWAYS: # no need to check operands, it's a write
+ return True
+
+ # Memory operands look like this: [%seg:][[-]0xHEXNUM][(%reg[,...])]
+ # Careful with immediate operands which are $0xHEXNUM
+ # and FPU register references which are st(N).
+ if _x86_mem_op1_regex.search(self.operands):
+ mem_op_pos = 0
+ else:
+ match = _x86_mem_op2_regex.search(self.operands)
+ if not match:
+ return False # no memory operands
+ mem_op_pos = match.start() + 1
+
+ if operand == _WRITES_IF_MEMREF: # any mem operand indicates write
+ return True
+
+ comma = self.operands.find(",")
+ if mem_op_pos < comma:
+ # "%cs:0x0(%rax,%rax,1),foo" - 1st operand is memory
+ # "%cs:0x0(%rax),foo" - 1st operand is memory
+ memory_operand = 1
+ elif comma < 0:
+ # "%cs:0x0(%rax)" - 1st operand is memory
+ memory_operand = 1
+ else:
+ # mem_op_pos is after comma
+ # "foo,%cs:0x0(%rax,%rax,1)" - 2nd operand is memory
+ # (It also can be a third, fourth etc operand)
+ memory_operand = 2
+
+ if operand == memory_operand:
+ return True
+ return False
+
+ def x86_get_instruction(self):
try:
# just "disassemble $pc" won't work if $pc doesn't point
# inside a known function
- instructions = gdb.execute("disassemble $pc,$pc+32", to_string=True)
+ raw_instructions = gdb.execute("disassemble $pc,$pc+32", to_string=True)
except gdb.error:
# For example, if tracee already exited normally.
# Another observed case is if $pc points to unmapped area.
# We get "Python Exception <class 'gdb.error'> No registers"
return
- raw_instructions = instructions
instructions = []
current = None
for line in raw_instructions.split("\n"):
@@ -316,7 +336,7 @@ class SignalAndInsn:
if line:
instructions.append(line)
if current == None:
- # not False! we determined that $pc points to a bad address,
+ # we determined that $pc points to a bad address,
# which is an interesting fact.
return
@@ -344,76 +364,62 @@ class SignalAndInsn:
self.operands = t[1]
break
- mem_op1_regex = re.compile("^((-?0x)|[(])")
- mem_op2_regex = re.compile("[,:]((-?0x)|[(])")
+ self.instruction_is_pushing = (_fetch_insn_from_table(self.mnemonic, _x86_pushing_instr) is not None)
+ self.instruction_is_division = (_fetch_insn_from_table(self.mnemonic, _x86_intdiv_instr) is not None)
+ self.instruction_is_branch = (_fetch_insn_from_table(self.mnemonic, _x86_jumping_instr) is not None)
+ self.instruction_is_return = (_fetch_insn_from_table(self.mnemonic, _x86_return_instr) is not None)
+ self.instruction_is_store = self.x86_instruction_is_store()
- def instruction_is_writing(self):
- operand = _fetch_insn_from_table(self.mnemonic, _writing_instr)
- if not operand:
- if not self.mnemonic:
- return False
- # There are far too many SSE store instructions,
- # don't want to pollute the table with them.
- # Special-case the check for MOVxxx
- # and its SIMD cousins VMOVxxx:
- if self.mnemonic[:3] != "mov" and self.mnemonic[:4] != "vmov":
- return False
- operand = 2
- if operand == _WRITES_ALWAYS: # no need to check operands, it's a write
- return True
-
- # Memory operands look like this: [%seg:][[-]0xHEXNUM][(%reg[,...])]
- # Careful with immediate operands which are $0xHEXNUM
- # and FPU register references which are st(N).
- if SignalAndInsn.mem_op1_regex.search(self.operands):
- mem_op_pos = 0
- else:
- match = SignalAndInsn.mem_op2_regex.search(self.operands)
- if not match:
- return False # no memory operands
- mem_op_pos = match.start() + 1
-
- if operand == _WRITES_IF_MEMREF: # any mem operand indicates write
- return True
-
- comma = self.operands.find(",")
- if mem_op_pos < comma:
- # "%cs:0x0(%rax,%rax,1),foo" - 1st operand is memory
- # "%cs:0x0(%rax),foo" - 1st operand is memory
- memory_operand = 1
- elif comma < 0:
- # "%cs:0x0(%rax)" - 1st operand is memory
- memory_operand = 1
- else:
- # mem_op_pos is after comma
- # "foo,%cs:0x0(%rax,%rax,1)" - 2nd operand is memory
- # (It also can be a third, fourth etc operand)
- memory_operand = 2
-
- if operand == memory_operand:
- return True
- return False
-
- def instruction_is_pushing(self):
- if _fetch_insn_from_table(self.mnemonic, _pushing_instr):
- return True
- return False
-
- def instruction_is_division(self):
- if _fetch_insn_from_table(self.mnemonic, _intdiv_instr):
- return True
- return False
-
- def instruction_is_jumping(self):
- if _fetch_insn_from_table(self.mnemonic, _jumping_instr):
- return True
- return False
+ def get_instruction(self):
+ self.current_instruction = None
+ self.mnemonic = None
+ self.operands = ""
+ self.instruction_is_division = None
+ self.instruction_is_store = None
+ self.instruction_is_pushing = None
+ self.instruction_is_return = None
+ self.instruction_is_branch = None
+ try:
+ arch = gdb.execute("show architecture", to_string=True)
+ # Examples of the string we get:
+ # The target architecture is set automatically (currently i386)
+ # The target architecture is set automatically (currently i386:x86-64)
+ # The target architecture is set automatically (currently powerpc:common64)
+ if " i386" in arch:
+ return self.x86_get_instruction()
+ #if " powerpc" in arch:
+ # return self.ppc_get_instruction()
+ except gdb.error:
+ return
- def instruction_is_return(self):
- if _fetch_insn_from_table(self.mnemonic, _return_instr):
- return True
- return False
+ def get_signal(self):
+ self.signo = None
+ try:
+ # Requires new kernels which record complete siginfo
+ # in coredumps (Linux 3.9 still don't have it),
+ # and new gdb:
+ sig = gdb.parse_and_eval("$_siginfo.si_signo")
+ # Requires patched gdb:
+ #sig = gdb.parse_and_eval("$_signo")
+ #
+ # type(sig) = <type 'gdb.Value'>, convert to plain int:
+ self.signo = int(sig)
+ except gdb.error:
+ # "Python Exception <class 'gdb.error'>
+ # Attempt to extract a component of a value that is not a structure"
+ # Possible reasons why $_siginfo doesn't exist:
+ # program is still running, program exited normally,
+ # we work with a coredump from an old kernel.
+ #
+ # Lets see whether we are running from the abrt and it
+ # provided us with signal number. Horrible hack :(
+ #
+ try:
+ self.signo = int(os.environ["SIGNO_OF_THE_COREDUMP"])
+ except KeyError:
+ return False
+ return True
#Our initial set of testing will use the list Apple included in their
#CrashWrangler announcement:
@@ -467,7 +473,7 @@ class SignalAndInsn:
elif self.signo == signal.SIGFPE:
self.exploitable_rating = 1
self.exploitable_desc = _("Arithmetic exception")
- if self.instruction_is_division():
+ if self.instruction_is_division:
self.exploitable_rating = 0
self.exploitable_desc = _("Division by zero")
elif self.signo == signal.SIGILL:
@@ -476,18 +482,18 @@ class SignalAndInsn:
# TODO: check that sig is SIGSEGV/SIGBUS?
- elif self.instruction_is_pushing():
+ elif self.instruction_is_pushing:
self.exploitable_rating = 4
self.exploitable_desc = _("Stack overflow")
- elif self.instruction_is_writing():
+ elif self.instruction_is_store:
self.exploitable_rating = 6
self.exploitable_desc = _("Write to an invalid address")
- elif self.instruction_is_return():
+ elif self.instruction_is_return:
self.exploitable_rating = 7
self.exploitable_desc = _("Subroutine return to an invalid address (corrupted stack?)")
# Note: we check "ret" first, _then_ jumps.
# Corrupted stack is different from corrupted data.
- elif self.instruction_is_jumping():
+ elif self.instruction_is_branch:
self.exploitable_rating = 6
self.exploitable_desc = _("Jump to an invalid address")
elif not self.current_instruction:
--
1.8.1.4
10 years, 10 months
[PATCH 1/2] abrt-gdb-exploitable: x86 factoring-out
by Denys Vlasenko
No substantial logic changes.
Mostly renaming stuff and moving blocks of code around.
Signed-off-by: Denys Vlasenko <dvlasenk(a)redhat.com>
---
src/plugins/abrt-gdb-exploitable | 228 ++++++++++++++++++++-------------------
1 file changed, 117 insertions(+), 111 deletions(-)
diff --git a/src/plugins/abrt-gdb-exploitable b/src/plugins/abrt-gdb-exploitable
index e3d637b..f33ec45 100755
--- a/src/plugins/abrt-gdb-exploitable
+++ b/src/plugins/abrt-gdb-exploitable
@@ -30,11 +30,10 @@ def init_gettext():
gettext.bindtextdomain(GETTEXT_PROGNAME, '/usr/share/locale')
gettext.textdomain(GETTEXT_PROGNAME)
-
_WRITES_ALWAYS = -1
_WRITES_IF_MEMREF = -2
-_writing_instr = {
+_x86_writing_instr = {
# insn:N, where N:
# -1: this insn always writes to memory
# -2: writes to memory if any operand is a memory operand
@@ -198,7 +197,7 @@ _writing_instr = {
# Note: stack-writing instructions are omitted
}
-_pushing_instr = (
+_x86_pushing_instr = (
"push",
"pusha",
"pushf",
@@ -207,9 +206,9 @@ _pushing_instr = (
"lcall"
)
-_intdiv_instr = ("div", "idiv")
+_x86_intdiv_instr = ("div", "idiv")
-_jumping_instr = (
+_x86_jumping_instr = (
"jmp", # indirect jumps/calls with garbage data
"call", # call: also possible that stack is exhausted (infinite recursion)
"ljmp",
@@ -233,7 +232,10 @@ _jumping_instr = (
)
# stack was smashed if we crash on one of these
-_return_instr = ("iret", "lret", "ret")
+_x86_return_instr = ("iret", "lret", "ret")
+
+_x86_mem_op1_regex = re.compile("^((-?0x)|[(])")
+_x86_mem_op2_regex = re.compile("[,:]((-?0x)|[(])")
def _fetch_insn_from_table(ins, table):
if not ins:
@@ -251,50 +253,68 @@ def _fetch_insn_from_table(ins, table):
return ins
return None
+
class SignalAndInsn:
- def get_signal(self):
- self.signo = None
- try:
- # Requires new kernels which record complete siginfo
- # in coredumps (Linux 3.9 still don't have it),
- # and new gdb:
- sig = gdb.parse_and_eval("$_siginfo.si_signo")
- # Requires patched gdb:
- #sig = gdb.parse_and_eval("$_signo")
- #
- # type(sig) = <type 'gdb.Value'>, convert to plain int:
- self.signo = int(sig)
- except gdb.error:
- # "Python Exception <class 'gdb.error'>
- # Attempt to extract a component of a value that is not a structure"
- # Possible reasons why $_siginfo doesn't exist:
- # program is still running, program exited normally,
- # we work with a coredump from an old kernel.
- #
- # Lets see whether we are running from the abrt and it
- # provided us with signal number. Horrible hack :(
- #
- try:
- self.signo = int(os.environ["SIGNO_OF_THE_COREDUMP"])
- except KeyError:
+
+ def x86_instruction_is_store(self):
+ operand = _fetch_insn_from_table(self.mnemonic, _x86_writing_instr)
+ if not operand:
+ if not self.mnemonic:
return False
- return True
+ # There are far too many SSE store instructions,
+ # don't want to pollute the table with them.
+ # Special-case the check for MOVxxx
+ # and its SIMD cousins VMOVxxx:
+ if self.mnemonic[:3] != "mov" and self.mnemonic[:4] != "vmov":
+ return False
+ operand = 2
- def get_instruction(self):
- self.current_instruction = None
- self.mnemonic = None
- self.operands = ""
+ if operand == _WRITES_ALWAYS: # no need to check operands, it's a write
+ return True
+
+ # Memory operands look like this: [%seg:][[-]0xHEXNUM][(%reg[,...])]
+ # Careful with immediate operands which are $0xHEXNUM
+ # and FPU register references which are st(N).
+ if _x86_mem_op1_regex.search(self.operands):
+ mem_op_pos = 0
+ else:
+ match = _x86_mem_op2_regex.search(self.operands)
+ if not match:
+ return False # no memory operands
+ mem_op_pos = match.start() + 1
+
+ if operand == _WRITES_IF_MEMREF: # any mem operand indicates write
+ return True
+
+ comma = self.operands.find(",")
+ if mem_op_pos < comma:
+ # "%cs:0x0(%rax,%rax,1),foo" - 1st operand is memory
+ # "%cs:0x0(%rax),foo" - 1st operand is memory
+ memory_operand = 1
+ elif comma < 0:
+ # "%cs:0x0(%rax)" - 1st operand is memory
+ memory_operand = 1
+ else:
+ # mem_op_pos is after comma
+ # "foo,%cs:0x0(%rax,%rax,1)" - 2nd operand is memory
+ # (It also can be a third, fourth etc operand)
+ memory_operand = 2
+
+ if operand == memory_operand:
+ return True
+ return False
+
+ def x86_get_instruction(self):
try:
# just "disassemble $pc" won't work if $pc doesn't point
# inside a known function
- instructions = gdb.execute("disassemble $pc,$pc+32", to_string=True)
+ raw_instructions = gdb.execute("disassemble $pc,$pc+32", to_string=True)
except gdb.error:
# For example, if tracee already exited normally.
# Another observed case is if $pc points to unmapped area.
# We get "Python Exception <class 'gdb.error'> No registers"
return
- raw_instructions = instructions
instructions = []
current = None
for line in raw_instructions.split("\n"):
@@ -316,7 +336,7 @@ class SignalAndInsn:
if line:
instructions.append(line)
if current == None:
- # not False! we determined that $pc points to a bad address,
+ # we determined that $pc points to a bad address,
# which is an interesting fact.
return
@@ -344,76 +364,62 @@ class SignalAndInsn:
self.operands = t[1]
break
- mem_op1_regex = re.compile("^((-?0x)|[(])")
- mem_op2_regex = re.compile("[,:]((-?0x)|[(])")
+ self.instruction_is_pushing = (None != _fetch_insn_from_table(self.mnemonic, _x86_pushing_instr))
+ self.instruction_is_division = (None != _fetch_insn_from_table(self.mnemonic, _x86_intdiv_instr))
+ self.instruction_is_branch = (None != _fetch_insn_from_table(self.mnemonic, _x86_jumping_instr))
+ self.instruction_is_return = (None != _fetch_insn_from_table(self.mnemonic, _x86_return_instr))
+ self.instruction_is_store = self.x86_instruction_is_store()
- def instruction_is_writing(self):
- operand = _fetch_insn_from_table(self.mnemonic, _writing_instr)
- if not operand:
- if not self.mnemonic:
- return False
- # There are far too many SSE store instructions,
- # don't want to pollute the table with them.
- # Special-case the check for MOVxxx
- # and its SIMD cousins VMOVxxx:
- if self.mnemonic[:3] != "mov" and self.mnemonic[:4] != "vmov":
- return False
- operand = 2
- if operand == _WRITES_ALWAYS: # no need to check operands, it's a write
- return True
-
- # Memory operands look like this: [%seg:][[-]0xHEXNUM][(%reg[,...])]
- # Careful with immediate operands which are $0xHEXNUM
- # and FPU register references which are st(N).
- if SignalAndInsn.mem_op1_regex.search(self.operands):
- mem_op_pos = 0
- else:
- match = SignalAndInsn.mem_op2_regex.search(self.operands)
- if not match:
- return False # no memory operands
- mem_op_pos = match.start() + 1
-
- if operand == _WRITES_IF_MEMREF: # any mem operand indicates write
- return True
-
- comma = self.operands.find(",")
- if mem_op_pos < comma:
- # "%cs:0x0(%rax,%rax,1),foo" - 1st operand is memory
- # "%cs:0x0(%rax),foo" - 1st operand is memory
- memory_operand = 1
- elif comma < 0:
- # "%cs:0x0(%rax)" - 1st operand is memory
- memory_operand = 1
- else:
- # mem_op_pos is after comma
- # "foo,%cs:0x0(%rax,%rax,1)" - 2nd operand is memory
- # (It also can be a third, fourth etc operand)
- memory_operand = 2
-
- if operand == memory_operand:
- return True
- return False
-
- def instruction_is_pushing(self):
- if _fetch_insn_from_table(self.mnemonic, _pushing_instr):
- return True
- return False
-
- def instruction_is_division(self):
- if _fetch_insn_from_table(self.mnemonic, _intdiv_instr):
- return True
- return False
-
- def instruction_is_jumping(self):
- if _fetch_insn_from_table(self.mnemonic, _jumping_instr):
- return True
- return False
+ def get_instruction(self):
+ self.current_instruction = None
+ self.mnemonic = None
+ self.operands = ""
+ self.instruction_is_division = None
+ self.instruction_is_store = None
+ self.instruction_is_pushing = None
+ self.instruction_is_return = None
+ self.instruction_is_branch = None
+ try:
+ arch = gdb.execute("show architecture", to_string=True)
+ # Examples of the string we get:
+ # The target architecture is set automatically (currently i386)
+ # The target architecture is set automatically (currently i386:x86-64)
+ # The target architecture is set automatically (currently powerpc:common64)
+ if " i386" in arch:
+ return self.x86_get_instruction()
+ #if " powerpc" in arch:
+ # return self.ppc_get_instruction()
+ except gdb.error:
+ return
- def instruction_is_return(self):
- if _fetch_insn_from_table(self.mnemonic, _return_instr):
- return True
- return False
+ def get_signal(self):
+ self.signo = None
+ try:
+ # Requires new kernels which record complete siginfo
+ # in coredumps (Linux 3.9 still don't have it),
+ # and new gdb:
+ sig = gdb.parse_and_eval("$_siginfo.si_signo")
+ # Requires patched gdb:
+ #sig = gdb.parse_and_eval("$_signo")
+ #
+ # type(sig) = <type 'gdb.Value'>, convert to plain int:
+ self.signo = int(sig)
+ except gdb.error:
+ # "Python Exception <class 'gdb.error'>
+ # Attempt to extract a component of a value that is not a structure"
+ # Possible reasons why $_siginfo doesn't exist:
+ # program is still running, program exited normally,
+ # we work with a coredump from an old kernel.
+ #
+ # Lets see whether we are running from the abrt and it
+ # provided us with signal number. Horrible hack :(
+ #
+ try:
+ self.signo = int(os.environ["SIGNO_OF_THE_COREDUMP"])
+ except KeyError:
+ return False
+ return True
#Our initial set of testing will use the list Apple included in their
#CrashWrangler announcement:
@@ -467,7 +473,7 @@ class SignalAndInsn:
elif self.signo == signal.SIGFPE:
self.exploitable_rating = 1
self.exploitable_desc = _("Arithmetic exception")
- if self.instruction_is_division():
+ if self.instruction_is_division:
self.exploitable_rating = 0
self.exploitable_desc = _("Division by zero")
elif self.signo == signal.SIGILL:
@@ -476,18 +482,18 @@ class SignalAndInsn:
# TODO: check that sig is SIGSEGV/SIGBUS?
- elif self.instruction_is_pushing():
+ elif self.instruction_is_pushing:
self.exploitable_rating = 4
self.exploitable_desc = _("Stack overflow")
- elif self.instruction_is_writing():
+ elif self.instruction_is_store:
self.exploitable_rating = 6
self.exploitable_desc = _("Write to an invalid address")
- elif self.instruction_is_return():
+ elif self.instruction_is_return:
self.exploitable_rating = 7
self.exploitable_desc = _("Subroutine return to an invalid address (corrupted stack?)")
# Note: we check "ret" first, _then_ jumps.
# Corrupted stack is different from corrupted data.
- elif self.instruction_is_jumping():
+ elif self.instruction_is_branch:
self.exploitable_rating = 6
self.exploitable_desc = _("Jump to an invalid address")
elif not self.current_instruction:
--
1.8.1.4
10 years, 10 months
[GNOME-ABRT PATCH] Display two sets of problems (My/System)
by Jakub Filak
Closes rhbz#973125
Signed-off-by: Jakub Filak <jfilak(a)redhat.com>
---
src/gnome-abrt | 24 ++-
src/gnome_abrt/controller.py.in | 18 +-
src/gnome_abrt/dbus_problems.py | 207 ++++++++++++++-----
src/gnome_abrt/errors.py | 4 +-
src/gnome_abrt/oops.glade | 79 ++++++--
src/gnome_abrt/problems.py | 80 +++++---
src/gnome_abrt/views.py | 428 ++++++++++++++++++++++++++++------------
7 files changed, 595 insertions(+), 245 deletions(-)
diff --git a/src/gnome-abrt b/src/gnome-abrt
index 65db955..b9be029 100755
--- a/src/gnome-abrt
+++ b/src/gnome-abrt
@@ -22,6 +22,7 @@ import os
import sys
import logging
import signal
+import traceback
from argparse import ArgumentParser
# pygobject
@@ -40,7 +41,8 @@ from gnome_abrt.controller import Controller
from gnome_abrt.signals import glib_sigchld_signal_handler
from gnome_abrt.problems import MultipleSources
from gnome_abrt.directory_problems import DirectoryProblemSource
-from gnome_abrt.dbus_problems import DBusProblemSource
+from gnome_abrt.dbus_problems import (get_standard_problems_source,
+ get_foreign_problems_source)
from gnome_abrt.errors import UnavailableSource
from gnome_abrt.l10n import _
from gnome_abrt.wrappers import show_events_list_dialog
@@ -157,7 +159,7 @@ class OopsApplication(Gtk.Application):
self.connect("command-line", self._parse_command_line)
self.set_flags(Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
self.gcontext = None
- self.source = None
+ self.all_sources = None
self._url_pool = GetURLTitleSourcePool(GNOME_ABRT_URL_POOL_CAPACITY)
self._url_cache = GetURLTitleSourceCache(self._url_pool)
gnome_abrt.url.set_async_worker(self._url_cache.get_url_title_async)
@@ -188,26 +190,28 @@ class OopsApplication(Gtk.Application):
sources = []
try:
- sources.append(DBusProblemSource(self._dbus_srv.mainloop))
- except UnavailableSource as ex:
- logging.warning(ex)
+ sources.append(
+ get_standard_problems_source(self._dbus_srv.mainloop))
+ except UnavailableSource:
+ logging.warning(traceback.format_exc())
self.gcontext = GLib.main_context_default()
try:
path = os.path.join(GLib.get_user_cache_dir(), "abrt/spool")
dps = DirectoryProblemSource(path, context=self.gcontext)
sources.append(dps)
- except UnavailableSource as ex:
- logging.warning(ex)
+ except UnavailableSource:
+ logging.warning(traceback.format_exc())
if len(sources) == 0:
raise UnavailableSource("No available problem source.")
- self.source = MultipleSources(sources)
+ self.all_sources = [(_("My"), MultipleSources(sources)),
+ (_("System"), get_foreign_problems_source())]
- controller = Controller(self.source,
+ controller = Controller(self.all_sources,
glib_sigchld_signal_handler)
- main_window = OopsWindow(self, self.source, controller)
+ main_window = OopsWindow(self, self.all_sources, controller)
main_window.show_all()
self.add_window (main_window)
#pylint: disable=W0703
diff --git a/src/gnome_abrt/controller.py.in b/src/gnome_abrt/controller.py.in
index f48e26d..0dad426 100644
--- a/src/gnome_abrt/controller.py.in
+++ b/src/gnome_abrt/controller.py.in
@@ -18,11 +18,15 @@
import logging
import os
import sys
+import traceback
+
+# gnome-abrt
+from gnome_abrt import errors
class Controller(object):
- def __init__(self, source, sigchld_assign=None):
- self.source = source
+ def __init__(self, sources, sigchld_assign=None):
+ self.sources = sources
self.run_event_fn = self._first_event_run
self._sigchld_assign = sigchld_assign
@@ -47,9 +51,17 @@ class Controller(object):
self.run_event_fn("open-gui", problem)
+ def _refresh_sources(self):
+ for name, src in self.sources:
+ try:
+ src.refresh()
+ except errors.UnavailableSource:
+ logging.debug("Cannot refresh problem source {0}: {1}"
+ .format(name, traceback.format_exc()))
+
def _first_event_run(self, event, problem):
if self._sigchld_assign is not None:
- self._sigchld_assign(self.source.refresh)
+ self._sigchld_assign(self._refresh_sources)
self.run_event_fn = self._run_event_on_problem
self.run_event_fn(event, problem)
diff --git a/src/gnome_abrt/dbus_problems.py b/src/gnome_abrt/dbus_problems.py
index c035606..1c964d5 100644
--- a/src/gnome_abrt/dbus_problems.py
+++ b/src/gnome_abrt/dbus_problems.py
@@ -17,6 +17,7 @@
import os
import dbus
+import traceback
from dbus.mainloop.glib import DBusGMainLoop
import logging
@@ -33,9 +34,42 @@ BUS_IFACE = 'org.freedesktop.problems'
ABRTD_DBUS_PATH = '/org/freedesktop/problems'
ABRTD_DBUS_SIGNAL = 'Crash'
+def get_standard_problems_source(mainloop=None):
+ return DBusProblemSource(StandardProblems, mainloop)
+
+def get_foreign_problems_source(mainloop=None):
+ return DBusProblemSource(ForeignProblems, mainloop)
+
class DBusProblemSource(problems.CachedSource):
- def __init__(self, mainloop=None):
+ class Driver(object):
+ """Handles differences in behaviour while working with different sets
+ of problems obtained from D-Bus service.
+ """
+
+ def __init__(self, source):
+ self._source = source
+
+ @property
+ def get_problems_method(self):
+ """Gets an instance of a method used for obtaining problems from
+ D-Bus service.
+ """
+
+ raise NotImplementedError()
+
+ def on_new_problem(self, *args):
+ """Process a notification about detected problem."""
+
+ raise NotImplementedError()
+
+ def on_dbus_exception(self, problem_id, ex):
+ """Process D-Bus errors."""
+
+ raise NotImplementedError()
+
+
+ def __init__(self, driverclass, mainloop=None):
super(DBusProblemSource, self).__init__()
self._mainloop = mainloop
@@ -55,17 +89,7 @@ class DBusProblemSource(problems.CachedSource):
_("Can't add receiver of signal '{0}'on DBus system path '{1}': {2}")
.format(ABRTD_DBUS_SIGNAL, ABRTD_DBUS_PATH, ex))
- class ConfigObserver():
- def __init__(self, source):
- self.source = source
-
- #pylint: disable=W0613
- def option_updated(self, conf, option):
- if option == "all_problems":
- self.source.refresh()
-
- conf = config.get_configuration()
- conf.set_watch("all_problems", ConfigObserver(self))
+ self._driver = driverclass(self)
def _connect_to_problems_bus(self):
# I can't find any description of raised exceptions
@@ -74,14 +98,14 @@ class DBusProblemSource(problems.CachedSource):
try:
self._proxy = self._bus.get_object(BUS_NAME, BUS_PATH)
except dbus.exceptions.DBusException as ex:
- raise errors.UnavailableSource(
+ raise errors.UnavailableSource(self, False,
_("Can't connect to DBus system bus '{0}' path '{1}': {2}")
.format(BUS_NAME, BUS_PATH, ex))
try:
self._interface = dbus.Interface(self._proxy, BUS_IFACE)
except dbus.exceptions.DBusException as ex:
- raise errors.UnavailableSource(
+ raise errors.UnavailableSource(self, False,
_("Can't get interface '{0}' on path '{1}' in bus '{2}': {3}")
.format(BUS_IFACE, BUS_PATH, BUS_NAME, ex))
@@ -111,21 +135,9 @@ class DBusProblemSource(problems.CachedSource):
.format(args))
return
- conf = config.get_configuration()
- if (len(args) > 2
- and int(args[2]) != os.getuid()
- and not conf['all_problems']):
- logging.debug("Received the new problem signal with different uid "
- "'{0}' ('{1}') and the all problems option is not configured"
- .format(args[2], os.getuid()))
- return
-
- if len(args) == 2 and not conf['all_problems']:
- logging.debug("Received the new problem signal without the uid "
- "argument and the all problems option is not configured")
- return
-
- self.process_new_problem_id(str(args[1]))
+ prblm_id = self._driver.on_new_problem(*args)
+ if prblm_id is not None:
+ self.process_new_problem_id(prblm_id)
def chown_problem(self, problem_id):
try:
@@ -150,12 +162,7 @@ class DBusProblemSource(problems.CachedSource):
lambda iface, *params: iface.GetInfo(*params),
problem_id, args)
except dbus.exceptions.DBusException as ex:
- if ex.get_dbus_name() in [
- "org.freedesktop.problems.AuthFailure",
- "org.freedesktop.problems.InvalidProblemDir"]:
- self._remove_from_cache(problem_id)
- raise errors.InvalidProblem(problem_id, ex)
-
+ self._driver.on_dbus_exception(problem_id, ex)
logging.warning(
_("Can't get problem data from DBus service: {0!s}")
.format(ex))
@@ -163,19 +170,9 @@ class DBusProblemSource(problems.CachedSource):
return info
def _get_problems(self):
- conf = config.get_configuration()
-
prblms = []
-
try:
- if conf['all_problems']:
- prblms = self._send_dbus_message(
- #pylint: disable=W0142
- lambda iface, *args: iface.GetAllProblems(*args))
- else:
- prblms = self._send_dbus_message(
- #pylint: disable=W0142
- lambda iface, *args: self._interface.GetProblems(*args))
+ prblms = self._send_dbus_message(self._driver.get_problems_method)
except dbus.exceptions.DBusException as ex:
logging.warning(
_("Can't get list of problems from DBus service: {0!s}")
@@ -190,12 +187,120 @@ class DBusProblemSource(problems.CachedSource):
lambda iface, *args: iface.DeleteProblem(*args), [problem_id])
return True
except dbus.exceptions.DBusException as ex:
- if ex.get_dbus_name() in ["org.freedesktop.problems.AuthFailure",
- "org.freedesktop.problems.InvalidProblemDir"]:
- self._remove_from_cache(problem_id)
- raise errors.InvalidProblem(problem_id, ex)
-
+ self._driver.on_dbus_exception(problem_id, ex)
logging.warning(
_("Can't delete problem over DBus service: {0!s}")
.format(ex))
return False
+
+
+class StandardProblems(DBusProblemSource.Driver):
+ """The old behaviour."""
+
+ def __init__(self, source):
+ super(StandardProblems, self).__init__(source)
+
+ class ConfigObserver():
+ def __init__(self, source):
+ self._source = source
+
+ #pylint: disable=W0613
+ def option_updated(self, conf, option):
+ if option == "all_problems":
+ self._source.refresh()
+
+ conf = config.get_configuration()
+ conf.set_watch("all_problems", ConfigObserver(self._source))
+
+ @property
+ def get_problems_method(self):
+ conf = config.get_configuration()
+ if conf['all_problems']:
+ #pylint: disable=W0142
+ return lambda iface, *args: iface.GetAllProblems(*args)
+ else:
+ #pylint: disable=W0142
+ return lambda iface, *args: iface.GetProblems(*args)
+
+ def on_new_problem(self, *args):
+ """Accepts foreign problems only if the all_problems option is enabled
+ """
+
+ conf = config.get_configuration()
+ try:
+ if (len(args) > 2
+ and int(args[2]) != os.getuid()
+ and not conf['all_problems']):
+ logging.debug("Received the new problem signal with different "
+ "uid '{0}' ('{1}') and the all problems option "
+ "is not configured" .format(args[2], os.getuid()))
+ return None
+ except ValueError:
+ logging.debug(traceback.format_exc())
+ return None
+
+ if len(args) == 2 and not conf['all_problems']:
+ logging.debug("Received the new problem signal without the uid "
+ "argument and the all problems option is not configured")
+ return None
+
+ return str(args[1])
+
+ def on_dbus_exception(self, problem_id, ex):
+ """Process AuthFailure error in same way as InvalidProblemDir because
+ this sort of problem source provides all kinds of problems. The user
+ problems which should be always accessible and the foreign problems
+ which may be inaccessible when a user cancels the authentication
+ dialogue. So, an AuthFailure error in this context means that a one
+ of many problems is unavailable (something like an invalid problem).
+ """
+
+ if ex.get_dbus_name() in ["org.freedesktop.problems.AuthFailure",
+ "org.freedesktop.problems.InvalidProblemDir"]:
+ self._source._remove_from_cache(problem_id)
+ raise errors.InvalidProblem(problem_id, ex)
+
+
+class ForeignProblems(DBusProblemSource.Driver):
+
+ def __init__(self, source):
+ super(ForeignProblems, self).__init__(source)
+
+ @property
+ def get_problems_method(self):
+ #pylint: disable=W0142
+ return lambda iface, *args: iface.GetForeignProblems(*args)
+
+ def on_new_problem(self, *args):
+ """Accepts only foreign problems."""
+
+ args_len = len(args)
+ try:
+ if args_len == 2 or (args_len > 2 and int(args[2]) != os.getuid()):
+ return str(args[1])
+ except ValueError:
+ logging.debug(traceback.format_exc())
+ return None
+
+ logging.debug("Received the new problem signal with current user's uid "
+ "'{0}' ('{1}') in ForeignPorblems driver"
+ .format(args[2], os.getuid()))
+
+ return None
+
+ def on_dbus_exception(self, problem_id, ex):
+ """If the authentication fails, a foreign problems source become
+ temporary unavailable because we expect that the failure was caused by
+ dismissing of the authentication dialog and all problems provided by
+ this kind of source require authentication (none of them is available
+ now). So, it means that a view can try to query the temporary
+ unavailable source later.
+ """
+
+ if ex.get_dbus_name() == "org.freedesktop.problems.AuthFailure":
+ logging.debug("User dismissed D-Bus authentication")
+ raise errors.UnavailableSource(self._source, True)
+
+ if ex.get_dbus_name() == "org.freedesktop.problems.InvalidProblemDir":
+ self._source._remove_from_cache(problem_id)
+ raise errors.InvalidProblem(problem_id, ex)
diff --git a/src/gnome_abrt/errors.py b/src/gnome_abrt/errors.py
index fffaf5e..777b85e 100644
--- a/src/gnome_abrt/errors.py
+++ b/src/gnome_abrt/errors.py
@@ -29,5 +29,7 @@ class InvalidProblem(GnomeAbrtError):
class UnavailableSource(GnomeAbrtError):
- def __init__(self, message=None):
+ def __init__(self, source, temporary, message=None):
super(UnavailableSource, self).__init__(message)
+ self.source = source
+ self.temporary = temporary
diff --git a/src/gnome_abrt/oops.glade b/src/gnome_abrt/oops.glade
index b9b4b1a..497c386 100644
--- a/src/gnome_abrt/oops.glade
+++ b/src/gnome_abrt/oops.glade
@@ -433,9 +433,6 @@
</packing>
</child>
<child>
- <placeholder/>
- </child>
- <child>
<object class="GtkLabel" id="lbl_reported">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -469,6 +466,12 @@
<property name="height">1</property>
</packing>
</child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
</object>
<packing>
<property name="left_attach">1</property>
@@ -560,6 +563,39 @@
<property name="tab_fill">False</property>
</packing>
</child>
+ <child>
+ <object class="GtkBox" id="vbx_no_source_page">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="lbl_no_source">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">No source selected!</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="lbl_page_no_source">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">No source</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
</object>
<packing>
<property name="left_attach">1</property>
@@ -575,6 +611,26 @@
<property name="margin_top">10</property>
<property name="orientation">vertical</property>
<child>
+ <object class="GtkBox" id="hbox_source_btns">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
<object class="GtkScrolledWindow" id="sw_problems">
<property name="visible">True</property>
<property name="can_focus">True</property>
@@ -630,23 +686,6 @@
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkCheckButton" id="chb_all_problems">
- <property name="related_action">gac_opt_all_problems</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="margin_top">10</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
diff --git a/src/gnome_abrt/problems.py b/src/gnome_abrt/problems.py
index 314dfcf..96a34ee 100644
--- a/src/gnome_abrt/problems.py
+++ b/src/gnome_abrt/problems.py
@@ -22,8 +22,7 @@ import logging
import gnome_abrt.url
from gnome_abrt.application import find_application
from gnome_abrt.errors import (InvalidProblem,
- UnavailableSource,
- GnomeAbrtError)
+ UnavailableSource)
from gnome_abrt.l10n import _
class ProblemSource(object):
@@ -112,7 +111,7 @@ class Problem:
self.source = source
self.app = None
self.submission = None
- self.data = self._get_initial_data(self.source)
+ self.data = None
self._deleted = False
def __str__(self):
@@ -142,6 +141,9 @@ class Problem:
return items
def __getitem__(self, item, cached=True):
+ if self.data is None:
+ self.data = self._get_initial_data(self.source)
+
if item == 'date':
return datetime.datetime.fromtimestamp(float(self['time']))
if item == 'date_last':
@@ -197,17 +199,8 @@ class Problem:
self.source.notify(ProblemSource.CHANGED_PROBLEM, self)
def delete(self):
- # TODO : weird?? the assignemt can be moved
+ self.source.delete_problem(self.problem_id)
self._deleted = True
- try:
- self.source.delete_problem(self.problem_id)
- except GnomeAbrtError as ex:
- logging.warning(_("Can't delete problem '{0}': '{1}'")
- .format(self.problem_id, ex))
- self._deleted = False
- except Exception as ex:
- self._deleted = False
- raise
def is_reported(self):
return not self['reported_to'] is None
@@ -297,13 +290,34 @@ class MultipleSources(ProblemSource):
self._disable_notify = False
+ def __eq__(self, other):
+ if other is None:
+ return False
+
+ return (super(MultipleSources, self) == other
+ or other in self.sources)
+
+ def _pop_source(self, index):
+ self.sources.pop(index)
+ if not self.sources:
+ raise UnavailableSource()
+
def get_items(self, problem_id, *args):
pass
def get_problems(self):
result = []
- for src in self.sources:
- result.extend(src.get_problems())
+ i = 0
+ while i != len(self.sources):
+ try:
+ result.extend(self.sources[i].get_problems())
+ i += 1
+ except UnavailableSource as ex:
+ logging.debug("{0}".format(str(ex)))
+ if not ex.temporary:
+ self._pop_source(i)
+ else:
+ i += 1
return result
@@ -323,8 +337,17 @@ class MultipleSources(ProblemSource):
self._disable_notify = True
try:
- for src in self.sources:
- src.refresh()
+ i = 0
+ while i != len(self.sources):
+ try:
+ self.sources[i].refresh()
+ i += 1
+ except UnavailableSource as ex:
+ logging.debug("{0}".format(str(ex)))
+ if not ex.temporary:
+ self._pop_source(i)
+ else:
+ i += 1
finally:
self._disable_notify = False
@@ -345,9 +368,7 @@ class CachedSource(ProblemSource):
try:
self._cache.append(self._create_new_problem(prblmid))
except InvalidProblem as ex:
- logging.warning(ex)
- except UnavailableSource as ex:
- logging.warning(ex)
+ logging.warning(str(ex))
return self._cache if self._cache else []
@@ -390,19 +411,14 @@ class CachedSource(ProblemSource):
self._cache = None
self.notify()
-
def _create_new_problem(self, problem_id):
return Problem(problem_id, self)
def process_new_problem_id(self, problem_id):
- try:
- if self._problem_is_in_cache(problem_id):
- prblm = self._cache[self._cache.index(problem_id)]
- prblm.refresh()
- else:
- prblm = self._create_new_problem(problem_id)
- self._insert_to_cache(prblm)
- self.notify(ProblemSource.NEW_PROBLEM, prblm)
- except UnavailableSource as ex:
- logging.warning(_("Source failed on processing of '{0}': {1}")
- .format(problem_id, ex))
+ if self._problem_is_in_cache(problem_id):
+ prblm = self._cache[self._cache.index(problem_id)]
+ prblm.refresh()
+ else:
+ prblm = self._create_new_problem(problem_id)
+ self._insert_to_cache(prblm)
+ self.notify(ProblemSource.NEW_PROBLEM, prblm)
diff --git a/src/gnome_abrt/views.py b/src/gnome_abrt/views.py
index ebc8908..6fa512b 100644
--- a/src/gnome_abrt/views.py
+++ b/src/gnome_abrt/views.py
@@ -22,6 +22,7 @@ import time
import logging
import subprocess
import locale
+import traceback
#pygobject
#pylint: disable=E0611
@@ -97,6 +98,27 @@ def time_sort_func(model, first, second, view):
logging.debug(ex)
return 0
+def format_button_source_name(name, source):
+ return "{0} ({1})".format(name, len(source.get_problems()))
+
+def handle_problem_and_source_errors(func):
+ """Wraps repetitive exception handling."""
+
+ def wrapper_for_instance_function(oops_wnd, *args):
+ try:
+ return func(oops_wnd, *args)
+ except errors.InvalidProblem as ex:
+ logging.debug(traceback.format_exc())
+ oops_wnd._remove_problem_from_storage(ex.problem_id)
+ except errors.UnavailableSource as ex:
+ logging.debug(traceback.format_exc())
+ oops_wnd._disable_source(ex.source, ex.temporary)
+
+ return None
+
+ return wrapper_for_instance_function
+
+
#pylint: disable=R0902
class OopsWindow(Gtk.ApplicationWindow):
class OopsGtkBuilder(object):
@@ -154,11 +176,54 @@ class OopsWindow(Gtk.ApplicationWindow):
return obj
- def __init__(self, application, source, controller):
+ class SourceObserver:
+ def __init__(self, wnd):
+ self.wnd = wnd
+ self._enabled = True
+
+ def enable(self):
+ self._enabled = True
+
+ def disable(self):
+ self._enabled = False
+
+ def changed(self, source, change_type=None, problem=None):
+ if not self._enabled:
+ return
+
+ try:
+ if source == self.wnd._source:
+ if not change_type:
+ self.wnd._reload_problems(source)
+ elif change_type == problems.ProblemSource.NEW_PROBLEM:
+ self.wnd._add_problem_to_storage(problem)
+ elif change_type == problems.ProblemSource.DELETED_PROBLEM:
+ self.wnd._remove_problem_from_storage(problem)
+ elif change_type == problems.ProblemSource.CHANGED_PROBLEM:
+ self.wnd._update_problem_in_storage(problem)
+
+ self.wnd._update_source_button(source)
+ except errors.UnavailableSource as ex:
+ self.wnd._disable_source(ex.source, ex.temporary)
+
+
+ class OptionsObserver:
+ def __init__(self, wnd):
+ self.wnd = wnd
+
+ def option_updated(self, conf, option):
+ if option == 'problemid' and conf[option]:
+ self.wnd._select_problem_by_id(conf[option])
+
+
+ def __init__(self, application, sources, controller):
Gtk.ApplicationWindow.__init__(self,
title=_('Automatic Bug Reporting Tool'),
application=application)
+ if not sources:
+ raise ValueError("The source list cannot be empty!")
+
self._builder = OopsWindow.OopsGtkBuilder()
self.set_default_size(*self._builder.wnd_main.get_size())
self._builder.wnd_main.remove(self._builder.gr_main_layout)
@@ -176,60 +241,150 @@ class OopsWindow(Gtk.ApplicationWindow):
stl_ctx.add_provider_for_screen(stl_ctx.get_screen(), css_prv, 6000)
self._builder.connect_signals(self)
- self.selected_problem = None
- self._source = source
+ self._source_observer = OopsWindow.SourceObserver(self)
+ self._source_observer.disable()
+
self._reloading = False
self._controller = controller
+ self.selected_problem = None
+ self._all_sources = []
+ self._source = None
+ self._handling_source_click = False
+ for name, src in sources:
+ self._all_sources.append(src)
+ src.attach(self._source_observer)
+
+ label = None
+ try:
+ label = format_button_source_name(name, src)
+ except errors.UnavailableSource:
+ logging.debug("Unavailable source: {0}".format(name))
+ continue
+
+ src_btn = Gtk.ToggleButton(label)
+ src_btn.set_visible(True)
+ # add an extra member source (I don't like it but it so easy)
+ src_btn.source = src
+ self._builder.hbox_source_btns.pack_start(src_btn,
+ True, True, 0)
+
+ # add an extra member name (I don't like it but it so easy)
+ src.name = name
+ # add an extra member button (I don't like it but it so easy)
+ src.button = src_btn
+ src_btn.connect("clicked", self._on_source_btn_clicked, src)
+
+ self._source = self._all_sources[0]
+ self._set_button_toggled(self._source.button, True)
+
self._builder.ls_problems.set_sort_column_id(0, Gtk.SortType.DESCENDING)
self._builder.ls_problems.set_sort_func(0, time_sort_func, self)
self._filter = ProblemsFilter(self, self._builder.tv_problems)
- class SourceObserver:
- def __init__(self, wnd):
- self.wnd = wnd
-
- def changed(self, source, change_type=None, problem=None):
- if not change_type:
- self.wnd._reload_problems(source)
- elif change_type == problems.ProblemSource.NEW_PROBLEM:
- self.wnd._add_problem_to_storage(problem)
- elif change_type == problems.ProblemSource.DELETED_PROBLEM:
- self.wnd._remove_problem_from_storage(problem)
- elif change_type == problems.ProblemSource.CHANGED_PROBLEM:
- self.wnd._update_problem_in_storage(problem)
-
-
- self._source_observer = SourceObserver(self)
- self._source.attach(self._source_observer)
-
self._builder.tv_problems.grab_focus()
- self._reload_problems(self._source)
-
- class OptionsObserver:
- def __init__(self, wnd):
- self.wnd = wnd
-
- def option_updated(self, conf, option):
- if option == 'problemid' and conf[option]:
- self.wnd._select_problem_by_id(conf[option])
+ try:
+ self._reload_problems(self._source)
+ except errors.UnavailableSource as ex:
+ self._disable_source(ex.source, ex.temporary)
- self._options_observer = OptionsObserver(self)
+ self._options_observer = OopsWindow.OptionsObserver(self)
conf = config.get_configuration()
conf.set_watch('problemid', self._options_observer)
self._options_observer.option_updated(conf, 'problemid')
self._builder.btn_detail.set_visible(conf['expert'])
self._builder.mi_detail.set_visible(conf['expert'])
+ # enable observer
+ self._source_observer.enable()
+
+ def _update_source_button(self, source):
+ name = format_button_source_name(source.name, source)
+ source.button.set_label(name)
+
+ def _set_button_toggled(self, button, state):
+ # set_active() triggers the clicked signal
+ # and if we set the active in program,
+ # we don't want do any action in the clicked handler
+ self._handling_source_click = True
+ try:
+ button.set_active(state)
+ finally:
+ self._handling_source_click = False
+
+ def _on_source_btn_clicked(self, btn, args):
+ # If True, then button's state was not changed by click
+ # and we don't want to switch source
+ if self._handling_source_click:
+ return
+
+ res, old_source = self._switch_source(btn.source)
+ if not res:
+ self._set_button_toggled(btn, False)
+ elif old_source is not None:
+ self._set_button_toggled(old_source.button, False)
+
+ def _switch_source(self, source):
+ """Sets the passed source as the selected source."""
+
+ result = True
+ old_source = None
+ if source != self._source:
+ try:
+ self._reload_problems(source)
+ old_source = self._source
+ self._source = source
+ except errors.UnavailableSource as ex:
+ self._disable_source(source, ex.temporary)
+ result = False
+
+ return (result, old_source)
+
+ def _disable_source(self, source, temporary):
+ if self._source is None or not self._all_sources:
+ return
+
+ # Some sources can be components of other sources.
+ # Problems are connected directly to the component sources, therefore
+ # exception's source is a component source, thus we have to find an
+ # instance of composite source which the unavailable component source
+ # belongs.
+ source_index = self._all_sources.index(source)
+
+ if source_index != -1:
+ real_source = self._all_sources[source_index]
+ self._set_button_toggled(real_source.button, False)
+ if not temporary:
+ logging.debug("Disabling source")
+ real_source.button.set_sensitive(False)
+ self._all_sources.pop(source_index)
+
+ if source != self._source:
+ return
+
+ # We just disabled the currently selected source. So, we should select
+ # some other source. The simplest way is to select the first source
+ # but only if it is not the disabled source.
+ # If the disabled source is completely unavailable (not temporary) we
+ # can always select the source at index 0 because the disabled
+ # source was removed from the _all_sources list.
+ if (not temporary or source_index != 0) and self._all_sources:
+ self._source = self._all_sources[0]
+ self._set_button_toggled(self._source.button, True)
+ else:
+ self._source = None
+
+ try:
+ self._reload_problems(self._source)
+ except errors.UnavailableSource as ex:
+ self._disable_source(ex.source, ex.temporary)
+
+ @handle_problem_and_source_errors
def _find_problem_iter(self, problem, model):
pit = model.get_iter_first()
while pit:
- try:
- if model[pit][2] == problem:
- return pit
- except errors.InvalidProblem as ex:
- self._remove_problem_from_storage(ex.problem_id)
- logging.debug(ex)
+ if model[pit][2] == problem:
+ return pit
pit = model.iter_next(pit)
@@ -245,8 +400,8 @@ class OopsWindow(Gtk.ApplicationWindow):
self._builder.ls_problems.append(problem_to_storage_values(problem))
return True
except errors.InvalidProblem as ex:
+ logging.debug("Exception: {0}".format(traceback.format_exc()))
self._remove_problem_from_storage(ex.problem_id)
- logging.debug(ex)
return False
def _remove_problem_from_storage(self, problem):
@@ -262,35 +417,46 @@ class OopsWindow(Gtk.ApplicationWindow):
if pit:
try:
values = problem_to_storage_values(problem)
- for i in xrange(0, len(values)-1):
- self._builder.ls_problems.set_value(pit, i, values[i])
except errors.InvalidProblem as ex:
+ logging.debug("Exception: {0}".format(traceback.format_exc()))
self._remove_problem_from_storage(ex.problem_id)
- logging.debug(ex)
return
- try:
- if problem in self._get_selected(self._builder.tvs_problems):
- self._set_problem(problem)
- except errors.InvalidProblem as ex:
- self._remove_problem_from_storage(ex.problem_id)
- logging.debug(ex)
+ for i in xrange(0, len(values)-1):
+ self._builder.ls_problems.set_value(pit, i, values[i])
+
+ if problem in self._get_selected(self._builder.tvs_problems):
+ self._set_problem(problem)
def _reload_problems(self, source):
- self._reloading = True
+ # Try to load and prepare the list of selected problems before we
+ # clear the view. So, we can gracefully handle UnavailableSource
+ # exception. If the reloaded source is unavailable the old list
+ # of problems remains untouched.
+ storage_problems = []
+ if source is not None:
+ prblms = source.get_problems()
+ for p in prblms:
+ try:
+ storage_problems.append(problem_to_storage_values(p))
+ except errors.InvalidProblem:
+ logging.debug("Exception: {0}"
+ .format(traceback.format_exc()))
+
old_selection = self._get_selected(self._builder.tvs_problems)
- canselect = False
+
+ self._reloading = True
+ old_selection = None
try:
self._builder.ls_problems.clear()
- prblms = source.get_problems()
- # Can select a problem only if at least one problem was added to
- # the storage
- for p in prblms:
- canselect |= self._add_problem_to_storage(p)
+
+ if storage_problems:
+ for p in storage_problems:
+ self._builder.ls_problems.append(p)
finally:
self._reloading = False
- if canselect:
+ if storage_problems:
model = self._builder.tv_problems.get_model()
pit = None
if old_selection:
@@ -306,8 +472,22 @@ class OopsWindow(Gtk.ApplicationWindow):
self._set_problem(None)
def _select_problem_by_id(self, problem_id):
+ # The problem could come from a different source than the currently
+ # loaded source. If so, try to switch to problem's origin source and
+ # select the problem after that.
+ if (self._source is not None and
+ not problem_id in self._source.get_problems()):
+ for source in self._all_sources:
+ if problem_id in source.get_problems():
+ res, old_source = self._switch_source(source)
+ if res:
+ self._set_button_toggled(old_source.button, False)
+ self._set_button_toggled(source.button, True)
+ break
+
pit = self._find_problem_iter(problem_id,
self._builder.tv_problems.get_model())
+
if pit:
self._select_problem_iter(pit)
else:
@@ -358,58 +538,58 @@ class OopsWindow(Gtk.ApplicationWindow):
self._builder.vbx_problem_messages.pack_start(msg, False, True, 0)
+ @handle_problem_and_source_errors
def _set_problem(self, problem):
- try:
- self.selected_problem = problem
-
- sensitive_btn = problem is not None
- self._builder.btn_delete.set_sensitive(sensitive_btn)
- self._builder.btn_report.set_sensitive(
- sensitive_btn and not problem['not-reportable'] )
- self._builder.btn_detail.set_sensitive(sensitive_btn)
- self._builder.vbx_links.foreach(
- lambda w, u: w.destroy(), None)
- self._builder.vbx_problem_messages.foreach(
- lambda w, u: w.destroy(), None)
-
- if problem:
- self._builder.nb_problem_layout.set_current_page(0)
- app = problem['application']
- self._builder.lbl_summary.set_text(problem['reason'] or "")
- self._builder.lbl_app_name_value.set_text(app.name or _("N/A"))
- self._builder.lbl_reason.set_text("{0} {1}".format(
- app.name or _("N/A"), _(' crashed').strip()))
- self._builder.lbl_app_version_value.set_text(
- problem['package'] or "")
- self._builder.lbl_detected_value.set_text(
- problem['date'].strftime(
- locale.nl_langinfo(locale.D_FMT)))
-
- if app.icon:
- self._builder.img_app_icon.set_from_pixbuf(app.icon)
- else:
- self._builder.img_app_icon.clear()
-
- if problem['is_reported']:
- self._builder.lbl_reported_value.set_text(_('yes'))
- self._show_problem_links(problem['submission'])
- else:
- self._builder.lbl_reported_value.set_text(_('no'))
-
- if problem['not-reportable']:
- self._show_problem_message(problem['not-reportable'])
- elif (not problem['is_reported']
- or not any((s.name == "Bugzilla"
- for s in problem['submission']))):
- self._show_problem_message(
- _("This problem hasn't been reported to <i>Bugzilla</i> yet. "
- "Our developers may need more information to fix the problem.\n"
- "Please consider <b>reporting it</b> - you may help them. Thank you."))
+ self.selected_problem = problem
+
+ sensitive_btn = problem is not None
+ self._builder.btn_delete.set_sensitive(sensitive_btn)
+ self._builder.btn_report.set_sensitive(
+ sensitive_btn and not problem['not-reportable'] )
+ self._builder.btn_detail.set_sensitive(sensitive_btn)
+ self._builder.vbx_links.foreach(
+ lambda w, u: w.destroy(), None)
+ self._builder.vbx_problem_messages.foreach(
+ lambda w, u: w.destroy(), None)
+
+ if problem:
+ self._builder.nb_problem_layout.set_current_page(0)
+ app = problem['application']
+ self._builder.lbl_summary.set_text(problem['reason'] or "")
+ self._builder.lbl_app_name_value.set_text(app.name or _("N/A"))
+ self._builder.lbl_reason.set_text("{0} {1}".format(
+ app.name or _("N/A"), _(' crashed').strip()))
+ self._builder.lbl_app_version_value.set_text(
+ problem['package'] or "")
+ self._builder.lbl_detected_value.set_text(
+ problem['date'].strftime(
+ locale.nl_langinfo(locale.D_FMT)))
+
+ if app.icon:
+ self._builder.img_app_icon.set_from_pixbuf(app.icon)
+ else:
+ self._builder.img_app_icon.clear()
+
+ if problem['is_reported']:
+ self._builder.lbl_reported_value.set_text(_('yes'))
+ self._show_problem_links(problem['submission'])
else:
+ self._builder.lbl_reported_value.set_text(_('no'))
+
+ if problem['not-reportable']:
+ self._show_problem_message(problem['not-reportable'])
+ elif (not problem['is_reported']
+ or not any((s.name == "Bugzilla"
+ for s in problem['submission']))):
+ self._show_problem_message(
+_("This problem hasn't been reported to <i>Bugzilla</i> yet. "
+ "Our developers may need more information to fix the problem.\n"
+ "Please consider <b>reporting it</b> - you may help them. Thank you."))
+ else:
+ if self._source is not None:
self._builder.nb_problem_layout.set_current_page(1)
- except errors.InvalidProblem as ex:
- self._remove_problem_from_storage(ex.problem_id)
- logging.debug(ex)
+ else:
+ self._builder.nb_problem_layout.set_current_page(2)
def _get_selected(self, selection):
model, rows = selection.get_selected_rows()
@@ -434,38 +614,30 @@ class OopsWindow(Gtk.ApplicationWindow):
# Clear window because of empty list of problems!
self._set_problem(None)
+ @handle_problem_and_source_errors
def on_gac_delete_activate(self, action):
- try:
- for prblm in self._get_selected(self._builder.tvs_problems):
+ for prblm in self._get_selected(self._builder.tvs_problems):
+ try:
self._controller.delete(prblm)
- except errors.InvalidProblem as ex:
- self._remove_problem_from_storage(ex.problem_id)
- logging.debug(ex)
+ except errors.InvalidProblem as ex:
+ logging.debug(traceback.format_exc())
+ self._remove_problem_from_storage(ex.problem_id)
+ @handle_problem_and_source_errors
def on_gac_detail_activate(self, action):
- try:
- selected = self._get_selected(self._builder.tvs_problems)
- if selected:
- self._controller.detail(selected[0])
- except errors.InvalidProblem as ex:
- self._remove_problem_from_storage(ex.problem_id)
- logging.debug(ex)
+ selected = self._get_selected(self._builder.tvs_problems)
+ if selected:
+ self._controller.detail(selected[0])
+ @handle_problem_and_source_errors
def on_gac_report_activate(self, action):
- try:
- selected = self._get_selected(self._builder.tvs_problems)
- if selected and not selected[0]['not-reportable']:
- self._controller.report(selected[0])
- except errors.InvalidProblem as ex:
- self._remove_problem_from_storage(ex.problem_id)
- logging.debug(ex)
+ selected = self._get_selected(self._builder.tvs_problems)
+ if selected and not selected[0]['not-reportable']:
+ self._controller.report(selected[0])
+ @handle_problem_and_source_errors
def on_te_search_changed(self, entry):
- try:
- self._filter.set_pattern(entry.get_text())
- except errors.InvalidProblem as ex:
- self._remove_problem_from_storage(ex.problem_id)
- logging.debug(ex)
+ self._filter.set_pattern(entry.get_text())
def on_gac_opt_all_problems_activate(self, action):
conf = config.get_configuration()
--
1.8.2.1
10 years, 10 months