From 83a6ce9ad4b1828e163dc7172ef603201b748473 Mon Sep 17 00:00:00 2001 From: Nikola Pajkovsky Date: Tue, 10 Aug 2010 10:21:25 +0200 Subject: lower case direcotry(no code changed) Signed-off-by: Nikola Pajkovsky --- src/Applet/Applet.cpp | 302 -------- src/Applet/CCApplet.cpp | 456 ------------ src/Applet/CCApplet.h | 88 --- src/Applet/Makefile.am | 43 -- src/Applet/abrt-applet.desktop.in | 9 - src/CLI/CLI.cpp | 436 ------------ src/CLI/Makefile.am | 28 - src/CLI/abrt-cli.1 | 58 -- src/CLI/abrt-cli.bash | 50 -- src/CLI/dbus.cpp | 282 -------- src/CLI/dbus.h | 68 -- src/CLI/report.cpp | 743 -------------------- src/CLI/report.h | 28 - src/CLI/run-command.cpp | 73 -- src/CLI/run-command.h | 23 - src/Daemon/CommLayerServer.cpp | 29 - src/Daemon/CommLayerServer.h | 41 -- src/Daemon/CommLayerServerDBus.cpp | 624 ---------------- src/Daemon/CommLayerServerDBus.h | 42 -- src/Daemon/CrashWatcher.cpp | 236 ------- src/Daemon/CrashWatcher.h | 52 -- src/Daemon/Daemon.cpp | 1000 -------------------------- src/Daemon/Daemon.h | 36 - src/Daemon/Makefile.am | 65 -- src/Daemon/MiddleWare.cpp | 1132 ------------------------------ src/Daemon/MiddleWare.h | 158 ----- src/Daemon/PluginManager.cpp | 463 ------------ src/Daemon/PluginManager.h | 157 ----- src/Daemon/Settings.cpp | 562 --------------- src/Daemon/Settings.h | 45 -- src/Daemon/abrt-debuginfo-install | 418 ----------- src/Daemon/abrt-handle-upload | 74 -- src/Daemon/abrt.conf | 61 -- src/Daemon/abrt.conf.5 | 95 --- src/Daemon/abrtd.8 | 43 -- src/Daemon/abrtd.service | 11 - src/Daemon/com.redhat.abrt.service | 7 - src/Daemon/dbus-abrt.conf | 25 - src/Daemon/dumpsocket.cpp | 573 --------------- src/Daemon/dumpsocket.h | 81 --- src/Daemon/gpg_keys | 1 - src/Daemon/org.fedoraproject.abrt.policy | 38 - src/Daemon/rpm.c | 244 ------- src/Daemon/rpm.h | 90 --- src/Gui/ABRTExceptions.py | 16 - src/Gui/ABRTPlugin.py | 107 --- src/Gui/CCDBusBackend.py | 231 ------ src/Gui/CCDump.py | 152 ---- src/Gui/CCDumpList.py | 50 -- src/Gui/CCMainWindow.py | 457 ------------ src/Gui/CCReporterDialog.py | 578 --------------- src/Gui/CC_gui_functions.py | 253 ------- src/Gui/CReporterAssistant.py | 894 ----------------------- src/Gui/CellRenderers.py | 65 -- src/Gui/ConfBackend.py | 246 ------- src/Gui/Makefile.am | 33 - src/Gui/PluginList.py | 69 -- src/Gui/PluginSettingsUI.py | 91 --- src/Gui/PluginsSettingsDialog.py | 195 ----- src/Gui/SettingsDialog.py | 242 ------- src/Gui/abrt-gui | 8 - src/Gui/abrt.desktop.in | 11 - src/Gui/abrt.png | Bin 2791 -> 0 bytes src/Gui/abrt_utils.py | 46 -- src/Gui/ccgui.glade | 697 ------------------ src/Gui/dialogs.glade | 139 ---- src/Gui/progress_window.glade | 70 -- src/Gui/report.glade | 827 ---------------------- src/Gui/settings.glade | 855 ---------------------- src/Gui/settings_wizard.glade | 129 ---- src/Hooks/Makefile.am | 44 -- src/Hooks/abrt-hook-ccpp.cpp | 553 --------------- src/Hooks/abrt.pth | 1 - src/Hooks/abrt_exception_handler.py.in | 167 ----- src/Hooks/dumpoops.cpp | 125 ---- src/Makefile.am | 2 +- src/applet/Applet.cpp | 302 ++++++++ src/applet/CCApplet.cpp | 456 ++++++++++++ src/applet/CCApplet.h | 88 +++ src/applet/Makefile.am | 43 ++ src/applet/abrt-applet.desktop.in | 9 + src/cli/CLI.cpp | 436 ++++++++++++ src/cli/Makefile.am | 28 + src/cli/abrt-cli.1 | 58 ++ src/cli/abrt-cli.bash | 50 ++ src/cli/dbus.cpp | 282 ++++++++ src/cli/dbus.h | 68 ++ src/cli/report.cpp | 743 ++++++++++++++++++++ src/cli/report.h | 28 + src/cli/run-command.cpp | 73 ++ src/cli/run-command.h | 23 + src/daemon/CommLayerServer.cpp | 29 + src/daemon/CommLayerServer.h | 41 ++ src/daemon/CommLayerServerDBus.cpp | 624 ++++++++++++++++ src/daemon/CommLayerServerDBus.h | 42 ++ src/daemon/CrashWatcher.cpp | 236 +++++++ src/daemon/CrashWatcher.h | 52 ++ src/daemon/Daemon.cpp | 1000 ++++++++++++++++++++++++++ src/daemon/Daemon.h | 36 + src/daemon/Makefile.am | 65 ++ src/daemon/MiddleWare.cpp | 1132 ++++++++++++++++++++++++++++++ src/daemon/MiddleWare.h | 158 +++++ src/daemon/PluginManager.cpp | 463 ++++++++++++ src/daemon/PluginManager.h | 157 +++++ src/daemon/Settings.cpp | 562 +++++++++++++++ src/daemon/Settings.h | 45 ++ src/daemon/abrt-debuginfo-install | 418 +++++++++++ src/daemon/abrt-handle-upload | 74 ++ src/daemon/abrt.conf | 61 ++ src/daemon/abrt.conf.5 | 95 +++ src/daemon/abrtd.8 | 43 ++ src/daemon/abrtd.service | 11 + src/daemon/com.redhat.abrt.service | 7 + src/daemon/dbus-abrt.conf | 25 + src/daemon/dumpsocket.cpp | 573 +++++++++++++++ src/daemon/dumpsocket.h | 81 +++ src/daemon/gpg_keys | 1 + src/daemon/org.fedoraproject.abrt.policy | 38 + src/daemon/rpm.c | 244 +++++++ src/daemon/rpm.h | 90 +++ src/gui/ABRTExceptions.py | 16 + src/gui/ABRTPlugin.py | 107 +++ src/gui/CCDBusBackend.py | 231 ++++++ src/gui/CCDump.py | 152 ++++ src/gui/CCDumpList.py | 50 ++ src/gui/CCMainWindow.py | 457 ++++++++++++ src/gui/CCReporterDialog.py | 578 +++++++++++++++ src/gui/CC_gui_functions.py | 253 +++++++ src/gui/CReporterAssistant.py | 894 +++++++++++++++++++++++ src/gui/CellRenderers.py | 65 ++ src/gui/ConfBackend.py | 246 +++++++ src/gui/Makefile.am | 33 + src/gui/PluginList.py | 69 ++ src/gui/PluginSettingsUI.py | 91 +++ src/gui/PluginsSettingsDialog.py | 195 +++++ src/gui/SettingsDialog.py | 242 +++++++ src/gui/abrt-gui | 8 + src/gui/abrt.desktop.in | 11 + src/gui/abrt.png | Bin 0 -> 2791 bytes src/gui/abrt_utils.py | 46 ++ src/gui/ccgui.glade | 697 ++++++++++++++++++ src/gui/dialogs.glade | 139 ++++ src/gui/progress_window.glade | 70 ++ src/gui/report.glade | 827 ++++++++++++++++++++++ src/gui/settings.glade | 855 ++++++++++++++++++++++ src/gui/settings_wizard.glade | 129 ++++ src/hooks/Makefile.am | 44 ++ src/hooks/abrt-hook-ccpp.cpp | 553 +++++++++++++++ src/hooks/abrt.pth | 1 + src/hooks/abrt_exception_handler.py.in | 167 +++++ src/hooks/dumpoops.cpp | 125 ++++ src/utils/Makefile.am | 4 +- 152 files changed, 16444 insertions(+), 16444 deletions(-) delete mode 100644 src/Applet/Applet.cpp delete mode 100644 src/Applet/CCApplet.cpp delete mode 100644 src/Applet/CCApplet.h delete mode 100644 src/Applet/Makefile.am delete mode 100644 src/Applet/abrt-applet.desktop.in delete mode 100644 src/CLI/CLI.cpp delete mode 100644 src/CLI/Makefile.am delete mode 100644 src/CLI/abrt-cli.1 delete mode 100644 src/CLI/abrt-cli.bash delete mode 100644 src/CLI/dbus.cpp delete mode 100644 src/CLI/dbus.h delete mode 100644 src/CLI/report.cpp delete mode 100644 src/CLI/report.h delete mode 100644 src/CLI/run-command.cpp delete mode 100644 src/CLI/run-command.h delete mode 100644 src/Daemon/CommLayerServer.cpp delete mode 100644 src/Daemon/CommLayerServer.h delete mode 100644 src/Daemon/CommLayerServerDBus.cpp delete mode 100644 src/Daemon/CommLayerServerDBus.h delete mode 100644 src/Daemon/CrashWatcher.cpp delete mode 100644 src/Daemon/CrashWatcher.h delete mode 100644 src/Daemon/Daemon.cpp delete mode 100644 src/Daemon/Daemon.h delete mode 100644 src/Daemon/Makefile.am delete mode 100644 src/Daemon/MiddleWare.cpp delete mode 100644 src/Daemon/MiddleWare.h delete mode 100644 src/Daemon/PluginManager.cpp delete mode 100644 src/Daemon/PluginManager.h delete mode 100644 src/Daemon/Settings.cpp delete mode 100644 src/Daemon/Settings.h delete mode 100755 src/Daemon/abrt-debuginfo-install delete mode 100755 src/Daemon/abrt-handle-upload delete mode 100644 src/Daemon/abrt.conf delete mode 100644 src/Daemon/abrt.conf.5 delete mode 100644 src/Daemon/abrtd.8 delete mode 100644 src/Daemon/abrtd.service delete mode 100644 src/Daemon/com.redhat.abrt.service delete mode 100644 src/Daemon/dbus-abrt.conf delete mode 100644 src/Daemon/dumpsocket.cpp delete mode 100644 src/Daemon/dumpsocket.h delete mode 100644 src/Daemon/gpg_keys delete mode 100644 src/Daemon/org.fedoraproject.abrt.policy delete mode 100644 src/Daemon/rpm.c delete mode 100644 src/Daemon/rpm.h delete mode 100644 src/Gui/ABRTExceptions.py delete mode 100644 src/Gui/ABRTPlugin.py delete mode 100644 src/Gui/CCDBusBackend.py delete mode 100644 src/Gui/CCDump.py delete mode 100644 src/Gui/CCDumpList.py delete mode 100644 src/Gui/CCMainWindow.py delete mode 100644 src/Gui/CCReporterDialog.py delete mode 100644 src/Gui/CC_gui_functions.py delete mode 100644 src/Gui/CReporterAssistant.py delete mode 100644 src/Gui/CellRenderers.py delete mode 100644 src/Gui/ConfBackend.py delete mode 100644 src/Gui/Makefile.am delete mode 100644 src/Gui/PluginList.py delete mode 100644 src/Gui/PluginSettingsUI.py delete mode 100644 src/Gui/PluginsSettingsDialog.py delete mode 100644 src/Gui/SettingsDialog.py delete mode 100755 src/Gui/abrt-gui delete mode 100644 src/Gui/abrt.desktop.in delete mode 100644 src/Gui/abrt.png delete mode 100644 src/Gui/abrt_utils.py delete mode 100644 src/Gui/ccgui.glade delete mode 100644 src/Gui/dialogs.glade delete mode 100644 src/Gui/progress_window.glade delete mode 100644 src/Gui/report.glade delete mode 100644 src/Gui/settings.glade delete mode 100644 src/Gui/settings_wizard.glade delete mode 100644 src/Hooks/Makefile.am delete mode 100644 src/Hooks/abrt-hook-ccpp.cpp delete mode 100644 src/Hooks/abrt.pth delete mode 100644 src/Hooks/abrt_exception_handler.py.in delete mode 100644 src/Hooks/dumpoops.cpp create mode 100644 src/applet/Applet.cpp create mode 100644 src/applet/CCApplet.cpp create mode 100644 src/applet/CCApplet.h create mode 100644 src/applet/Makefile.am create mode 100644 src/applet/abrt-applet.desktop.in create mode 100644 src/cli/CLI.cpp create mode 100644 src/cli/Makefile.am create mode 100644 src/cli/abrt-cli.1 create mode 100644 src/cli/abrt-cli.bash create mode 100644 src/cli/dbus.cpp create mode 100644 src/cli/dbus.h create mode 100644 src/cli/report.cpp create mode 100644 src/cli/report.h create mode 100644 src/cli/run-command.cpp create mode 100644 src/cli/run-command.h create mode 100644 src/daemon/CommLayerServer.cpp create mode 100644 src/daemon/CommLayerServer.h create mode 100644 src/daemon/CommLayerServerDBus.cpp create mode 100644 src/daemon/CommLayerServerDBus.h create mode 100644 src/daemon/CrashWatcher.cpp create mode 100644 src/daemon/CrashWatcher.h create mode 100644 src/daemon/Daemon.cpp create mode 100644 src/daemon/Daemon.h create mode 100644 src/daemon/Makefile.am create mode 100644 src/daemon/MiddleWare.cpp create mode 100644 src/daemon/MiddleWare.h create mode 100644 src/daemon/PluginManager.cpp create mode 100644 src/daemon/PluginManager.h create mode 100644 src/daemon/Settings.cpp create mode 100644 src/daemon/Settings.h create mode 100755 src/daemon/abrt-debuginfo-install create mode 100755 src/daemon/abrt-handle-upload create mode 100644 src/daemon/abrt.conf create mode 100644 src/daemon/abrt.conf.5 create mode 100644 src/daemon/abrtd.8 create mode 100644 src/daemon/abrtd.service create mode 100644 src/daemon/com.redhat.abrt.service create mode 100644 src/daemon/dbus-abrt.conf create mode 100644 src/daemon/dumpsocket.cpp create mode 100644 src/daemon/dumpsocket.h create mode 100644 src/daemon/gpg_keys create mode 100644 src/daemon/org.fedoraproject.abrt.policy create mode 100644 src/daemon/rpm.c create mode 100644 src/daemon/rpm.h create mode 100644 src/gui/ABRTExceptions.py create mode 100644 src/gui/ABRTPlugin.py create mode 100644 src/gui/CCDBusBackend.py create mode 100644 src/gui/CCDump.py create mode 100644 src/gui/CCDumpList.py create mode 100644 src/gui/CCMainWindow.py create mode 100644 src/gui/CCReporterDialog.py create mode 100644 src/gui/CC_gui_functions.py create mode 100644 src/gui/CReporterAssistant.py create mode 100644 src/gui/CellRenderers.py create mode 100644 src/gui/ConfBackend.py create mode 100644 src/gui/Makefile.am create mode 100644 src/gui/PluginList.py create mode 100644 src/gui/PluginSettingsUI.py create mode 100644 src/gui/PluginsSettingsDialog.py create mode 100644 src/gui/SettingsDialog.py create mode 100755 src/gui/abrt-gui create mode 100644 src/gui/abrt.desktop.in create mode 100644 src/gui/abrt.png create mode 100644 src/gui/abrt_utils.py create mode 100644 src/gui/ccgui.glade create mode 100644 src/gui/dialogs.glade create mode 100644 src/gui/progress_window.glade create mode 100644 src/gui/report.glade create mode 100644 src/gui/settings.glade create mode 100644 src/gui/settings_wizard.glade create mode 100644 src/hooks/Makefile.am create mode 100644 src/hooks/abrt-hook-ccpp.cpp create mode 100644 src/hooks/abrt.pth create mode 100644 src/hooks/abrt_exception_handler.py.in create mode 100644 src/hooks/dumpoops.cpp (limited to 'src') diff --git a/src/Applet/Applet.cpp b/src/Applet/Applet.cpp deleted file mode 100644 index 85ee0db8..00000000 --- a/src/Applet/Applet.cpp +++ /dev/null @@ -1,302 +0,0 @@ -/* - Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#include -#include -#include -#if HAVE_CONFIG_H -# include -#endif -#if HAVE_LOCALE_H -# include -#endif -#if ENABLE_NLS -# include -# define _(S) gettext(S) -#else -# define _(S) (S) -#endif -#include "abrtlib.h" -#include "abrt_dbus.h" -#include "dbus_common.h" -#include "CCApplet.h" - - -static CApplet* applet; - - -static void Crash(DBusMessage* signal) -{ - int r; - DBusMessageIter in_iter; - dbus_message_iter_init(signal, &in_iter); - - /* 1st param: package */ - const char* package_name; - r = load_val(&in_iter, package_name); - - /* 2nd param: crash_id */ - const char* crash_id = NULL; - if (r != ABRT_DBUS_MORE_FIELDS) - { - error_msg("dbus signal %s: parameter type mismatch", __func__); - return; - } - r = load_val(&in_iter, crash_id); - - /* Optional 3rd param: uid */ - const char* uid_str = NULL; - if (r == ABRT_DBUS_MORE_FIELDS) - { - r = load_val(&in_iter, uid_str); - } - if (r != ABRT_DBUS_LAST_FIELD) - { - error_msg("dbus signal %s: parameter type mismatch", __func__); - return; - } - - if (uid_str != NULL) - { - char *end; - errno = 0; - unsigned long uid_num = strtoul(uid_str, &end, 10); - if (errno || *end != '\0' || uid_num != getuid()) - { - return; - } - } - - const char* message = _("A crash in the %s package has been detected"); - if (package_name[0] == '\0') - message = _("A crash has been detected"); - //applet->AddEvent(uid, package_name); - applet->SetIconTooltip(message, package_name); - applet->ShowIcon(); - - /* If this crash seems to be repeating, do not annoy user with popup dialog. - * (The icon in the tray is not suppressed) - */ - static time_t last_time = 0; - static char* last_package_name = NULL; - static char* last_crash_id = NULL; - time_t cur_time = time(NULL); - if (last_package_name && strcmp(last_package_name, package_name) == 0 - && last_crash_id && strcmp(last_crash_id, crash_id) == 0 - && (unsigned)(cur_time - last_time) < 2 * 60 * 60 - ) { - log_msg("repeated crash in %s, not showing the notification", package_name); - return; - } - last_time = cur_time; - free(last_package_name); - last_package_name = xstrdup(package_name); - free(last_crash_id); - last_crash_id = xstrdup(crash_id); - - applet->CrashNotify(crash_id, message, package_name); -} - -static void QuotaExceed(DBusMessage* signal) -{ - int r; - DBusMessageIter in_iter; - dbus_message_iter_init(signal, &in_iter); - const char* str; - r = load_val(&in_iter, str); - if (r != ABRT_DBUS_LAST_FIELD) - { - error_msg("dbus signal %s: parameter type mismatch", __func__); - return; - } - - //if (m_pSessionDBus->has_name("com.redhat.abrt.gui")) - // return; - applet->ShowIcon(); - applet->MessageNotify("%s", str); -} - -static void NameOwnerChanged(DBusMessage* signal) -{ - int r; - DBusMessageIter in_iter; - dbus_message_iter_init(signal, &in_iter); - const char* name; - r = load_val(&in_iter, name); - if (r != ABRT_DBUS_MORE_FIELDS) - { - error_msg("dbus signal %s: parameter type mismatch", __func__); - return; - } - - /* We are only interested in (dis)appearances of our daemon */ - if (strcmp(name, "com.redhat.abrt") != 0) - return; - - const char* old_owner; - r = load_val(&in_iter, old_owner); - if (r != ABRT_DBUS_MORE_FIELDS) - { - error_msg("dbus signal %s: parameter type mismatch", __func__); - return; - } - const char* new_owner; - r = load_val(&in_iter, new_owner); - if (r != ABRT_DBUS_LAST_FIELD) - { - error_msg("dbus signal %s: parameter type mismatch", __func__); - return; - } - -// hide icon if it's visible - as NM and don't show it, if it's not - if (!new_owner[0]) - applet->HideIcon(); -} - -static DBusHandlerResult handle_message(DBusConnection* conn, DBusMessage* msg, void* user_data) -{ - const char* member = dbus_message_get_member(msg); - - VERB1 log("%s(member:'%s')", __func__, member); - - int type = dbus_message_get_type(msg); - if (type != DBUS_MESSAGE_TYPE_SIGNAL) - { - log("The message is not a signal. ignoring"); - return DBUS_HANDLER_RESULT_HANDLED; - } - - if (strcmp(member, "NameOwnerChanged") == 0) - NameOwnerChanged(msg); - else if (strcmp(member, "Crash") == 0) - Crash(msg); - else if (strcmp(member, "QuotaExceed") == 0) - QuotaExceed(msg); - - return DBUS_HANDLER_RESULT_HANDLED; -} - -//TODO: move to abrt_dbus.cpp -static void die_if_dbus_error(bool error_flag, DBusError* err, const char* msg) -{ - if (dbus_error_is_set(err)) - { - error_msg("dbus error: %s", err->message); - /*dbus_error_free(&err); - why bother, we will exit in a microsecond */ - error_flag = true; - } - if (!error_flag) - return; - error_msg_and_die("%s", msg); -} - -int main(int argc, char** argv) -{ - const char * app_name = "abrt-gui"; - /* I18n */ - setlocale(LC_ALL, ""); -#if ENABLE_NLS - bindtextdomain(PACKAGE, LOCALEDIR); - textdomain(PACKAGE); -#endif - - /* Need to be thread safe */ - g_thread_init(NULL); - gdk_threads_init(); - gdk_threads_enter(); - - /* Parse options */ - int opt; - while ((opt = getopt(argc, argv, "dv")) != -1) - { - switch (opt) - { - case 'v': - g_verbose++; - break; - default: - error_msg_and_die( - "Usage: abrt-applet [-v]\n" - "\nOptions:" - "\n\t-v\tVerbose" - ); - } - } - gtk_init(&argc, &argv); - - /* Prevent zombies when we spawn abrt-gui */ - signal(SIGCHLD, SIG_IGN); - - /* Initialize our (dbus_abrt) machinery: hook _system_ dbus to glib main loop. - * (session bus is left to be handled by libnotify, see below) */ - DBusError err; - dbus_error_init(&err); - DBusConnection* system_conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); - die_if_dbus_error(system_conn == NULL, &err, "Can't connect to system dbus"); - attach_dbus_conn_to_glib_main_loop(system_conn); - if (!dbus_connection_add_filter(system_conn, handle_message, NULL, NULL)) - error_msg_and_die("Can't add dbus filter"); - /* which messages do we want to be fed to handle_message()? */ - //signal sender=org.freedesktop.DBus -> path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged - // string "com.redhat.abrt" - // string "" - // string ":1.70" - dbus_bus_add_match(system_conn, "type='signal',member='NameOwnerChanged'", &err); - die_if_dbus_error(false, &err, "Can't add dbus match"); - //signal sender=:1.73 -> path=/com/redhat/abrt; interface=com.redhat.abrt; member=Crash - // string "coreutils-7.2-3.fc11" - // string "0" - dbus_bus_add_match(system_conn, "type='signal',path='/com/redhat/abrt'", &err); - die_if_dbus_error(false, &err, "Can't add dbus match"); - - /* Initialize GUI stuff. - * Note: inside CApplet ctor, libnotify hooks session dbus - * to glib main loop */ - applet = new CApplet(app_name); - /* dbus_abrt cannot handle more than one bus, and we don't really need to. - * The only thing we want to do is to announce ourself on session dbus */ - DBusConnection* session_conn = dbus_bus_get(DBUS_BUS_SESSION, &err); - die_if_dbus_error(session_conn == NULL, &err, "Can't connect to session dbus"); - int r = dbus_bus_request_name(session_conn, - "com.redhat.abrt.applet", - /* flags */ DBUS_NAME_FLAG_DO_NOT_QUEUE, &err); - die_if_dbus_error(r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER, &err, - "Problem connecting to dbus, or applet is already running"); - - /* show the warning in terminal, as nm-applet does */ - if (!dbus_bus_name_has_owner(system_conn, ABRTD_DBUS_NAME, &err)) - { - const char* msg = _("ABRT service is not running"); - puts(msg); - } - - /* dbus_bus_request_name can already read some data. Thus while dbus fd hasn't - * any data anymore, dbus library can buffer a message or two. - * If we don't do this, the data won't be processed until next dbus data arrives. - */ - int cnt = 10; - while (dbus_connection_dispatch(system_conn) != DBUS_DISPATCH_COMPLETE && --cnt) - continue; - - /* Enter main loop */ - gtk_main(); - - gdk_threads_leave(); - delete applet; - return 0; -} diff --git a/src/Applet/CCApplet.cpp b/src/Applet/CCApplet.cpp deleted file mode 100644 index 5349c7a5..00000000 --- a/src/Applet/CCApplet.cpp +++ /dev/null @@ -1,456 +0,0 @@ -/* - Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#if HAVE_CONFIG_H -# include -#endif -#if ENABLE_NLS -# include -# define _(S) gettext(S) -#else -# define _(S) (S) -#endif -#include "abrtlib.h" -#include "CCApplet.h" - -static void on_notify_close(NotifyNotification *notification, gpointer user_data) -{ - g_object_unref(notification); -} - -static NotifyNotification *new_warn_notification() -{ - NotifyNotification *notification; - notification = notify_notification_new(_("Warning"), NULL, NULL, NULL); - g_signal_connect(notification, "closed", G_CALLBACK(on_notify_close), NULL); - - GdkPixbuf *pixbuf = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), - GTK_STOCK_DIALOG_WARNING, 48, GTK_ICON_LOOKUP_USE_BUILTIN, NULL); - - if (pixbuf) - notify_notification_set_icon_from_pixbuf(notification, pixbuf); - notify_notification_set_urgency(notification, NOTIFY_URGENCY_NORMAL); - notify_notification_set_timeout(notification, NOTIFY_EXPIRES_DEFAULT); - - return notification; -} - - -static void on_hide_cb(GtkMenuItem *menuitem, gpointer applet) -{ - if (applet) - ((CApplet*)applet)->HideIcon(); -} - -static void on_about_cb(GtkMenuItem *menuitem, gpointer dialog) -{ - if (dialog) - { - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_hide(GTK_WIDGET(dialog)); - } -} - -static GtkWidget *create_about_dialog() -{ - const char *copyright_str = "Copyright © 2009 Red Hat, Inc\nCopyright © 2010 Red Hat, Inc"; - const char *license_str = "This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version." - "\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details." - "\n\nYou should have received a copy of the GNU General Public License along with this program. If not, see ."; - - const char *website_url = "https://fedorahosted.org/abrt/"; - const char *authors[] = {"Anton Arapov ", - "Karel Klic ", - "Jiri Moskovcak ", - "Nikola Pajkovsky ", - "Zdenek Prikryl ", - "Denys Vlasenko ", - NULL}; - - const char *artists[] = {"Patrick Connelly ", - "Lapo Calamandrei", - NULL}; - - const char *comments = _("Notification area applet that notifies users about " - "issues detected by ABRT"); - GtkWidget *about_d = gtk_about_dialog_new(); - if (about_d) - { - gtk_window_set_default_icon_name("abrt"); - gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(about_d), VERSION); - gtk_about_dialog_set_logo_icon_name(GTK_ABOUT_DIALOG(about_d), "abrt"); - gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(about_d), comments); - gtk_about_dialog_set_program_name(GTK_ABOUT_DIALOG(about_d), "ABRT"); - gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(about_d), copyright_str); - gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(about_d), license_str); - gtk_about_dialog_set_wrap_license(GTK_ABOUT_DIALOG(about_d),true); - gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(about_d), website_url); - gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(about_d), authors); - gtk_about_dialog_set_artists(GTK_ABOUT_DIALOG(about_d), artists); - gtk_about_dialog_set_translator_credits(GTK_ABOUT_DIALOG(about_d), _("translator-credits")); - } - return about_d; -} - -static GtkWidget *create_menu(CApplet *applet) -{ - GtkWidget *menu = gtk_menu_new(); - GtkWidget *b_quit = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL); - g_signal_connect(b_quit, "activate", gtk_main_quit, NULL); - GtkWidget *b_hide = gtk_menu_item_new_with_label(_("Hide")); - g_signal_connect(b_hide, "activate", G_CALLBACK(on_hide_cb), applet); - GtkWidget *b_about = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, NULL); - GtkWidget *about_dialog = create_about_dialog(); - g_signal_connect(b_about, "activate", G_CALLBACK(on_about_cb), about_dialog); - GtkWidget *separator = gtk_separator_menu_item_new(); - if (menu) - { - gtk_menu_shell_append(GTK_MENU_SHELL(menu),b_hide); - gtk_widget_show(b_hide); - gtk_menu_shell_append(GTK_MENU_SHELL(menu),b_about); - gtk_widget_show(b_about); - gtk_menu_shell_append(GTK_MENU_SHELL(menu),separator); - gtk_widget_show(separator); - gtk_menu_shell_append(GTK_MENU_SHELL(menu),b_quit); - gtk_widget_show(b_quit); - } - return menu; -} - -CApplet::CApplet(const char* app_name) -{ - m_bDaemonRunning = true; - /* set-up icon buffers */ - m_iAnimator = 0; - m_iAnimationStage = ICON_DEFAULT; - m_bIconsLoaded = load_icons(); - /* - animation - */ - if (m_bIconsLoaded == true) - { - //FIXME: animation is disabled for now - m_pStatusIcon = gtk_status_icon_new_from_pixbuf(icon_stages_buff[ICON_DEFAULT]); - } - else - { - m_pStatusIcon = gtk_status_icon_new_from_icon_name("abrt"); - } - notify_init(app_name); - - gtk_status_icon_set_visible(m_pStatusIcon, FALSE); - - g_signal_connect(G_OBJECT(m_pStatusIcon), "activate", GTK_SIGNAL_FUNC(CApplet::OnAppletActivate_CB), this); - g_signal_connect(G_OBJECT(m_pStatusIcon), "popup_menu", GTK_SIGNAL_FUNC(CApplet::OnMenuPopup_cb), this); - -// SetIconTooltip(_("Pending events: %i"), m_mapEvents.size()); - - m_pMenu = create_menu(this); -} - -CApplet::~CApplet() -{ - if (notify_is_initted()) - notify_uninit(); -} - -void CApplet::SetIconTooltip(const char *format, ...) -{ - va_list args; - int n; - char *buf; - - va_start(args, format); - buf = NULL; - n = vasprintf(&buf, format, args); - va_end(args); - - gtk_status_icon_set_tooltip_text(m_pStatusIcon, (n >= 0 && buf) ? buf : ""); - free(buf); -} - -void CApplet::action_report(NotifyNotification *notification, gchar *action, gpointer user_data) -{ - CApplet *applet = (CApplet *)user_data; - if (applet->m_bDaemonRunning) - { - pid_t pid = vfork(); - if (pid < 0) - perror_msg("vfork"); - if (pid == 0) - { /* child */ - char *buf = xasprintf("--report=%s", applet->m_pLastCrashID); - signal(SIGCHLD, SIG_DFL); /* undo SIG_IGN in abrt-applet */ - execl(BIN_DIR"/abrt-gui", "abrt-gui", buf, (char*) NULL); - /* Did not find abrt-gui in installation directory. Oh well */ - /* Trying to find it in PATH */ - execlp("abrt-gui", "abrt-gui", buf, (char*) NULL); - perror_msg_and_die("Can't execute abrt-gui"); - } - GError *err = NULL; - notify_notification_close(notification, &err); - if (err != NULL) - { - error_msg("%s", err->message); - g_error_free(err); - } - gtk_status_icon_set_visible(applet->m_pStatusIcon, false); - applet->stop_animate_icon(); - } -} - -void CApplet::action_open_gui(NotifyNotification *notification, gchar *action, gpointer user_data) -{ - CApplet *applet = (CApplet *)user_data; - if (applet->m_bDaemonRunning) - { - pid_t pid = vfork(); - if (pid < 0) - perror_msg("vfork"); - if (pid == 0) - { /* child */ - signal(SIGCHLD, SIG_DFL); /* undo SIG_IGN in abrt-applet */ - execl(BIN_DIR"/abrt-gui", "abrt-gui", (char*) NULL); - /* Did not find abrt-gui in installation directory. Oh well */ - /* Trying to find it in PATH */ - execlp("abrt-gui", "abrt-gui", (char*) NULL); - perror_msg_and_die("Can't execute abrt-gui"); - } - GError *err = NULL; - notify_notification_close(notification, &err); - if (err != NULL) - { - error_msg("%s", err->message); - g_error_free(err); - } - gtk_status_icon_set_visible(applet->m_pStatusIcon, false); - applet->stop_animate_icon(); - } -} - -void CApplet::CrashNotify(const char* crash_id, const char *format, ...) -{ - m_pLastCrashID = crash_id; - va_list args; - va_start(args, format); - char *buf = xvasprintf(format, args); - va_end(args); - - NotifyNotification *notification = new_warn_notification(); - notify_notification_add_action(notification, "REPORT", _("Report"), - NOTIFY_ACTION_CALLBACK(CApplet::action_report), - this, NULL); - notify_notification_add_action(notification, "OPEN_MAIN_WINDOW", _("Open ABRT"), - NOTIFY_ACTION_CALLBACK(CApplet::action_open_gui), - this, NULL); - - notify_notification_update(notification, _("Warning"), buf, NULL); - free(buf); - GError *err = NULL; - notify_notification_show(notification, &err); - if (err != NULL) - { - error_msg("%s", err->message); - g_error_free(err); - } -} - -void CApplet::MessageNotify(const char *format, ...) -{ - va_list args; - - va_start(args, format); - char *buf = xvasprintf(format, args); - va_end(args); - - /* we don't want to show any buttons now, - maybe later we can add action binded to message - like >>Clear old dumps<< for quota exceeded - */ - NotifyNotification *notification = new_warn_notification(); - notify_notification_add_action(notification, "OPEN_MAIN_WINDOW", _("Open ABRT"), - NOTIFY_ACTION_CALLBACK(CApplet::action_open_gui), - this, NULL); - notify_notification_update(notification, _("Warning"), buf, NULL); - free(buf); - GError *err = NULL; - notify_notification_show(notification, &err); - if (err != NULL) - { - error_msg("%s", err->message); - g_error_free(err); - } -} - -void CApplet::OnAppletActivate_CB(GtkStatusIcon *status_icon, gpointer user_data) -{ - CApplet *applet = (CApplet *)user_data; - if (applet->m_bDaemonRunning) - { - pid_t pid = vfork(); - if (pid < 0) - perror_msg("vfork"); - if (pid == 0) - { /* child */ - signal(SIGCHLD, SIG_DFL); /* undo SIG_IGN in abrt-applet */ - execl(BIN_DIR"/abrt-gui", "abrt-gui", (char*) NULL); - /* Did not find abrt-gui in installation directory. Oh well */ - /* Trying to find it in PATH */ - execlp("abrt-gui", "abrt-gui", (char*) NULL); - perror_msg_and_die("Can't execute abrt-gui"); - } - gtk_status_icon_set_visible(applet->m_pStatusIcon, false); - applet->stop_animate_icon(); - } -} - -void CApplet::OnMenuPopup_cb(GtkStatusIcon *status_icon, - guint button, - guint activate_time, - gpointer user_data) -{ - CApplet *applet = (CApplet *)user_data; - /* stop the animation */ - applet->stop_animate_icon(); - - if (applet->m_pMenu != NULL) - { - gtk_menu_popup(GTK_MENU(((CApplet *)user_data)->m_pMenu), - NULL, NULL, - gtk_status_icon_position_menu, - status_icon, button, activate_time); - } -} - -void CApplet::ShowIcon() -{ - gtk_status_icon_set_visible(m_pStatusIcon, true); - /* only animate if all icons are loaded, use the "gtk-warning" instead */ - if (m_bIconsLoaded) - animate_icon(); -} - -void CApplet::HideIcon() -{ - gtk_status_icon_set_visible(m_pStatusIcon, false); - stop_animate_icon(); -} - -void CApplet::Disable(const char *reason) -{ - /* - FIXME: once we have our icon - */ - m_bDaemonRunning = false; - GdkPixbuf *gray_scaled; - GdkPixbuf *pixbuf = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), - GTK_STOCK_DIALOG_WARNING, 24, GTK_ICON_LOOKUP_USE_BUILTIN, NULL); - if (pixbuf) - { - gray_scaled = gdk_pixbuf_copy(pixbuf); - gdk_pixbuf_saturate_and_pixelate(pixbuf, gray_scaled, 0.0, false); - gtk_status_icon_set_from_pixbuf(m_pStatusIcon, gray_scaled); -//do we need to free pixbufs nere? - } - else - error_msg("Can't load icon"); - SetIconTooltip(reason); - ShowIcon(); -} - -void CApplet::Enable(const char *reason) -{ - /* restore the original icon */ - m_bDaemonRunning = true; - SetIconTooltip(reason); - gtk_status_icon_set_from_stock(m_pStatusIcon, GTK_STOCK_DIALOG_WARNING); - ShowIcon(); -} - -gboolean CApplet::update_icon(void *user_data) -{ - CApplet* applet = (CApplet*)user_data; - if (applet->m_pStatusIcon && applet->m_iAnimationStage < ICON_STAGE_LAST) - { - gtk_status_icon_set_from_pixbuf(applet->m_pStatusIcon, - applet->icon_stages_buff[applet->m_iAnimationStage++]); - } - if (applet->m_iAnimationStage == ICON_STAGE_LAST) - { - applet->m_iAnimationStage = 0; - } - if (--applet->m_iAnimCountdown == 0) - { - applet->stop_animate_icon(); - } - return true; -} - -void CApplet::animate_icon() -{ - if (m_iAnimator == 0) - { - m_iAnimator = g_timeout_add(100, update_icon, this); - m_iAnimCountdown = 10 * 3; /* 3 sec */ - } -} - -void CApplet::stop_animate_icon() -{ - /* animator should be 0 if icons are not loaded, so this should be safe */ - if (m_iAnimator != 0) - { - g_source_remove(m_iAnimator); - gtk_status_icon_set_from_pixbuf(m_pStatusIcon, icon_stages_buff[ICON_DEFAULT]); - m_iAnimator = 0; - } -} - -bool CApplet::load_icons() -{ - //FIXME: just a tmp workaround - return false; - int stage; - for (stage = ICON_DEFAULT; stage < ICON_STAGE_LAST; stage++) - { - char name[sizeof(ICON_DIR"/abrt%02d.png")]; - GError *error = NULL; - if (snprintf(name, sizeof(ICON_DIR"/abrt%02d.png"), ICON_DIR"/abrt%02d.png", stage) > 0) - { - icon_stages_buff[stage] = gdk_pixbuf_new_from_file(name, &error); - if (error != NULL) - { - error_msg("Can't load pixbuf from %s, animation is disabled", name); - return false; - } - } - } - return true; -} - - -//int CApplet::AddEvent(int pUUID, const char *pProgname) -//{ -// m_mapEvents[pUUID] = "pProgname"; -// SetIconTooltip(_("Pending events: %i"), m_mapEvents.size()); -// return 0; -//} -// -//int CApplet::RemoveEvent(int pUUID) -//{ -// m_mapEvents.erase(pUUID); -// return 0; -//} diff --git a/src/Applet/CCApplet.h b/src/Applet/CCApplet.h deleted file mode 100644 index a58ec687..00000000 --- a/src/Applet/CCApplet.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#ifndef CC_APPLET_H_ -#define CC_APPLET_H_ - -#include -#include -#include -#include - -class CApplet -{ - private: - GtkStatusIcon* m_pStatusIcon; - GtkWidget *m_pMenu; - -// std::map m_mapEvents; - bool m_bDaemonRunning; - int m_iAnimationStage; - guint m_iAnimator; - unsigned m_iAnimCountdown; - bool m_bIconsLoaded; - const char *m_pLastCrashID; - - enum ICON_STAGES - { - ICON_DEFAULT, - ICON_STAGE1, - ICON_STAGE2, - ICON_STAGE3, - ICON_STAGE4, - ICON_STAGE5, - /* this must be always the last */ - ICON_STAGE_LAST - } icon_stages; - GdkPixbuf *icon_stages_buff[ICON_STAGE_LAST]; - - public: - CApplet(const char* app_name); - ~CApplet(); - void ShowIcon(); - void HideIcon(); - void SetIconTooltip(const char *format, ...); - void CrashNotify(const char* crash_id, const char *format, ...); - void MessageNotify(const char *format, ...); - void Disable(const char *reason); - void Enable(const char *reason); - // create some event storage, to let user choose - // or ask the daemon every time? - // maybe just events which occured during current session - // map:: -// int AddEvent(int pUUID, const char *pProgname); -// int RemoveEvent(int pUUID); - - protected: - //@@TODO applet menus - static void OnAppletActivate_CB(GtkStatusIcon *status_icon, gpointer user_data); - //this action should open the reporter dialog directly, without showing the main window - static void action_report(NotifyNotification *notification, gchar *action, gpointer user_data); - //this action should open the main window - static void action_open_gui(NotifyNotification *notification, gchar *action, gpointer user_data); - static void OnMenuPopup_cb(GtkStatusIcon *status_icon, - guint button, - guint activate_time, - gpointer user_data); - static gboolean update_icon(void *data); - void animate_icon(); - void stop_animate_icon(); - bool load_icons(); -}; - -#endif diff --git a/src/Applet/Makefile.am b/src/Applet/Makefile.am deleted file mode 100644 index b96104a0..00000000 --- a/src/Applet/Makefile.am +++ /dev/null @@ -1,43 +0,0 @@ -bin_PROGRAMS = abrt-applet - -abrt_applet_SOURCES = \ - Applet.cpp \ - CCApplet.h CCApplet.cpp -abrt_applet_CPPFLAGS = \ - -Wall -Werror \ - -I$(srcdir)/../../inc \ - -I$(srcdir)/../../lib/Utils \ - -I/usr/include/glib-2.0 \ - -I/usr/lib/glib-2.0/include \ - -DBIN_DIR=\"$(bindir)\" \ - -DVAR_RUN=\"$(VAR_RUN)\" \ - -DCONF_DIR=\"$(CONF_DIR)\" \ - -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ - -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ - -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ - -DICON_DIR=\"${datadir}/abrt/icons/hicolor/48x48/status\" \ - $(GTK_CFLAGS) \ - $(DBUS_CFLAGS) \ - -D_GNU_SOURCE -# $(LIBNOTIFY_CFLAGS) -# $(DBUS_GLIB_CFLAGS) -abrt_applet_LDADD = \ - ../../lib/Utils/libABRTUtils.la \ - -lglib-2.0 \ - -lgthread-2.0 \ - $(DBUS_LIBS) \ - $(LIBNOTIFY_LIBS) \ - $(GTK_LIBS) -# ../../lib/Utils/libABRTdUtils.la -# $(DL_LIBS) - -DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ - -@INTLTOOL_DESKTOP_RULE@ - -autostartdir = $(sysconfdir)/xdg/autostart -autostart_in_files = abrt-applet.desktop.in - -autostart_DATA = $(autostart_in_files:.desktop.in=.desktop) - -EXTRA_DIST = $(autostart_in_files) diff --git a/src/Applet/abrt-applet.desktop.in b/src/Applet/abrt-applet.desktop.in deleted file mode 100644 index 57a6278e..00000000 --- a/src/Applet/abrt-applet.desktop.in +++ /dev/null @@ -1,9 +0,0 @@ -[Desktop Entry] -Encoding=UTF-8 -_Name=Automatic Bug Reporting Tool -_Comment=ABRT notification applet -Icon=abrt -Exec=abrt-applet -Terminal=false -Type=Application -X-GNOME-Autostart-enabled=true diff --git a/src/CLI/CLI.cpp b/src/CLI/CLI.cpp deleted file mode 100644 index 276703dc..00000000 --- a/src/CLI/CLI.cpp +++ /dev/null @@ -1,436 +0,0 @@ -/* - Copyright (C) 2009, 2010 Red Hat, Inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#include -#include "abrt_exception.h" -#include "abrtlib.h" -#include "abrt_dbus.h" -#include "dbus_common.h" -#include "report.h" -#include "dbus.h" -#if HAVE_CONFIG_H -# include -#endif -#if HAVE_LOCALE_H -# include -#endif -#if ENABLE_NLS -# include -# define _(S) gettext(S) -#else -# define _(S) (S) -#endif - -/** Creates a localized string from crash time. */ -static char *localize_crash_time(const char *timestr) -{ - long time = xatou(timestr); - char timeloc[256]; - int success = strftime(timeloc, 128, "%c", localtime(&time)); - if (!success) - error_msg_and_die("Error while converting time to string"); - return xasprintf("%s", timeloc); -} - -/** Prints basic information about a crash to stdout. */ -static void print_crash(const map_crash_data_t &crash) -{ - /* Create a localized string from crash time. */ - const char *timestr = get_crash_data_item_content(crash, FILENAME_TIME).c_str(); - const char *timeloc = localize_crash_time(timestr); - - printf(_("\tUID : %s\n" - "\tUUID : %s\n" - "\tPackage : %s\n" - "\tExecutable : %s\n" - "\tCrash Time : %s\n" - "\tCrash Count: %s\n"), - get_crash_data_item_content(crash, CD_UID).c_str(), - get_crash_data_item_content(crash, CD_UUID).c_str(), - get_crash_data_item_content(crash, FILENAME_PACKAGE).c_str(), - get_crash_data_item_content(crash, FILENAME_EXECUTABLE).c_str(), - timeloc, - get_crash_data_item_content(crash, CD_COUNT).c_str()); - - free((void *)timeloc); - - /* Print the hostname if it's available. */ - const char *hostname = get_crash_data_item_content_or_NULL(crash, FILENAME_HOSTNAME); - if (hostname) - printf(_("\tHostname : %s\n"), hostname); -} - -/** - * Prints a list containing "crashes" to stdout. - * @param include_reported - * Do not skip entries marked as already reported. - */ -static void print_crash_list(const vector_map_crash_data_t& crash_list, bool include_reported) -{ - for (unsigned i = 0; i < crash_list.size(); ++i) - { - const map_crash_data_t& crash = crash_list[i]; - if (get_crash_data_item_content(crash, CD_REPORTED) == "1" && !include_reported) - continue; - - printf("%u.\n", i); - print_crash(crash); - } -} - -/** - * Prints full information about a crash - */ -static void print_crash_info(const map_crash_data_t& crash, bool show_backtrace) -{ - const char *timestr = get_crash_data_item_content(crash, FILENAME_TIME).c_str(); - const char *timeloc = localize_crash_time(timestr); - - printf(_("Crash ID: %s:%s\n" - "Last crash: %s\n" - "Analyzer: %s\n" - "Component: %s\n" - "Package: %s\n" - "Command: %s\n" - "Executable: %s\n" - "System: %s, kernel %s\n" - "Rating: %s\n" - "Coredump file: %s\n" - "Reason: %s\n"), - get_crash_data_item_content(crash, CD_UID).c_str(), - get_crash_data_item_content(crash, CD_UUID).c_str(), - timeloc, - get_crash_data_item_content(crash, FILENAME_ANALYZER).c_str(), - get_crash_data_item_content(crash, FILENAME_COMPONENT).c_str(), - get_crash_data_item_content(crash, FILENAME_PACKAGE).c_str(), - get_crash_data_item_content(crash, FILENAME_CMDLINE).c_str(), - get_crash_data_item_content(crash, FILENAME_EXECUTABLE).c_str(), - get_crash_data_item_content(crash, FILENAME_RELEASE).c_str(), - get_crash_data_item_content(crash, FILENAME_KERNEL).c_str(), - get_crash_data_item_content(crash, FILENAME_RATING).c_str(), - get_crash_data_item_content(crash, FILENAME_COREDUMP).c_str(), - get_crash_data_item_content(crash, FILENAME_REASON).c_str()); - - free((void *)timeloc); - - /* print only if available */ - const char *crash_function = get_crash_data_item_content_or_NULL(crash, FILENAME_CRASH_FUNCTION); - if (crash_function) - printf(_("Crash function: %s\n"), crash_function); - - const char *hostname = get_crash_data_item_content_or_NULL(crash, FILENAME_HOSTNAME); - if (hostname) - printf(_("Hostname: %s\n"), hostname); - - const char *reproduce = get_crash_data_item_content_or_NULL(crash, FILENAME_REPRODUCE); - if (reproduce) - printf(_("\nHow to reproduce:\n%s\n"), reproduce); - - const char *comment = get_crash_data_item_content_or_NULL(crash, FILENAME_COMMENT); - if (comment) - printf(_("\nComment:\n%s\n"), comment); - - if (show_backtrace) - { - const char *backtrace = get_crash_data_item_content_or_NULL(crash, FILENAME_BACKTRACE); - if (backtrace) - printf(_("\nBacktrace:\n%s\n"), backtrace); - } -} - -/** - * Converts crash reference from user's input to unique crash identification - * in form UID:UUID. - * The returned string must be released by caller. - */ -static char *guess_crash_id(const char *str) -{ - vector_map_crash_data_t ci = call_GetCrashInfos(); - unsigned num_crashinfos = ci.size(); - if (str[0] == '@') /* "--report @N" syntax */ - { - unsigned position = xatoi_u(str + 1); - if (position >= num_crashinfos) - error_msg_and_die("There are only %u crash infos", num_crashinfos); - map_crash_data_t& info = ci[position]; - return xasprintf("%s:%s", - get_crash_data_item_content(info, CD_UID).c_str(), - get_crash_data_item_content(info, CD_UUID).c_str() - ); - } - - unsigned len = strlen(str); - unsigned ii; - char *result = NULL; - for (ii = 0; ii < num_crashinfos; ii++) - { - map_crash_data_t& info = ci[ii]; - const char *this_uuid = get_crash_data_item_content(info, CD_UUID).c_str(); - if (strncmp(str, this_uuid, len) == 0) - { - if (result) - error_msg_and_die("Crash prefix '%s' is not unique", str); - result = xasprintf("%s:%s", - get_crash_data_item_content(info, CD_UID).c_str(), - this_uuid - ); - } - } - if (!result) - error_msg_and_die("Crash '%s' not found", str); - return result; -} - -/* Program options */ -enum -{ - OPT_GET_LIST, - OPT_REPORT, - OPT_DELETE, - OPT_INFO -}; - -/** - * Long options. - * Do not use the has_arg field. Arguments are handled after parsing all options. - * The reason is that we want to use all the following combinations: - * --report ID - * --report ID --always - * --report --always ID - */ -static const struct option longopts[] = -{ - /* name, has_arg, flag, val */ - { "help" , no_argument, NULL, '?' }, - { "version" , no_argument, NULL, 'V' }, - { "list" , no_argument, NULL, 'l' }, - { "full" , no_argument, NULL, 'f' }, - { "always" , no_argument, NULL, 'y' }, - { "report" , no_argument, NULL, 'r' }, - { "delete" , no_argument, NULL, 'd' }, - { "info" , no_argument, NULL, 'i' }, - { "backtrace", no_argument, NULL, 'b' }, - { 0, 0, 0, 0 } /* prevents crashes for unknown options*/ -}; - -/* Gets the program name from the first command line argument. */ -static const char *progname(const char *argv0) -{ - const char* name = strrchr(argv0, '/'); - if (name) - return ++name; - return argv0; -} - -/** - * Prints abrt-cli version and some help text. - * Then exits the program with return value 1. - */ -static void usage(char *argv0) -{ - const char *name = progname(argv0); - printf("%s "VERSION"\n\n", name); - - /* Message has embedded tabs. */ - printf(_("Usage: %s [OPTION]\n\n" - "Startup:\n" - " -V, --version display the version of %s and exit\n" - " -?, --help print this help\n\n" - "Actions:\n" - " -l, --list print a list of all crashes which are not yet reported\n" - " -f, --full print a list of all crashes, including the already reported ones\n" - " -r, --report CRASH_ID create and send a report\n" - " -y, --always create and send a report without asking\n" - " -d, --delete CRASH_ID remove a crash\n" - " -i, --info CRASH_ID print detailed information about a crash\n" - " -b, --backtrace print detailed information about a crash including backtrace\n" - "CRASH_ID can be:\n" - " UID:UUID pair,\n" - " unique UUID prefix - the crash with matching UUID will be acted upon\n" - " @N - N'th crash (as displayed by --list --full) will be acted upon\n" - ), - name, name); - - exit(1); -} - -int main(int argc, char** argv) -{ - const char* crash_id = NULL; - int op = -1; - bool full = false; - bool always = false; - bool backtrace = false; - - setlocale(LC_ALL, ""); -#if ENABLE_NLS - bindtextdomain(PACKAGE, LOCALEDIR); - textdomain(PACKAGE); -#endif - - while (1) - { - int option_index; - /* Do not use colons, arguments are handled after parsing all options. */ - int c = getopt_long_only(argc, argv, "?Vrdlfyib", - longopts, &option_index); - -#define SET_OP(newop) \ - if (op != -1 && op != newop) \ - { \ - error_msg(_("You must specify exactly one operation")); \ - return 1; \ - } \ - op = newop; - - switch (c) - { - case 'r': SET_OP(OPT_REPORT); break; - case 'd': SET_OP(OPT_DELETE); break; - case 'l': SET_OP(OPT_GET_LIST); break; - case 'i': SET_OP(OPT_INFO); break; - case 'f': full = true; break; - case 'y': always = true; break; - case 'b': backtrace = true; break; - case -1: /* end of options */ break; - default: /* some error */ - case '?': - usage(argv[0]); /* exits app */ - case 'V': - printf("%s "VERSION"\n", progname(argv[0])); - return 0; - } -#undef SET_OP - if (c == -1) - break; - } - - /* Handle option arguments. */ - int arg_count = argc - optind; - switch (arg_count) - { - case 0: - if (op == OPT_REPORT || op == OPT_DELETE || op == OPT_INFO) - usage(argv[0]); - break; - case 1: - if (op != OPT_REPORT && op != OPT_DELETE && op != OPT_INFO) - usage(argv[0]); - crash_id = argv[optind]; - break; - default: - usage(argv[0]); - } - - /* Check if we have an operation. - * Limit --full and --always to certain operations. - */ - if ((full && op != OPT_GET_LIST) || - (always && op != OPT_REPORT) || - (backtrace && op != OPT_INFO) || - op == -1) - { - usage(argv[0]); - return 1; - } - - DBusError err; - dbus_error_init(&err); - s_dbus_conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); - handle_dbus_err(s_dbus_conn == NULL, &err); - - /* Do the selected operation. */ - int exitcode = 0; - switch (op) - { - case OPT_GET_LIST: - { - vector_map_crash_data_t ci = call_GetCrashInfos(); - print_crash_list(ci, full); - break; - } - case OPT_REPORT: - { - int flags = CLI_REPORT_SILENT_IF_NOT_FOUND; - if (always) - flags |= CLI_REPORT_BATCH; - exitcode = report(crash_id, flags); - if (exitcode == -1) /* no such crash_id */ - { - crash_id = guess_crash_id(crash_id); - exitcode = report(crash_id, always ? CLI_REPORT_BATCH : 0); - if (exitcode == -1) - { - error_msg("Crash '%s' not found", crash_id); - free((void *)crash_id); - xfunc_die(); - } - - free((void *)crash_id); - } - break; - } - case OPT_DELETE: - { - exitcode = call_DeleteDebugDump(crash_id); - if (exitcode == ENOENT) - { - crash_id = guess_crash_id(crash_id); - exitcode = call_DeleteDebugDump(crash_id); - if (exitcode == ENOENT) - { - error_msg("Crash '%s' not found", crash_id); - free((void *)crash_id); - xfunc_die(); - } - - free((void *)crash_id); - } - if (exitcode != 0) - error_msg_and_die("Can't delete debug dump '%s'", crash_id); - break; - } - case OPT_INFO: - { - int old_logmode = logmode; - logmode = 0; - - map_crash_data_t crashData = call_CreateReport(crash_id); - if (crashData.empty()) /* no such crash_id */ - { - crash_id = guess_crash_id(crash_id); - crashData = call_CreateReport(crash_id); - if (crashData.empty()) - { - error_msg("Crash '%s' not found", crash_id); - free((void *)crash_id); - xfunc_die(); - } - - free((void *)crash_id); - } - - logmode = old_logmode; - - print_crash_info(crashData, backtrace); - - break; - } - } - - return exitcode; -} diff --git a/src/CLI/Makefile.am b/src/CLI/Makefile.am deleted file mode 100644 index 361b64f7..00000000 --- a/src/CLI/Makefile.am +++ /dev/null @@ -1,28 +0,0 @@ -bin_PROGRAMS = abrt-cli - -abrt_cli_SOURCES = \ - CLI.cpp \ - run-command.h run-command.cpp \ - report.h report.cpp \ - dbus.h dbus.cpp - -abrt_cli_CPPFLAGS = \ - -I$(srcdir)/../../inc \ - -I$(srcdir)/../../lib/Utils \ - -DVAR_RUN=\"$(VAR_RUN)\" \ - $(ENABLE_SOCKET_OR_DBUS) \ - $(DBUS_CFLAGS) \ - -D_GNU_SOURCE -# $(GTK_CFLAGS) - -abrt_cli_LDADD = \ - ../../lib/Utils/libABRTUtils.la \ - ../../lib/Utils/libABRTdUtils.la - -man_MANS = abrt-cli.1 -EXTRA_DIST = $(man_MANS) - -completiondir = $(sysconfdir)/bash_completion.d -dist_completion_DATA = abrt-cli.bash - -DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ diff --git a/src/CLI/abrt-cli.1 b/src/CLI/abrt-cli.1 deleted file mode 100644 index 3ad858b3..00000000 --- a/src/CLI/abrt-cli.1 +++ /dev/null @@ -1,58 +0,0 @@ -.TH abrt\-cli "1" "12 Oct 2009" "" -.SH NAME -abrt\-cli \- a command line interface to abrt -.SH SYNOPSIS -.B abrt\-cli -[option] -.SH DESCRIPTION -.I abrt\-cli -is a command line tool that manages application crashes catched by -.I abrtd -daemon. It enables access to crash data, and allows to report -crashes depending on active abrt plugins. -.SH OPTIONS -.B Basic startup options -.IP "\-V, \-\-version" -Displays version of abrt\-cli. -.IP "\-?, \-\-help" -Print a help message describing all of abrt-cli’s command-line options. - -.PP -.B Crash action options -.IP "\-l, \-\-list" -Prints list of crashes which are not reported yet. -.IP "\-r, \-\-report \fIUUID\fR" -Creates a crash report and then the text editor is invoked on that -report. When you are done with editing the report just exit the editor -and then you will be asked if you want to send the report. -.IP "\-d, \-\-delete \fIUUID\fR" -Removes data about particular crash. -.IP "\-i, \-\-info \fIUUID\fR" -Prints detailed information about particular crash. - -.PP -.B Listing options -.IP "\-f, \-\-full" -List all crashes, including already reported. - -.PP -.B Report options -.IP "\-y, \-\-always" -Creates and sends the crash report automatically, without asking -any questions. - -.PP -.B Info options -.IP "\-b, \-\-backtrace" -Includes the crash backtrace in the info output if the backtrace is -available. - -.SH ENVIRONMENT VARIABLES -The editor used to edit the crash report is chosen from the -ABRT_EDITOR environment variable, the VISUAL environment variable, or -the EDITOR environment variable, in that order. - -.SH SEE ALSO -.IR abrtd (8), -.IR abrt.conf (5), -.IR abrt-plugins (7) diff --git a/src/CLI/abrt-cli.bash b/src/CLI/abrt-cli.bash deleted file mode 100644 index fd0a85f3..00000000 --- a/src/CLI/abrt-cli.bash +++ /dev/null @@ -1,50 +0,0 @@ -# bash-completion add-on for abrt-cli(1) -# http://bash-completion.alioth.debian.org/ - -# $1 = additional options for abrt-cli -_abrt_list() -{ - echo $(abrt-cli --list $1 | grep UUID | awk '{print $3}') - return 0 -} - -_abrt_cli() -{ - local cur prev opts - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - opts="--help --version --list --report --delete" - - # - # Complete the arguments to some of the basic commands. - # - case "${prev}" in - --list) - opts="--full" - ;; - --report) - # Include only not-yet-reported crashes. - opts="--always $(_abrt_list)" - ;; - --always) # This is for --report --always - # Include only not-yet-reported crashes. - opts=$(_abrt_list) - ;; - --delete) - opts=$(_abrt_list "--full") - ;; - esac - - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - return 0 -} -complete -F _abrt_cli abrt-cli - -# Local variables: -# mode: shell-script -# sh-basic-offset: 4 -# sh-indent-comment: t -# indent-tabs-mode: nil -# End: -# ex: ts=4 sw=4 et filetype=sh \ No newline at end of file diff --git a/src/CLI/dbus.cpp b/src/CLI/dbus.cpp deleted file mode 100644 index 7565d5bc..00000000 --- a/src/CLI/dbus.cpp +++ /dev/null @@ -1,282 +0,0 @@ -/* - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#include "dbus.h" -#include "dbus_common.h" - -DBusConnection* s_dbus_conn; - -/* - * DBus member calls - */ - -/* helpers */ -static DBusMessage* new_call_msg(const char* method) -{ - DBusMessage* msg = dbus_message_new_method_call(ABRTD_DBUS_NAME, ABRTD_DBUS_PATH, ABRTD_DBUS_IFACE, method); - if (!msg) - die_out_of_memory(); - return msg; -} - -static DBusMessage* send_get_reply_and_unref(DBusMessage* msg) -{ - dbus_uint32_t serial; - if (TRUE != dbus_connection_send(s_dbus_conn, msg, &serial)) - error_msg_and_die("Error sending DBus message"); - dbus_message_unref(msg); - - while (true) - { - DBusMessage *received = dbus_connection_pop_message(s_dbus_conn); - if (!received) - { - if (FALSE == dbus_connection_read_write(s_dbus_conn, -1)) - error_msg_and_die("dbus connection closed"); - continue; - } - - int tp = dbus_message_get_type(received); - const char *error_str = dbus_message_get_error_name(received); -#if 0 - /* Debugging */ - printf("type:%u (CALL:%u, RETURN:%u, ERROR:%u, SIGNAL:%u)\n", tp, - DBUS_MESSAGE_TYPE_METHOD_CALL, - DBUS_MESSAGE_TYPE_METHOD_RETURN, - DBUS_MESSAGE_TYPE_ERROR, - DBUS_MESSAGE_TYPE_SIGNAL - ); - const char *sender = dbus_message_get_sender(received); - if (sender) - printf("sender: %s\n", sender); - const char *path = dbus_message_get_path(received); - if (path) - printf("path: %s\n", path); - const char *member = dbus_message_get_member(received); - if (member) - printf("member: %s\n", member); - const char *interface = dbus_message_get_interface(received); - if (interface) - printf("interface: %s\n", interface); - const char *destination = dbus_message_get_destination(received); - if (destination) - printf("destination: %s\n", destination); - if (error_str) - printf("error: '%s'\n", error_str); -#endif - - DBusError err; - dbus_error_init(&err); - - if (dbus_message_is_signal(received, ABRTD_DBUS_IFACE, "Update")) - { - const char *update_msg; - if (!dbus_message_get_args(received, &err, - DBUS_TYPE_STRING, &update_msg, - DBUS_TYPE_INVALID)) - { - error_msg_and_die("dbus Update message: arguments mismatch"); - } - printf(">> %s\n", update_msg); - } - else if (dbus_message_is_signal(received, ABRTD_DBUS_IFACE, "Warning")) - { - const char *warning_msg; - if (!dbus_message_get_args(received, &err, - DBUS_TYPE_STRING, &warning_msg, - DBUS_TYPE_INVALID)) - { - error_msg_and_die("dbus Warning message: arguments mismatch"); - } - log(">! %s\n", warning_msg); - } - else - if (tp == DBUS_MESSAGE_TYPE_METHOD_RETURN - && dbus_message_get_reply_serial(received) == serial - ) { - return received; - } - else - if (tp == DBUS_MESSAGE_TYPE_ERROR - && dbus_message_get_reply_serial(received) == serial - ) { - error_msg_and_die("dbus call returned error: '%s'", error_str); - } - - dbus_message_unref(received); - } -} - -vector_map_crash_data_t call_GetCrashInfos() -{ - DBusMessage* msg = new_call_msg(__func__ + 5); - DBusMessage *reply = send_get_reply_and_unref(msg); - - DBusMessageIter in_iter; - dbus_message_iter_init(reply, &in_iter); - - vector_map_crash_data_t argout; - int r = load_val(&in_iter, argout); - if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */ - error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5); - - dbus_message_unref(reply); - return argout; -} - -map_crash_data_t call_CreateReport(const char* crash_id) -{ - DBusMessage* msg = new_call_msg(__func__ + 5); - dbus_message_append_args(msg, - DBUS_TYPE_STRING, &crash_id, - DBUS_TYPE_INVALID); - - DBusMessage *reply = send_get_reply_and_unref(msg); - - DBusMessageIter in_iter; - dbus_message_iter_init(reply, &in_iter); - - map_crash_data_t argout; - int r = load_val(&in_iter, argout); - if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */ - error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5); - - dbus_message_unref(reply); - return argout; -} - -report_status_t call_Report(const map_crash_data_t& report, - const vector_string_t& reporters, - const map_map_string_t &plugins) -{ - DBusMessage* msg = new_call_msg(__func__ + 5); - DBusMessageIter out_iter; - dbus_message_iter_init_append(msg, &out_iter); - - /* parameter #1: report data */ - store_val(&out_iter, report); - /* parameter #2: reporters to use */ - store_val(&out_iter, reporters); - /* parameter #3 (opt): plugin config */ - if (!plugins.empty()) - store_val(&out_iter, plugins); - - DBusMessage *reply = send_get_reply_and_unref(msg); - - DBusMessageIter in_iter; - dbus_message_iter_init(reply, &in_iter); - - report_status_t result; - int r = load_val(&in_iter, result); - if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */ - error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5); - - dbus_message_unref(reply); - return result; -} - -int32_t call_DeleteDebugDump(const char* crash_id) -{ - DBusMessage* msg = new_call_msg(__func__ + 5); - dbus_message_append_args(msg, - DBUS_TYPE_STRING, &crash_id, - DBUS_TYPE_INVALID); - - DBusMessage *reply = send_get_reply_and_unref(msg); - - DBusMessageIter in_iter; - dbus_message_iter_init(reply, &in_iter); - - int32_t result; - int r = load_val(&in_iter, result); - if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */ - error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5); - - dbus_message_unref(reply); - return result; -} - -map_map_string_t call_GetPluginsInfo() -{ - DBusMessage *msg = new_call_msg(__func__ + 5); - DBusMessage *reply = send_get_reply_and_unref(msg); - - DBusMessageIter in_iter; - dbus_message_iter_init(reply, &in_iter); - - map_map_string_t argout; - int r = load_val(&in_iter, argout); - if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */ - error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5); - - dbus_message_unref(reply); - return argout; -} - -map_plugin_settings_t call_GetPluginSettings(const char *name) -{ - DBusMessage *msg = new_call_msg(__func__ + 5); - dbus_message_append_args(msg, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_INVALID); - - DBusMessage *reply = send_get_reply_and_unref(msg); - - DBusMessageIter in_iter; - dbus_message_iter_init(reply, &in_iter); - - map_string_t argout; - int r = load_val(&in_iter, argout); - if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */ - error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5); - - dbus_message_unref(reply); - return argout; -} - -map_map_string_t call_GetSettings() -{ - DBusMessage *msg = new_call_msg(__func__ + 5); - DBusMessage *reply = send_get_reply_and_unref(msg); - - DBusMessageIter in_iter; - dbus_message_iter_init(reply, &in_iter); - map_map_string_t argout; - int r = load_val(&in_iter, argout); - if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */ - error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5); - - dbus_message_unref(reply); - return argout; -} - -void handle_dbus_err(bool error_flag, DBusError *err) -{ - if (dbus_error_is_set(err)) - { - error_msg("dbus error: %s", err->message); - /* dbus_error_free(&err); */ - error_flag = true; - } - if (!error_flag) - return; - error_msg_and_die( - "error requesting DBus name %s, possible reasons: " - "abrt run by non-root; dbus config is incorrect; " - "or dbus daemon needs to be restarted to reload dbus config", - ABRTD_DBUS_NAME); -} diff --git a/src/CLI/dbus.h b/src/CLI/dbus.h deleted file mode 100644 index 8b56b152..00000000 --- a/src/CLI/dbus.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#ifndef ABRT_CLI_DBUS_H -#define ABRT_CLI_DBUS_H - -#include "abrt_dbus.h" -#include "crash_types.h" - -extern DBusConnection* s_dbus_conn; - -vector_map_crash_data_t call_GetCrashInfos(); - -map_crash_data_t call_CreateReport(const char *crash_id); - -/** Sends report using enabled Reporter plugins. - * @param report - * The report sent to Reporter plugins. - * @param reporters - * List of names of Reporters which should be called. - * @param plugins - * Optional settings for Reporter plugins, can be empty. - * Format: plugins["PluginName"]["SettingsKey"] = "SettingsValue" - * If it contains settings for some plugin, it must contain _all fields_ - * obtained by call_GetPluginSettings, otherwise the plugin might ignore - * the settings. - */ -report_status_t call_Report(const map_crash_data_t& report, - const vector_string_t& reporters, - const map_map_string_t &plugins); - -int32_t call_DeleteDebugDump(const char* crash_id); - -/* Gets basic data about all installed plugins. - * @todo - * Return more semantically structured output - maybe a struct instead of a map. - */ -map_map_string_t call_GetPluginsInfo(); - -/** Gets default plugin settings. - * @param name - * Corresponds to name obtained from call_GetPluginsInfo. - */ -map_plugin_settings_t call_GetPluginSettings(const char *name); - -/** Gets global daemon settings. - * @todo - * Return more semantically structured output - maybe a struct instead of a map. - */ -map_map_string_t call_GetSettings(); - -void handle_dbus_err(bool error_flag, DBusError *err); - -#endif diff --git a/src/CLI/report.cpp b/src/CLI/report.cpp deleted file mode 100644 index 96dec553..00000000 --- a/src/CLI/report.cpp +++ /dev/null @@ -1,743 +0,0 @@ -/* - Copyright (C) 2009, 2010 Red Hat, Inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#include "report.h" -#include "run-command.h" -#include "dbus.h" -#include "abrtlib.h" -#include "debug_dump.h" -#include "crash_types.h" // FILENAME_* defines -#include "plugin.h" // LoadPluginSettings -#include -#include -#include -#if HAVE_CONFIG_H -# include -#endif -#if ENABLE_NLS -# include -# define _(S) gettext(S) -#else -# define _(S) (S) -#endif - -#include "abrt_packages.h" - -/* Field separator for the crash report file that is edited by user. */ -#define FIELD_SEP "%----" - -/* - * Trims whitespace characters both from left and right side of a string. - * Modifies the string in-place. Returns the trimmed string. - */ -static char *trim(char *str) -{ - if (!str) - return NULL; - - // Remove leading spaces. - char *ibuf; - ibuf = skip_whitespace(str); - int i = strlen(ibuf); - if (str != ibuf) - memmove(str, ibuf, i + 1); - - // Remove trailing spaces. - while (--i >= 0) - { - if (!isspace(str[i])) - break; - } - str[++i] = '\0'; - return str; -} - -/* - * Escapes the field content string to avoid confusion with file comments. - * Returned field must be free()d by caller. - */ -static char *escape(const char *str) -{ - // Determine the size of resultant string. - // Count the required number of escape characters. - // 1. NEWLINE followed by # - // 2. NEWLINE followed by \# (escaped version) - const char *ptr = str; - bool newline = true; - int count = 0; - while (*ptr) - { - if (newline) - { - if (*ptr == '#') - ++count; - if (*ptr == '\\' && ptr[1] == '#') - ++count; - } - - newline = (*ptr == '\n'); - ++ptr; - } - - // Copy the input string to the resultant string, and escape all - // occurences of \# and #. - char *result = (char*)xmalloc(strlen(str) + 1 + count); - - const char *src = str; - char *dest = result; - newline = true; - while (*src) - { - if (newline) - { - if (*src == '#') - *dest++ = '\\'; - else if (*src == '\\' && *(src + 1) == '#') - *dest++ = '\\'; - } - - newline = (*src == '\n'); - *dest++ = *src++; - } - *dest = '\0'; - return result; -} - -/* - * Removes all comment lines, and unescapes the string previously escaped - * by escape(). Works in-place. - */ -static void remove_comments_and_unescape(char *str) -{ - char *src = str, *dest = str; - bool newline = true; - while (*src) - { - if (newline) - { - if (*src == '#') - { // Skip the comment line! - while (*src && *src != '\n') - ++src; - - if (*src == '\0') - break; - - ++src; - continue; - } - if (*src == '\\' - && (src[1] == '#' || (src[1] == '\\' && src[2] == '#')) - ) { - ++src; // Unescape escaped char. - } - } - - newline = (*src == '\n'); - *dest++ = *src++; - } - *dest = '\0'; -} - -/* - * Writes a field of crash report to a file. - * Field must be writable. - */ -static void write_crash_report_field(FILE *fp, const map_crash_data_t &report, - const char *field, const char *description) -{ - const map_crash_data_t::const_iterator it = report.find(field); - if (it == report.end()) - { - // exit silently, all fields are optional for now - //error_msg("Field %s not found", field); - return; - } - - if (it->second[CD_TYPE] == CD_SYS) - { - error_msg("Cannot update field %s because it is a system value", field); - return; - } - - fprintf(fp, "%s%s\n", FIELD_SEP, it->first.c_str()); - - fprintf(fp, "%s\n", description); - if (it->second[CD_EDITABLE] != CD_ISEDITABLE) - fprintf(fp, _("# This field is read only\n")); - - char *escaped_content = escape(it->second[CD_CONTENT].c_str()); - fprintf(fp, "%s\n", escaped_content); - free(escaped_content); -} - -/* - * Saves the crash report to a file. - * Parameter 'fp' must be opened before write_crash_report is called. - * Returned value: - * If the report is successfully stored to the file, a zero value is returned. - * On failure, nonzero value is returned. - */ -static void write_crash_report(const map_crash_data_t &report, FILE *fp) -{ - fprintf(fp, "# Please check this report. Lines starting with '#' will be ignored.\n" - "# Lines starting with '%%----' separate fields, please do not delete them.\n\n"); - - write_crash_report_field(fp, report, FILENAME_COMMENT, - _("# Describe the circumstances of this crash below")); - write_crash_report_field(fp, report, FILENAME_REPRODUCE, - _("# How to reproduce the crash?")); - write_crash_report_field(fp, report, FILENAME_BACKTRACE, - _("# Backtrace\n# Check that it does not contain any sensitive data (passwords, etc.)")); - write_crash_report_field(fp, report, CD_DUPHASH, "# DUPHASH"); - write_crash_report_field(fp, report, FILENAME_ARCHITECTURE, _("# Architecture")); - write_crash_report_field(fp, report, FILENAME_CMDLINE, _("# Command line")); - write_crash_report_field(fp, report, FILENAME_COMPONENT, _("# Component")); - write_crash_report_field(fp, report, FILENAME_COREDUMP, _("# Core dump")); - write_crash_report_field(fp, report, FILENAME_EXECUTABLE, _("# Executable")); - write_crash_report_field(fp, report, FILENAME_KERNEL, _("# Kernel version")); - write_crash_report_field(fp, report, FILENAME_PACKAGE, _("# Package")); - write_crash_report_field(fp, report, FILENAME_REASON, _("# Reason of crash")); - write_crash_report_field(fp, report, FILENAME_RELEASE, _("# Release string of the operating system")); -} - -/* - * Updates appropriate field in the report from the text. The text can - * contain multiple fields. - * Returns: - * 0 if no change to the field was detected. - * 1 if the field was changed. - * Changes to read-only fields are ignored. - */ -static int read_crash_report_field(const char *text, map_crash_data_t &report, - const char *field) -{ - char separator[sizeof("\n" FIELD_SEP)-1 + strlen(field) + 2]; // 2 = '\n\0' - sprintf(separator, "\n%s%s\n", FIELD_SEP, field); - const char *textfield = strstr(text, separator); - if (!textfield) - return 0; // exit silently because all fields are optional - - textfield += strlen(separator); - int length = 0; - const char *end = strstr(textfield, "\n" FIELD_SEP); - if (!end) - length = strlen(textfield); - else - length = end - textfield; - - const map_crash_data_t::iterator it = report.find(field); - if (it == report.end()) - { - error_msg("Field %s not found", field); - return 0; - } - - if (it->second[CD_TYPE] == CD_SYS) - { - error_msg("Cannot update field %s because it is a system value", field); - return 0; - } - - // Do not change noneditable fields. - if (it->second[CD_EDITABLE] != CD_ISEDITABLE) - return 0; - - // Compare the old field contents with the new field contents. - char newvalue[length + 1]; - strncpy(newvalue, textfield, length); - newvalue[length] = '\0'; - trim(newvalue); - - char oldvalue[it->second[CD_CONTENT].length() + 1]; - strcpy(oldvalue, it->second[CD_CONTENT].c_str()); - trim(oldvalue); - - // Return if no change in the contents detected. - int cmp = strcmp(newvalue, oldvalue); - if (!cmp) - return 0; - - it->second[CD_CONTENT].assign(newvalue); - return 1; -} - -/* - * Updates the crash report 'report' from the text. The text must not contain - * any comments. - * Returns: - * 0 if no field was changed. - * 1 if any field was changed. - * Changes to read-only fields are ignored. - */ -static int read_crash_report(map_crash_data_t &report, const char *text) -{ - int result = 0; - result |= read_crash_report_field(text, report, FILENAME_COMMENT); - result |= read_crash_report_field(text, report, FILENAME_REPRODUCE); - result |= read_crash_report_field(text, report, FILENAME_BACKTRACE); - result |= read_crash_report_field(text, report, CD_DUPHASH); - result |= read_crash_report_field(text, report, FILENAME_ARCHITECTURE); - result |= read_crash_report_field(text, report, FILENAME_CMDLINE); - result |= read_crash_report_field(text, report, FILENAME_COMPONENT); - result |= read_crash_report_field(text, report, FILENAME_COREDUMP); - result |= read_crash_report_field(text, report, FILENAME_EXECUTABLE); - result |= read_crash_report_field(text, report, FILENAME_KERNEL); - result |= read_crash_report_field(text, report, FILENAME_PACKAGE); - result |= read_crash_report_field(text, report, FILENAME_REASON); - result |= read_crash_report_field(text, report, FILENAME_RELEASE); - return result; -} - -/** - * Ensures that the fields needed for editor are present in the crash data. - * Fields: comments, how to reproduce. - */ -static void create_fields_for_editor(map_crash_data_t &crash_data) -{ - if (crash_data.find(FILENAME_COMMENT) == crash_data.end()) - add_to_crash_data_ext(crash_data, FILENAME_COMMENT, CD_TXT, CD_ISEDITABLE, ""); - - if (crash_data.find(FILENAME_REPRODUCE) == crash_data.end()) - add_to_crash_data_ext(crash_data, FILENAME_REPRODUCE, CD_TXT, CD_ISEDITABLE, "1. \n2. \n3. \n"); -} - -/** - * Runs external editor. - * Returns: - * 0 if the launch was successful - * 1 if it failed. The error reason is logged using error_msg() - */ -static int launch_editor(const char *path) -{ - const char *editor, *terminal; - - editor = getenv("ABRT_EDITOR"); - if (!editor) - editor = getenv("VISUAL"); - if (!editor) - editor = getenv("EDITOR"); - - terminal = getenv("TERM"); - if (!editor && (!terminal || strcmp(terminal, "dumb") == 0)) - { - error_msg(_("Cannot run vi: $TERM, $VISUAL and $EDITOR are not set")); - return 1; - } - - if (!editor) - editor = "vi"; - - char *args[3]; - args[0] = (char*)editor; - args[1] = (char*)path; - args[2] = NULL; - run_command(args); - - return 0; -} - -/** - * Returns: - * 0 on success, crash data has been updated - * 2 on failure, unable to create, open, or close temporary file - * 3 on failure, cannot launch text editor - */ -static int run_report_editor(map_crash_data_t &cr) -{ - /* Open a temporary file and write the crash report to it. */ - char filename[] = "/tmp/abrt-report.XXXXXX"; - int fd = mkstemp(filename); - if (fd == -1) /* errno is set */ - { - perror_msg("can't generate temporary file name"); - return 2; - } - - FILE *fp = fdopen(fd, "w"); - if (!fp) /* errno is set */ - { - perror_msg("can't open '%s' to save the crash report", filename); - return 2; - } - - write_crash_report(cr, fp); - - if (fclose(fp)) /* errno is set */ - { - perror_msg("can't close '%s'", filename); - return 2; - } - - // Start a text editor on the temporary file. - if (launch_editor(filename) != 0) - return 3; /* exit with error */ - - // Read the file back and update the report from the file. - fp = fopen(filename, "r"); - if (!fp) /* errno is set */ - { - perror_msg("can't open '%s' to read the crash report", filename); - return 2; - } - - fseek(fp, 0, SEEK_END); - long size = ftell(fp); - fseek(fp, 0, SEEK_SET); - - char *text = (char*)xmalloc(size + 1); - if (fread(text, 1, size, fp) != size) - { - error_msg("can't read '%s'", filename); - return 2; - } - text[size] = '\0'; - if (fclose(fp) != 0) /* errno is set */ - { - perror_msg("can't close '%s'", filename); - return 2; - } - - // Delete the tempfile. - if (unlink(filename) == -1) /* errno is set */ - { - perror_msg("can't unlink %s", filename); - } - - remove_comments_and_unescape(text); - // Updates the crash report from the file text. - int report_changed = read_crash_report(cr, text); - free(text); - if (report_changed) - puts(_("\nThe report has been updated")); - else - puts(_("\nNo changes were detected in the report")); - - return 0; -} - -/** - * Asks user for a text response. - * @param question - * Question displayed to user. - * @param result - * Output array. - * @param result_size - * Maximum byte count to be written. - */ -static void read_from_stdin(const char *question, char *result, int result_size) -{ - assert(result_size > 1); - printf("%s", question); - fflush(NULL); - if (NULL == fgets(result, result_size, stdin)) - result[0] = '\0'; - // Remove the newline from the login. - strchrnul(result, '\n')[0] = '\0'; -} - -/** Splits a string into substrings using chosen delimiters. - * @param delim - * Specifies a set of characters that delimit the - * tokens in the parsed string - */ -static vector_string_t split(const char *s, const char delim) -{ - std::vector elems; - while (1) - { - const char *end = strchrnul(s, delim); - elems.push_back(std::string(s, end - s)); - if (*end == '\0') - break; - s = end + 1; - } - return elems; -} - -/** Returns a list of enabled Reporter plugins, that are used to report - * a particular crash. - * @todo - * Very similar code is used in the GUI, and also in the Daemon. - * It should be shared. - */ -static vector_string_t get_enabled_reporters(map_crash_data_t &crash_data) -{ - vector_string_t result; - - /* Get global daemon settings. Analyzer->Reporters mapping is stored there. */ - map_map_string_t settings = call_GetSettings(); - /* Reporters are separated by comma in the following map. */ - map_string_t &analyzer_to_reporters = settings["AnalyzerActionsAndReporters"]; - - /* Get the analyzer from the crash. */ - const char *analyzer = get_crash_data_item_content_or_NULL(crash_data, FILENAME_ANALYZER); - if (!analyzer) - return result; /* No analyzer found in the crash data. */ - - /* First try to find package name dependent analyzer. - * nvr = name-version-release - * TODO: Similar code is in MiddleWare.cpp. It should not be duplicated. - */ - const char *package_nvr = get_crash_data_item_content_or_NULL(crash_data, FILENAME_PACKAGE); - if (!package_nvr) - return result; /* No package name found in the crash data. */ - char * package_name = get_package_name_from_NVR_or_NULL(package_nvr); - // analyzer with package name (CCpp:xorg-x11-app) has higher priority - map_string_t::const_iterator reporters_iter; - if (package_name != NULL) - { - char* package_specific_analyzer = xasprintf("%s:%s", analyzer, package_name); - reporters_iter = analyzer_to_reporters.find(package_specific_analyzer); - free(package_specific_analyzer); - free(package_name); - } - - if (analyzer_to_reporters.end() == reporters_iter) - { - reporters_iter = analyzer_to_reporters.find(analyzer); - if (analyzer_to_reporters.end() == reporters_iter) - return result; /* No reporters found for the analyzer. */ - } - - /* Reporters found, now parse the list. */ - vector_string_t reporter_vec = split(reporters_iter->second.c_str(), ','); - - // Get informations about all plugins. - map_map_string_t plugins = call_GetPluginsInfo(); - // Check the configuration of each enabled Reporter plugin. - map_map_string_t::iterator it, itend = plugins.end(); - for (it = plugins.begin(); it != itend; ++it) - { - // Skip disabled plugins. - if (string_to_bool(it->second["Enabled"].c_str()) != true) - continue; - // Skip nonReporter plugins. - if (0 != strcmp(it->second["Type"].c_str(), "Reporter")) - continue; - // Skip plugins not used in this particular crash. - if (reporter_vec.end() == std::find(reporter_vec.begin(), reporter_vec.end(), std::string(it->first))) - continue; - result.push_back(it->first); - } - return result; -} - -/* Asks a [y/n] question on stdin/stdout. - * Returns true if the answer is yes, false otherwise. - */ -static bool ask_yesno(const char *question) -{ - printf(question); - fflush(NULL); - char answer[16] = "n"; - fgets(answer, sizeof(answer), stdin); - /* TODO: localize 'y' */ - return ((answer[0] | 0x20) == 'y'); -} - -/* Returns true if echo has been changed from another state. */ -static bool set_echo(bool enabled) -{ - if (isatty(STDIN_FILENO) == 0) - { - /* Clean errno, which is set by isatty. */ - errno = 0; - return false; - } - - struct termios t; - if (tcgetattr(STDIN_FILENO, &t) < 0) - return false; - - /* No change needed. */ - if ((bool)(t.c_lflag & ECHO) == enabled) - return false; - - if (enabled) - t.c_lflag |= ECHO; - else - t.c_lflag &= ~ECHO; - - if (tcsetattr(STDIN_FILENO, TCSANOW, &t) < 0) - perror_msg_and_die("tcsetattr"); - - return true; -} - -/** - * Gets reporter plugin settings. - * @param reporters - * List of reporter names. Settings of these reporters are handled. - * @param settings - * A structure filled with reporter plugin settings. - * @param ask_user - * If it's set to true and some reporter plugin settings are found to be missing - * (like login name or password), user is asked to provide the missing parts. - */ -static void get_reporter_plugin_settings(const vector_string_t& reporters, - map_map_string_t &settings, - bool ask_user) -{ - /* First of all, load system-wide report plugin settings. */ - for (vector_string_t::const_iterator it = reporters.begin(); it != reporters.end(); ++it) - { - map_string_t single_plugin_settings = call_GetPluginSettings(it->c_str()); - // Copy the received settings as defaults. - // Plugins won't work without it, if some value is missing - // they use their default values for all fields. - settings[it->c_str()] = single_plugin_settings; - } - - /* Second, load user-specific settings, which override - the system-wide settings. */ - struct passwd* pw = getpwuid(geteuid()); - const char* homedir = pw ? pw->pw_dir : NULL; - if (homedir) - { - map_map_string_t::const_iterator itend = settings.end(); - for (map_map_string_t::iterator it = settings.begin(); it != itend; ++it) - { - map_string_t single_plugin_settings; - std::string path = std::string(homedir) + "/.abrt/" - + it->first + "."PLUGINS_CONF_EXTENSION; - /* Load plugin config in the home dir. Do not skip lines with empty value (but containing a "key="), - because user may want to override password from /etc/abrt/plugins/\*.conf, but he prefers to - enter it every time he reports. */ - bool success = LoadPluginSettings(path.c_str(), single_plugin_settings, false); - if (!success) - continue; - // Merge user's plugin settings into already loaded settings. - map_string_t::const_iterator valit, valitend = single_plugin_settings.end(); - for (valit = single_plugin_settings.begin(); valit != valitend; ++valit) - it->second[valit->first] = valit->second; - } - } - - if (!ask_user) - return; - - /* Third, check if a login or password is missing, and ask for it. */ - map_map_string_t::const_iterator itend = settings.end(); - for (map_map_string_t::iterator it = settings.begin(); it != itend; ++it) - { - map_string_t &single_plugin_settings = it->second; - // Login information is missing. - bool loginMissing = single_plugin_settings.find("Login") != single_plugin_settings.end() - && 0 == strcmp(single_plugin_settings["Login"].c_str(), ""); - bool passwordMissing = single_plugin_settings.find("Password") != single_plugin_settings.end() - && 0 == strcmp(single_plugin_settings["Password"].c_str(), ""); - if (!loginMissing && !passwordMissing) - continue; - - // Read the missing information and push it to plugin settings. - printf(_("Wrong settings were detected for plugin %s\n"), it->first.c_str()); - char result[64]; - if (loginMissing) - { - read_from_stdin(_("Enter your login: "), result, 64); - single_plugin_settings["Login"] = std::string(result); - } - if (passwordMissing) - { - bool changed = set_echo(false); - read_from_stdin(_("Enter your password: "), result, 64); - if (changed) - set_echo(true); - - // Newline was not added by pressing Enter because ECHO was disabled, so add it now. - puts(""); - single_plugin_settings["Password"] = std::string(result); - } - } -} - -/* Reports the crash with corresponding crash_id over DBus. */ -int report(const char *crash_id, int flags) -{ - int old_logmode = logmode; - if (flags & CLI_REPORT_SILENT_IF_NOT_FOUND) - logmode = 0; - // Ask for an initial report. - map_crash_data_t cr = call_CreateReport(crash_id); - logmode = old_logmode; - if (cr.size() == 0) - { - return -1; - } - - /* Open text editor and give a chance to review the backtrace etc. */ - if (!(flags & CLI_REPORT_BATCH)) - { - create_fields_for_editor(cr); - int result = run_report_editor(cr); - if (result != 0) - return result; - } - - /* Get enabled reporters associated with this particular crash. */ - vector_string_t reporters = get_enabled_reporters(cr); - - int errors = 0; - int plugins = 0; - if (flags & CLI_REPORT_BATCH) - { - map_map_string_t reporters_settings; /* to be filled on the next line */ - get_reporter_plugin_settings(reporters, reporters_settings, false); - - puts(_("Reporting...")); - report_status_t r = call_Report(cr, reporters, reporters_settings); - report_status_t::iterator it = r.begin(); - while (it != r.end()) - { - vector_string_t &v = it->second; - printf("%s: %s\n", it->first.c_str(), v[REPORT_STATUS_IDX_MSG].c_str()); - plugins++; - if (v[REPORT_STATUS_IDX_FLAG] == "0") - errors++; - it++; - } - } - else - { - /* For every reporter, ask if user really wants to report using it. */ - for (vector_string_t::const_iterator it = reporters.begin(); it != reporters.end(); ++it) - { - char question[255]; - snprintf(question, 255, _("Report using %s? [y/N]: "), it->c_str()); - if (!ask_yesno(question)) - { - puts(_("Skipping...")); - continue; - } - - vector_string_t cur_reporter(1, *it); - map_map_string_t reporters_settings; /* to be filled on the next line */ - get_reporter_plugin_settings(cur_reporter, reporters_settings, true); - report_status_t r = call_Report(cr, cur_reporter, reporters_settings); - assert(r.size() == 1); /* one reporter --> one report status */ - vector_string_t &v = r.begin()->second; - printf("%s: %s\n", r.begin()->first.c_str(), v[REPORT_STATUS_IDX_MSG].c_str()); - plugins++; - if (v[REPORT_STATUS_IDX_FLAG] == "0") - errors++; - } - } - - printf(_("Crash reported via %d plugins (%d errors)\n"), plugins, errors); - return errors != 0; -} diff --git a/src/CLI/report.h b/src/CLI/report.h deleted file mode 100644 index 8a851b8e..00000000 --- a/src/CLI/report.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#ifndef ABRT_CLI_REPORT_H -#define ABRT_CLI_REPORT_H - -/* Reports the crash with corresponding uuid over DBus. */ -enum { - CLI_REPORT_BATCH = 1 << 0, - CLI_REPORT_SILENT_IF_NOT_FOUND = 1 << 1, -}; -int report(const char *uuid, int flags); - -#endif diff --git a/src/CLI/run-command.cpp b/src/CLI/run-command.cpp deleted file mode 100644 index 1f6836d6..00000000 --- a/src/CLI/run-command.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#include "run-command.h" -#include "abrtlib.h" - -/* - Inspired by git code. - http://git.kernel.org/?p=git/git.git;a=blob;f=run-command.c;hb=HEAD -*/ - -static pid_t start_command(char **argv) -{ - pid_t pid = vfork(); - if (pid < 0) - { - perror_msg_and_die("vfork"); - } - if (pid == 0) - { // new process - execvp(argv[0], argv); - exit(127); - } - return pid; -} - -static int finish_command(pid_t pid, char **argv) -{ - pid_t waiting; - int status; - while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) - continue; - if (waiting < 0) - perror_msg_and_die("waitpid"); - - int code = -1; - if (WIFSIGNALED(status)) - { - code = WTERMSIG(status); - error_msg("'%s' killed by signal %d", argv[0], code); - code += 128; /* shells use this convention for deaths by signal */ - } - else /* if (WIFEXITED(status)) */ - { - code = WEXITSTATUS(status); - if (code == 127) - { - error_msg_and_die("Can't run '%s'", argv[0]); - } - } - - return code; -} - -int run_command(char **argv) -{ - pid_t pid = start_command(argv); - return finish_command(pid, argv); -} diff --git a/src/CLI/run-command.h b/src/CLI/run-command.h deleted file mode 100644 index 71391579..00000000 --- a/src/CLI/run-command.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#ifndef ABRT_CLI_RUN_COMMAND_H -#define ABRT_CLI_RUN_COMMAND_H - -int run_command(char **argv); - -#endif diff --git a/src/Daemon/CommLayerServer.cpp b/src/Daemon/CommLayerServer.cpp deleted file mode 100644 index 5e250121..00000000 --- a/src/Daemon/CommLayerServer.cpp +++ /dev/null @@ -1,29 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 RedHat Inc - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#include "CommLayerServer.h" -#include "CrashWatcher.h" - -CCommLayerServer::CCommLayerServer() -{ - m_init_error = 0; -} - -CCommLayerServer::~CCommLayerServer() -{ -} diff --git a/src/Daemon/CommLayerServer.h b/src/Daemon/CommLayerServer.h deleted file mode 100644 index c6bf71ee..00000000 --- a/src/Daemon/CommLayerServer.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 RedHat Inc - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#ifndef COMMLAYERSERVER_H_ -#define COMMLAYERSERVER_H_ - -#include "abrtlib.h" -#include "crash_types.h" - -class CCommLayerServer { - public: - int m_init_error; - - CCommLayerServer(); - virtual ~CCommLayerServer(); - - /* just stubs to be called when not implemented in specific comm layer */ - virtual void Crash(const char *package_name, const char* crash_id, const char *uid_str) {} - virtual void JobDone(const char* peer) = 0; - virtual void QuotaExceed(const char* str) {} - - virtual void Update(const char* pMessage, const char* peer) {}; - virtual void Warning(const char* pMessage, const char* peer) {}; -}; - -#endif diff --git a/src/Daemon/CommLayerServerDBus.cpp b/src/Daemon/CommLayerServerDBus.cpp deleted file mode 100644 index 4b11fc04..00000000 --- a/src/Daemon/CommLayerServerDBus.cpp +++ /dev/null @@ -1,624 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 RedHat Inc - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#include -#include "abrtlib.h" -#include "abrt_dbus.h" -#include "abrt_exception.h" -#include "CrashWatcher.h" -#include "Settings.h" -#include "Daemon.h" -#include "CommLayerServerDBus.h" - -// 16kB message limit -#define LIMIT_MESSAGE 16384 - -#if HAVE_CONFIG_H - #include -#endif -#if ENABLE_NLS - #include - #define _(S) gettext(S) -#else - #define _(S) (S) -#endif - -/* - * DBus signal emitters - */ - -/* helpers */ -static DBusMessage* new_signal_msg(const char* member, const char* peer = NULL) -{ - /* path, interface, member name */ - DBusMessage* msg = dbus_message_new_signal(ABRTD_DBUS_PATH, ABRTD_DBUS_IFACE, member); - if (!msg) - die_out_of_memory(); - /* Send unicast dbus signal if peer is known */ - if (peer && !dbus_message_set_destination(msg, peer)) - die_out_of_memory(); - return msg; -} -static void send_flush_and_unref(DBusMessage* msg) -{ - if (!dbus_connection_send(g_dbus_conn, msg, NULL /* &serial */)) - error_msg_and_die("Error sending DBus message"); - dbus_connection_flush(g_dbus_conn); - VERB3 log("DBus message sent"); - dbus_message_unref(msg); -} - -/* Notify the clients (UI) about a new crash */ -void CCommLayerServerDBus::Crash(const char *package_name, - const char* crash_id, - const char *uid_str) -{ - DBusMessage* msg = new_signal_msg("Crash"); - if (uid_str) - { - dbus_message_append_args(msg, - DBUS_TYPE_STRING, &package_name, - DBUS_TYPE_STRING, &crash_id, - DBUS_TYPE_STRING, &uid_str, - DBUS_TYPE_INVALID); - VERB2 log("Sending signal Crash('%s','%s','%s')", package_name, crash_id, uid_str); - } - else - { - dbus_message_append_args(msg, - DBUS_TYPE_STRING, &package_name, - DBUS_TYPE_STRING, &crash_id, - DBUS_TYPE_INVALID); - VERB2 log("Sending signal Crash('%s','%s')", package_name, crash_id); - } - send_flush_and_unref(msg); -} - -void CCommLayerServerDBus::QuotaExceed(const char* str) -{ - DBusMessage* msg = new_signal_msg("QuotaExceed"); - dbus_message_append_args(msg, - DBUS_TYPE_STRING, &str, - DBUS_TYPE_INVALID); - VERB2 log("Sending signal QuotaExceed('%s')", str); - send_flush_and_unref(msg); -} - -void CCommLayerServerDBus::JobDone(const char* peer) -{ - DBusMessage* msg = new_signal_msg("JobDone", peer); - VERB2 log("Sending signal JobDone() to peer %s", peer); - send_flush_and_unref(msg); -} - -void CCommLayerServerDBus::Update(const char* pMessage, const char* peer) -{ - DBusMessage* msg = new_signal_msg("Update", peer); - dbus_message_append_args(msg, - DBUS_TYPE_STRING, &pMessage, - DBUS_TYPE_INVALID); - send_flush_and_unref(msg); -} - -void CCommLayerServerDBus::Warning(const char* pMessage, const char* peer) -{ - DBusMessage* msg = new_signal_msg("Warning", peer); - dbus_message_append_args(msg, - DBUS_TYPE_STRING, &pMessage, - DBUS_TYPE_INVALID); - send_flush_and_unref(msg); -} - - -/* - * DBus call handlers - */ - -static long get_remote_uid(DBusMessage* call, const char** ppSender = NULL) -{ - DBusError err; - dbus_error_init(&err); - const char* sender = dbus_message_get_sender(call); - if (ppSender) - *ppSender = sender; - long uid = dbus_bus_get_unix_user(g_dbus_conn, sender, &err); - if (dbus_error_is_set(&err)) - { - dbus_error_free(&err); - error_msg("Can't determine remote uid, assuming 0"); - return 0; - } - return uid; -} - -static int handle_GetCrashInfos(DBusMessage* call, DBusMessage* reply) -{ - long unix_uid = get_remote_uid(call); - vector_map_crash_data_t argout1 = GetCrashInfos(unix_uid); - - DBusMessageIter out_iter; - dbus_message_iter_init_append(reply, &out_iter); - store_val(&out_iter, argout1); - - send_flush_and_unref(reply); - return 0; -} - -static int handle_StartJob(DBusMessage* call, DBusMessage* reply) -{ - int r; - DBusMessageIter in_iter; - dbus_message_iter_init(call, &in_iter); - const char* crash_id; - r = load_val(&in_iter, crash_id); - if (r != ABRT_DBUS_MORE_FIELDS) - { - error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; - } - int32_t force; - r = load_val(&in_iter, force); - if (r != ABRT_DBUS_LAST_FIELD) - { - error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; - } - - const char* sender; - long unix_uid = get_remote_uid(call, &sender); - if (CreateReportThread(crash_id, unix_uid, force, sender) != 0) - return -1; /* can't create thread (err msg is already logged) */ - - send_flush_and_unref(reply); - return 0; -} - -static int handle_CreateReport(DBusMessage* call, DBusMessage* reply) -{ - int r; - DBusMessageIter in_iter; - dbus_message_iter_init(call, &in_iter); - const char* crash_id; - r = load_val(&in_iter, crash_id); - if (r != ABRT_DBUS_LAST_FIELD) - { - error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; - } - - long unix_uid = get_remote_uid(call); - map_crash_data_t report; - CreateReport(crash_id, unix_uid, /*force:*/ 0, report); - - DBusMessageIter out_iter; - dbus_message_iter_init_append(reply, &out_iter); - store_val(&out_iter, report); - - send_flush_and_unref(reply); - return 0; -} - -static int handle_Report(DBusMessage* call, DBusMessage* reply) -{ - int r; - DBusMessageIter in_iter; - dbus_message_iter_init(call, &in_iter); - - map_crash_data_t argin1; - r = load_val(&in_iter, argin1); - if (r != ABRT_DBUS_MORE_FIELDS) - { - error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; - } - const char* comment = get_crash_data_item_content_or_NULL(argin1, FILENAME_COMMENT) ? : ""; - const char* reproduce = get_crash_data_item_content_or_NULL(argin1, FILENAME_REPRODUCE) ? : ""; - const char* errmsg = NULL; - if (strlen(comment) > LIMIT_MESSAGE) - { - errmsg = _("Comment is too long"); - } - else if (strlen(reproduce) > LIMIT_MESSAGE) - { - errmsg = _("'How to reproduce' is too long"); - } - if (errmsg) - { - dbus_message_unref(reply); - reply = dbus_message_new_error(call, DBUS_ERROR_FAILED, errmsg); - if (!reply) - die_out_of_memory(); - send_flush_and_unref(reply); - return 0; - } - - /* Second parameter: list of reporters to use */ - vector_string_t reporters; - r = load_val(&in_iter, reporters); - if (r == ABRT_DBUS_ERROR) - { - error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; - } - - /* Third parameter (optional): configuration data for plugins */ - map_map_string_t user_conf_data; - if (r == ABRT_DBUS_MORE_FIELDS) - { - r = load_val(&in_iter, user_conf_data); - if (r != ABRT_DBUS_LAST_FIELD) - { - error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; - } - } - -#if 0 - //const char * sender = dbus_message_get_sender(call); - if (!user_conf_data.empty()) - { - map_map_string_t::const_iterator it_user_conf_data = user_conf_data.begin(); - for (; it_user_conf_data != user_conf_data.end(); it_user_conf_data++) - { - //std::string PluginName = it_user_conf_data->first; - log("plugin:'%s'", it_user_conf_data->first.c_str()); - map_string_t::const_iterator it_plugin_config; - for (it_plugin_config = it_user_conf_data->second.begin(); - it_plugin_config != it_user_conf_data->second.end(); - it_plugin_config++) - { - log("key:'%s' val:'%s'", it_plugin_config->first.c_str(), it_plugin_config->second.c_str()); - } - // this would overwrite the default settings - //g_pPluginManager->SetPluginSettings(PluginName, sender, plugin_settings); - } - } -#endif - - long unix_uid = get_remote_uid(call); - report_status_t argout1; - try - { - argout1 = Report(argin1, reporters, user_conf_data, unix_uid); - } - catch (CABRTException &e) - { - dbus_message_unref(reply); - reply = dbus_message_new_error(call, DBUS_ERROR_FAILED, e.what()); - if (!reply) - die_out_of_memory(); - send_flush_and_unref(reply); - return 0; - } - - DBusMessageIter out_iter; - dbus_message_iter_init_append(reply, &out_iter); - store_val(&out_iter, argout1); - - send_flush_and_unref(reply); - return 0; -} - -static int handle_DeleteDebugDump(DBusMessage* call, DBusMessage* reply) -{ - int r; - DBusMessageIter in_iter; - dbus_message_iter_init(call, &in_iter); - const char* crash_id; - r = load_val(&in_iter, crash_id); - if (r != ABRT_DBUS_LAST_FIELD) - { - error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; - } - - long unix_uid = get_remote_uid(call); - int32_t result = DeleteDebugDump(crash_id, unix_uid); - - DBusMessageIter out_iter; - dbus_message_iter_init_append(reply, &out_iter); - store_val(&out_iter, result); - - send_flush_and_unref(reply); - return 0; -} - -static int handle_GetPluginsInfo(DBusMessage* call, DBusMessage* reply) -{ - DBusMessageIter out_iter; - dbus_message_iter_init_append(reply, &out_iter); - store_val(&out_iter, g_pPluginManager->GetPluginsInfo()); - - send_flush_and_unref(reply); - return 0; -} - -static int handle_GetPluginSettings(DBusMessage* call, DBusMessage* reply) -{ - int r; - DBusMessageIter in_iter; - dbus_message_iter_init(call, &in_iter); - const char* PluginName; - r = load_val(&in_iter, PluginName); - if (r != ABRT_DBUS_LAST_FIELD) - { - error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; - } - - //long unix_uid = get_remote_uid(call); - //VERB1 log("got %s('%s') call from uid %ld", "GetPluginSettings", PluginName, unix_uid); - map_plugin_settings_t plugin_settings = g_pPluginManager->GetPluginSettings(PluginName); //, to_string(unix_uid).c_str()); - - DBusMessageIter out_iter; - dbus_message_iter_init_append(reply, &out_iter); - store_val(&out_iter, plugin_settings); - - send_flush_and_unref(reply); - return 0; -} - -static int handle_SetPluginSettings(DBusMessage* call, DBusMessage* reply) -{ - int r; - DBusMessageIter in_iter; - dbus_message_iter_init(call, &in_iter); - std::string PluginName; - r = load_val(&in_iter, PluginName); - if (r != ABRT_DBUS_MORE_FIELDS) - { - error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; - } - map_plugin_settings_t plugin_settings; - r = load_val(&in_iter, plugin_settings); - if (r != ABRT_DBUS_LAST_FIELD) - { - error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; - } - - long unix_uid = get_remote_uid(call); - VERB1 log("got %s('%s',...) call from uid %ld", "SetPluginSettings", PluginName.c_str(), unix_uid); - /* Disabled, as we don't use it, we use only temporary user settings while reporting - this method should be used to change the default setting and thus should - be protected by polkit - */ - //FIXME: protect with polkit -// g_pPluginManager->SetPluginSettings(PluginName, to_string(unix_uid), plugin_settings); - - send_flush_and_unref(reply); - return 0; -} - -#ifdef PLUGIN_DYNAMIC_LOAD_UNLOAD -static int handle_RegisterPlugin(DBusMessage* call, DBusMessage* reply) -{ - int r; - DBusMessageIter in_iter; - dbus_message_iter_init(call, &in_iter); - const char* PluginName; - r = load_val(&in_iter, PluginName); - if (r != ABRT_DBUS_LAST_FIELD) - { - error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; - } - - const char * sender = dbus_message_get_sender(call); - g_pPluginManager->RegisterPluginDBUS(PluginName, sender); - - send_flush_and_unref(reply); - return 0; -} - -static int handle_UnRegisterPlugin(DBusMessage* call, DBusMessage* reply) -{ - int r; - DBusMessageIter in_iter; - dbus_message_iter_init(call, &in_iter); - const char* PluginName; - r = load_val(&in_iter, PluginName); - if (r != ABRT_DBUS_LAST_FIELD) - { - error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; - } - - const char * sender = dbus_message_get_sender(call); - g_pPluginManager->UnRegisterPluginDBUS(PluginName, sender); - - send_flush_and_unref(reply); - return 0; -} -#endif - -static int handle_GetSettings(DBusMessage* call, DBusMessage* reply) -{ - map_abrt_settings_t result = GetSettings(); - - DBusMessageIter out_iter; - dbus_message_iter_init_append(reply, &out_iter); - store_val(&out_iter, result); - - send_flush_and_unref(reply); - return 0; -} - -static int handle_SetSettings(DBusMessage* call, DBusMessage* reply) -{ - int r; - DBusMessageIter in_iter; - dbus_message_iter_init(call, &in_iter); - map_abrt_settings_t param1; - r = load_val(&in_iter, param1); - if (r != ABRT_DBUS_LAST_FIELD) - { - error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; - } - - const char * sender = dbus_message_get_sender(call); - SetSettings(param1, sender); - - send_flush_and_unref(reply); - return 0; -} - - -/* - * Glib integration machinery - */ - -/* Callback: "a message is received to a registered object path" */ -static DBusHandlerResult message_received(DBusConnection* conn, DBusMessage* msg, void* data) -{ - const char* member = dbus_message_get_member(msg); - VERB1 log("%s(method:'%s')", __func__, member); - - set_client_name(dbus_message_get_sender(msg)); - - DBusMessage* reply = dbus_message_new_method_return(msg); - int r = -1; - if (strcmp(member, "GetCrashInfos") == 0) - r = handle_GetCrashInfos(msg, reply); - else if (strcmp(member, "StartJob") == 0) - r = handle_StartJob(msg, reply); - else if (strcmp(member, "Report") == 0) - r = handle_Report(msg, reply); - else if (strcmp(member, "DeleteDebugDump") == 0) - r = handle_DeleteDebugDump(msg, reply); - else if (strcmp(member, "CreateReport") == 0) - r = handle_CreateReport(msg, reply); - else if (strcmp(member, "GetPluginsInfo") == 0) - r = handle_GetPluginsInfo(msg, reply); - else if (strcmp(member, "GetPluginSettings") == 0) - r = handle_GetPluginSettings(msg, reply); - else if (strcmp(member, "SetPluginSettings") == 0) - r = handle_SetPluginSettings(msg, reply); -#ifdef PLUGIN_DYNAMIC_LOAD_UNLOAD - else if (strcmp(member, "RegisterPlugin") == 0) - r = handle_RegisterPlugin(msg, reply); - else if (strcmp(member, "UnRegisterPlugin") == 0) - r = handle_UnRegisterPlugin(msg, reply); -#endif - else if (strcmp(member, "GetSettings") == 0) - r = handle_GetSettings(msg, reply); - else if (strcmp(member, "SetSettings") == 0) - r = handle_SetSettings(msg, reply); -// NB: C++ binding also handles "Introspect" method, which returns a string. -// It was sending "dummy" introspection answer whick looks like this: -// "\n" -// "\n" -// "\n" -// Apart from a warning from abrt-gui, just sending error back works as well. -// NB2: we may want to handle "Disconnected" here too. - - if (r < 0) - { - /* handle_XXX experienced an error (and did not send any reply) */ - dbus_message_unref(reply); - if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_CALL) - { - /* Create and send error reply */ - reply = dbus_message_new_error(msg, DBUS_ERROR_FAILED, "not supported"); - if (!reply) - die_out_of_memory(); - send_flush_and_unref(reply); - } - } - - set_client_name(NULL); - - return DBUS_HANDLER_RESULT_HANDLED; -} - -static void handle_dbus_err(bool error_flag, DBusError *err) -{ - if (dbus_error_is_set(err)) - { - error_msg("dbus error: %s", err->message); - /* dbus_error_free(&err); */ - error_flag = true; - } - if (!error_flag) - return; - error_msg_and_die( - "Error requesting DBus name %s, possible reasons: " - "abrt run by non-root; dbus config is incorrect; " - "or dbus daemon needs to be restarted to reload dbus config", - ABRTD_DBUS_NAME); -} - -CCommLayerServerDBus::CCommLayerServerDBus() -{ - DBusConnection* conn; - DBusError err; - - dbus_error_init(&err); - VERB3 log("dbus_bus_get"); - conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); - handle_dbus_err(conn == NULL, &err); - // dbus api says: - // "If dbus_bus_get obtains a new connection object never before returned - // from dbus_bus_get(), it will call dbus_connection_set_exit_on_disconnect(), - // so the application will exit if the connection closes. You can undo this - // by calling dbus_connection_set_exit_on_disconnect() yourself after you get - // the connection." - // ... - // "When a connection is disconnected, you are guaranteed to get a signal - // "Disconnected" from the interface DBUS_INTERFACE_LOCAL, path DBUS_PATH_LOCAL" - // - // dbus-daemon drops connections if it recvs a malformed message - // (we actually observed this when we sent bad UTF-8 string). - // Currently, in this case abrtd just exits with exitcode 1. - // (symptom: last two log messages are "abrtd: remove_watch()") - // If we want to have better logging or other nontrivial handling, - // here we need to do: - // - //dbus_connection_set_exit_on_disconnect(conn, FALSE); - //dbus_connection_add_filter(conn, handle_message, NULL, NULL); - // - // and need to code up handle_message to check for "Disconnected" dbus signal - - /* Also sets g_dbus_conn to conn. */ - attach_dbus_conn_to_glib_main_loop(conn, "/com/redhat/abrt", message_received); - - VERB3 log("dbus_bus_request_name"); - int rc = dbus_bus_request_name(conn, ABRTD_DBUS_NAME, DBUS_NAME_FLAG_REPLACE_EXISTING, &err); -//maybe check that r == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER instead? - handle_dbus_err(rc < 0, &err); - VERB3 log("dbus init done"); - - /* dbus_bus_request_name can already read some data. For example, - * if we were autostarted, the call which caused autostart arrives - * at this moment. Thus while dbus fd hasn't any data anymore, - * dbus library can buffer a message or two. - * If we don't do this, the data won't be processed - * until next dbus data arrives. - */ - int cnt = 10; - while (dbus_connection_dispatch(conn) != DBUS_DISPATCH_COMPLETE && --cnt) - VERB3 log("processed initial buffered dbus message"); -} - -CCommLayerServerDBus::~CCommLayerServerDBus() -{ - dbus_connection_unref(g_dbus_conn); -} diff --git a/src/Daemon/CommLayerServerDBus.h b/src/Daemon/CommLayerServerDBus.h deleted file mode 100644 index 7ccad083..00000000 --- a/src/Daemon/CommLayerServerDBus.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 RedHat Inc - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#ifndef COMMLAYERSERVERDBUS_H_ -#define COMMLAYERSERVERDBUS_H_ - -#include "CommLayerServer.h" - -class CCommLayerServerDBus -: public CCommLayerServer -{ - public: - CCommLayerServerDBus(); - virtual ~CCommLayerServerDBus(); - - /* DBus signal senders */ - virtual void Crash(const char *package_name, - const char *crash_id, - const char *uid_str); - virtual void JobDone(const char* peer); - virtual void QuotaExceed(const char* str); - - virtual void Update(const char* pMessage, const char* peer); - virtual void Warning(const char* pMessage, const char* peer); -}; - -#endif diff --git a/src/Daemon/CrashWatcher.cpp b/src/Daemon/CrashWatcher.cpp deleted file mode 100644 index dad43815..00000000 --- a/src/Daemon/CrashWatcher.cpp +++ /dev/null @@ -1,236 +0,0 @@ -/* - Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#include "abrtlib.h" -#include "Daemon.h" -#include "abrt_exception.h" -#include "debug_dump.h" -#include "CrashWatcher.h" - -void CCrashWatcher::Status(const char *pMessage, const char* peer) -{ - VERB1 log("Update('%s'): %s", peer, pMessage); - if (g_pCommLayer != NULL) - g_pCommLayer->Update(pMessage, peer); -} - -void CCrashWatcher::Warning(const char *pMessage, const char* peer) -{ - VERB1 log("Warning('%s'): %s", peer, pMessage); - if (g_pCommLayer != NULL) - g_pCommLayer->Warning(pMessage, peer); -} - -CCrashWatcher::CCrashWatcher() -{ -} - -CCrashWatcher::~CCrashWatcher() -{ -} - -vector_map_crash_data_t GetCrashInfos(long caller_uid) -{ - vector_map_crash_data_t retval; - log("Getting crash infos..."); - try - { - vector_string_t crash_ids; - GetUUIDsOfCrash(caller_uid, crash_ids); - - unsigned int ii; - for (ii = 0; ii < crash_ids.size(); ii++) - { - const char *crash_id = crash_ids[ii].c_str(); - - map_crash_data_t info; - mw_result_t res = FillCrashInfo(crash_id, info); - switch (res) - { - case MW_OK: - retval.push_back(info); - break; - case MW_ERROR: - error_msg("Dump directory for crash_id %s doesn't exist or misses crucial files, deleting", crash_id); - /* Deletes both DB record and dump dir */ - DeleteDebugDump(crash_id, /*caller_uid:*/ 0); - break; - default: - break; - } - } - } - catch (CABRTException& e) - { - error_msg("%s", e.what()); - } - - return retval; -} - -/* - * Called in two cases: - * (1) by StartJob dbus call -> CreateReportThread(), in the thread - * (2) by CreateReport dbus call - * In the second case, it finishes quickly, because previous - * StartJob dbus call already did all the processing, and we just retrieve - * the result from dump directory, which is fast. - */ -void CreateReport(const char* crash_id, long caller_uid, int force, map_crash_data_t& crashReport) -{ - /* FIXME: starting from here, any shared data must be protected with a mutex. - * For example, CreateCrashReport does: - * g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); - * which is unsafe wrt concurrent updates to g_pPluginManager state. - */ - mw_result_t res = CreateCrashReport(crash_id, caller_uid, force, crashReport); - switch (res) - { - case MW_OK: - VERB2 log_map_crash_data(crashReport, "crashReport"); - break; - case MW_IN_DB_ERROR: - error_msg("Can't find crash with id %s in database", crash_id); - break; - case MW_PLUGIN_ERROR: - error_msg("Particular analyzer plugin isn't loaded or there is an error within plugin(s)"); - break; - default: - error_msg("Corrupted crash with id %s, deleting", crash_id); - DeleteDebugDump(crash_id, /*caller_uid:*/ 0); - break; - } -} - -typedef struct thread_data_t { - pthread_t thread_id; - long caller_uid; - int force; - char* crash_id; - char* peer; -} thread_data_t; -static void* create_report(void* arg) -{ - thread_data_t *thread_data = (thread_data_t *) arg; - - /* Client name is per-thread, need to set it */ - set_client_name(thread_data->peer); - - try - { - log("Creating report..."); - map_crash_data_t crashReport; - CreateReport(thread_data->crash_id, thread_data->caller_uid, thread_data->force, crashReport); - g_pCommLayer->JobDone(thread_data->peer); - } - catch (CABRTException& e) - { - error_msg("%s", e.what()); - } - catch (...) {} - set_client_name(NULL); - - /* free strduped strings */ - free(thread_data->crash_id); - free(thread_data->peer); - free(thread_data); - - /* Bogus value. pthreads require us to return void* */ - return NULL; -} -int CreateReportThread(const char* crash_id, long caller_uid, int force, const char* pSender) -{ - thread_data_t *thread_data = (thread_data_t *)xzalloc(sizeof(thread_data_t)); - thread_data->crash_id = xstrdup(crash_id); - thread_data->caller_uid = caller_uid; - thread_data->force = force; - thread_data->peer = xstrdup(pSender); - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - int r = pthread_create(&thread_data->thread_id, &attr, create_report, thread_data); - pthread_attr_destroy(&attr); - if (r != 0) - { - free(thread_data->crash_id); - free(thread_data->peer); - free(thread_data); - /* The only reason this may happen is system-wide resource starvation, - * or ulimit is exceeded (someone floods us with CreateReport() dbus calls?) - */ - error_msg("Can't create thread"); - return r; - } - VERB3 log("Thread %llx created", (unsigned long long)thread_data->thread_id); - return r; -} - - -/* Remove dump dir and its DB record */ -int DeleteDebugDump(const char *crash_id, long caller_uid) -{ - try - { - CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); - database->Connect(); - database_row_t row = database->GetRow(crash_id); - if (row.m_sUUID == "") - { - database->DisConnect(); - return ENOENT; - } - if (caller_uid != 0 /* not called by root */ - && row.m_sInformAll != "1" - && to_string(caller_uid) != row.m_sUID - ) { - database->DisConnect(); - return EPERM; - } - database->DeleteRow(crash_id); - database->DisConnect(); - const char *dump_dir = row.m_sDebugDumpDir.c_str(); - if (dump_dir[0] != '\0') - { - delete_debug_dump_dir(dump_dir); - return 0; /* success */ - } - } - catch (CABRTException& e) - { - error_msg("%s", e.what()); - } - return EIO; /* generic failure code */ -} - -void DeleteDebugDump_by_dir(const char *dump_dir) -{ - try - { - CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); - database->Connect(); - database->DeleteRows_by_dir(dump_dir); - database->DisConnect(); - - delete_debug_dump_dir(dump_dir); - } - catch (CABRTException& e) - { - error_msg("%s", e.what()); - } -} diff --git a/src/Daemon/CrashWatcher.h b/src/Daemon/CrashWatcher.h deleted file mode 100644 index 44c04137..00000000 --- a/src/Daemon/CrashWatcher.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#ifndef CRASHWATCHER_H_ -#define CRASHWATCHER_H_ - -#include -#include -#include -#include -#include "MiddleWare.h" -#include "Settings.h" - -#include "CommLayerServerDBus.h" -#include "comm_layer_inner.h" - - -class CCrashWatcher -: public CObserver -{ - public: - CCrashWatcher(); - virtual ~CCrashWatcher(); - - public: - /* Observer methods */ - virtual void Status(const char *pMessage, const char* peer); - virtual void Warning(const char *pMessage, const char* peer); -}; - -vector_map_crash_data_t GetCrashInfos(long caller_uid); -int CreateReportThread(const char* crash_id, long caller_uid, int force, const char* pSender); -void CreateReport(const char* crash_id, long caller_uid, int force, map_crash_data_t&); -int DeleteDebugDump(const char *crash_id, long caller_uid); -void DeleteDebugDump_by_dir(const char *dump_dir); - -#endif diff --git a/src/Daemon/Daemon.cpp b/src/Daemon/Daemon.cpp deleted file mode 100644 index 735da5af..00000000 --- a/src/Daemon/Daemon.cpp +++ /dev/null @@ -1,1000 +0,0 @@ -/* - Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#include -#include -#include /* res_init */ -#include -#include -#include /* ioctl(FIONREAD) */ -#include -#include -#include -#if HAVE_CONFIG_H - #include -#endif -#if HAVE_LOCALE_H - #include -#endif -#if ENABLE_NLS - #include - #define _(S) gettext(S) -#else - #define _(S) (S) -#endif -#include "abrtlib.h" -#include "abrt_exception.h" -#include "CrashWatcher.h" -#include "debug_dump.h" -#include "Daemon.h" -#include "dumpsocket.h" -#include "rpm.h" - -using namespace std; - - -/* Daemon initializes, then sits in glib main loop, waiting for events. - * Events can be: - * - inotify: something new appeared under /var/spool/abrt - * - DBus: dbus message arrived - * - signal: we got SIGTERM or SIGINT - * - * DBus methods we have: - * - GetCrashInfos(): returns a vector_map_crash_data_t (vector_map_vector_string_t) - * of crashes for given uid - * v[N]["executable"/"uid"/"kernel"/"backtrace"][N] = "contents" - * - StartJob(crash_id,force): starts creating a report for /var/spool/abrt/DIR with this UID:UUID. - * Returns job id (uint64). - * After thread returns, when report creation thread has finished, - * JobDone() dbus signal is emitted. - * - CreateReport(crash_id): returns map_crash_data_t (map_vector_string_t) - * - Report(map_crash_data_t (map_vector_string_t[, map_map_string_t])): - * "Please report this crash": calls Report() of all registered reporter plugins. - * Returns report_status_t (map_vector_string_t) - the status of each call. - * 2nd parameter is the contents of user's abrt.conf. - * - DeleteDebugDump(crash_id): delete it from DB and delete corresponding /var/spool/abrt/DIR - * - GetPluginsInfo(): returns map_map_string_t - * map["plugin"] = { "Name": "plugin", "Enabled": "yes" ... } - * - GetPluginSettings(PluginName): returns map_plugin_settings_t (map_string_t) - * - SetPluginSettings(PluginName, map_plugin_settings_t): returns void - * - RegisterPlugin(PluginName): returns void - * - UnRegisterPlugin(PluginName): returns void - * - GetSettings(): returns map_abrt_settings_t (map_map_string_t) - * - SetSettings(map_abrt_settings_t): returns void - * - * DBus signals we emit: - * - Crash(progname, crash_id, uid) - a new crash occurred (new /var/spool/abrt/DIR is found) - * - JobDone(client_dbus_ID) - see StartJob above. - * Sent as unicast to the client which did StartJob. - * - Warning(msg) - * - Update(msg) - * Both are sent as unicast to last client set by set_client_name(name). - * If set_client_name(NULL) was done, they are not sent. - */ - - -#define VAR_RUN_LOCK_FILE VAR_RUN"/abrt/abrtd.lock" -#define VAR_RUN_PIDFILE VAR_RUN"/abrtd.pid" - - -//FIXME: add some struct to be able to join all threads! -typedef struct cron_callback_data_t -{ - std::string m_sPluginName; - std::string m_sPluginArgs; - unsigned int m_nTimeout; - - cron_callback_data_t( - const std::string& pPluginName, - const std::string& pPluginArgs, - const unsigned int& pTimeout) : - m_sPluginName(pPluginName), - m_sPluginArgs(pPluginArgs), - m_nTimeout(pTimeout) - {} -} cron_callback_data_t; - - -static volatile sig_atomic_t s_sig_caught; -static int s_signal_pipe[2]; -static int s_signal_pipe_write = -1; -static int s_upload_watch = -1; -static unsigned s_timeout; -static bool s_exiting; - -CCommLayerServer* g_pCommLayer; - - -static void cron_delete_callback_data_cb(gpointer data) -{ - cron_callback_data_t* cronDeleteCallbackData = static_cast(data); - delete cronDeleteCallbackData; -} - -static gboolean cron_activation_periodic_cb(gpointer data) -{ - cron_callback_data_t* cronPeriodicCallbackData = static_cast(data); - VERB1 log("Activating plugin: %s", cronPeriodicCallbackData->m_sPluginName.c_str()); - RunAction(DEBUG_DUMPS_DIR, - cronPeriodicCallbackData->m_sPluginName.c_str(), - cronPeriodicCallbackData->m_sPluginArgs.c_str() - ); - return TRUE; -} -static gboolean cron_activation_one_cb(gpointer data) -{ - cron_callback_data_t* cronOneCallbackData = static_cast(data); - VERB1 log("Activating plugin: %s", cronOneCallbackData->m_sPluginName.c_str()); - RunAction(DEBUG_DUMPS_DIR, - cronOneCallbackData->m_sPluginName.c_str(), - cronOneCallbackData->m_sPluginArgs.c_str() - ); - return FALSE; -} -static gboolean cron_activation_reshedule_cb(gpointer data) -{ - cron_callback_data_t* cronResheduleCallbackData = static_cast(data); - VERB1 log("Rescheduling plugin: %s", cronResheduleCallbackData->m_sPluginName.c_str()); - cron_callback_data_t* cronPeriodicCallbackData = new cron_callback_data_t(cronResheduleCallbackData->m_sPluginName, - cronResheduleCallbackData->m_sPluginArgs, - cronResheduleCallbackData->m_nTimeout); - g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, - cronPeriodicCallbackData->m_nTimeout, - cron_activation_periodic_cb, - static_cast(cronPeriodicCallbackData), - cron_delete_callback_data_cb - ); - return FALSE; -} - -static int SetUpMW() -{ - set_string_t::iterator it_k = g_settings_setOpenGPGPublicKeys.begin(); - for (; it_k != g_settings_setOpenGPGPublicKeys.end(); it_k++) - { - LoadOpenGPGPublicKey(it_k->c_str()); - } - VERB1 log("Adding actions or reporters"); - vector_pair_string_string_t::iterator it_ar = g_settings_vectorActionsAndReporters.begin(); - for (; it_ar != g_settings_vectorActionsAndReporters.end(); it_ar++) - { - AddActionOrReporter(it_ar->first.c_str(), it_ar->second.c_str()); - } - VERB1 log("Adding analyzers, actions or reporters"); - map_analyzer_actions_and_reporters_t::iterator it_aar = g_settings_mapAnalyzerActionsAndReporters.begin(); - for (; it_aar != g_settings_mapAnalyzerActionsAndReporters.end(); it_aar++) - { - vector_pair_string_string_t::iterator it_ar = it_aar->second.begin(); - for (; it_ar != it_aar->second.end(); it_ar++) - { - AddAnalyzerActionOrReporter(it_aar->first.c_str(), it_ar->first.c_str(), it_ar->second.c_str()); - } - } - return 0; -} - -static int SetUpCron() -{ - map_cron_t::iterator it_c = g_settings_mapCron.begin(); - for (; it_c != g_settings_mapCron.end(); it_c++) - { - std::string::size_type pos = it_c->first.find(":"); - int timeout = 0; - int nH = -1; - int nM = -1; - int nS = -1; - -//TODO: rewrite using good old sscanf? - - if (pos != std::string::npos) - { - std::string sH; - std::string sM; - - sH = it_c->first.substr(0, pos); - nH = xatou(sH.c_str()); - nH = nH > 23 ? 23 : nH; - nH = nH < 0 ? 0 : nH; - timeout += nH * 60 * 60; - sM = it_c->first.substr(pos + 1); - nM = xatou(sM.c_str()); - nM = nM > 59 ? 59 : nM; - nM = nM < 0 ? 0 : nM; - timeout += nM * 60; - } - else - { - std::string sS; - - sS = it_c->first; - nS = xatou(sS.c_str()); - nS = nS <= 0 ? 1 : nS; - timeout = nS; - } - - if (nS != -1) - { - vector_pair_string_string_t::iterator it_ar = it_c->second.begin(); - for (; it_ar != it_c->second.end(); it_ar++) - { - cron_callback_data_t* cronPeriodicCallbackData = new cron_callback_data_t(it_ar->first, it_ar->second, timeout); - g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, - timeout, - cron_activation_periodic_cb, - static_cast(cronPeriodicCallbackData), - cron_delete_callback_data_cb); - } - } - else - { - time_t actTime = time(NULL); - struct tm locTime; - localtime_r(&actTime, &locTime); - locTime.tm_hour = nH; - locTime.tm_min = nM; - locTime.tm_sec = 0; - time_t nextTime = mktime(&locTime); - if (nextTime == ((time_t)-1)) - { - /* paranoia */ - perror_msg("Can't set up cron time"); - return -1; - } - if (actTime > nextTime) - { - timeout = 24*60*60 + (nextTime - actTime); - } - else - { - timeout = nextTime - actTime; - } - vector_pair_string_string_t::iterator it_ar = it_c->second.begin(); - for (; it_ar != it_c->second.end(); it_ar++) - { - cron_callback_data_t* cronOneCallbackData = new cron_callback_data_t(it_ar->first, it_ar->second, timeout); - g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, - timeout, - cron_activation_one_cb, - static_cast(cronOneCallbackData), - cron_delete_callback_data_cb); - cron_callback_data_t* cronResheduleCallbackData = new cron_callback_data_t(it_ar->first, it_ar->second, 24 * 60 * 60); - g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, - timeout, - cron_activation_reshedule_cb, - static_cast(cronResheduleCallbackData), - cron_delete_callback_data_cb); - } - } - } - return 0; -} - -static void FindNewDumps(const char* pPath) -{ - /* Get all debugdump directories in the pPath directory */ - vector_string_t dirs; - DIR *dp = opendir(pPath); - if (dp == NULL) - { - perror_msg("Can't open directory '%s'", pPath); - return; - } - struct dirent *ep; - while ((ep = readdir(dp))) - { - if (dot_or_dotdot(ep->d_name)) - continue; /* skip "." and ".." */ - std::string dname = concat_path_file(pPath, ep->d_name); - struct stat stats; - if (lstat(dname.c_str(), &stats) == 0) - { - if (S_ISDIR(stats.st_mode)) - { - VERB1 log("Will check directory '%s'", ep->d_name); - dirs.push_back(dname); - } - } - } - closedir(dp); - - unsigned size = dirs.size(); - if (size == 0) - return; - log("Checking for unsaved crashes (dirs to check:%u)", size); - - /* Get potentially non-processed debugdumps */ - vector_string_t::iterator itt = dirs.begin(); - for (; itt != dirs.end(); ++itt) - { - try - { - const char *dir_name = itt->c_str(); - map_crash_data_t crashinfo; - mw_result_t res = SaveDebugDump(dir_name, crashinfo); - switch (res) - { - case MW_OK: - /* Not VERB1: this is new, unprocessed crash dump. - * Last abrtd somehow missed it - need to inform user */ - log("Non-processed crash in %s, saving into database", dir_name); - /* Run automatic actions and reporters on it (if we have them configured) */ - RunActionsAndReporters(dir_name); - break; - case MW_IN_DB: - /* This debugdump was found in DB, nothing else was done - * by SaveDebugDump or needs to be done by us */ - VERB1 log("%s is already saved in database", dir_name); - break; - case MW_REPORTED: /* already reported dup */ - case MW_OCCURRED: /* not-yet-reported dup */ - VERB1 log("Duplicate crash %s, deleting", dir_name); - delete_debug_dump_dir(dir_name); - break; - default: - log("Corrupted or bad crash %s (res:%d), deleting", dir_name, (int)res); - delete_debug_dump_dir(dir_name); - break; - } - } - catch (CABRTException& e) - { - error_msg("%s", e.what()); - } - } - log("Done checking for unsaved crashes"); -} - -static int CreatePidFile() -{ - int fd; - - /* JIC */ - unlink(VAR_RUN_PIDFILE); - - /* open the pidfile */ - fd = open(VAR_RUN_PIDFILE, O_WRONLY|O_CREAT|O_EXCL, 0644); - if (fd >= 0) - { - /* write our pid to it */ - char buf[sizeof(long)*3 + 2]; - int len = sprintf(buf, "%lu\n", (long)getpid()); - write(fd, buf, len); - close(fd); - return 0; - } - - /* something went wrong */ - perror_msg("Can't open '%s'", VAR_RUN_PIDFILE); - return -1; -} - -static int Lock() -{ - int lfd = open(VAR_RUN_LOCK_FILE, O_RDWR|O_CREAT, 0640); - if (lfd < 0) - { - perror_msg("Can't open '%s'", VAR_RUN_LOCK_FILE); - return -1; - } - if (lockf(lfd, F_TLOCK, 0) < 0) - { - perror_msg("Can't lock file '%s'", VAR_RUN_LOCK_FILE); - return -1; - } - close_on_exec_on(lfd); - return 0; - /* we leak opened lfd intentionally */ -} - -static void handle_fatal_signal(int signo) -{ - // Enable for debugging only, malloc/printf are unsafe in signal handlers - //VERB3 log("Got signal %d", signo); - - uint8_t l_sig_caught; - s_sig_caught = l_sig_caught = signo; - /* Using local copy of s_sig_caught so that concurrent signal - * won't change it under us */ - if (s_signal_pipe_write >= 0) - write(s_signal_pipe_write, &l_sig_caught, 1); -} - -/* Signal pipe handler */ -static gboolean handle_signal_cb(GIOChannel *gio, GIOCondition condition, gpointer ptr_unused) -{ - char signo; - gsize len = 0; - g_io_channel_read(gio, &signo, 1, &len); - if (len == 1) - { - /* we did receive a signal */ - VERB3 log("Got signal %d through signal pipe", signo); - s_exiting = 1; - return TRUE; - } - return FALSE; -} - -/* Inotify handler */ -static gboolean handle_inotify_cb(GIOChannel *gio, GIOCondition condition, gpointer ptr_unused) -{ - /* Default size: 128 simultaneous actions (about 1/2 meg) */ -#define INOTIFY_BUF_SIZE ((sizeof(struct inotify_event) + FILENAME_MAX)*128) - /* Determine how much to read (it usually is much smaller) */ - /* NB: this variable _must_ be int-sized, ioctl expects that! */ - int inotify_bytes = INOTIFY_BUF_SIZE; - if (ioctl(g_io_channel_unix_get_fd(gio), FIONREAD, &inotify_bytes) != 0 - || inotify_bytes < sizeof(struct inotify_event) - || inotify_bytes > INOTIFY_BUF_SIZE - ) { - inotify_bytes = INOTIFY_BUF_SIZE; - } - VERB3 log("FIONREAD:%d", inotify_bytes); - - char *buf = (char*)xmalloc(inotify_bytes); - errno = 0; - gsize len; - GIOError err = g_io_channel_read(gio, buf, inotify_bytes, &len); - if (err != G_IO_ERROR_NONE) - { - perror_msg("Error reading inotify fd"); - free(buf); - return FALSE; - } - - /* Reconstruct each event and send message to the dbus */ - gsize i = 0; - while (i < len) - { - struct inotify_event *event = (struct inotify_event *) &buf[i]; - const char *name = NULL; - if (event->len) - name = event->name; - //log("i:%d len:%d event->mask:%x IN_ISDIR:%x IN_CLOSE_WRITE:%x event->len:%d", - // i, len, event->mask, IN_ISDIR, IN_CLOSE_WRITE, event->len); - i += sizeof(*event) + event->len; - - if (event->wd == s_upload_watch) - { - /* Was the (presumable newly created) file closed in upload dir, - * or a file moved to upload dir? */ - if (!(event->mask & IN_ISDIR) - && event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO) - && name - ) { - const char *ext = strrchr(name, '.'); - if (ext && strcmp(ext + 1, "working") == 0) - continue; - - const char *dir = g_settings_sWatchCrashdumpArchiveDir.c_str(); - log("Detected creation of file '%s' in upload directory '%s'", name, dir); - if (fork() == 0) - { - xchdir(dir); - execlp("abrt-handle-upload", "abrt-handle-upload", DEBUG_DUMPS_DIR, dir, name, (char*)NULL); - error_msg_and_die("Can't execute '%s'", "abrt-handle-upload"); - } - } - continue; - } - - if (!(event->mask & IN_ISDIR) || !name) - { - /* ignore lock files and such */ - // Happens all the time during normal run - //VERB3 log("File '%s' creation detected, ignoring", name); - continue; - } - if (strcmp(strchrnul(name, '.'), ".new") == 0) - { - //VERB3 log("Directory '%s' creation detected, ignoring", name); - continue; - } - log("Directory '%s' creation detected", name); - - if (g_settings_nMaxCrashReportsSize > 0) - { - std::string worst_dir; - while (g_settings_nMaxCrashReportsSize > 0 - && get_dirsize_find_largest_dir(DEBUG_DUMPS_DIR, &worst_dir, name) / (1024*1024) >= g_settings_nMaxCrashReportsSize - && worst_dir != "" - ) { - log("Size of '%s' >= %u MB, deleting '%s'", DEBUG_DUMPS_DIR, g_settings_nMaxCrashReportsSize, worst_dir.c_str()); - g_pCommLayer->QuotaExceed(_("The size of the report exceeded the quota. Please check system's MaxCrashReportsSize value in abrt.conf.")); - /* deletes both directory and DB record */ - DeleteDebugDump_by_dir(concat_path_file(DEBUG_DUMPS_DIR, worst_dir.c_str()).c_str()); - worst_dir = ""; - } - } - - try - { - std::string fullname = concat_path_file(DEBUG_DUMPS_DIR, name); - /* Note: SaveDebugDump does not save crashinfo, it _fetches_ crashinfo */ - map_crash_data_t crashinfo; - mw_result_t res = SaveDebugDump(fullname.c_str(), crashinfo); - switch (res) - { - case MW_OK: - log("New crash %s, processing", fullname.c_str()); - /* Run automatic actions and reporters on it (if we have them configured) */ - RunActionsAndReporters(fullname.c_str()); - /* Fall through */ - - case MW_REPORTED: /* already reported dup */ - case MW_OCCURRED: /* not-yet-reported dup */ - { - if (res != MW_OK) - { - const char *first = get_crash_data_item_content(crashinfo, CD_DUMPDIR).c_str(); - log("Deleting crash %s (dup of %s), sending dbus signal", - strrchr(fullname.c_str(), '/') + 1, - strrchr(first, '/') + 1); - delete_debug_dump_dir(fullname.c_str()); - } -#define fullname fullname_should_not_be_used_here - - const char *analyzer = get_crash_data_item_content(crashinfo, FILENAME_ANALYZER).c_str(); - const char *uid_str = get_crash_data_item_content(crashinfo, CD_UID).c_str(); - - /* Autoreport it if configured to do so */ - if (res != MW_REPORTED - && analyzer_has_AutoReportUIDs(analyzer, uid_str) - ) { - VERB1 log("Reporting the crash automatically"); - map_crash_data_t crash_report; - string crash_id = ssprintf("%s:%s", uid_str, get_crash_data_item_content(crashinfo, CD_UUID).c_str()); - mw_result_t crash_result = CreateCrashReport( - crash_id.c_str(), - /*caller_uid:*/ 0, - /*force:*/ 0, - crash_report - ); - if (crash_result == MW_OK) - { - map_analyzer_actions_and_reporters_t::const_iterator it = g_settings_mapAnalyzerActionsAndReporters.find(analyzer); - map_analyzer_actions_and_reporters_t::const_iterator end = g_settings_mapAnalyzerActionsAndReporters.end(); - if (it != end) - { - vector_pair_string_string_t keys = it->second; - unsigned size = keys.size(); - for (unsigned ii = 0; ii < size; ii++) - { - autoreport(keys[ii], crash_report); - } - } - } - } - /* Send dbus signal */ - if (analyzer_has_InformAllUsers(analyzer)) - uid_str = NULL; - char *crash_id = xasprintf("%s:%s", - get_crash_data_item_content(crashinfo, CD_UID).c_str(), - get_crash_data_item_content(crashinfo, CD_UUID).c_str() - ); - g_pCommLayer->Crash(get_crash_data_item_content(crashinfo, FILENAME_PACKAGE).c_str(), - crash_id, - uid_str); - free(crash_id); - break; -#undef fullname - } - case MW_IN_DB: - log("Huh, this crash is already in db?! Nothing to do"); - break; - case MW_BLACKLISTED: - case MW_CORRUPTED: - case MW_PACKAGE_ERROR: - case MW_GPG_ERROR: - case MW_FILE_ERROR: - default: - log("Corrupted or bad crash %s (res:%d), deleting", fullname.c_str(), (int)res); - delete_debug_dump_dir(fullname.c_str()); - break; - } - } - catch (CABRTException& e) - { - error_msg("%s", e.what()); - } - catch (...) - { - free(buf); - throw; - } - } /* while */ - - free(buf); - return TRUE; -} - -/* Run main loop with idle timeout. - * Basically, almost like glib's g_main_run(loop) - */ -static void run_main_loop(GMainLoop* loop) -{ - GMainContext *context = g_main_loop_get_context(loop); - time_t old_time = 0; - time_t dns_conf_hash = 0; - - while (!s_exiting) - { - /* we have just a handful of sources, 32 should be ample */ - const unsigned NUM_POLLFDS = 32; - GPollFD fds[NUM_POLLFDS]; - gboolean some_ready; - gint max_priority; - gint timeout; - - some_ready = g_main_context_prepare(context, &max_priority); - if (some_ready) - g_main_context_dispatch(context); - - gint nfds = g_main_context_query(context, max_priority, &timeout, fds, NUM_POLLFDS); - if (nfds > NUM_POLLFDS) - error_msg_and_die("Internal error"); - - if (s_timeout) - alarm(s_timeout); - g_poll(fds, nfds, timeout); - if (s_timeout) - alarm(0); - - /* res_init() makes glibc reread /etc/resolv.conf. - * I'd think libc should be clever enough to do it itself - * at every name resolution attempt, but no... - * We need to guess ourself whether we want to do it. - */ - time_t now = time(NULL) >> 2; - if (old_time != now) /* check once in 4 seconds */ - { - old_time = now; - - time_t hash = 0; - struct stat sb; - if (stat("/etc/resolv.conf", &sb) == 0) - hash = sb.st_mtime; - if (stat("/etc/host.conf", &sb) == 0) - hash += sb.st_mtime; - if (stat("/etc/hosts", &sb) == 0) - hash += sb.st_mtime; - if (stat("/etc/nsswitch.conf", &sb) == 0) - hash += sb.st_mtime; - if (dns_conf_hash != hash) - { - dns_conf_hash = hash; - res_init(); - } - } - - some_ready = g_main_context_check(context, max_priority, fds, nfds); - if (some_ready) - g_main_context_dispatch(context); - } - - g_main_context_unref(context); -} - -static void start_syslog_logging() -{ - /* Open stdin to /dev/null */ - xmove_fd(xopen("/dev/null", O_RDWR), STDIN_FILENO); - /* We must not leave fds 0,1,2 closed. - * Otherwise fprintf(stderr) dumps messages into random fds, etc. */ - xdup2(STDIN_FILENO, STDOUT_FILENO); - xdup2(STDIN_FILENO, STDERR_FILENO); - openlog("abrtd", 0, LOG_DAEMON); - logmode = LOGMODE_SYSLOG; -} - -static void ensure_writable_dir(const char *dir, mode_t mode, const char *user) -{ - struct stat sb; - - if (mkdir(dir, mode) != 0 && errno != EEXIST) - perror_msg_and_die("Can't create '%s'", dir); - if (stat(dir, &sb) != 0 || !S_ISDIR(sb.st_mode)) - error_msg_and_die("'%s' is not a directory", dir); - - struct passwd *pw = getpwnam(user); - if (!pw) - perror_msg_and_die("Can't find user '%s'", user); - - if ((sb.st_uid != pw->pw_uid || sb.st_gid != pw->pw_gid) && chown(dir, pw->pw_uid, pw->pw_gid) != 0) - perror_msg_and_die("Can't set owner %u:%u on '%s'", (unsigned int)pw->pw_uid, (unsigned int)pw->pw_gid, dir); - if ((sb.st_mode & 07777) != mode && chmod(dir, mode) != 0) - perror_msg_and_die("Can't set mode %o on '%s'", mode, dir); -} - -static void sanitize_dump_dir_rights() -{ - /* We can't allow everyone to create dumps: otherwise users can flood - * us with thousands of bogus or malicious dumps */ - /* 07000 bits are setuid, setgit, and sticky, and they must be unset */ - /* 00777 bits are usual "rwxrwxrwx" access rights */ - ensure_writable_dir(DEBUG_DUMPS_DIR, 0755, "abrt"); - /* debuginfo cache */ - ensure_writable_dir(DEBUG_INFO_DIR, 0755, "root"); - /* temp dir */ - ensure_writable_dir(VAR_RUN"/abrt", 0755, "root"); -} - -int main(int argc, char** argv) -{ - bool daemonize = true; - int opt; - int parent_pid = getpid(); - - setlocale(LC_ALL, ""); - -#if ENABLE_NLS - bindtextdomain(PACKAGE, LOCALEDIR); - textdomain(PACKAGE); -#endif - - if (getuid() != 0) - error_msg_and_die("ABRT daemon must be run as root"); - - while ((opt = getopt(argc, argv, "dsvt:")) != -1) - { - unsigned long ul; - - switch (opt) - { - case 'd': - daemonize = false; - break; - case 's': - start_syslog_logging(); - break; - case 'v': - g_verbose++; - break; - case 't': - char *end; - errno = 0; - s_timeout = ul = strtoul(optarg, &end, 0); - if (errno == 0 && *end == '\0' && ul <= INT_MAX) - break; - /* fall through to error */ - default: - error_msg_and_die( - "Usage: abrtd [-dsv] [-t SEC]\n" - "\nOptions:" - "\n\t-d\tDo not daemonize" - "\n\t-s\tLog to syslog even with -d" - "\n\t-t SEC\tExit after SEC seconds of inactivity" - "\n\t-v\tVerbose" - ); - } - } - - msg_prefix = "abrtd: "; /* for log(), error_msg() and such */ - - xpipe(s_signal_pipe); - close_on_exec_on(s_signal_pipe[0]); - close_on_exec_on(s_signal_pipe[1]); - signal(SIGTERM, handle_fatal_signal); - signal(SIGINT, handle_fatal_signal); - if (s_timeout) - signal(SIGALRM, handle_fatal_signal); - - /* Daemonize unless -d */ - if (daemonize) - { - /* forking to background */ - pid_t pid = fork(); - if (pid < 0) - { - perror_msg_and_die("fork"); - } - if (pid > 0) - { - /* Parent */ - /* Wait for child to notify us via SIGTERM that it feels ok */ - int i = 20; /* 2 sec */ - while (s_sig_caught == 0 && --i) - { - usleep(100 * 1000); - } - if (s_sig_caught == SIGTERM) - { - exit(0); - } - if (s_sig_caught) - { - error_msg_and_die("Failed to start: got sig %d", s_sig_caught); - } - error_msg_and_die("Failed to start: timeout waiting for child"); - } - /* Child (daemon) continues */ - setsid(); /* never fails */ - if (g_verbose == 0 && logmode != LOGMODE_SYSLOG) - start_syslog_logging(); - } - - GMainLoop* pMainloop = NULL; - GIOChannel* channel_inotify = NULL; - guint channel_inotify_event_id = 0; - GIOChannel* channel_signal = NULL; - guint channel_signal_event_id = 0; - bool lockfile_created = false; - bool pidfile_created = false; - CCrashWatcher watcher; - - /* Initialization */ - try - { - init_daemon_logging(&watcher); - - VERB1 log("Loading settings"); - if (LoadSettings() != 0) - throw 1; - - VERB1 log("Initializing XML-RPC library"); - xmlrpc_env env; - xmlrpc_env_init(&env); - xmlrpc_client_setup_global_const(&env); - if (env.fault_occurred) - error_msg_and_die("XML-RPC Fault: %s(%d)", env.fault_string, env.fault_code); - - VERB1 log("Initializing rpm library"); - rpm_init(); - - VERB1 log("Creating glib main loop"); - pMainloop = g_main_loop_new(NULL, FALSE); - /* Watching DEBUG_DUMPS_DIR for new files... */ - - VERB1 log("Initializing inotify"); - sanitize_dump_dir_rights(); - errno = 0; - int inotify_fd = inotify_init(); - if (inotify_fd == -1) - perror_msg_and_die("inotify_init failed"); - close_on_exec_on(inotify_fd); - if (inotify_add_watch(inotify_fd, DEBUG_DUMPS_DIR, IN_CREATE | IN_MOVED_TO) < 0) - perror_msg_and_die("inotify_add_watch failed on '%s'", DEBUG_DUMPS_DIR); - if (!g_settings_sWatchCrashdumpArchiveDir.empty()) - { - s_upload_watch = inotify_add_watch(inotify_fd, g_settings_sWatchCrashdumpArchiveDir.c_str(), IN_CLOSE_WRITE|IN_MOVED_TO); - if (s_upload_watch < 0) - perror_msg_and_die("inotify_add_watch failed on '%s'", g_settings_sWatchCrashdumpArchiveDir.c_str()); - } - VERB1 log("Adding inotify watch to glib main loop"); - channel_inotify = g_io_channel_unix_new(inotify_fd); - channel_inotify_event_id = g_io_add_watch(channel_inotify, - G_IO_IN, - handle_inotify_cb, - NULL); - - VERB1 log("Loading plugins from "PLUGINS_LIB_DIR); - g_pPluginManager = new CPluginManager(); - g_pPluginManager->LoadPlugins(); - - if (SetUpMW() != 0) /* logging is inside */ - throw 1; - if (SetUpCron() != 0) - throw 1; - - /* Add an event source which waits for INT/TERM signal */ - VERB1 log("Adding signal pipe watch to glib main loop"); - channel_signal = g_io_channel_unix_new(s_signal_pipe[0]); - channel_signal_event_id = g_io_add_watch(channel_signal, - G_IO_IN, - handle_signal_cb, - NULL); - - /* Mark the territory */ - VERB1 log("Creating lock file"); - if (Lock() != 0) - throw 1; - lockfile_created = true; - VERB1 log("Creating pid file"); - if (CreatePidFile() != 0) - throw 1; - pidfile_created = true; - - /* Open socket to receive new crashes. */ - dumpsocket_init(); - - /* Note: this already may process a few dbus messages, - * therefore it should be the last thing to initialize. - */ - VERB1 log("Initializing dbus"); - g_pCommLayer = new CCommLayerServerDBus(); - if (g_pCommLayer->m_init_error) - throw 1; - } - catch (...) - { - /* Initialization error */ - error_msg("Error while initializing daemon"); - /* Inform parent that initialization failed */ - if (daemonize) - kill(parent_pid, SIGINT); - goto cleanup; - } - - /* Inform parent that we initialized ok */ - if (daemonize) - { - VERB1 log("Signalling parent"); - kill(parent_pid, SIGTERM); - if (logmode != LOGMODE_SYSLOG) - start_syslog_logging(); - } - - /* Only now we want signal pipe to work */ - s_signal_pipe_write = s_signal_pipe[1]; - - /* Enter the event loop */ - try - { - /* This may take a while, therefore we don't do it in init section */ - FindNewDumps(DEBUG_DUMPS_DIR); - log("Init complete, entering main loop"); - run_main_loop(pMainloop); - } - catch (CABRTException& e) - { - error_msg("Error: %s", e.what()); - } - catch (std::exception& e) - { - error_msg("Error: %s", e.what()); - } - - cleanup: - /* Error or INT/TERM. Clean up, in reverse order. - * Take care to not undo things we did not do. - */ - dumpsocket_shutdown(); - rpm_destroy(); - if (pidfile_created) - unlink(VAR_RUN_PIDFILE); - if (lockfile_created) - unlink(VAR_RUN_LOCK_FILE); - - if (channel_signal_event_id > 0) - g_source_remove(channel_signal_event_id); - if (channel_signal) - g_io_channel_unref(channel_signal); - if (channel_inotify_event_id > 0) - g_source_remove(channel_inotify_event_id); - if (channel_inotify) - g_io_channel_unref(channel_inotify); - - delete g_pCommLayer; - if (g_pPluginManager) - { - /* This restores /proc/sys/kernel/core_pattern, among other things: */ - g_pPluginManager->UnLoadPlugins(); - delete g_pPluginManager; - } - if (pMainloop) - g_main_loop_unref(pMainloop); - - /* Exiting */ - if (s_sig_caught && s_sig_caught != SIGALRM) - { - error_msg_and_die("Got signal %d, exiting", s_sig_caught); - signal(s_sig_caught, SIG_DFL); - raise(s_sig_caught); - } - error_msg_and_die("Exiting"); -} diff --git a/src/Daemon/Daemon.h b/src/Daemon/Daemon.h deleted file mode 100644 index 62088651..00000000 --- a/src/Daemon/Daemon.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - Copyright (C) 2009 Denys Vlasenko (dvlasenk@redhat.com) - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#ifndef DAEMON_H_ -#define DAEMON_H_ - -#include -#include "abrt_types.h" -#include "crash_types.h" - -class CCrashWatcher; -class CCommLayerServer; -class CPluginManager; - -/* Used for sending dbus signals */ -extern CCommLayerServer *g_pCommLayer; - -/* Collection of loaded plugins */ -extern CPluginManager* g_pPluginManager; - -#endif diff --git a/src/Daemon/Makefile.am b/src/Daemon/Makefile.am deleted file mode 100644 index 089f3787..00000000 --- a/src/Daemon/Makefile.am +++ /dev/null @@ -1,65 +0,0 @@ -bin_SCRIPTS = abrt-debuginfo-install abrt-handle-upload - -sbin_PROGRAMS = abrtd - -abrtd_SOURCES = \ - PluginManager.h PluginManager.cpp \ - rpm.h rpm.c \ - MiddleWare.h MiddleWare.cpp \ - CrashWatcher.h CrashWatcher.cpp \ - CommLayerServer.h CommLayerServer.cpp \ - CommLayerServerDBus.h CommLayerServerDBus.cpp \ - Daemon.h Daemon.cpp \ - Settings.h Settings.cpp \ - dumpsocket.h dumpsocket.cpp -abrtd_CPPFLAGS = \ - -I$(srcdir)/../../inc \ - -I$(srcdir)/../../lib/Utils \ - -DBIN_DIR=\"$(bindir)\" \ - -DVAR_RUN=\"$(VAR_RUN)\" \ - -DCONF_DIR=\"$(CONF_DIR)\" \ - -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ - -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ - -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ - -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ - $(GLIB_CFLAGS) \ - $(DBUS_CFLAGS) \ - $(XMLRPC_CFLAGS) $(XMLRPC_CLIENT_CFLAGS) \ - $(ENABLE_SOCKET_OR_DBUS) \ - -D_GNU_SOURCE \ - -Wall -abrtd_LDADD = \ - ../../lib/Utils/libABRTUtils.la \ - ../../lib/Utils/libABRTdUtils.la \ - $(DL_LIBS) \ - $(DBUS_LIBS) \ - $(RPM_LIBS) \ - $(XMLRPC_LIBS) $(XMLRPC_CLIENT_LIBS) - -dbusabrtconfdir = ${sysconfdir}/dbus-1/system.d/ -dist_dbusabrtconf_DATA = dbus-abrt.conf - -daemonconfdir = $(CONF_DIR) -dist_daemonconf_DATA = \ - abrt.conf \ - gpg_keys - -polkitconfdir = ${datadir}/polkit-1/actions -dist_polkitconf_DATA = org.fedoraproject.abrt.policy - -comredhatabrtservicedir = ${datadir}/dbus-1/system-services -dist_comredhatabrtservice_DATA = com.redhat.abrt.service - -man_MANS = abrtd.8 abrt.conf.5 - -EXTRA_DIST = $(man_MANS) abrt-debuginfo-install abrt-handle-upload - -if HAVE_SYSTEMD -dist_systemdsystemunit_DATA = \ - abrtd.service -endif - -DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ - -install-data-local: - $(mkdir_p) '$(DESTDIR)/$(VAR_RUN)' diff --git a/src/Daemon/MiddleWare.cpp b/src/Daemon/MiddleWare.cpp deleted file mode 100644 index c7ed4df5..00000000 --- a/src/Daemon/MiddleWare.cpp +++ /dev/null @@ -1,1132 +0,0 @@ -/* - MiddleWare.cpp - - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#include -#include -#include "abrtlib.h" -#include "abrt_types.h" -#include "Daemon.h" -#include "Settings.h" -#include "rpm.h" -#include "debug_dump.h" -#include "abrt_exception.h" -#include "abrt_packages.h" -#include "comm_layer_inner.h" -#include "MiddleWare.h" - -using namespace std; - -/** - * An instance of CPluginManager. When MiddleWare wants to do something - * with plugins, it calls the plugin manager. - * @see PluginManager.h - */ -CPluginManager* g_pPluginManager; - -/** - * A map, which associates particular analyzer to one or more - * action or reporter plugins. These are activated when a crash, which - * is maintained by particular analyzer, occurs. - */ -typedef std::map map_analyzer_actions_and_reporters_t; -static map_analyzer_actions_and_reporters_t s_mapAnalyzerActionsAndReporters; -/** - * A vector of one or more action or reporter plugins. These are - * activated when any crash occurs. - */ -static vector_pair_string_string_t s_vectorActionsAndReporters; - - -static void RunAnalyzerActions(const char *pAnalyzer, const char* pPackageName, const char *pDebugDumpDir, int force); - - -static char* is_text_file(const char *name, ssize_t *sz) -{ - /* We were using magic.h API to check for file being text, but it thinks - * that file containing just "0" is not text (!!) - * So, we do it ourself. - */ - - int fd = open(name, O_RDONLY); - if (fd < 0) - return NULL; /* it's not text (because it does not exist! :) */ - - char *buf = (char*)xmalloc(*sz); - ssize_t r = *sz = full_read(fd, buf, *sz); - close(fd); - if (r < 0) - { - free(buf); - return NULL; /* it's not text (because we can't read it) */ - } - - /* Some files in our dump directories are known to always be textual */ - const char *base = strrchr(name, '/'); - if (base) - { - base++; - if (strcmp(base, FILENAME_BACKTRACE) == 0 - || strcmp(base, FILENAME_CMDLINE) == 0 - ) { - return buf; - } - } - - /* Every once in a while, even a text file contains a few garbled - * or unexpected non-ASCII chars. We should not declare it "binary". - */ - const unsigned RATIO = 50; - unsigned total_chars = r + RATIO; - unsigned bad_chars = 1; /* 1 prevents division by 0 later */ - while (--r >= 0) - { - if (buf[r] >= 0x7f - /* among control chars, only '\t','\n' etc are allowed */ - || (buf[r] < ' ' && !isspace(buf[r])) - ) { - if (buf[r] == '\0') - { - /* We don't like NULs very much. Not text for sure! */ - free(buf); - return NULL; - } - bad_chars++; - } - } - - if ((total_chars / bad_chars) >= RATIO) - return buf; /* looks like text to me */ - - free(buf); - return NULL; /* it's binary */ -} - -static void load_crash_data_from_debug_dump(CDebugDump& dd, map_crash_data_t& data) -{ - std::string short_name; - std::string full_name; - - dd.InitGetNextFile(); - while (dd.GetNextFile(&short_name, &full_name)) - { - ssize_t sz = 4*1024; - char *text = NULL; - bool editable = is_editable_file(short_name.c_str()); - - if (!editable) - { - text = is_text_file(full_name.c_str(), &sz); - if (!text) - { - add_to_crash_data_ext(data, - short_name.c_str(), - CD_BIN, - CD_ISNOTEDITABLE, - full_name.c_str() - ); - continue; - } - } - - std::string content; - if (sz < 4*1024) /* is_text_file did read entire file */ - content.assign(text, sz); - else /* no, need to read it all */ - dd.LoadText(short_name.c_str(), content); - free(text); - - add_to_crash_data_ext(data, - short_name.c_str(), - CD_TXT, - editable ? CD_ISEDITABLE : CD_ISNOTEDITABLE, - content.c_str() - ); - } -} - -/** - * Transforms a debugdump directory to inner crash - * report form. This form is used for later reporting. - * @param pDebugDumpDir A debugdump dir containing all necessary data. - * @param pCrashData A created crash report. - */ -static void DebugDumpToCrashReport(const char *pDebugDumpDir, map_crash_data_t& pCrashData) -{ - VERB3 log(" DebugDumpToCrashReport('%s')", pDebugDumpDir); - - CDebugDump dd; - dd.Open(pDebugDumpDir); - - const char *const *v = must_have_files; - while (*v) - { - if (!dd.Exist(*v)) - { - throw CABRTException(EXCEP_ERROR, "DebugDumpToCrashReport(): important file '%s' is missing", *v); - } - v++; - } - - load_crash_data_from_debug_dump(dd, pCrashData); -} - -/** - * Get a local UUID from particular analyzer plugin. - * @param pAnalyzer A name of an analyzer plugin. - * @param pDebugDumpDir A debugdump dir containing all necessary data. - * @return A local UUID. - */ -static std::string GetLocalUUID(const char *pAnalyzer, const char *pDebugDumpDir) -{ - CAnalyzer* analyzer = g_pPluginManager->GetAnalyzer(pAnalyzer); - if (analyzer) - { - return analyzer->GetLocalUUID(pDebugDumpDir); - } - throw CABRTException(EXCEP_PLUGIN, "Error running '%s'", pAnalyzer); -} - -/** - * Get a global UUID from particular analyzer plugin. - * @param pAnalyzer A name of an analyzer plugin. - * @param pDebugDumpDir A debugdump dir containing all necessary data. - * @return A global UUID. - */ -static std::string GetGlobalUUID(const char *pAnalyzer, - const char *pDebugDumpDir) -{ - CAnalyzer* analyzer = g_pPluginManager->GetAnalyzer(pAnalyzer); - if (analyzer) - { - return analyzer->GetGlobalUUID(pDebugDumpDir); - } - throw CABRTException(EXCEP_PLUGIN, "Error running '%s'", pAnalyzer); -} - -/** - * Take care of getting all additional data needed - * for computing UUIDs and creating a report for particular analyzer - * plugin. This report could be send somewhere afterwards. - * @param pAnalyzer A name of an analyzer plugin. - * @param pDebugDumpPath A debugdump dir containing all necessary data. - */ -static void run_analyser_CreateReport(const char *pAnalyzer, - const char *pDebugDumpDir, - int force) -{ - CAnalyzer* analyzer = g_pPluginManager->GetAnalyzer(pAnalyzer); - if (analyzer) - { - analyzer->CreateReport(pDebugDumpDir, force); - } - /* else: GetAnalyzer() already complained, no need to handle it here */ -} - -/* - * Called in three cases: - * (1) by StartJob dbus call -> CreateReportThread(), in the thread - * (2) by CreateReport dbus call - * (3) by daemon if AutoReportUID is set for this user's crashes - */ -mw_result_t CreateCrashReport(const char *crash_id, - long caller_uid, - int force, - map_crash_data_t& pCrashData) -{ - VERB2 log("CreateCrashReport('%s',%ld,result)", crash_id, caller_uid); - - database_row_t row; - CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); - database->Connect(); - row = database->GetRow(crash_id); - database->DisConnect(); - if (row.m_sUUID == "") - { - error_msg("crash '%s' is not in database", crash_id); - return MW_IN_DB_ERROR; - } - if (caller_uid != 0 /* not called by root */ - && row.m_sInformAll != "1" - && to_string(caller_uid) != row.m_sUID - ) { - error_msg("crash '%s' can't be accessed by user with uid %ld", crash_id, caller_uid); - return MW_IN_DB_ERROR; - } - - mw_result_t r = MW_OK; - try - { - { - CDebugDump dd; - dd.Open(row.m_sDebugDumpDir.c_str()); - load_crash_data_from_debug_dump(dd, pCrashData); - } - - std::string analyzer = get_crash_data_item_content(pCrashData, FILENAME_ANALYZER); - const char* package = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_PACKAGE); - char* package_name = get_package_name_from_NVR_or_NULL(package); - - // TODO: explain what run_analyser_CreateReport and RunAnalyzerActions are expected to do. - // Do they potentially add more files to dump dir? - // Why we calculate dup_hash after run_analyser_CreateReport but before RunAnalyzerActions? - // Why do we reload dump dir's data via DebugDumpToCrashReport? - - VERB3 log(" run_analyser_CreateReport('%s')", analyzer.c_str()); - run_analyser_CreateReport(analyzer.c_str(), row.m_sDebugDumpDir.c_str(), force); - - std::string dup_hash = GetGlobalUUID(analyzer.c_str(), row.m_sDebugDumpDir.c_str()); - VERB3 log(" DUPHASH:'%s'", dup_hash.c_str()); - - VERB3 log(" RunAnalyzerActions('%s','%s','%s',force=%d)", analyzer.c_str(), package_name, row.m_sDebugDumpDir.c_str(), force); - RunAnalyzerActions(analyzer.c_str(), package_name, row.m_sDebugDumpDir.c_str(), force); - free(package_name); - DebugDumpToCrashReport(row.m_sDebugDumpDir.c_str(), pCrashData); - add_to_crash_data_ext(pCrashData, CD_UUID , CD_SYS, CD_ISNOTEDITABLE, row.m_sUUID.c_str()); - add_to_crash_data_ext(pCrashData, CD_DUPHASH, CD_TXT, CD_ISNOTEDITABLE, dup_hash.c_str()); - } - catch (CABRTException& e) - { - r = MW_CORRUPTED; - error_msg("%s", e.what()); - if (e.type() == EXCEP_DD_OPEN) - { - r = MW_ERROR; - } - else if (e.type() == EXCEP_DD_LOAD) - { - r = MW_FILE_ERROR; - } - else if (e.type() == EXCEP_PLUGIN) - { - r = MW_PLUGIN_ERROR; - } - } - - VERB3 log("CreateCrashReport() returns %d", r); - return r; -} - -void RunAction(const char *pActionDir, - const char *pPluginName, - const char *pPluginArgs) -{ - CAction* action = g_pPluginManager->GetAction(pPluginName); - if (!action) - { - /* GetAction() already complained */ - return; - } - try - { - action->Run(pActionDir, pPluginArgs, /*force:*/ 0); - } - catch (CABRTException& e) - { - error_msg("Execution of '%s' was not successful: %s", pPluginName, e.what()); - } -} - -void RunActionsAndReporters(const char *pDebugDumpDir) -{ - vector_pair_string_string_t::iterator it_ar = s_vectorActionsAndReporters.begin(); - map_plugin_settings_t plugin_settings; - for (; it_ar != s_vectorActionsAndReporters.end(); it_ar++) - { - const char *plugin_name = it_ar->first.c_str(); - try - { - VERB3 log("RunActionsAndReporters: checking %s", plugin_name); - plugin_type_t tp = g_pPluginManager->GetPluginType(plugin_name); - if (tp == REPORTER) - { - CReporter* reporter = g_pPluginManager->GetReporter(plugin_name); /* can't be NULL */ - map_crash_data_t crashReport; - DebugDumpToCrashReport(pDebugDumpDir, crashReport); - VERB2 log("%s.Report(...)", plugin_name); - reporter->Report(crashReport, plugin_settings, it_ar->second.c_str()); - } - else if (tp == ACTION) - { - CAction* action = g_pPluginManager->GetAction(plugin_name); /* can't be NULL */ - VERB2 log("%s.Run('%s','%s')", plugin_name, pDebugDumpDir, it_ar->second.c_str()); - action->Run(pDebugDumpDir, it_ar->second.c_str(), /*force:*/ 0); - } - } - catch (CABRTException& e) - { - error_msg("Activation of plugin '%s' was not successful: %s", plugin_name, e.what()); - } - } -} - - -// Do not trust client_report here! -// dbus handler passes it from user without checking -report_status_t Report(const map_crash_data_t& client_report, - const vector_string_t &reporters, - map_map_string_t& settings, - long caller_uid) -{ - // Get ID fields - const char *UID = get_crash_data_item_content_or_NULL(client_report, CD_UID); - const char *UUID = get_crash_data_item_content_or_NULL(client_report, CD_UUID); - if (!UID || !UUID) - { - throw CABRTException(EXCEP_ERROR, "Report(): UID or UUID is missing in client's report data"); - } - string crash_id = ssprintf("%s:%s", UID, UUID); - - // Retrieve corresponding stored record - map_crash_data_t stored_report; - mw_result_t r = FillCrashInfo(crash_id.c_str(), stored_report); - if (r != MW_OK) - { - return report_status_t(); - } - - // Is it allowed for this user to report? - if (caller_uid != 0 // not called by root - && get_crash_data_item_content(stored_report, CD_INFORMALL) != "1" - && strcmp(to_string(caller_uid).c_str(), UID) != 0 - ) { - throw CABRTException(EXCEP_ERROR, "Report(): user with uid %ld can't report crash %s", - caller_uid, crash_id.c_str()); - } - - const std::string& pDumpDir = get_crash_data_item_content(stored_report, CD_DUMPDIR); - - // Save comment, "how to reproduce", backtrace -//TODO: we should iterate through stored_report and modify all -//modifiable fields which have new data in client_report - const char *comment = get_crash_data_item_content_or_NULL(client_report, FILENAME_COMMENT); - const char *reproduce = get_crash_data_item_content_or_NULL(client_report, FILENAME_REPRODUCE); - const char *backtrace = get_crash_data_item_content_or_NULL(client_report, FILENAME_BACKTRACE); - if (comment || reproduce || backtrace) - { - CDebugDump dd; - dd.Open(pDumpDir.c_str()); - if (comment) - { - dd.SaveText(FILENAME_COMMENT, comment); - add_to_crash_data_ext(stored_report, FILENAME_COMMENT, CD_TXT, CD_ISEDITABLE, comment); - } - if (reproduce) - { - dd.SaveText(FILENAME_REPRODUCE, reproduce); - add_to_crash_data_ext(stored_report, FILENAME_REPRODUCE, CD_TXT, CD_ISEDITABLE, reproduce); - } - if (backtrace) - { - dd.SaveText(FILENAME_BACKTRACE, backtrace); - add_to_crash_data_ext(stored_report, FILENAME_BACKTRACE, CD_TXT, CD_ISEDITABLE, backtrace); - } - } - - /* Remove BIN filenames from stored_report if they are not present in client's data */ - map_crash_data_t::const_iterator its = stored_report.begin(); - while (its != stored_report.end()) - { - if (its->second[CD_TYPE] == CD_BIN) - { - std::string key = its->first; - if (get_crash_data_item_content_or_NULL(client_report, key.c_str()) == NULL) - { - /* client does not have it -> does not want it passed to reporters */ - VERB3 log("Won't report BIN file %s:'%s'", key.c_str(), its->second[CD_CONTENT].c_str()); - its++; /* move off the element we will erase */ - stored_report.erase(key); - continue; - } - } - its++; - } - - const std::string& analyzer = get_crash_data_item_content(stored_report, FILENAME_ANALYZER); - - std::string dup_hash = GetGlobalUUID(analyzer.c_str(), pDumpDir.c_str()); - VERB3 log(" DUPHASH:'%s'", dup_hash.c_str()); - add_to_crash_data_ext(stored_report, CD_DUPHASH, CD_TXT, CD_ISNOTEDITABLE, dup_hash.c_str()); - - // Run reporters - - VERB3 { - log("Run reporters"); - log_map_crash_data(client_report, " client_report"); - log_map_crash_data(stored_report, " stored_report"); - } -#define client_report client_report_must_not_be_used_below - - map_crash_data_t::const_iterator its_PACKAGE = stored_report.find(FILENAME_PACKAGE); - std::string packageNVR = its_PACKAGE->second[CD_CONTENT]; - char * packageName = get_package_name_from_NVR_or_NULL(packageNVR.c_str()); - - // analyzer with package name (CCpp:xorg-x11-app) has higher priority - char* key = xasprintf("%s:%s",analyzer.c_str(),packageName); - free(packageName); - map_analyzer_actions_and_reporters_t::iterator end = s_mapAnalyzerActionsAndReporters.end(); - map_analyzer_actions_and_reporters_t::iterator keyPtr = s_mapAnalyzerActionsAndReporters.find(key); - if (keyPtr == end) - { - VERB3 log("'%s' not found, looking for '%s'", key, analyzer.c_str()); - // if there is no such settings, then try default analyzer - keyPtr = s_mapAnalyzerActionsAndReporters.find(analyzer); - } - free(key); - - bool at_least_one_reporter_succeeded = false; - report_status_t ret; - std::string message; - if (keyPtr != end) - { - VERB2 log("Found AnalyzerActionsAndReporters for '%s'", analyzer.c_str()); - - vector_pair_string_string_t::iterator it_r = keyPtr->second.begin(); - for (; it_r != keyPtr->second.end(); it_r++) - { - const char *plugin_name = it_r->first.c_str(); - - /* Check if the reporter is in the input list of allowed reporters. */ - if (reporters.end() == std::find(reporters.begin(), reporters.end(), plugin_name)) - { - continue; - } - - try - { - if (g_pPluginManager->GetPluginType(plugin_name) == REPORTER) - { - CReporter* reporter = g_pPluginManager->GetReporter(plugin_name); /* can't be NULL */ - map_plugin_settings_t plugin_settings = settings[plugin_name]; - std::string res = reporter->Report(stored_report, plugin_settings, it_r->second.c_str()); - ret[plugin_name].push_back("1"); // REPORT_STATUS_IDX_FLAG - ret[plugin_name].push_back(res); // REPORT_STATUS_IDX_MSG - if (message != "") - message += ";"; - message += res; - at_least_one_reporter_succeeded = true; - } - } - catch (CABRTException& e) - { - ret[plugin_name].push_back("0"); // REPORT_STATUS_IDX_FLAG - ret[plugin_name].push_back(e.what()); // REPORT_STATUS_IDX_MSG - update_client("Reporting via '%s' was not successful: %s", plugin_name, e.what()); - } - } // for - } // if - - if (at_least_one_reporter_succeeded) - { - CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); - database->Connect(); - report_status_t::iterator ret_it = ret.begin(); - while (ret_it != ret.end()) - { - const string &plugin_name = ret_it->first; - const vector_string_t &v = ret_it->second; - if (v[REPORT_STATUS_IDX_FLAG] == "1") - { - database->SetReportedPerReporter(crash_id.c_str(), plugin_name.c_str(), v[REPORT_STATUS_IDX_MSG].c_str()); - } - ret_it++; - } - database->SetReported(crash_id.c_str(), message.c_str()); - database->DisConnect(); - } - - return ret; -#undef client_report -} - -/** - * Check whether particular debugdump directory is saved - * in database. This check is done together with an UID of an user. - * @param uid - * An UID of an user. - * @param debug_dump_dir - * A debugdump dir containing all necessary data. - * @return - * It returns true if debugdump dir is already saved, otherwise - * it returns false. - * @todo - * Use database query instead of dumping all rows and searching in them. - */ -static bool is_debug_dump_saved(long uid, const char *debug_dump_dir) -{ - if (g_settings_sDatabase.empty()) - error_msg_and_die(_("Database plugin not specified. Please check abrtd settings.")); - - CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); - database->Connect(); - vector_database_rows_t rows = database->GetUIDData(uid); - database->DisConnect(); - - size_t ii; - bool found = false; - for (ii = 0; ii < rows.size(); ii++) - { - if (0 == strcmp(rows[ii].m_sDebugDumpDir.c_str(), debug_dump_dir)) - { - found = true; - break; - } - } - - return found; -} - -void LoadOpenGPGPublicKey(const char* key) -{ - VERB1 log("Loading GPG key '%s'", key); - rpm_load_gpgkey(key); -} - -/** - * Returns the first full path argument in the command line or NULL. - * Skips options are in form "-XXX". - * Caller must delete the returned string using free(). - */ -static char *get_argv1_if_full_path(const char* cmdline) -{ - const char *argv1 = strpbrk(cmdline, " \t"); - while (argv1 != NULL) - { - /* we found space in cmdline, so it might contain - * path to some script like: - * /usr/bin/python [-XXX] /usr/bin/system-control-network - */ - argv1++; /* skip the space */ - if (*argv1 == '-') /* skip arguments */ - { - /* looks like -XXX in "perl -XXX /usr/bin/script.pl", skip */ - argv1 = strpbrk(argv1, " \t"); - continue; - } - else if (*argv1 == ' ' || *argv1 == '\t') /* skip multiple spaces */ - continue; - else if (*argv1 != '/') - { - /* if the string following the space doesn't start - * with '/' it's probably not a full path to script - * and we can't use it to determine the package name - */ - break; - } - - /* cut the rest of cmdline arguments */ - int len = strchrnul(argv1, ' ') - argv1; - return xstrndup(argv1, len); - } - return NULL; -} - -static bool is_path_blacklisted(const char *path) -{ - set_string_t::iterator it = g_settings_setBlackListedPaths.begin(); - while (it != g_settings_setBlackListedPaths.end()) - { - if (fnmatch(it->c_str(), path, /*flags:*/ 0) == 0) - { - return true; - } - it++; - } - return false; -} - - -/** - * Get a package name from executable name and save - * package description to particular debugdump directory of a crash. - * @param pExecutable A name of crashed application. - * @param pDebugDumpDir A debugdump dir containing all necessary data. - * @return It return results of operation. See mw_result_t. - */ -static mw_result_t SavePackageDescriptionToDebugDump( - const char *pExecutable, - const char *cmdline, - bool remote, - const char *pDebugDumpDir) -{ - char* rpm_pkg = NULL; - char* packageName = NULL; - char* component = NULL; - std::string scriptName; /* only if "interpreter /path/to/script" */ - - if (strcmp(pExecutable, "kernel") == 0) - { - component = xstrdup("kenel"); - rpm_pkg = xstrdup("kernel"); - packageName = xstrdup("kernel"); - } - else - { - if (is_path_blacklisted(pExecutable)) - { - log("Blacklisted executable '%s'", pExecutable); - return MW_BLACKLISTED; - } - - rpm_pkg = rpm_get_package_nvr(pExecutable); - if (rpm_pkg == NULL) - { - if (g_settings_bProcessUnpackaged || remote) - { - VERB2 log("Crash in unpackaged executable '%s', proceeding without packaging information", pExecutable); - try - { - CDebugDump dd; - dd.Open(pDebugDumpDir); - dd.SaveText(FILENAME_PACKAGE, ""); - dd.SaveText(FILENAME_COMPONENT, ""); - dd.SaveText(FILENAME_DESCRIPTION, "Crashed executable does not belong to any installed package"); - return MW_OK; - } - catch (CABRTException& e) - { - error_msg("%s", e.what()); - return MW_ERROR; - } - } - else - { - log("Executable '%s' doesn't belong to any package", pExecutable); - return MW_PACKAGE_ERROR; - } - } - - /* Check well-known interpreter names */ - - const char *basename = strrchr(pExecutable, '/'); - if (basename) basename++; else basename = pExecutable; - - /* Add more interpreters as needed */ - if (strcmp(basename, "python") == 0 - || strcmp(basename, "perl") == 0 - ) { -// TODO: we don't verify that python executable is not modified -// or that python package is properly signed -// (see CheckFingerprint/CheckHash below) - - /* Try to find package for the script by looking at argv[1]. - * This will work only if the cmdline contains the whole path. - * Example: python /usr/bin/system-control-network - */ - bool knownOrigin = false; - char *script_name = get_argv1_if_full_path(cmdline); - if (script_name) - { - char *script_pkg = rpm_get_package_nvr(script_name); - if (script_pkg) - { - /* There is a well-formed script name in argv[1], - * and it does belong to some package. - * Replace interpreter's rpm_pkg and pExecutable - * with data pertaining to the script. - */ - free(rpm_pkg); - rpm_pkg = script_pkg; - scriptName = script_name; - pExecutable = scriptName.c_str(); - knownOrigin = true; - /* pExecutable has changed, check it again */ - if (is_path_blacklisted(pExecutable)) - { - log("Blacklisted executable '%s'", pExecutable); - return MW_BLACKLISTED; - } - } - free(script_name); - } - - if (!knownOrigin && !g_settings_bProcessUnpackaged && !remote) - { - log("Interpreter crashed, but no packaged script detected: '%s'", cmdline); - return MW_PACKAGE_ERROR; - } - } - - packageName = get_package_name_from_NVR_or_NULL(rpm_pkg); - VERB2 log("Package:'%s' short:'%s'", rpm_pkg, packageName); - - if (g_settings_setBlackListedPkgs.find(packageName) != g_settings_setBlackListedPkgs.end()) - { - log("Blacklisted package '%s'", packageName); - free(packageName); - return MW_BLACKLISTED; - } - if (g_settings_bOpenGPGCheck && !remote) - { - if (rpm_chk_fingerprint(packageName)) - { - log("Package '%s' isn't signed with proper key", packageName); - free(packageName); - return MW_GPG_ERROR; - } - /* - Checking the MD5 sum requires to run prelink to "un-prelink" the - binaries - this is considered potential security risk so we don't - use it, until we find some non-intrusive way - - Delete? - */ - /* - if (!CheckHash(packageName.c_str(), pExecutable)) - { - error_msg("Executable '%s' seems to be modified, " - "doesn't match one from package '%s'", - pExecutable, packageName.c_str()); - return MW_GPG_ERROR; - } - */ - } - component = rpm_get_component(pExecutable); - } - - char *dsc = rpm_get_description(packageName); - free(packageName); - - char host[HOST_NAME_MAX + 1]; - if (!remote) - { - // HOST_NAME_MAX is defined in limits.h - int ret = gethostname(host, HOST_NAME_MAX); - host[HOST_NAME_MAX] = '\0'; - if (ret < 0) - { - perror_msg("gethostname"); - host[0] = '\0'; - } - } - - try - { - CDebugDump dd; - dd.Open(pDebugDumpDir); - if (rpm_pkg) - { - dd.SaveText(FILENAME_PACKAGE, rpm_pkg); - free(rpm_pkg); - } - - if (dsc) - { - dd.SaveText(FILENAME_DESCRIPTION, dsc); - free(dsc); - } - - if (component) - { - dd.SaveText(FILENAME_COMPONENT, component); - free(component); - } - - if (!remote) - dd.SaveText(FILENAME_HOSTNAME, host); - } - catch (CABRTException& e) - { - error_msg("%s", e.what()); - return MW_ERROR; - } - - return MW_OK; -} - -bool analyzer_has_InformAllUsers(const char *analyzer_name) -{ - CAnalyzer* analyzer = g_pPluginManager->GetAnalyzer(analyzer_name); - if (!analyzer) - { - return false; - } - map_plugin_settings_t settings = analyzer->GetSettings(); - map_plugin_settings_t::const_iterator it = settings.find("InformAllUsers"); - if (it == settings.end()) - return false; - return string_to_bool(it->second.c_str()); -} - -bool analyzer_has_AutoReportUIDs(const char *analyzer_name, const char *uid_str) -{ - CAnalyzer* analyzer = g_pPluginManager->GetAnalyzer(analyzer_name); - if (!analyzer) - { - return false; - } - map_plugin_settings_t settings = analyzer->GetSettings(); - map_plugin_settings_t::const_iterator it = settings.find("AutoReportUIDs"); - if (it == settings.end()) - return false; - - vector_string_t logins; - parse_args(it->second.c_str(), logins); - - uid_t uid = xatoi_u(uid_str); - unsigned size = logins.size(); - for (unsigned ii = 0; ii < size; ii++) - { - struct passwd* pw = getpwnam(logins[ii].c_str()); - if (!pw) - continue; - if (pw->pw_uid == uid) - return true; - } - - return false; -} - -void autoreport(const pair_string_string_t& reporter_options, const map_crash_data_t& crash_report) -{ - CReporter* reporter = g_pPluginManager->GetReporter(reporter_options.first.c_str()); - if (!reporter) - { - return; - } - map_plugin_settings_t plugin_settings; - /*std::string res =*/ reporter->Report(crash_report, plugin_settings, reporter_options.second.c_str()); -} - -/** - * Execute all action plugins, which are associated to - * particular analyzer plugin. - * @param pAnalyzer A name of an analyzer plugin. - * @param pDebugDumpPath A debugdump dir containing all necessary data. - */ -static void RunAnalyzerActions(const char *pAnalyzer, const char *pPackageName, const char *pDebugDumpDir, int force) -{ - map_analyzer_actions_and_reporters_t::iterator analyzer; - if (pPackageName != NULL) - { - /*try to find analyzer:component first*/ - char *analyzer_component = xasprintf("%s:%s", pAnalyzer, pPackageName); - analyzer = s_mapAnalyzerActionsAndReporters.find(analyzer_component); - /* if we didn't find an action for specific package, use the generic one */ - if (analyzer == s_mapAnalyzerActionsAndReporters.end()) - { - VERB2 log("didn't find action for %s, trying just %s", analyzer_component, pAnalyzer); - map_analyzer_actions_and_reporters_t::iterator analyzer = s_mapAnalyzerActionsAndReporters.find(pAnalyzer); - } - free(analyzer_component); - } - else - { - VERB2 log("no package name specified, trying to find action for: %s", pAnalyzer); - analyzer = s_mapAnalyzerActionsAndReporters.find(pAnalyzer); - } - if (analyzer != s_mapAnalyzerActionsAndReporters.end()) - { - vector_pair_string_string_t::iterator it_a = analyzer->second.begin(); - for (; it_a != analyzer->second.end(); it_a++) - { - const char *plugin_name = it_a->first.c_str(); - CAction* action = g_pPluginManager->GetAction(plugin_name, /*silent:*/ true); - if (!action) - { - /* GetAction() already complained if no such plugin. - * If plugin exists but isn't an Action, it's not an error. - */ - continue; - } - try - { - action->Run(pDebugDumpDir, it_a->second.c_str(), force); - } - catch (CABRTException& e) - { - update_client("Action performed by '%s' was not successful: %s", plugin_name, e.what()); - } - } - } -} - -/** - * Save a debugdump into database. If saving is - * successful, then crash info is filled. Otherwise the crash info is - * not changed. - * @param pUUID A local UUID of a crash. - * @param pUID An UID of an user. - * @param pTime Time when a crash occurs. - * @param pDebugDumpPath A debugdump path. - * @param pCrashData A filled crash info. - * @return It return results of operation. See mw_result_t. - */ -static mw_result_t SaveDebugDumpToDatabase(const char *crash_id, - bool inform_all_users, - const char *pTime, - const char *pDebugDumpDir, - map_crash_data_t& pCrashData) -{ - CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); - database->Connect(); - /* note: if [UUID,UID] record exists, pDebugDumpDir is not updated in the record */ - database->Insert_or_Update(crash_id, inform_all_users, pDebugDumpDir, pTime); - database_row_t row = database->GetRow(crash_id); - database->DisConnect(); - - mw_result_t res = FillCrashInfo(crash_id, pCrashData); - if (res == MW_OK) - { - const char *first = get_crash_data_item_content(pCrashData, CD_DUMPDIR).c_str(); - if (row.m_sReported == "1") - { - log("Crash is in database already (dup of %s) and is reported", first); - return MW_REPORTED; - } - if (row.m_sCount != "1") - { - log("Crash is in database already (dup of %s)", first); - return MW_OCCURRED; - } - } - return res; -} - -mw_result_t SaveDebugDump(const char *pDebugDumpDir, - map_crash_data_t& pCrashData) -{ - std::string UID; - std::string time; - std::string analyzer; - std::string executable; - std::string cmdline; - bool remote = false; - try - { - CDebugDump dd; - dd.Open(pDebugDumpDir); - dd.LoadText(FILENAME_TIME, time); - dd.LoadText(CD_UID, UID); - dd.LoadText(FILENAME_ANALYZER, analyzer); - dd.LoadText(FILENAME_EXECUTABLE, executable); - dd.LoadText(FILENAME_CMDLINE, cmdline); - if (dd.Exist(FILENAME_REMOTE)) - { - std::string remote_str; - dd.LoadText(FILENAME_REMOTE, remote_str); - remote = (remote_str.find('1') != std::string::npos); - } - } - catch (CABRTException& e) - { - error_msg("%s", e.what()); - return MW_ERROR; - } - - /* Convert UID string to number uid_num. The UID string can be modified by user or - wrongly saved (empty or non-numeric), so xatou() cannot be used here, - because it would kill the daemon. */ - char *endptr; - errno = 0; - unsigned long uid_num = strtoul(UID.c_str(), &endptr, 10); - if (errno || UID.c_str() == endptr || *endptr != '\0' || uid_num > UINT_MAX) - { - error_msg("Invalid UID '%s' loaded from %s", UID.c_str(), pDebugDumpDir); - return MW_ERROR; - } - - if (is_debug_dump_saved(uid_num, pDebugDumpDir)) - return MW_IN_DB; - - mw_result_t res = SavePackageDescriptionToDebugDump(executable.c_str(), cmdline.c_str(), remote, pDebugDumpDir); - if (res != MW_OK) - return res; - - std::string UUID = GetLocalUUID(analyzer.c_str(), pDebugDumpDir); - std::string crash_id = ssprintf("%s:%s", UID.c_str(), UUID.c_str()); - /* Loads pCrashData (from the *first debugdump dir* if this one is a dup) - * Returns: - * MW_REPORTED: "the crash is flagged as reported in DB" (which also means it's a dup) - * MW_OCCURRED: "crash count is != 1" (iow: it is > 1 - dup) - * MW_OK: "crash count is 1" (iow: this is a new crash, not a dup) - * else: an error code - */ - return SaveDebugDumpToDatabase(crash_id.c_str(), - analyzer_has_InformAllUsers(analyzer.c_str()), - time.c_str(), - pDebugDumpDir, - pCrashData); -} - -mw_result_t FillCrashInfo(const char *crash_id, - map_crash_data_t& pCrashData) -{ - CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); - database->Connect(); - database_row_t row = database->GetRow(crash_id); - database->DisConnect(); - - std::string package; - std::string executable; - std::string description; - std::string analyzer; - try - { - CDebugDump dd; - dd.Open(row.m_sDebugDumpDir.c_str()); - load_crash_data_from_debug_dump(dd, pCrashData); - } - catch (CABRTException& e) - { - error_msg("%s", e.what()); - return MW_ERROR; - } - - add_to_crash_data(pCrashData, CD_UID , row.m_sUID.c_str() ); - add_to_crash_data(pCrashData, CD_UUID , row.m_sUUID.c_str() ); - add_to_crash_data(pCrashData, CD_INFORMALL , row.m_sInformAll.c_str() ); - add_to_crash_data(pCrashData, CD_COUNT , row.m_sCount.c_str() ); - add_to_crash_data(pCrashData, CD_REPORTED , row.m_sReported.c_str() ); - add_to_crash_data(pCrashData, CD_MESSAGE , row.m_sMessage.c_str() ); - add_to_crash_data(pCrashData, CD_DUMPDIR , row.m_sDebugDumpDir.c_str()); - add_to_crash_data(pCrashData, FILENAME_TIME , row.m_sTime.c_str() ); - - return MW_OK; -} - -void GetUUIDsOfCrash(long caller_uid, vector_string_t &result) -{ - CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); - vector_database_rows_t rows; - database->Connect(); - rows = database->GetUIDData(caller_uid); - database->DisConnect(); - - unsigned ii; - for (ii = 0; ii < rows.size(); ii++) - { - string crash_id = ssprintf("%s:%s", rows[ii].m_sUID.c_str(), rows[ii].m_sUUID.c_str()); - result.push_back(crash_id); - } -} - -void AddAnalyzerActionOrReporter(const char *pAnalyzer, - const char *pAnalyzerOrReporter, - const char *pArgs) -{ - s_mapAnalyzerActionsAndReporters[pAnalyzer].push_back(make_pair(std::string(pAnalyzerOrReporter), std::string(pArgs))); -} - -void AddActionOrReporter(const char *pActionOrReporter, - const char *pArgs) -{ - VERB3 log("AddActionOrReporter('%s','%s')", pActionOrReporter, pArgs); - s_vectorActionsAndReporters.push_back(make_pair(std::string(pActionOrReporter), std::string(pArgs))); -} diff --git a/src/Daemon/MiddleWare.h b/src/Daemon/MiddleWare.h deleted file mode 100644 index ac998872..00000000 --- a/src/Daemon/MiddleWare.h +++ /dev/null @@ -1,158 +0,0 @@ -/* - MiddleWare.h - header file for MiddleWare library. It wraps plugins and - take case of them. - - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#ifndef MIDDLEWARE_H_ -#define MIDDLEWARE_H_ - -#include "abrt_types.h" -#include "PluginManager.h" - -/** - * An enum contains all return codes. - */ -typedef enum { - MW_OK, /**< No error.*/ - MW_ERROR, /**< Common error.*/ - MW_BLACKLISTED, /**< Package is blacklisted.*/ - MW_CORRUPTED, /**< Debugdump directory is corrupted.*/ - MW_PACKAGE_ERROR, /**< Cannot determine package name.*/ - MW_GPG_ERROR, /**< Package is not signed properly.*/ - MW_REPORTED, /**< Crash is already reported.*/ - MW_OCCURRED, /**< Crash occurred in the past, but it is not reported yet.*/ - MW_IN_DB, /**< Debugdump directory is already saved in a database.*/ - MW_IN_DB_ERROR, /**< Error while working with a database.*/ - MW_PLUGIN_ERROR, /**< plugin wasn't found or error within plugin*/ - MW_FILE_ERROR /**< Error when trying open debugdump directory or - when trying open file in debug dump directory..*/ -} mw_result_t; - -typedef enum { - RS_CODE, - RS_MESSAGE -} report_status_items_t; - - -void LoadOpenGPGPublicKey(const char* key); - -/** - * Takes care of getting all additional data needed - * for computing UUIDs and creating a report for particular analyzer - * plugin. This report could be send somewhere afterwards. If a creation - * is successful, then a crash report is filled. - * @param pAnalyzer A name of an analyzer plugin. - * @param pDebugDumpPath A debugdump dir containing all necessary data. - * @param pCrashData A filled crash report. - * @return It return results of operation. See mw_result_t. - */ -mw_result_t CreateCrashReport(const char *crash_id, - long caller_uid, - int force, - map_crash_data_t& pCrashData); -/** - * Activates particular action plugin. - * @param pActionDir A directory, which is passed as working to a action plugin. - * @param pPluginName An action plugin name. - * @param pPluginArgs Action plugin's arguments. - */ -void RunAction(const char *pActionDir, - const char *pPluginName, - const char *pPluginArgs); -/** - * Activates all action and reporter plugins when any - * crash occurs. - * @param pDebugDumpDir A debugdump dir containing all necessary data. - */ -void RunActionsAndReporters(const char *pDebugDumpDir); -/** - * Reports a crash report to particular receiver. It - * takes an user uid, tries to find user config file and load it. If it - * fails, then default config is used. If pUID is emply string, default - * config is used. - * ...). - * @param crash_data - * A crash report. - * @param reporters - * List of allowed reporters. Which reporters will be used depends - * on the analyzer of the crash_data. Reporters missing from this list - * will not be used. - * @param caller_uid - * An user uid. - * @return - * A report status, which reporters ends successfuly with messages. - */ -report_status_t Report(const map_crash_data_t& crash_data, - const vector_string_t& reporters, - map_map_string_t& settings, - long caller_uid); -/** - * Adds package name and description to debugdump dir. - * Saves debugdump into database. - * Detects whether it's a duplicate crash. - * Fills crash info. - * Note that if it's a dup, loads _first crash_ info, not this one's. - * @param pDebugDumpDir A debugdump directory. - * @param pCrashData A crash info. - * @return It return results of operation. See mw_result_t. - */ -mw_result_t SaveDebugDump(const char *pDebugDumpDir, - map_crash_data_t& pCrashData); -/** - * Get one crash info. If getting is successful, - * then crash info is filled. - * @param pUUID A local UUID of a crash. - * @param pUID An UID of an user. - * @param pCrashData A crash info. - * @return It return results of operation. See mw_result_t. - */ -mw_result_t FillCrashInfo(const char *crash_id, - map_crash_data_t& pCrashData); -/** - * Gets all local UUIDs and UIDs of crashes. These crashes - * occurred when a particular user was logged in. - * @param pUID an UID of an user. - * @return A vector of pairs (local UUID, UID). - */ -void GetUUIDsOfCrash(long caller_uid, vector_string_t &result); -/** - * Adds one association among alanyzer plugin and its - * action and reporter plugins. - * @param pAnalyzer A name of an analyzer plugin. - * @param pActionOrReporter A name of an action or reporter plugin. - * @param pArgs An arguments for action or reporter plugin. - */ -void AddAnalyzerActionOrReporter(const char *pAnalyzer, - const char *pActionOrReporter, - const char *pArgs); -/** - * Add action and reporter plugins, which are activated - * when any crash occurs. - * @param pActionOrReporter A name of an action or reporter plugin. - * @param pArgs An arguments for action or reporter plugin. - */ -void AddActionOrReporter(const char *pActionOrReporter, - const char *pArgs); - -bool analyzer_has_InformAllUsers(const char *analyzer_name); - -bool analyzer_has_AutoReportUIDs(const char *analyzer_name, const char *uid_str); - -void autoreport(const pair_string_string_t& reporter_options, const map_crash_data_t& crash_report); -#endif /*MIDDLEWARE_H_*/ diff --git a/src/Daemon/PluginManager.cpp b/src/Daemon/PluginManager.cpp deleted file mode 100644 index 4f64ed00..00000000 --- a/src/Daemon/PluginManager.cpp +++ /dev/null @@ -1,463 +0,0 @@ -/* - PluginManager.cpp - - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#include -#include "abrtlib.h" -#include "abrt_exception.h" -#include "comm_layer_inner.h" -#include "Polkit.h" -#include "PluginManager.h" - -using namespace std; - - -/** - * CLoadedModule class. A class which contains a loaded plugin. - */ -class CLoadedModule -{ - private: - /* dlopen'ed library */ - void *m_pHandle; - const plugin_info_t *m_pPluginInfo; - CPlugin* (*m_pFnPluginNew)(); - - public: - CLoadedModule(void *handle, const char *mod_name); - ~CLoadedModule() { dlclose(m_pHandle); } - int GetMagicNumber() { return m_pPluginInfo->m_nMagicNumber; } - const char *GetVersion() { return m_pPluginInfo->m_sVersion; } - const char *GetName() { return m_pPluginInfo->m_sName; } - const char *GetDescription() { return m_pPluginInfo->m_sDescription; } - const char *GetEmail() { return m_pPluginInfo->m_sEmail; } - const char *GetWWW() { return m_pPluginInfo->m_sWWW; } - const char *GetGTKBuilder() { return m_pPluginInfo->m_sGTKBuilder; } - plugin_type_t GetType() { return m_pPluginInfo->m_Type; } - CPlugin *PluginNew() { return m_pFnPluginNew(); } -}; -CLoadedModule::CLoadedModule(void *handle, const char *mod_name) -{ - m_pHandle = handle; - /* All errors are fatal */ -#define LOADSYM(fp, handle, name) \ - do { \ - fp = (typeof(fp)) (dlsym(handle, name)); \ - if (!fp) \ - error_msg_and_die("'%s' has no %s entry", mod_name, name); \ - } while (0) - - LOADSYM(m_pPluginInfo, handle, "plugin_info"); - LOADSYM(m_pFnPluginNew, handle, "plugin_new"); -#undef LOADSYM -} - - -/** - * Text representation of plugin types. - */ -static const char *const plugin_type_str[] = { - "Analyzer", - "Action", - "Reporter", - "Database" -}; - - -/** - * A function. It saves settings. On success it returns true, otherwise returns false. - * @param path A path of config file. - * @param settings Plugin's settings. - * @return if it success it returns true, otherwise it returns false. - */ -static bool SavePluginSettings(const char *pPath, const map_plugin_settings_t& pSettings) -{ - FILE* fOut = fopen(pPath, "w"); - if (fOut) - { - fprintf(fOut, "# Settings were written by abrt\n"); - map_plugin_settings_t::const_iterator it = pSettings.begin(); - for (; it != pSettings.end(); it++) - { - fprintf(fOut, "%s = %s\n", it->first.c_str(), it->second.c_str()); - } - fclose(fOut); - return true; - } - return false; -} - - -CPluginManager::CPluginManager() -{} - -CPluginManager::~CPluginManager() -{} - -void CPluginManager::LoadPlugins() -{ - DIR *dir = opendir(PLUGINS_LIB_DIR); - if (dir != NULL) - { - struct dirent *dent; - while ((dent = readdir(dir)) != NULL) - { - if (!is_regular_file(dent, PLUGINS_LIB_DIR)) - continue; - char *ext = strrchr(dent->d_name, '.'); - if (!ext || strcmp(ext + 1, PLUGINS_LIB_EXTENSION) != 0) - continue; - *ext = '\0'; - if (strncmp(dent->d_name, PLUGINS_LIB_PREFIX, sizeof(PLUGINS_LIB_PREFIX)-1) != 0) - continue; - LoadPlugin(dent->d_name + sizeof(PLUGINS_LIB_PREFIX)-1, /*enabled_only:*/ true); - } - closedir(dir); - } -} - -void CPluginManager::UnLoadPlugins() -{ - map_loaded_module_t::iterator it_module; - while ((it_module = m_mapLoadedModules.begin()) != m_mapLoadedModules.end()) - { - UnLoadPlugin(it_module->first.c_str()); - } -} - -CPlugin* CPluginManager::LoadPlugin(const char *pName, bool enabled_only) -{ - map_plugin_t::iterator it_plugin = m_mapPlugins.find(pName); - if (it_plugin != m_mapPlugins.end()) - { - return it_plugin->second; /* ok */ - } - - map_string_t plugin_info; - plugin_info["Name"] = pName; - - const char *conf_name = pName; - if (strncmp(pName, "Kerneloops", sizeof("Kerneloops")-1) == 0) - { - /* Kerneloops{,Scanner,Reporter} share the same .conf file */ - conf_name = "Kerneloops"; - } - map_plugin_settings_t pluginSettings; - string conf_fullname = ssprintf(PLUGINS_CONF_DIR"/%s."PLUGINS_CONF_EXTENSION, conf_name); - LoadPluginSettings(conf_fullname.c_str(), pluginSettings); - m_map_plugin_settings[pName] = pluginSettings; - /* If settings are empty, most likely .conf file does not exist. - * Don't mislead the user: */ - VERB3 if (!pluginSettings.empty()) log("Loaded %s.conf", conf_name); - - if (enabled_only) - { - map_plugin_settings_t::iterator it = pluginSettings.find("Enabled"); - if (it == pluginSettings.end() || !string_to_bool(it->second.c_str())) - { - plugin_info["Enabled"] = "no"; - string empty; - plugin_info["Type"] = empty; - plugin_info["Version"] = empty; - plugin_info["Description"] = empty; - plugin_info["Email"] = empty; - plugin_info["WWW"] = empty; - plugin_info["GTKBuilder"] = empty; - m_map_plugin_info[pName] = plugin_info; - VERB3 log("Plugin %s: 'Enabled' is not set, not loading it (yet)", pName); - return NULL; /* error */ - } - } - - string libPath = ssprintf(PLUGINS_LIB_DIR"/"PLUGINS_LIB_PREFIX"%s."PLUGINS_LIB_EXTENSION, pName); - void *handle = dlopen(libPath.c_str(), RTLD_NOW); - if (!handle) - { - error_msg("Can't load '%s': %s", libPath.c_str(), dlerror()); - return NULL; /* error */ - } - CLoadedModule *module = new CLoadedModule(handle, pName); - if (module->GetMagicNumber() != PLUGINS_MAGIC_NUMBER - || module->GetType() < 0 - || module->GetType() > MAX_PLUGIN_TYPE - ) { - error_msg("Can't load non-compatible plugin %s: magic %d != %d or type %d is not in [0,%d]", - pName, - module->GetMagicNumber(), PLUGINS_MAGIC_NUMBER, - module->GetType(), MAX_PLUGIN_TYPE); - delete module; - return NULL; /* error */ - } - VERB3 log("Loaded plugin %s v.%s", pName, module->GetVersion()); - - CPlugin *plugin = NULL; - try - { - plugin = module->PluginNew(); - plugin->Init(); - plugin->SetSettings(pluginSettings); - } - catch (CABRTException& e) - { - error_msg("Can't initialize plugin %s: %s", - pName, - e.what() - ); - delete plugin; - delete module; - return NULL; /* error */ - } - - plugin_info["Enabled"] = "yes"; - plugin_info["Type"] = plugin_type_str[module->GetType()]; - //plugin_info["Name"] = module->GetName(); - plugin_info["Version"] = module->GetVersion(); - plugin_info["Description"] = module->GetDescription(); - plugin_info["Email"] = module->GetEmail(); - plugin_info["WWW"] = module->GetWWW(); - plugin_info["GTKBuilder"] = module->GetGTKBuilder(); - - m_map_plugin_info[pName] = plugin_info; - m_mapLoadedModules[pName] = module; - m_mapPlugins[pName] = plugin; - log("Registered %s plugin '%s'", plugin_type_str[module->GetType()], pName); - return plugin; /* ok */ -} - -void CPluginManager::UnLoadPlugin(const char *pName) -{ - map_loaded_module_t::iterator it_module = m_mapLoadedModules.find(pName); - if (it_module != m_mapLoadedModules.end()) - { - map_plugin_t::iterator it_plugin = m_mapPlugins.find(pName); - if (it_plugin != m_mapPlugins.end()) /* always true */ - { - it_plugin->second->DeInit(); - delete it_plugin->second; - m_mapPlugins.erase(it_plugin); - } - log("UnRegistered %s plugin %s", plugin_type_str[it_module->second->GetType()], pName); - delete it_module->second; - m_mapLoadedModules.erase(it_module); - } -} - -#ifdef PLUGIN_DYNAMIC_LOAD_UNLOAD -void CPluginManager::RegisterPluginDBUS(const char *pName, const char *pDBUSSender) -{ - int polkit_result = polkit_check_authorization(pDBUSSender, - "org.fedoraproject.abrt.change-daemon-settings"); - if (polkit_result == PolkitYes) - { -//TODO: report success/failure - LoadPlugin(pName); - } else - { - log("User %s not authorized, returned %d", pDBUSSender, polkit_result); - } -} - -void CPluginManager::UnRegisterPluginDBUS(const char *pName, const char *pDBUSSender) -{ - int polkit_result = polkit_check_authorization(pDBUSSender, - "org.fedoraproject.abrt.change-daemon-settings"); - if (polkit_result == PolkitYes) - { - UnLoadPlugin(pName); - } else - { - log("user %s not authorized, returned %d", pDBUSSender, polkit_result); - } -} -#endif - -CAnalyzer* CPluginManager::GetAnalyzer(const char *pName) -{ - CPlugin *plugin = LoadPlugin(pName); - if (!plugin) - { - error_msg("Plugin '%s' is not registered", pName); - return NULL; - } - if (m_mapLoadedModules[pName]->GetType() != ANALYZER) - { - error_msg("Plugin '%s' is not an analyzer plugin", pName); - return NULL; - } - return (CAnalyzer*)plugin; -} - -CReporter* CPluginManager::GetReporter(const char *pName) -{ - CPlugin *plugin = LoadPlugin(pName); - if (!plugin) - { - error_msg("Plugin '%s' is not registered", pName); - return NULL; - } - if (m_mapLoadedModules[pName]->GetType() != REPORTER) - { - error_msg("Plugin '%s' is not a reporter plugin", pName); - return NULL; - } - return (CReporter*)plugin; -} - -CAction* CPluginManager::GetAction(const char *pName, bool silent) -{ - CPlugin *plugin = LoadPlugin(pName); - if (!plugin) - { - error_msg("Plugin '%s' is not registered", pName); - return NULL; - } - if (m_mapLoadedModules[pName]->GetType() != ACTION) - { - if (!silent) - error_msg("Plugin '%s' is not an action plugin", pName); - return NULL; - } - return (CAction*)plugin; -} - -CDatabase* CPluginManager::GetDatabase(const char *pName) -{ - CPlugin *plugin = LoadPlugin(pName); - if (!plugin) - { - throw CABRTException(EXCEP_PLUGIN, "Plugin '%s' is not registered", pName); - } - if (m_mapLoadedModules[pName]->GetType() != DATABASE) - { - throw CABRTException(EXCEP_PLUGIN, "Plugin '%s' is not a database plugin", pName); - } - return (CDatabase*)plugin; -} - -plugin_type_t CPluginManager::GetPluginType(const char *pName) -{ - CPlugin *plugin = LoadPlugin(pName); - if (!plugin) - { - throw CABRTException(EXCEP_PLUGIN, "Plugin '%s' is not registered", pName); - } - map_loaded_module_t::iterator it_module = m_mapLoadedModules.find(pName); - return it_module->second->GetType(); -} - -void CPluginManager::SetPluginSettings(const char *pName, - const char *pUID, - const map_plugin_settings_t& pSettings) -{ - map_loaded_module_t::iterator it_module = m_mapLoadedModules.find(pName); - if (it_module == m_mapLoadedModules.end()) - { - return; - } - map_plugin_t::iterator it_plugin = m_mapPlugins.find(pName); - if (it_plugin == m_mapPlugins.end()) - { - return; - } - it_plugin->second->SetSettings(pSettings); - -#if 0 /* Writing to ~user/.abrt/ is bad wrt security */ - if (it_module->second->GetType() != REPORTER) - { - return; - } - - const char *home = get_home_dir(xatoi_u(pUID.c_str())); - if (home == NULL || strlen(home) == 0) - return; - - string confDir = home + "/.abrt"; - string confPath = confDir + "/" + pName + "."PLUGINS_CONF_EXTENSION; - uid_t uid = xatoi_u(pUID.c_str()); - struct passwd* pw = getpwuid(uid); - gid_t gid = pw ? pw->pw_gid : uid; - - struct stat buf; - if (stat(confDir.c_str(), &buf) != 0) - { - if (mkdir(confDir.c_str(), 0700) == -1) - { - perror_msg("Can't create dir '%s'", confDir.c_str()); - return; - } - if (chmod(confDir.c_str(), 0700) == -1) - { - perror_msg("Can't change mod of dir '%s'", confDir.c_str()); - return; - } - if (chown(confDir.c_str(), uid, gid) == -1) - { - perror_msg("Can't change '%s' ownership to %lu:%lu", confPath.c_str(), (long)uid, (long)gid); - return; - } - } - else if (!S_ISDIR(buf.st_mode)) - { - perror_msg("'%s' is not a directory", confDir.c_str()); - return; - } - - /** we don't want to save it from daemon if it's running under root - but wi might get back to this once we make the daemon to not run - with root privileges - */ - /* - SavePluginSettings(confPath, pSettings); - if (chown(confPath.c_str(), uid, gid) == -1) - { - perror_msg("Can't change '%s' ownership to %lu:%lu", confPath.c_str(), (long)uid, (long)gid); - return; - } - */ -#endif -} - -map_plugin_settings_t CPluginManager::GetPluginSettings(const char *pName) -{ - map_plugin_settings_t ret; - - map_loaded_module_t::iterator it_module = m_mapLoadedModules.find(pName); - if (it_module != m_mapLoadedModules.end()) - { - map_plugin_t::iterator it_plugin = m_mapPlugins.find(pName); - if (it_plugin != m_mapPlugins.end()) - { - VERB3 log("Returning settings for loaded plugin %s", pName); - ret = it_plugin->second->GetSettings(); - return ret; - } - } - /* else: module is not loaded */ - map_map_string_t::iterator it_settings = m_map_plugin_settings.find(pName); - if (it_settings != m_map_plugin_settings.end()) - { - /* but it exists, its settings are available nevertheless */ - VERB3 log("Returning settings for non-loaded plugin %s", pName); - ret = it_settings->second; - return ret; - } - - VERB3 log("Request for settings of unknown plugin %s, returning null result", pName); - return ret; -} diff --git a/src/Daemon/PluginManager.h b/src/Daemon/PluginManager.h deleted file mode 100644 index a00fd3e4..00000000 --- a/src/Daemon/PluginManager.h +++ /dev/null @@ -1,157 +0,0 @@ -/* - PluginManager.h - header file for plugin manager. it takes care about - (un)loading plugins - - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#ifndef PLUGINMANAGER_H_ -#define PLUGINMANAGER_H_ - -#include "abrt_types.h" -#include "plugin.h" -#include "analyzer.h" -#include "reporter.h" -#include "database.h" -#include "action.h" - -class CLoadedModule; /* opaque */ - -/** - * A class. It takes care of loading, registering and manipulating with - * plugins. When a plugin is loaded, its library is opened, but no plugin - * instance is created. It is possible after plugin registration. - */ -class CPluginManager -{ - private: - typedef std::map map_loaded_module_t; - typedef std::map map_plugin_t; - - /** - * Loaded plugins. A key is a plugin name. - */ - map_loaded_module_t m_mapLoadedModules; - /** - * Registered plugins. A key is a plugin name. - */ - map_plugin_t m_mapPlugins; - /** - * List of all possible plugins (loaded or not), with some attributes. - */ - map_map_string_t m_map_plugin_info; - map_map_string_t m_map_plugin_settings; - - public: - /** - * A constructor. - * @param pPluginsConfDir A plugins configuration directory. - * @param pPluginsLibDir A plugins library directory. - */ - CPluginManager(); - /** - * A destructor. - */ - ~CPluginManager(); - /** - * A method, which loads all plugins in plugins library direcotry. - */ - void LoadPlugins(); - /** - * A method, which unregister and unload all loaded plugins. - */ - void UnLoadPlugins(); - /** - * A method, which loads particular plugin. - * @param pName A plugin name. - */ - CPlugin* LoadPlugin(const char *pName, bool enabled_only = false); - /** - * A method, which unloads particular plugin. - * @param pName A plugin name. - */ - void UnLoadPlugin(const char *pName); -#ifdef PLUGIN_DYNAMIC_LOAD_UNLOAD - /** - * A method, which registers particular plugin. - * @param pName A plugin name. - */ - void RegisterPluginDBUS(const char *pName, const char *pDBUSSender); - /** - * A method, which unregister particular plugin, - * called via DBUS - * @param pName A plugin name. - * @param pDBUSSender DBUS user identification - */ - void UnRegisterPluginDBUS(const char *pName, const char *pDBUSSender); -#endif - /** - * A method, which returns instance of particular analyzer plugin. - * @param pName A plugin name. - * @return An analyzer plugin. - */ - CAnalyzer* GetAnalyzer(const char *pName); - /** - * A method, which returns instance of particular reporter plugin. - * @param pName A plugin name. - * @return A reporter plugin. - */ - CReporter* GetReporter(const char *pName); - /** - * A method, which returns instance of particular action plugin. - * @param pName A plugin name. - * @return An action plugin. - */ - CAction* GetAction(const char *pName, bool silent = false); - /** - * A method, which returns instance of particular database plugin. - * @param pName A plugin name. - * @return A database plugin. - */ - CDatabase* GetDatabase(const char *pName); - /** - * A method, which returns type of particular plugin. - * @param pName A plugin name. - * @return A plugin type. - */ - plugin_type_t GetPluginType(const char *pName); - /** - * A method, which gets all plugins info (even those plugins which are - * disabled). It can be sent via DBus to GUI and displayed to an user. - * Then user can fill all needed informations like URLs etc. - * @return A vector of maps - */ - const map_map_string_t& GetPluginsInfo() { return m_map_plugin_info; } - /** - * A method, which sets up a plugin. The settings are also saved in home - * directory of an user. - * @param pName A plugin name. - * @param pUID An uid of user. - * @param pSettings A plugin's settings. - */ - void SetPluginSettings(const char *pName, - const char *pUID, - const map_plugin_settings_t& pSettings); - /** - * A method, which returns plugin's settings according to user. - * @param pName A plugin name. - * @return Plugin's settings. - */ - map_plugin_settings_t GetPluginSettings(const char *pName); -}; - -#endif /*PLUGINMANAGER_H_*/ diff --git a/src/Daemon/Settings.cpp b/src/Daemon/Settings.cpp deleted file mode 100644 index 5dda12a0..00000000 --- a/src/Daemon/Settings.cpp +++ /dev/null @@ -1,562 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 RedHat Inc - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#include "Settings.h" -#include "abrtlib.h" -#include "abrt_types.h" -#include "Polkit.h" - -#define SECTION_COMMON "Common" -#define SECTION_ANALYZER_ACTIONS_AND_REPORTERS "AnalyzerActionsAndReporters" -#define SECTION_CRON "Cron" - -/* Conf file has this format: - * [ section_name1 ] - * name1 = value1 - * name2 = value2 - * [ section_name2 ] - * name = value - */ - -/* Static data */ -/* Filled by LoadSettings() */ - -/* map["name"] = "value" strings from [ Common ] section. - * If the same name found on more than one line, - * the values are appended, separated by comma: map["name"] = "value1,value2" */ -static map_string_t s_mapSectionCommon; -/* ... from [ AnalyzerActionsAndReporters ] */ -static map_string_t s_mapSectionAnalyzerActionsAndReporters; -/* ... from [ Cron ] */ -static map_string_t s_mapSectionCron; - -/* Public data */ -/* Written out exactly in this order by SaveSettings() */ - -/* [ Common ] */ -/* one line: "OpenGPGCheck = value" */ -bool g_settings_bOpenGPGCheck = false; -/* one line: "OpenGPGPublicKeys = value1,value2" */ -set_string_t g_settings_setOpenGPGPublicKeys; -set_string_t g_settings_setBlackListedPkgs; -set_string_t g_settings_setBlackListedPaths; -std::string g_settings_sDatabase; -std::string g_settings_sWatchCrashdumpArchiveDir; -unsigned int g_settings_nMaxCrashReportsSize = 1000; -bool g_settings_bProcessUnpackaged = false; - -/* one line: "ActionsAndReporters = aa_first,bb_first(bb_second),cc_first" */ -vector_pair_string_string_t g_settings_vectorActionsAndReporters; -/* [ AnalyzerActionsAndReporters ] */ -/* many lines, one per key: "map_key = aa_first,bb_first(bb_second),cc_first" */ -map_analyzer_actions_and_reporters_t g_settings_mapAnalyzerActionsAndReporters; -/* [ Cron ] */ -/* many lines, one per key: "map_key = aa_first,bb_first(bb_second),cc_first" */ -map_cron_t g_settings_mapCron; - - -/* - * Loading - */ - -static set_string_t ParseList(const char* pList) -{ - unsigned ii; - std::string item; - set_string_t set; - for (ii = 0; pList[ii]; ii++) - { - if (pList[ii] == ',') - { - set.insert(item); - item = ""; - } - else - { - item += pList[ii]; - } - } - if (item != "") - { - set.insert(item); - } - return set; -} - -/* Format: name, name(param),name("param with spaces \"and quotes\"") */ -static vector_pair_string_string_t ParseListWithArgs(const char *pValue, int *err) -{ - VERB3 log(" ParseListWithArgs(%s)", pValue); - - vector_pair_string_string_t pluginsWithArgs; - std::string item; - std::string action; - bool is_quote = false; - bool is_arg = false; - for (int ii = 0; pValue[ii]; ii++) - { - if (is_quote && pValue[ii] == '\\' && pValue[ii + 1]) - { - ii++; - item += pValue[ii]; - continue; - } - if (pValue[ii] == '"') - { - is_quote = !is_quote; - /*item += pValue[ii]; - wrong! name("param") must be == name(param) */ - continue; - } - if (is_quote) - { - item += pValue[ii]; - continue; - } - if (pValue[ii] == '(') - { - if (!is_arg) - { - action = item; - item = ""; - is_arg = true; - } - else - { - *err = 1; - error_msg("Parser error: Invalid syntax on column %d in \"%s\"", ii, pValue); - } - - continue; - } - if (pValue[ii] == ')') - { - if (is_arg) - { - VERB3 log(" adding (%s,%s)", action.c_str(), item.c_str()); - pluginsWithArgs.push_back(make_pair(action, item)); - item = ""; - is_arg = false; - action = ""; - } - else - { - *err = 1; - error_msg("Parser error: Invalid syntax on column %d in \"%s\"", ii, pValue); - } - - continue; - } - if (pValue[ii] == ',' && !is_arg) - { - if (item != "") - { - VERB3 log(" adding (%s,%s)", item.c_str(), ""); - pluginsWithArgs.push_back(make_pair(item, "")); - item = ""; - } - continue; - } - item += pValue[ii]; - } - - if (is_quote) - { - *err = 1; - error_msg("Parser error: Unclosed quote in \"%s\"", pValue); - } - - if (is_arg) - { - *err = 1; - error_msg("Parser error: Unclosed argument in \"%s\"", pValue); - } - else if (item != "") - { - VERB3 log(" adding (%s,%s)", item.c_str(), ""); - pluginsWithArgs.push_back(make_pair(item, "")); - } - return pluginsWithArgs; -} - -static int ParseCommon() -{ - map_string_t::const_iterator end = s_mapSectionCommon.end(); - map_string_t::const_iterator it = s_mapSectionCommon.find("OpenGPGCheck"); - if (it != end) - { - g_settings_bOpenGPGCheck = string_to_bool(it->second.c_str()); - } - it = s_mapSectionCommon.find("BlackList"); - if (it != end) - { - g_settings_setBlackListedPkgs = ParseList(it->second.c_str()); - } - it = s_mapSectionCommon.find("BlackListedPaths"); - if (it != end) - { - g_settings_setBlackListedPaths = ParseList(it->second.c_str()); - } - it = s_mapSectionCommon.find("Database"); - if (it != end) - { - g_settings_sDatabase = it->second; - } - it = s_mapSectionCommon.find("WatchCrashdumpArchiveDir"); - if (it != end) - { - g_settings_sWatchCrashdumpArchiveDir = it->second; - } - it = s_mapSectionCommon.find("MaxCrashReportsSize"); - if (it != end) - { - g_settings_nMaxCrashReportsSize = xatoi_u(it->second.c_str()); - } - it = s_mapSectionCommon.find("ActionsAndReporters"); - if (it != end) - { - int err = 0; - g_settings_vectorActionsAndReporters = ParseListWithArgs(it->second.c_str(), &err); - if (err) - return err; - } - it = s_mapSectionCommon.find("ProcessUnpackaged"); - if (it != end) - { - g_settings_bProcessUnpackaged = string_to_bool(it->second.c_str()); - } - return 0; /* no error */ -} - -static int ParseCron() -{ - map_string_t::iterator it = s_mapSectionCron.begin(); - for (; it != s_mapSectionCron.end(); it++) - { - int err = 0; - vector_pair_string_string_t actionsAndReporters = ParseListWithArgs(it->second.c_str(), &err); - if (err) - return err; - g_settings_mapCron[it->first] = actionsAndReporters; - } - return 0; /* no error */ -} - -static set_string_t ParseKey(const char *Key, int *err) -{ - unsigned int ii; - std::string item; - std::string key; - set_string_t set; - bool is_quote = false; - for (ii = 0; Key[ii]; ii++) - { - if (Key[ii] == '\"') - { - is_quote = !is_quote; - } - else if (Key[ii] == ':' && !is_quote) - { - key = item; - item = ""; - } - else if (isspace(Key[ii]) && !is_quote) - { - continue; - } - else if ((Key[ii] == ',') && !is_quote) - { - if (!key.empty()) - { - set.insert(key + ":" + item); - item = ""; - } - else - { - *err = 1; - error_msg("Parser error: Invalid syntax on column %d in \"%s\"", ii, Key); - } - } - else - { - item += Key[ii]; - } - } - if (is_quote) - { - *err = 1; - error_msg("Parser error: Unclosed quote in \"%s\"", Key); - } - else if (item != "") - { - if (key == "") - { - set.insert(item); - } - else - { - set.insert(key + ":" + item); - } - } - return set; -} - -static int ParseAnalyzerActionsAndReporters() -{ - map_string_t::iterator it = s_mapSectionAnalyzerActionsAndReporters.begin(); - for (; it != s_mapSectionAnalyzerActionsAndReporters.end(); it++) - { - int err = 0; - set_string_t keys = ParseKey(it->first.c_str(), &err); - vector_pair_string_string_t actionsAndReporters = ParseListWithArgs(it->second.c_str(), &err); - if (err) - return err; - set_string_t::iterator it_keys = keys.begin(); - for (; it_keys != keys.end(); it_keys++) - { - VERB2 log("AnalyzerActionsAndReporters['%s']=...", it_keys->c_str()); - g_settings_mapAnalyzerActionsAndReporters[*it_keys] = actionsAndReporters; - } - } - return 0; /* no error */ -} - -static void LoadGPGKeys() -{ - FILE *fp = fopen(CONF_DIR"/gpg_keys", "r"); - if (fp) - { - /* every line is one key - * FIXME: make it more robust, it doesn't handle comments - */ - char line[512]; - while (fgets(line, sizeof(line), fp)) - { - if (line[0] == '/') // probably the begining of path, so let's handle it as a key - { - strchrnul(line, '\n')[0] = '\0'; - g_settings_setOpenGPGPublicKeys.insert(line); - } - } - fclose(fp); - } -} - -/** - * Reads configuration from file to s_mapSection* static variables. - * The file must be opened for reading. - */ -static int ReadConfigurationFromFile(FILE *fp) -{ - char line[512]; - std::string section; - int lineno = 0; - while (fgets(line, sizeof(line), fp)) - { - strchrnul(line, '\n')[0] = '\0'; - ++lineno; - bool is_key = true; - bool is_section = false; - bool is_quote = false; - unsigned ii; - std::string key, value; - for (ii = 0; line[ii] != '\0'; ii++) - { - if (is_quote && line[ii] == '\\' && line[ii + 1]) - { - value += line[ii]; - ii++; - value += line[ii]; - continue; - } - if (isspace(line[ii]) && !is_quote) - { - continue; - } - if (line[ii] == '#' && !is_quote && key == "") - { - break; - } - if (line[ii] == '[' && !is_quote) - { - is_section = true; - section = ""; - continue; - } - if (line[ii] == '"') - { - is_quote = !is_quote; - value += line[ii]; - continue; - } - if (is_section) - { - if (line[ii] == ']') - break; - section += line[ii]; - continue; - } - if (line[ii] == '=' && !is_quote) - { - is_key = false; - key = value; - value = ""; - continue; - } - value += line[ii]; - } - - if (is_quote) - { - error_msg("abrt.conf: Invalid syntax on line %d", lineno); - return 1; /* error */ - } - - if (is_section) - { - if (line[ii] != ']') /* section not closed */ - { - error_msg("abrt.conf: Section not closed on line %d", lineno); - return 1; /* error */ - } - continue; - } - - if (is_key) - { - if (!value.empty()) /* the key is stored in value */ - { - error_msg("abrt.conf: Invalid syntax on line %d", lineno); - return 1; /* error */ - } - continue; - } - else if (key.empty()) /* A line without key: " = something" */ - { - error_msg("abrt.conf: Invalid syntax on line %d", lineno); - return 1; /* error */ - } - - if (section == SECTION_COMMON) - { - if (s_mapSectionCommon[key] != "") - s_mapSectionCommon[key] += ","; - s_mapSectionCommon[key] += value; - } - else if (section == SECTION_ANALYZER_ACTIONS_AND_REPORTERS) - { - if (s_mapSectionAnalyzerActionsAndReporters[key] != "") - s_mapSectionAnalyzerActionsAndReporters[key] += ","; - s_mapSectionAnalyzerActionsAndReporters[key] += value; - } - else if (section == SECTION_CRON) - { - if (s_mapSectionCron[key] != "") - s_mapSectionCron[key] += ","; - s_mapSectionCron[key] += value; - } - else - { - error_msg("abrt.conf: Ignoring entry in invalid section [%s]", section.c_str()); - return 1; /* error */ - } - } - return 0; /* success */ -} - -/* abrt daemon loads .conf file */ -int LoadSettings() -{ - int err = 0; - - FILE *fp = fopen(CONF_DIR"/abrt.conf", "r"); - if (fp) - { - err = ReadConfigurationFromFile(fp); - fclose(fp); - } - else - error_msg("Unable to read configuration file %s", CONF_DIR"/abrt.conf"); - - if (err == 0) - err = ParseCommon(); - if (err == 0) - err = ParseAnalyzerActionsAndReporters(); - if (err == 0) - err = ParseCron(); - - if (err == 0) - { - /* - * loading gpg keys will invoke LoadOpenGPGPublicKey() from rpm.cpp - * pgpReadPkts which makes nss to re-init and thus makes - * bugzilla plugin work :-/ - */ - //FIXME FIXME FIXME FIXME FIXME FIXME!!! - //if (g_settings_bOpenGPGCheck) - LoadGPGKeys(); - } - - return err; -} - -/* dbus call to retrieve .conf file data from daemon */ -map_abrt_settings_t GetSettings() -{ - map_abrt_settings_t ABRTSettings; - - ABRTSettings[SECTION_COMMON] = s_mapSectionCommon; - ABRTSettings[SECTION_ANALYZER_ACTIONS_AND_REPORTERS] = s_mapSectionAnalyzerActionsAndReporters; - ABRTSettings[SECTION_CRON] = s_mapSectionCron; - - return ABRTSettings; -} - -/* dbus call to change some .conf file data */ -void SetSettings(const map_abrt_settings_t& pSettings, const char *dbus_sender) -{ - int polkit_result; - - polkit_result = polkit_check_authorization(dbus_sender, - "org.fedoraproject.abrt.change-daemon-settings"); - if (polkit_result != PolkitYes) - { - error_msg("user %s not authorized, returned %d", dbus_sender, polkit_result); - return; - } - log("user %s succesfully authorized", dbus_sender); - - map_abrt_settings_t::const_iterator it = pSettings.find(SECTION_COMMON); - map_abrt_settings_t::const_iterator end = pSettings.end(); - if (it != end) - { - s_mapSectionCommon = it->second; - ParseCommon(); - } - it = pSettings.find(SECTION_ANALYZER_ACTIONS_AND_REPORTERS); - if (it != end) - { - s_mapSectionAnalyzerActionsAndReporters = it->second; - ParseAnalyzerActionsAndReporters(); - } - it = pSettings.find(SECTION_CRON); - if (it != end) - { - s_mapSectionCron = it->second; - ParseCron(); - } -} diff --git a/src/Daemon/Settings.h b/src/Daemon/Settings.h deleted file mode 100644 index 3103dbd5..00000000 --- a/src/Daemon/Settings.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 RedHat Inc - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#ifndef SETTINGS_H_ -#define SETTINGS_H_ - -#include "abrt_types.h" - -typedef map_vector_pair_string_string_t map_analyzer_actions_and_reporters_t; -typedef map_vector_pair_string_string_t map_cron_t; -typedef map_map_string_t map_abrt_settings_t; - -extern set_string_t g_settings_setOpenGPGPublicKeys; -extern set_string_t g_settings_setBlackListedPkgs; -extern set_string_t g_settings_setBlackListedPaths; -extern unsigned int g_settings_nMaxCrashReportsSize; -extern bool g_settings_bOpenGPGCheck; -extern bool g_settings_bProcessUnpackaged; -extern std::string g_settings_sDatabase; -extern std::string g_settings_sWatchCrashdumpArchiveDir; -extern map_cron_t g_settings_mapCron; -extern vector_pair_string_string_t g_settings_vectorActionsAndReporters; -extern map_analyzer_actions_and_reporters_t g_settings_mapAnalyzerActionsAndReporters; - -int LoadSettings(); -void SaveSettings(); -void SetSettings(const map_abrt_settings_t& pSettings, const char * dbus_sender); -map_abrt_settings_t GetSettings(); - -#endif diff --git a/src/Daemon/abrt-debuginfo-install b/src/Daemon/abrt-debuginfo-install deleted file mode 100755 index 3a236b59..00000000 --- a/src/Daemon/abrt-debuginfo-install +++ /dev/null @@ -1,418 +0,0 @@ -#!/bin/sh -# Called by abrtd before producing a backtrace. -# The task of this script is to install debuginfos. -# -# Just using [pk-]debuginfo-install does not work well. -# - they can't install more than one version of debuginfo -# for a package -# - their output is unsuitable for scripting -# - debuginfo-install aborts if yum lock is busy -# - pk-debuginfo-install was observed to hang -# -# Usage: abrt-debuginfo-install CORE TEMPDIR [CACHEDIR[:DEBUGINFODIR1:DEBUGINFODIR2...]] -# If CACHEDIR is specified, debuginfos should be installed there. -# If not, debuginfos should be installed into TEMPDIR. -# -# Currently, we are called with CACHEDIR set to "/var/cache/abrt-di", -# but in the future it may be omitted or set to something else. -# Script must be ready for those cases too. Consider, for example, -# corner cases of "" and "/". -# -# Output goes to GUI as debuginfo install log. The script should be careful -# to give useful, but not overly cluttered info to stdout. -# Additionally, abrt daemon handles "MISSING:xxxx" messages specially: -# it is used to inform about missing debuginfos. -# -# Exitcodes: -# 0 - all debuginfos are installed -# 1 - not all debuginfos are installed -# 2+ - serious problem -# -# Algorithm: -# - Create TEMPDIR -# - Extract build-ids from coredump -# - For every build-id, check /usr/lib/debug/.build-id/XX/XXXX.debug -# and CACHEDIR/usr/lib/debug/.build-id/XX/XXXX.debug -# - If they all exist, exit 0 -# - Using "yum provides /usr/lib/debug/.build-id/XX/XXXX.debug", -# figure out which debuginfo packages are needed -# - Download them using "yumdownloader PACKAGE..." -# - Unpack them with rpm2cpio | cpio to TEMPDIR -# - If CACHEDIR is specified, copy usr/lib/debug/.build-id/XX/XXXX.debug -# to CACHEDIR/usr/lib/debug/.build-id/XX/XXXX.debug and delete TEMPDIR -# - Report which XX/XXXX.debug are still missing. -# -# For better debuggability, eu_unstrip.OUT, yum_provides.OUT etc files -# are saved in TEMPDIR, and TEMPDIR is not deleted if we exit with exitcode 2 -# ("serious problem"). - - -debug=false -# Useful if you need to see saved rpms, command outputs etc -keep_tmp=false - - -# Handle options -if test x"$1" = x"--"; then - shift -else - if test x"$1" = x"-v"; then - debug=true - shift - fi - if test $# -lt 2 || test x"$1" = x"--help"; then - echo "Usage:" - echo - echo "abrt-debuginfo-install [-v] CORE TEMPDIR [CACHEDIR[:DEBUGINFODIR...]]" - echo - echo "TEMPDIR must be a name of a new temporary directory. It must not exist." - echo "If CACHEDIR is specified, debuginfos are installed in CACHEDIR," - echo "and TEMPDIR is deleted on exit." - echo "Otherwise, debuginfos are installed into TEMPDIR, which is not deleted." - echo - echo "Options:" - echo " -v Verbose (for debugging)" - echo - exit - fi -fi - - -# Parse params -core="$1" -tempdir="$2" -debuginfodirs="${3//:/ }" -cachedir="${3%%:*}" - - -# stderr may be used for status messages too -exec 2>&1 - - -error_msg_and_die() { - echo "$*" - exit 2 -} - -count_words() { - echo $# -} - -print_missing_build_ids() { - local build_id - local build_id1 - local build_id2 - local file - local d - for build_id in $build_ids; do - build_id1=${build_id:0:2} - build_id2=${build_id:2} - file="usr/lib/debug/.build-id/$build_id1/$build_id2.debug" - test -f "/$file" && continue - # On 2nd pass, we may already have some debuginfos in tempdir - test -f "$tempdir/$file" && continue - # Check cachedir if we have one - for d in $debuginfodirs; do - test -f "$d/$file" && continue 2 - done - echo -n "$build_id " - done -} - -# Note: it is run in `backticks`, use >&2 for error messages -print_missing_debuginfos() { - local build_id - local build_id1 - local build_id2 - local file - local d - for build_id in $build_ids; do - build_id1=${build_id:0:2} - build_id2=${build_id:2} - file="usr/lib/debug/.build-id/$build_id1/$build_id2.debug" - test -f "/$file" && continue - # On 2nd pass, we may already have some debuginfos in tempdir - test -f "$tempdir/$file" && continue - # Check cachedir if we have one - if test x"$cachedir" != x""; then - for d in $debuginfodirs; do - test -f "$d/$file" && continue 2 - done - fi - echo -n "/$file " - done -} - -cleanup_and_report_missing() { -# Which debuginfo files are still missing, including those we just unpacked? - missing_build_ids=`print_missing_build_ids` - $debug && echo "missing_build_ids:$missing_build_ids" >&2 - - # If cachedir is specified, tempdir is just a staging area. Delete it - if test x"$cachedir" != x""; then - $keep_tmp && echo "NOT removing $tempdir (keep_tmp debugging is on)" >&2 - $keep_tmp || { $debug && echo "Removing $tempdir" >&2; rm -rf "$tempdir"; } - fi - - for missing in $missing_build_ids; do - echo "MISSING:$missing" >&2 - done - - test x"$missing_build_ids" != x"" && echo "`count_words $missing_build_ids` debuginfos can't be found" >&2 -} - -# $1: iteration (1,2...) -# Note: it is run in `backticks`, use >&2 for error messages -print_package_names() { - # We'll run something like: - # yum --enablerepo=*debuginfo* --quiet provides \ - # /usr/lib/debug/.build-id/bb/11528d59940983f495e9cb099cafb0cb206051.debug \ - # /usr/lib/debug/.build-id/c5/b84c0ad3676509dc30bfa7d42191574dac5b06.debug ... - local yumopts="" - if test x"$1" = x"1"; then - yumopts="-C" - echo "`count_words $missing_debuginfo_files` missing debuginfos, getting package list from cache" >&2 - else - echo "`count_words $missing_debuginfo_files` missing debuginfos, getting package list from repositories" >&2 - fi - # --showduplicates: do not just show the latest package - # (tried to use -R2 to abort on stuck yum lock but -R is not about that) - local cmd="yum $yumopts $yum_repo_opts --showduplicates --quiet provides $missing_debuginfo_files" - echo "$cmd" >"yum_provides.$1.OUT" - $debug && echo "Running: $cmd" >&2 - # eval is needed to strip away ''s in $yum_repo_opts; cant remove them and just use - # unquoted $cmd, that would perform globbing on '*' - local yum_provides_OUT="`eval $cmd 2>&1`" - local err=$? - printf "%s\nyum exitcode:%s\n" "$yum_provides_OUT" $err >>"yum_provides.$1.OUT" - test $err = 0 || error_msg_and_die "yum provides... exited with $err: -`head yum_provides.$1.OUT`" >&2 - - # The output is pretty machine-unfriendly: - # glibc-debuginfo-2.10.90-24.x86_64 : Debug information for package glibc - # Repo : rawhide-debuginfo - # Matched from: - # Filename : /usr/lib/debug/.build-id/5b/c784c8d63f87dbdeb747a773940956a18ecd2f.debug - # - # 1:dbus-debuginfo-1.2.12-2.fc11.x86_64 : Debug information for package dbus - # Repo : updates-debuginfo - # Matched from: - # Filename : /usr/lib/debug/.build-id/bc/da7d09eb6c9ee380dae0ed3d591d4311decc31.debug - # Need to massage it a lot. - # There can be duplicates (one package may provide many debuginfos). - printf "%s\n" "$yum_provides_OUT" \ - | grep -- -debuginfo- \ - | sed 's/^[0-9]*://' \ - | sed -e 's/ .*//' -e 's/:.*//' \ - | sort | uniq | xargs -} - -abort_if_low_on_disk_space() { - local mb - # free_blocks * block_size / (1024*1024), careful to not overflow: - mb=$((`stat -f -c "%a / 8192 * %S / 128" "$tempdir"`)) - if test $mb -lt $1; then - $debug && echo "Removing $tempdir" >&2 - rm -rf "$tempdir" - error_msg_and_die "Less than $1 Mb of free space in $tempdir: $mb Mb" - fi - if test x"$cachedir" != x"" && test -d "$cachedir"; then - mb=$((`stat -f -c "%a / 8192 * %S / 128" "$cachedir"`)) - if test $mb -lt $1; then - $debug && echo "Removing $tempdir" >&2 - rm -rf "$tempdir" - error_msg_and_die "Less than $1 Mb of free space in $cachedir: $mb Mb" - fi - fi -} - -download_packages() { - local pkg - local err - local file - local build_id - local build_id1 - local build_id2 - local d - - ## Download with one command (too silent): - ## Redirecting, since progress bar stuff only messes up our output - ##yumdownloader --enablerepo=*debuginfo* --quiet $packages >yumdownloader.OUT 2>&1 - ##err=$? - ##echo "exitcode:$err" >>yumdownloader.OUT - ##test $err = 0 || error_msg_and_die ... - >yumdownloader.OUT - i=1 - for pkg in $packages; do - echo "Download $i/$num_packages: $pkg" - echo "Download $i/$num_packages: $pkg" >>yumdownloader.OUT - cmd="yumdownloader $yum_repo_opts --quiet $pkg" - $debug && echo "Running: $cmd" >&2 - # eval is needed to strip away ''s in $yum_repo_opts - eval $cmd >>yumdownloader.OUT 2>&1 & - # using EXIT handler and this, make sure we kill yumdownloader if we exit: - CHILD_PID=$! - wait - err=$? - CHILD_PID="" - echo "exitcode:$err" >>yumdownloader.OUT - echo >>yumdownloader.OUT - test $err = 0 || echo "Download of $pkg failed!" - abort_if_low_on_disk_space 256 - - # Process and delete the *.rpm file just downloaded - # We do it right after download: some users have smallish disks... - for file in *.rpm; do - # Happens if no .rpm's were downloaded (yumdownloader problem) - # In this case, $f is the literal "*.rpm" string - test -f "$file" || { echo "No rpm file downloaded"; continue; } - echo "Unpacking: $file" - echo "Processing: $file" >>unpack.OUT - rpm2cpio <"$file" >"unpacked.cpio" 2>>unpack.OUT || error_msg_and_die "Can't convert '$file' to cpio" - $keep_tmp || rm "$file" - abort_if_low_on_disk_space 256 - cpio -id <"unpacked.cpio" >>unpack.OUT 2>&1 || error_msg_and_die "Can't unpack '$file' cpio archive" - rm "unpacked.cpio" - abort_if_low_on_disk_space 256 - # Copy debuginfo files to cachedir - if test x"$cachedir" != x"" && test -d "$cachedir"; then - # For every needed debuginfo, check whether we have it - for build_id in $build_ids; do - build_id1=${build_id:0:2} - build_id2=${build_id:2} - file="usr/lib/debug/.build-id/$build_id1/$build_id2.debug" - # Do not copy it if it can be found in any of $debuginfodirs - test -f "/$file" && continue - if test x"$cachedir" != x""; then - for d in $debuginfodirs; do - test -f "$d/$file" && continue 2 - done - fi - if test -f "$file"; then - # File is one of those we just installed, cache it - mkdir -p "$cachedir/usr/lib/debug/.build-id/$build_id1" - # Note: this does not preserve symlinks. This is intentional - $debug && echo Copying "$file" to "$cachedir/$file" >&2 - echo "Caching debuginfo: $file" - cp --remove-destination "$file" "$cachedir/$file" || error_msg_and_die "Can't copy $file (disk full?)" - continue - fi - done - fi - # Delete remaining files unpacked from .cpio - # which we didn't need after all - rm -r etc bin sbin usr var opt 2>/dev/null - done - : $((i++)) - done -} - - -# Sanity checking -test -f "$core" || error_msg_and_die "not a file: '$core'" -# cachedir is optional -test x"$cachedir" = x"" || test -d "$cachedir" || error_msg_and_die "bad cachedir '$cachedir'" -# tempdir must not exist -test -e "$tempdir" && error_msg_and_die "tempdir exists: '$tempdir'" - -# Intentionally not using -p: we want to abort if tempdir exists -mkdir -- "$tempdir" || exit 2 -cd "$tempdir" || exit 2 - - -abort_if_low_on_disk_space 1024 - - -# A hook to stop yumdownloader, in case we are terminated by kill -TERM etc. -CHILD_PID="" -trap 'test x"$CHILD_PID" != x"" && kill -- "$CHILD_PID"' EXIT - - -$debug && echo "Downloading rpms to $tempdir" - - -echo "Getting list of build IDs" -# Observed errors: -# eu-unstrip: /var/spool/abrt/ccpp-1256301004-2754/coredump: Callback returned failure -eu_unstrip_OUT=`eu-unstrip "--core=$core" -n 2>eu_unstrip.ERR` -err=$? -printf "%s\neu-unstrip exitcode:%s\n" "$eu_unstrip_OUT" $err >eu_unstrip.OUT -test $err = 0 || error_msg_and_die "eu-unstrip exited with $err: -`cat eu_unstrip.ERR` -`head eu_unstrip.OUT`" - -# eu-unstrip output example: -# 0x400000+0x209000 23c77451cf6adff77fc1f5ee2a01d75de6511dda@0x40024c - - [exe] -# or -# 0x400000+0x20d000 233aa1a57e9ffda65f53efdaf5e5058657a39993@0x40024c /usr/libexec/im-settings-daemon /usr/lib/debug/usr/libexec/im-settings-daemon.debug [exe] -# 0x7fff5cdff000+0x1000 0d3eb4326fd7489fcf9b598269f1edc420e2c560@0x7fff5cdff2f8 . - linux-vdso.so.1 -# 0x3d15600000+0x208000 20196628d1bc062279622615cc9955554e5bb227@0x3d156001a0 /usr/lib64/libnotify.so.1.1.3 /usr/lib/debug/usr/lib64/libnotify.so.1.1.3.debug libnotify.so.1 -# 0x7fd8ae931000+0x62d000 dd49f44f958b5a11a1635523b2f09cb2e45c1734@0x7fd8ae9311a0 /usr/lib64/libgtk-x11-2.0.so.0.1600.6 /usr/lib/debug/usr/lib64/libgtk-x11-2.0.so.0.1600.6.debug -# -# Get space-separated list of all build-ids -# There can be duplicates (observed in real world) -build_ids=`printf "%s\n" "$eu_unstrip_OUT" \ -| while read junk1 build_id binary_file di_file lib_name junk2; do - build_id=${build_id%%@*} - - # This filters out linux-vdso.so, among others - test x"$lib_name" != x"[exe]" && test x"${binary_file:0:1}" != x"/" && continue - # Sanitize build_id: must be longer than 2 chars - test ${#build_id} -le 2 && continue - # Sanitize build_id: must have only hex digits - test x"${build_id//[0-9a-f]/}" != x"" && continue - - echo "$build_id" -done | sort | uniq | xargs` -$debug && echo "build_ids:$build_ids" - - -# Prepare list of repos to use. -# When we look for debuginfo we need only -debuginfo* repos, we can disable the rest -# and thus make it faster. -yum_repo_opts="'--disablerepo=*'" -#// Disabled. Too often, debuginfo repos have names which do not conform to "foo-debuginfo" scheme, -#// and users get bad backtraces. -#// # (Without -C, yum for some reason wants to talk to repos! If one is down, it becomes S..L..O..W) -#// for enabled_repo in `LANG=C yum -C repolist all | grep 'enabled:' | cut -f1 -d' ' | grep -v -- '-debuginfo'`; do -#// yum_repo_opts="$yum_repo_opts '--enablerepo=${enabled_repo}-debuginfo*'" -#// done -yum_repo_opts="$yum_repo_opts '--enablerepo=*-debug*'" - - -# We try to not run yum without -C unless absolutely necessary. -# Therefore we loop. yum is run by print_package_names function, -# on first iteration it is run with -C, on second - without, -# which usually causes yum to download updated filelists, -# which in turn takes several minutes and annoys users. -iter=0 -while test $((++iter)) -le 2; do - # Analyze $build_ids and check which debuginfos are present - missing_debuginfo_files=`print_missing_debuginfos` - # Did print_missing_debuginfos fail? - test $? = 0 || exit 2 - $debug && echo "missing_debuginfo_files:$missing_debuginfo_files" - - test x"$missing_debuginfo_files" = x"" && break - - # Map $missing_debuginfo_files to package names. - # yum is run here. - packages=`print_package_names $iter` - # Did print_package_names fail? - test $? = 0 || exit 2 - $debug && echo "packages ($iter):$packages" - - # yum may return "" here if it found no packages (say, if coredump - # is from a new, unreleased package fresh from koji). - test x"$packages" = x"" && continue - - num_packages=`count_words $packages` - echo "Downloading $num_packages packages" - download_packages -done - -cleanup_and_report_missing - -test x"$missing_build_ids" != x"" && exit 1 -echo "All needed debuginfos are present" -exit 0 diff --git a/src/Daemon/abrt-handle-upload b/src/Daemon/abrt-handle-upload deleted file mode 100755 index 55eeb7a6..00000000 --- a/src/Daemon/abrt-handle-upload +++ /dev/null @@ -1,74 +0,0 @@ -#!/bin/sh -# Called by abrtd when a new file is noticed in upload directory. -# The task of this script is to unpack the file and move -# crashdump(s) found in it to abrt's crashdump directory. -# -# Usage: abrt-handle-upload ABRT_DIR UPLOAD_DIR FILENAME - -#echo "Started: $0 $*" - -print_clean_and_die() -{ - printf "%s\n" "$*" - #echo delete_on_exit="$delete_on_exit" - test "$delete_on_exit" && rm -rf -- $delete_on_exit - exit $die_exitcode -} - -die_exitcode=1 -delete_on_exit="" - -abrt_dir="$1" -upload_dir="$2" -archive="$3" - -test -d "$abrt_dir" || print_clean_and_die "Not a directory: '$abrt_dir'" -test -d "$upload_dir" || print_clean_and_die "Not a directory: '$upload_dir'" -test x"${archive%.working}" != x"$archive" && print_clean_and_die "Skipping: '$archive'" -test x"${archive#/}" != x"$archive" && print_clean_and_die "Skipping: '$archive' (starts with slash)" -test x"${archive#.}" != x"$archive" && print_clean_and_die "Skipping: '$archive' (starts with dot)" -test x"${archive#*..}" != x"$archive" && print_clean_and_die "Skipping: '$archive' (contains ..)" -test x"${archive#* }" != x"$archive" && print_clean_and_die "Skipping: '$archive' (contains space)" -# Note: next line has a tab! -test x"${archive#* }" != x"$archive" && print_clean_and_die "Skipping: '$archive' (contains tab)" - -cd -- "$upload_dir" || print_clean_and_die "Can't chdir to '$upload_dir'" - -unpacker="" -test x"${archive%.tar.gz}" != x"$archive" && unpacker="gunzip" -test x"${archive%.tar.bz2}" != x"$archive" && unpacker="bunzip2" -test x"${archive%.tar.xz}" != x"$archive" && unpacker="unxz" - -test "$unpacker" || print_clean_and_die "Unknown file type: '$archive'" - -tempdir="remote.`date +%Y-%m-%d-%H:%M:%S.%N`.$$" - -mv -- "$archive" "$archive.working" || print_clean_and_die "Can't lock '$archive'" - -delete_on_exit="$archive.working" -$unpacker -t -- "$archive.working" || print_clean_and_die "Verification error on '$archive'" - -echo "Unpacking '$archive'" -mkdir "$tempdir" || print_clean_and_die "Can't create '$tempdir' directory" -delete_on_exit="$archive.working $tempdir" -$unpacker <"$archive.working" | tar xf - -C "$tempdir" || print_clean_and_die "Can't unpack '$archive'" - -# The archive can contain either plain dump files -# or one or more complete crashdump directories. -# Checking second possibility first. -if test -f "$tempdir/analyzer" && test -f "$tempdir/time" && test -f "$tempdir/uid"; then - printf "1" >"$tempdir/remote" - mv -- "$tempdir" "$abrt_dir" -else - for d in "$tempdir"/*; do - test -d "$d" || continue - printf "1" >"$d/remote" - dst="$abrt_dir/$d" - test -e "$dst" && dst="$abrt_dir/$d.$$" - test -e "$dst" && continue - mv -- "$d" "$dst" - done -fi - -die_exitcode=0 -print_clean_and_die "'$archive' processed successfully" diff --git a/src/Daemon/abrt.conf b/src/Daemon/abrt.conf deleted file mode 100644 index 534aef8f..00000000 --- a/src/Daemon/abrt.conf +++ /dev/null @@ -1,61 +0,0 @@ -[ Common ] -# With this option set to "yes", -# only crashes in signed packages will be analyzed. -# the list of public keys used to check the signature is -# in the file gpg_keys -# -OpenGPGCheck = yes - -# Blacklisted packages -# -BlackList = nspluginwrapper, valgrind, strace - -# Process crashes in executables which do not belong to any package? -# -ProcessUnpackaged = no - -# Blacklisted executable paths (shell patterns) -# -BlackListedPaths = /usr/share/doc/*, */example* - -# Which database plugin to use -# -Database = SQLite3 - -# Enable this if you want abrtd to auto-unpack crashdump tarballs which appear -# in this directory (for example, uploaded via ftp, scp etc). -# Note: you must ensure that whatever directory you specify here exists -# and is writable for abrtd. abrtd will not create it automatically. -# -#WatchCrashdumpArchiveDir = /var/spool/abrt-upload - -# Max size for crash storage [MiB] or 0 for unlimited -# -MaxCrashReportsSize = 1000 - -# Vector of actions and reporters which are activated immediately -# after a crash occurs, comma separated. -# -#ActionsAndReporters = Mailx("[abrt] new crash was detected") -#ActionsAndReporters = FileTransfer("store") -ActionsAndReporters = RunApp("test x\"`cat component`\" = x\"xorg-x11-server-Xorg\" && cp /var/log/Xorg.0.log .") - - -# What actions or reporters to run on each crash type -# -[ AnalyzerActionsAndReporters ] -Kerneloops = KerneloopsReporter -CCpp = Bugzilla, Logger -Python = Bugzilla, Logger -#CCpp:xorg-x11-apps = RunApp("date", "date.txt") - - -# Which Action plugins to run repeatedly -# -[ Cron ] -# h:m - at h:m -# s - every s seconds - -120 = KerneloopsScanner - -#02:00 = FileTransfer diff --git a/src/Daemon/abrt.conf.5 b/src/Daemon/abrt.conf.5 deleted file mode 100644 index f8afe393..00000000 --- a/src/Daemon/abrt.conf.5 +++ /dev/null @@ -1,95 +0,0 @@ -.TH "abrt.conf" "5" "28 May 2009" "" -.SH NAME -abrt.conf \- configuration file for abrt -.SH DESCRIPTION -.P -.I abrt -is a daemon that watches for application crashes. When a crash occurs, -it collects the crash data and takes action according to -its configuration. This manual page describes \fIabrt\fP's configuration -file. -.P -The configuration file consists of sections, each section contains -several items in the format "Option = Value". A description of each -section follows: -.SS [Common] -.TP -.B OpenGPGCheck = \fIyes\fP | \fIno\fP -When set to "yes", -.I abrt -will report crashes only in GPG signed packages. When set to "no", -it will report crashes also in unsigned packages. The default is "no". -.TP -.B OpenGPGPublicKeys = \fIfilename\fP , \fIfilename\fP ... -These are the trusted GPG keys with which packages have to be -signed for -.I abrt -to report them if "OpenGPGCheck = yes". -.TP -.B BlackList = \fIpackageName\fP, \fIpackageName\fP ... -.I abrt -will ignore packages in this list and will not handle their crashes. -.TP -.B BlackListedPaths = \fI/path/to/ignore/*\fP, \fI*/another/ignored/path*\fP ... -.I abrt -will ignore crashes in executables whose absolute path matches -one of specified patterns. -.TP -.B Database = \fIdatabasePlugin\fP -This specifies which database plugin -.I abrt -uses to store metadata about the crash. -.TP -.B MaxCrashReportsSize = \fInumber\fP -The maximum disk space (specified in megabytes) that -.I abrt -will use for all the crash dumps. Specify a value here to ensure -that the crash dumps will not fill all available storage space. -The default is 1000. -.TP -.B ActionsAndReporters = \fIplugin\fP, \fIplugin(parameters)\fP -List of reporter and action plugins which will be -run at crash time. -.TP -.B ProcessUnpackaged = \fIyes\fP | \fIno\fP -When set to "yes", -.I abrt -will catch all crashes in the system. When set to "no", -it will catch crashes only in Fedora packages. -The default is "no". - -.SS [ AnalyzerActionsAndReporters ] -This section contains associations between analyzers and action -or reporter plugins. -.TP -.B CCpp = \fIplugin\fP -.I abrt -will use this plugin when a C/C++ program crashes. -You can specify a plugin for specific program, for example -.br -CCpp:bind = Mailx("[abrt] Bind crashed") -.br -The Mailx plugin will run when bind crashes, instead of the plugin specified for -all the C/C++ programs. -.TP -.B Kerneloops = \fIplugin\fP -This plugin will be used in the case of kernel crashes. -.SS [ Cron ] -This section specifies tasks that will be run at some specified time. You can specify -either a time of day in the format -.br -.B hh:mm = \fIplugin\fP -.br -or an interval -.br -.B ss = \fIplugin2\fP -.br -in this case, \fIplugin2\fP will be run every \fIss\fP seconds (this number -can be greater than 60). -.SH "SEE ALSO" -.IR abrtd (8), -.IR abrt-plugins (7) -.SH AUTHOR -Written by Zdeněk Přikryl and -Jiří Moskovčák . Manual page written by Daniel -Novotný . diff --git a/src/Daemon/abrtd.8 b/src/Daemon/abrtd.8 deleted file mode 100644 index 150f0c3d..00000000 --- a/src/Daemon/abrtd.8 +++ /dev/null @@ -1,43 +0,0 @@ -.TH abrtd "8" "28 May 2009" "" -.SH NAME -abrtd \- automated bug reporting tool's daemon -.SH SYNOPSIS -.B abrtd [-dsv[v]...] -.SH DESCRIPTION -.I abrtd -is a daemon that watches for application crashes. When a crash occurs, -it collects the crash data (core file, application's command line etc.) -and takes action according to the type of application that -crashed and according to the configuration in the -.I abrt.conf -config file. There are plugins for various actions: for example to report -the crash to Bugzilla, to mail the report, or to transfer the -report via FTP or SCP. See the manual pages for the -respective plugins. -.SH OPTIONS - -.TP -.B "\-d" - -Stay in the foreground and log to standard error. -.TP -.B "\-s" - -Log to system log even with option -d. -.TP -.B "\-v" - -Log more detailed debugging information. -.SH CAVEATS -When you use some other crash-catching tool specific for an application -or an application type (for example BugBuddy for GNOME applications), -crashes of this type will be handled by that tool and -not by \fIabrtd\fP. If you want \fIabrtd\fP to handle these crashes, -turn off the higher-level crash-catching tool. -.SH "SEE ALSO" -.IR abrt.conf (5), -.IR abrt-plugins (7) -.SH AUTHOR -Written by Zdeněk Přikryl and -Jiří Moskovčák . Manual page written by Daniel -Novotný . diff --git a/src/Daemon/abrtd.service b/src/Daemon/abrtd.service deleted file mode 100644 index e170e324..00000000 --- a/src/Daemon/abrtd.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=ABRT Automated Bug Reporting Tool -After=syslog.target - -[Service] -Type=dbus -BusName=com.redhat.abrt -ExecStart=/usr/sbin/abrtd -d -s - -[Install] -WantedBy=multi-user.target diff --git a/src/Daemon/com.redhat.abrt.service b/src/Daemon/com.redhat.abrt.service deleted file mode 100644 index b251ef7f..00000000 --- a/src/Daemon/com.redhat.abrt.service +++ /dev/null @@ -1,7 +0,0 @@ -[D-BUS Service] -Name=com.redhat.abrt -# For testing, you may add -t33 to use small timeout of 33 seconds. -# This will make "abrtd exited while clients existed but were idle" -# situations easy to trigger -Exec=/usr/sbin/abrtd -ds -User=root diff --git a/src/Daemon/dbus-abrt.conf b/src/Daemon/dbus-abrt.conf deleted file mode 100644 index 11b2a111..00000000 --- a/src/Daemon/dbus-abrt.conf +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Daemon/dumpsocket.cpp b/src/Daemon/dumpsocket.cpp deleted file mode 100644 index 699a0609..00000000 --- a/src/Daemon/dumpsocket.cpp +++ /dev/null @@ -1,573 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#include "dumpsocket.h" -#include "abrtlib.h" -#include -#include -#include -#include -#include "debug_dump.h" -#include "crash_types.h" -#include "abrt_exception.h" -#include "hooklib.h" -#include "strbuf.h" - -#define SOCKET_FILE VAR_RUN"/abrt/abrt.socket" -#define SOCKET_PERMISSION 0666 - -/* Maximal length of backtrace. */ -#define MAX_BACKTRACE_SIZE (1024*1024) - -/* Amount of data received from one client for a message before reporting error. */ -#define MAX_MESSAGE_SIZE (4*MAX_BACKTRACE_SIZE) - -/* Maximum number of simultaneously opened client connections. */ -#define MAX_CLIENT_COUNT 10 - -/* Interval between checks of client halt, in seconds. */ -#define CLIENT_CHECK_INTERVAL 10 - -/* Interval with no data received from client, after which the client is - considered halted, in seconds. */ -#define CLIENT_HALT_INTERVAL 10 - -/* Maximal number of characters read from socket at once. */ -#define INPUT_BUFFER_SIZE 1024 - -static GIOChannel *channel = NULL; -static guint channel_cb_id = 0; -static int client_count = 0; - -/* Information about single socket session. */ -struct client -{ - /* Client user id */ - uid_t uid; - /* Buffer for incomplete incoming messages. */ - GByteArray *messagebuf; - /* Executable. */ - char *executable; - /* Process ID. */ - int pid; - char *backtrace; - /* Python, Ruby etc. */ - char *analyzer; - /* Directory base name: python (or pyhook), ruby etc. */ - char *basename; - /* Crash reason. - * Python example: "CCMainWindow.py:1::ZeroDivisionError: - * integer division or modulo by zero" - */ - char *reason; - /* Last time some data were received over the socket - * from the client. - */ - time_t lastwrite; - /* Timer checking client halt. */ - guint timer_id; - /* Client socket callback id. */ - guint socket_id; - /* Client socket channel */ - GIOChannel *channel; -}; - -static gboolean server_socket_cb(GIOChannel *source, - GIOCondition condition, - gpointer data); - -/* Releases all memory that belongs to a client session. */ -static void client_free(struct client *client) -{ - /* Delete the uncompleted message if there is some. */ - g_byte_array_free(client->messagebuf, TRUE); - free(client->executable); - free(client->backtrace); - free(client->analyzer); - free(client->basename); - free(client->reason); - g_source_remove(client->timer_id); - g_source_remove(client->socket_id); - g_io_channel_unref(client->channel); - free(client); - --client_count; - if (!channel_cb_id) - { - channel_cb_id = g_io_add_watch(channel, - (GIOCondition)(G_IO_IN | G_IO_PRI), - (GIOFunc)server_socket_cb, - NULL); - if (!channel_cb_id) - perror_msg_and_die("dumpsocket: Can't add socket watch"); - } -} - -/* Callback called by glib main loop at regular intervals when - some client is connected. */ -static gboolean client_check_cb(gpointer data) -{ - struct client *client = (struct client*)data; - if (time(NULL) - client->lastwrite > CLIENT_HALT_INTERVAL) - { - log("dumpsocket: client socket timeout reached, closing connection"); - client_free(client); - return FALSE; - } - return TRUE; -} - -/* Caller is responsible to free() the returned value. */ -static char *giocondition_to_string(GIOCondition condition) -{ - struct strbuf *strbuf = strbuf_new(); - if (condition & G_IO_HUP) - strbuf_append_str(strbuf, "G_IO_HUP | "); - if (condition & G_IO_ERR) - strbuf_append_str(strbuf, "G_IO_ERR | "); - if (condition & G_IO_NVAL) - strbuf_append_str(strbuf, "G_IO_NVAL | "); - if (condition & G_IO_IN) - strbuf_append_str(strbuf, "G_IO_IN | "); - if (condition & G_IO_OUT) - strbuf_append_str(strbuf, "G_IO_OUT | "); - if (condition & G_IO_PRI) - strbuf_append_str(strbuf, "G_IO_PRI | "); - if (strbuf->len == 0) - strbuf_append_str(strbuf, "none"); - else - { - /* remove the last " | " */ - strbuf->len -= 3; - strbuf->buf[strbuf->len] = '\0'; - } - char *result = strbuf->buf; - strbuf_free_nobuf(strbuf); - return result; -} - -/* Create a new debug dump from client session. - * Caller must ensure that all fields in struct client - * are properly filled. - */ -static void create_debug_dump(struct client *client) -{ - /* Create temp directory with the debug dump. - This directory is renamed to final directory name after - all files have been stored into it. - */ - char *path = xasprintf(DEBUG_DUMPS_DIR"/%s-%ld-%u.new", - client->basename, - (long)time(NULL), - client->pid); - /* No need to check the path length, as all variables used are limited, and dd.Create() - fails if the path is too long. */ - - CDebugDump dd; - try { - dd.Create(path, client->uid); - } catch (CABRTException &e) { - dd.Delete(); - dd.Close(); - error_msg_and_die("dumpsocket: Error while creating crash dump %s: %s", path, e.what()); - } - - dd.SaveText(FILENAME_ANALYZER, client->analyzer); - dd.SaveText(FILENAME_EXECUTABLE, client->executable); - dd.SaveText(FILENAME_BACKTRACE, client->backtrace); - dd.SaveText(FILENAME_REASON, client->reason); - - /* Obtain and save the command line. */ - char *cmdline = get_cmdline(client->pid); // never NULL - dd.SaveText(FILENAME_CMDLINE, cmdline); - free(cmdline); - - /* Store id of the user whose application crashed. */ - char uid_str[sizeof(long) * 3 + 2]; - sprintf(uid_str, "%lu", (long)client->uid); - dd.SaveText(CD_UID, uid_str); - - dd.Close(); - - /* Move the completely created debug dump to - final directory. */ - char *newpath = xstrndup(path, strlen(path) - strlen(".new")); - if (rename(path, newpath) == 0) - strcpy(path, newpath); - free(newpath); - - log("dumpsocket: Saved %s crash dump of pid %u to %s", - client->analyzer, client->pid, path); - - /* Handle free space checking. */ - unsigned maxCrashReportsSize = 0; - parse_conf(NULL, &maxCrashReportsSize, NULL, NULL); - if (maxCrashReportsSize > 0) - { - check_free_space(maxCrashReportsSize); - trim_debug_dumps(maxCrashReportsSize, path); - } - - free(path); -} - -/* Checks if a string contains only printable characters. */ -static bool printable_str(const char *str) -{ - do { - if ((unsigned char)(*str) < ' ' || *str == 0x7f) - return false; - str++; - } while (*str); - return true; -} - -/* Checks if a string has certain prefix. */ -static bool starts_with(const char *str, const char *start) -{ - return strncmp(str, start, strlen(start)) == 0; -} - -/* @returns - * Caller is responsible to call free() on the returned - * pointer. - * If NULL is returned, string extraction failed. - */ -static char *try_to_get_string(const char *message, - const char *tag, - size_t max_len, - bool printable, - bool allow_slashes) -{ - if (!starts_with(message, tag)) - return NULL; - - const char *contents = message + strlen(tag); - if ((printable && !printable_str(contents)) || - (!allow_slashes && strchr(contents, '/'))) - { - error_msg("dumpsocket: Received %s contains invalid characters -> skipping", tag); - return NULL; - } - - if (strlen(contents) > max_len) - { - char *max_len_str = g_format_size_for_display(max_len); - error_msg("dumpsocket: Received %s too long -> trimming to %s", tag, max_len_str); - g_free(max_len_str); - } - - return xstrndup(contents, max_len); -} - -/* Handles a message received from client over socket. */ -static void process_message(struct client *client, const char *message) -{ -/* @param tag - * The message identifier. Message starting with it - * is handled by this macro. - * @param field - * Member in struct client, which should be filled by - * the field contents. - * @param max_len - * Maximum length of the field in bytes. - * Exceeding bytes are trimmed. - * @param printable - * Whether to limit the field contents to ASCII only. - * @param allow_slashes - * Whether to allow slashes to be a part of input. - */ -#define HANDLE_INCOMING_STRING(tag, field, max_len, printable, allow_slashes) \ - char *field = try_to_get_string(message, tag, max_len, printable, allow_slashes); \ - if (field) \ - { \ - free(client->field); \ - client->field = field; \ - return; \ - } - - HANDLE_INCOMING_STRING("EXECUTABLE=", executable, PATH_MAX, true, true); - HANDLE_INCOMING_STRING("BACKTRACE=", backtrace, MAX_BACKTRACE_SIZE, false, true); - HANDLE_INCOMING_STRING("BASENAME=", basename, 100, true, false); - HANDLE_INCOMING_STRING("ANALYZER=", analyzer, 100, true, true); - HANDLE_INCOMING_STRING("REASON=", reason, 512, false, true); - -#undef HANDLE_INCOMING_STRING - - /* PID is not handled as a string, we convert it to pid_t. */ - if (starts_with(message, "PID=")) - { - /* xatou() cannot be used here, because it would - * kill whole daemon by non-numeric string. - */ - char *endptr; - errno = 0; - const char *nptr = message + strlen("PID="); - unsigned long number = strtoul(nptr, &endptr, 10); - /* pid == 0 is error, the lowest PID is 1. */ - if (errno || nptr == endptr || *endptr != '\0' || number > UINT_MAX || number == 0) - { - error_msg("dumpsocket: invalid PID received -> ignoring"); - return; - } - client->pid = number; - return; - } - - /* Creates debug dump if all fields were already provided. */ - if (starts_with(message, "DONE")) - { - if (client->pid == 0 || - client->backtrace == NULL || - client->executable == NULL || - client->analyzer == NULL || - client->basename == NULL || - client->reason == NULL) - { - error_msg("dumpsocket: DONE received, but some data are missing -> ignoring"); - return; - } - - create_debug_dump(client); - return; - } -} - -/* Callback called by glib main loop when ABRT receives data that have - * been written to the socket by some client. - */ -static gboolean client_socket_cb(GIOChannel *source, - GIOCondition condition, - gpointer data) -{ - struct client *client = (struct client*)data; - - /* Detailed logging, useful for debugging. */ - if (g_verbose >= 3) - { - char *cond = giocondition_to_string(condition); - log("dumpsocket: client condition %s", cond); - free(cond); - } - - /* Handle incoming data. */ - if (condition & (G_IO_IN | G_IO_PRI)) - { - guint loop = client->messagebuf->len; - gsize len; - gchar buf[INPUT_BUFFER_SIZE]; - GError *err = NULL; - /* Read data in chunks of size INPUT_BUFFER_SIZE. This allows to limit the number of - bytes received (to prevent memory exhaustion). */ - do { - GIOStatus result = g_io_channel_read_chars(source, buf, INPUT_BUFFER_SIZE, &len, &err); - if (result == G_IO_STATUS_ERROR) - { - g_assert(err); - error_msg("dumpsocket: Error while reading data from client socket: %s", err->message); - g_error_free(err); - client_free(client); - return FALSE; - } - - if (g_verbose >= 3) - log("dumpsocket: received %zu bytes of data", len); - - /* Append the incoming data to the message buffer. */ - g_byte_array_append(client->messagebuf, (guint8*)buf, len); - - if (client->messagebuf->len > MAX_MESSAGE_SIZE) - { - error_msg("dumpsocket: Message too long."); - client_free(client); - return FALSE; - } - } while (len > 0); - - /* Check, if we received a complete message now. */ - for (; loop < client->messagebuf->len; ++loop) - { - if (client->messagebuf->data[loop] != '\0') - continue; - - VERB3 log("dumpsocket: Processing message: %s", - client->messagebuf->data); - - /* Process the message. */ - process_message(client, (char*)client->messagebuf->data); - /* Remove the message including the ending \0 */ - g_byte_array_remove_range(client->messagebuf, 0, loop + 1); - loop = 0; - } - - /* Update the last write access time */ - client->lastwrite = time(NULL); - } - - /* Handle socket disconnection. - It is important to do it after handling G_IO_IN, because sometimes - G_IO_HUP comes together with G_IO_IN. It means that some data arrived - and then the socket has been closed. - */ - if (condition & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) - { - log("dumpsocket: Socket client disconnected"); - client_free(client); - return FALSE; - } - - return TRUE; -} - -/* If the status indicates failure, report it. */ -static void check_status(GIOStatus status, GError *err, const char *operation) -{ - if (status == G_IO_STATUS_NORMAL) - return; - - if (err) - { - error_msg("dumpsocket: Error while %s: %s", operation, err->message); - g_error_free(err); - } - else - error_msg("dumpsocket: Error while %s", operation); -} - -/* Initializes a new client session data structure. */ -static struct client *client_new(int socket) -{ - struct client *client = (struct client*)xzalloc(sizeof(struct client)); - - /* Get credentials for the socket client. */ - struct ucred cr; - socklen_t crlen = sizeof(struct ucred); - if (0 != getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &cr, &crlen)) - perror_msg_and_die("dumpsocket: Failed to get client uid"); - if (crlen != sizeof(struct ucred)) - perror_msg_and_die("dumpsocket: Failed to get client uid (crlen)"); - client->uid = cr.uid; - - client->messagebuf = g_byte_array_new(); - client->lastwrite = time(NULL); - - close_on_exec_on(socket); - - /* Create client IO channel. */ - client->channel = g_io_channel_unix_new(socket); - g_io_channel_set_close_on_unref(client->channel, TRUE); - - /* Set nonblocking access. */ - GError *err = NULL; - GIOStatus status = g_io_channel_set_flags(client->channel, G_IO_FLAG_NONBLOCK, &err); - check_status(status, err, "setting NONBLOCK flag"); - - /* Disable channel encoding to protect binary data. */ - err = NULL; - status = g_io_channel_set_encoding(client->channel, NULL, &err); - check_status(status, err, "setting encoding"); - - /* Start timer to check the client problems. */ - client->timer_id = g_timeout_add_seconds(CLIENT_CHECK_INTERVAL, client_check_cb, client); - if (!client->timer_id) - error_msg_and_die("dumpsocket: Can't add client timer"); - - /* Register client callback. */ - client->socket_id = g_io_add_watch(client->channel, - (GIOCondition)(G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL), - (GIOFunc)client_socket_cb, - client); - if (!client->socket_id) - error_msg_and_die("dumpsocket: Can't add client socket watch"); - - ++client_count; - return client; -} - -/* Callback called by glib main loop when a client newly opens ABRT's socket. */ -static gboolean server_socket_cb(GIOChannel *source, - GIOCondition condition, - gpointer data) -{ - /* Check the limit for number of simultaneously attached clients. */ - if (client_count >= MAX_CLIENT_COUNT) - { - error_msg("dumpsocket: Too many clients, refusing connection."); - /* To avoid infinite loop caused by the descriptor in "ready" state, - the callback must be disabled. - It is added back in client_free(). */ - g_source_remove(channel_cb_id); - channel_cb_id = 0; - return TRUE; - } - - struct sockaddr_un remote; - socklen_t len = sizeof(remote); - int socket = accept(g_io_channel_unix_get_fd(source), - (struct sockaddr*)&remote, &len); - if (socket == -1) - { - perror_msg("dumpsocket: Server can not accept client"); - return TRUE; - } - - log("dumpsocket: New client connected"); - client_new(socket); - return TRUE; -} - -/* Initializes the dump socket, usually in /var/run directory - * (the path depends on compile-time configuration). - */ -void dumpsocket_init() -{ - struct sockaddr_un local; - unlink(SOCKET_FILE); /* not caring about the result */ - int socketfd = xsocket(AF_UNIX, SOCK_STREAM, 0); - close_on_exec_on(socketfd); - memset(&local, 0, sizeof(local)); - local.sun_family = AF_UNIX; - strcpy(local.sun_path, SOCKET_FILE); - xbind(socketfd, (struct sockaddr*)&local, sizeof(local)); - xlisten(socketfd, MAX_CLIENT_COUNT); - - if (chmod(SOCKET_FILE, SOCKET_PERMISSION) != 0) - perror_msg_and_die("dumpsocket: failed to chmod socket file"); - - channel = g_io_channel_unix_new(socketfd); - g_io_channel_set_close_on_unref(channel, TRUE); - channel_cb_id = g_io_add_watch(channel, - (GIOCondition)(G_IO_IN | G_IO_PRI), - (GIOFunc)server_socket_cb, - NULL); - if (!channel_cb_id) - perror_msg_and_die("dumpsocket: Can't add socket watch"); -} - -/* Releases all resources used by dumpsocket. */ -void dumpsocket_shutdown() -{ - /* Set everything to pre-initialization state. */ - if (channel) - { - /* This one is for g_io_add_watch. */ - if (channel_cb_id) - g_source_remove(channel_cb_id); - /* This one is for g_io_channel_unix_new. */ - g_io_channel_unref(channel); - channel = NULL; - } -} diff --git a/src/Daemon/dumpsocket.h b/src/Daemon/dumpsocket.h deleted file mode 100644 index e5b79d60..00000000 --- a/src/Daemon/dumpsocket.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#ifndef ABRT_DUMPSOCKET_H -#define ABRT_DUMPSOCKET_H - -/* -Unix socket in ABRT daemon for creating new dump directories. - -Why to use socket for creating dump dirs? Security. When a Python -script throwns unexpected exception, ABRT handler catches it, running -as a part of that broken Python application. The application is running -with certain SELinux privileges, for example it can not execute other -programs, or to create files in /var/cache or anything else required -to properly fill a dump directory. Adding these privileges to every -application would weaken the security. -The most suitable solution is for the Python application -to open a socket where ABRT daemon is listening, write all relevant -data to that socket, and close it. ABRT daemon handles the rest. - -** Protocol - -Initializing new dump: -open /var/run/abrt.socket - -Providing dump data (hook writes to the socket): --> "PID=" - number 0 - PID_MAX (/proc/sys/kernel/pid_max) - \0 --> "EXECUTABLE=" - string (maximum length ~MAX_PATH) - \0 --> "BACKTRACE=" - string (maximum length 1 MB) - \0 --> "ANALYZER=" - string (maximum length 100 bytes) - \0 --> "BASENAME=" - string (maximum length 100 bytes, no slashes) - \0 --> "REASON=" - string (maximum length 512 bytes) - \0 - -Finalizing dump creation: --> "DONE" - \0 -*/ - -#ifdef __cplusplus -extern "C" { -#endif - -/* Initializes the dump socket, usually in /var/run directory - * (the path depends on compile-time configuration). - */ -extern void dumpsocket_init(); - -/* Releases all resources used by dumpsocket. */ -extern void dumpsocket_shutdown(); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/Daemon/gpg_keys b/src/Daemon/gpg_keys deleted file mode 100644 index cde50f13..00000000 --- a/src/Daemon/gpg_keys +++ /dev/null @@ -1 +0,0 @@ -/etc/pki/rpm-gpg/RPM-GPG-KEY-fedora diff --git a/src/Daemon/org.fedoraproject.abrt.policy b/src/Daemon/org.fedoraproject.abrt.policy deleted file mode 100644 index 9261acdc..00000000 --- a/src/Daemon/org.fedoraproject.abrt.policy +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - The ABRT Team - https://fedorahosted.org/abrt/ - - - Manage settings - Changing the global settings requires authentication - - no - auth_admin_keep - no - - - - - - Install debuginfos - Installing debuginfos requires authentication - - yes - yes - yes - - - diff --git a/src/Daemon/rpm.c b/src/Daemon/rpm.c deleted file mode 100644 index d367dccd..00000000 --- a/src/Daemon/rpm.c +++ /dev/null @@ -1,244 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 RedHat Inc - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -//#include "abrtlib.h" -#include "rpm.h" - -/** -* A set, which contains finger prints. -*/ - -static GList *list_fingerprints = NULL; - -//TODO: npajkovs: where to place it? Should it be sotred in value or query from rpm? -/* cuts the name from the NVR format: foo-1.2.3-1.el6 - returns a newly allocated string -*/ -char* get_package_name_from_NVR_or_NULL(const char* package_nvr) -{ - char* package_name = NULL; - if (package_nvr != NULL) - { - VERB1 log("package_nvr %s", package_nvr); - package_name = xstrdup(package_nvr); - char *pos = strrchr(package_name, '-'); - if (pos != NULL) - { - *pos = 0; - pos = strrchr(package_name, '-'); - if (pos != NULL) - { - *pos = 0; - } - } - } - return package_name; -} - -void rpm_init() -{ - int status = rpmReadConfigFiles((const char*)NULL, (const char*)NULL); - if (status != 0) - error_msg("error reading rc files"); - - list_fingerprints = g_list_alloc(); -} - -static void list_free(gpointer data, gpointer user_data) -{ - free(data); -} - -void rpm_destroy() -{ - rpmFreeRpmrc(); - rpmFreeCrypto(); - - g_list_foreach(list_fingerprints, list_free, NULL); - g_list_free(list_fingerprints); -} - -void rpm_load_gpgkey(const char* filename) -{ - uint8_t *pkt = NULL; - size_t pklen; - if (pgpReadPkts(filename, &pkt, &pklen) != PGPARMOR_PUBKEY) - { - free(pkt); - error_msg("Can't load public GPG key %s", filename); - return; - } - - uint8_t keyID[8]; - if (pgpPubkeyFingerprint(pkt, pklen, keyID) == 0) - { - char *fingerprint = pgpHexStr(keyID, sizeof(keyID)); - if (fingerprint != NULL) - list_fingerprints = g_list_append(list_fingerprints, fingerprint); - } - free(pkt); -} - -bool rpm_chk_fingerprint(const char* pkg) -{ - bool ret = false; - char *pgpsig = NULL; - const char *errmsg = NULL; - - rpmts ts = rpmtsCreate(); - rpmdbMatchIterator iter = rpmtsInitIterator(ts, RPMTAG_NAME, pkg, 0); - Header header = rpmdbNextIterator(iter); - - if (!header) - goto error; - - pgpsig = headerFormat(header, "%{SIGGPG:pgpsig}", &errmsg); - if (!pgpsig && errmsg) - { - VERB1 log("cannot get siggpg:pgpsig. reason: %s", errmsg); - goto error; - } - - { - char *pgpsig_tmp = strstr(pgpsig, " Key ID "); - if (pgpsig_tmp) - { - pgpsig_tmp += sizeof(" Key ID ") - 1; - ret = g_list_find(list_fingerprints, pgpsig_tmp) != NULL; - } - } - -error: - free(pgpsig); - rpmdbFreeIterator(iter); - rpmtsFree(ts); - return ret; -} - -/* - Checking the MD5 sum requires to run prelink to "un-prelink" the - binaries - this is considered potential security risk so we don't - use it, until we find some non-intrusive way -*/ - -/* - * Not woking, need to be rewriten - * -bool CheckHash(const char* pPackage, const char* pPath) -{ - bool ret = true; - rpmts ts = rpmtsCreate(); - rpmdbMatchIterator iter = rpmtsInitIterator(ts, RPMTAG_NAME, pPackage, 0); - Header header = rpmdbNextIterator(iter); - if (header == NULL) - goto error; - - rpmfi fi = rpmfiNew(ts, header, RPMTAG_BASENAMES, RPMFI_NOHEADER); - pgpHashAlgo hashAlgo; - std::string headerHash; - char computedHash[1024] = ""; - - while (rpmfiNext(fi) != -1) - { - if (strcmp(pPath, rpmfiFN(fi)) == 0) - { - headerHash = rpmfiFDigestHex(fi, &hashAlgo); - rpmDoDigest(hashAlgo, pPath, 1, (unsigned char*) computedHash, NULL); - ret = (headerHash != "" && headerHash == computedHash); - break; - } - } - rpmfiFree(fi); -error: - rpmdbFreeIterator(iter); - rpmtsFree(ts); - return ret; -} -*/ - -char* rpm_get_description(const char* pkg) -{ - char *dsc = NULL; - const char *errmsg = NULL; - rpmts ts = rpmtsCreate(); - - rpmdbMatchIterator iter = rpmtsInitIterator(ts, RPMTAG_NAME, pkg, 0); - Header header = rpmdbNextIterator(iter); - if (!header) - goto error; - - dsc = headerFormat(header, "%{SUMMARY}\n\n%{DESCRIPTION}", &errmsg); - if (!dsc && errmsg) - error_msg("cannot get summary and description. reason: %s", errmsg); - -error: - rpmdbFreeIterator(iter); - rpmtsFree(ts); - return dsc; -} - -char* rpm_get_component(const char* filename) -{ - char *ret = NULL; - char *srpm = NULL; - const char *errmsg = NULL; - - rpmts ts = rpmtsCreate(); - rpmdbMatchIterator iter = rpmtsInitIterator(ts, RPMTAG_BASENAMES, filename, 0); - Header header = rpmdbNextIterator(iter); - if (!header) - goto error; - - srpm = headerFormat(header, "%{SOURCERPM}", &errmsg); - if (!srpm && errmsg) - { - error_msg("cannot get srpm. reason: %s", errmsg); - goto error; - } - - ret = get_package_name_from_NVR_or_NULL(srpm); - free(srpm); - -error: - rpmdbFreeIterator(iter); - rpmtsFree(ts); - return ret; -} - -// caller is responsible to free returned value -char* rpm_get_package_nvr(const char* filename) -{ - char* nvr = NULL; - const char *errmsg = NULL; - - rpmts ts = rpmtsCreate(); - rpmdbMatchIterator iter = rpmtsInitIterator(ts, RPMTAG_BASENAMES, filename, 0); - Header header = rpmdbNextIterator(iter); - - if (!header) - goto error; - - nvr = headerFormat(header, "%{NAME}-%{VERSION}-%{RELEASE}", &errmsg); - if (!nvr && errmsg) - error_msg("cannot get nvr. reason: %s", errmsg); - -error: - rpmdbFreeIterator(iter); - rpmtsFree(ts); - return nvr; -} diff --git a/src/Daemon/rpm.h b/src/Daemon/rpm.h deleted file mode 100644 index 9b6b339c..00000000 --- a/src/Daemon/rpm.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - RPM.h - header file for rpm database - - it implements query for local rpm database - - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#ifndef RPM_H_ -#define RPM_H_ - -#include -#include -#include -#include -#include -#include - -#include "xfuncs.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Checks if an application is modified by third party. - * @param pPackage A package name. The package contains the application. - * @param pPath A path to the application. - * - * Not used. Delete? - */ -//bool CheckHash(const char* pPackage, const char* pPath); - -void rpm_init(); - -void rpm_destroy(); - -/** - * A function, which loads one GPG public key. - * @param filename A path to the public key. - */ -void rpm_load_gpgkey(const char* filename); - -/** - * A function, which checks if package's finger print is valid. - * @param pkg A package name. - */ -bool rpm_chk_fingerprint(const char* pkg); - -/** - * Gets a package description. - * @param pkg A package name. - * @return A package description. - */ -char* rpm_get_description(const char* pkg); -/** - * Gets a package name. This package contains particular - * file. If the file doesn't belong to any package, empty string is - * returned. - * @param filename A file name. - * @return A package name (malloc'ed string) - */ -char* rpm_get_package_nvr(const char* filename); -/** - * Finds a main package for given file. This package contains particular - * file. If the file doesn't belong to any package, empty string is - * returned. - * @param filename A file name. - * @return a malloc'ed package name. Need to be freed. - */ -char* rpm_get_component(const char* filename); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/Gui/ABRTExceptions.py b/src/Gui/ABRTExceptions.py deleted file mode 100644 index 0016632c..00000000 --- a/src/Gui/ABRTExceptions.py +++ /dev/null @@ -1,16 +0,0 @@ -from abrt_utils import _, log, log1, log2 - -class IsRunning(Exception): - def __init__(self): - Exception.__init__(self) - self.what = _("Another client is already running, trying to wake it...") - def __str__(self): - return self.what - -class WrongData(Exception): - def __init__(self): - Exception.__init__(self) - self.what = _("Got unexpected data from the daemon (is the database properly updated?).") - - def __str__(self): - return self.what diff --git a/src/Gui/ABRTPlugin.py b/src/Gui/ABRTPlugin.py deleted file mode 100644 index c54f670c..00000000 --- a/src/Gui/ABRTPlugin.py +++ /dev/null @@ -1,107 +0,0 @@ -# -*- coding: utf-8 -*- - -""" PluginInfo keys: -WWW -Name -Enabled -GTKBuilder -Version -Type -Email -Description -""" -from abrt_utils import _, log, log1, log2 -from ConfBackend import getCurrentConfBackend, ConfBackendInitError - -class PluginSettings(dict): - def __init__(self): - dict.__init__(self) - self.client_side_conf = None - try: - self.client_side_conf = getCurrentConfBackend() - except ConfBackendInitError, e: - print e - - def check(self): - # if present, these should be non-empty - for key in ["Password", "Login"]: - if key in self.keys(): - if not self[key]: - # some of the required keys are missing - return False - # settings are OK - return True - - def load_daemon_settings(self, name, daemon_settings): - # load settings from daemon - for key in daemon_settings.keys(): - self[str(key)] = str(daemon_settings[key]) - - if self.client_side_conf: - # FIXME: this fails when gk-authoriaztion fails - # we need to show a dialog to user and let him know - # for now just silently ignore it to avoid rhbz#559342 - settings = {} - try: - settings = self.client_side_conf.load(name) - except Exception, e: - print e - # overwrite daemon data with user setting - for key in settings.keys(): - # only rewrite keys which exist in plugin's keys. - # e.g. we don't want a password field for logger plugin - if key in daemon_settings.keys(): - self[str(key)] = str(settings[key]) - - def save_on_client_side(self, name): - if self.client_side_conf: - self.client_side_conf.save(name, self) - -class PluginInfo(): - """Class to represent common plugin info""" - types = {"":_("Not loaded plugins"), - "Analyzer":_("Analyzer plugins"), - "Action":_("Action plugins"), - "Reporter":_("Reporter plugins"), - "Database":_("Database plugins")} - keys = ["WWW", "Name", "Enabled", - "GTKBuilder", "Version", - "Type", "Email", "Description"] - - def __init__(self): - self.WWW = None - self.Name = None - self.Enabled = None - self.GTKBuilder = None - self.Version = None - self.Type = None - self.Email = None - self.Description = None - self.Settings = PluginSettings() - - def getName(self): - return self.Name - - def getDescription(self): - return self.Description - - def getType(self): - return self.Type - - def getGUI(self): - return self.GTKBuilder - - def __str__(self): - return self.Name - - def __getitem__(self, item): - return self.__dict__[item] - - def load_daemon_settings(self, daemon_settings): - if self.Name: - self.Settings.load_daemon_settings(self.Name, daemon_settings) - else: - log("Plugin name is not set, can't load its settings") - - def save_settings_on_client_side(self): - self.Settings.save_on_client_side(str(self.Name)) diff --git a/src/Gui/CCDBusBackend.py b/src/Gui/CCDBusBackend.py deleted file mode 100644 index 5ddb9cdf..00000000 --- a/src/Gui/CCDBusBackend.py +++ /dev/null @@ -1,231 +0,0 @@ -# -*- coding: utf-8 -*- -import time # for sleep() -import gobject -import dbus -import dbus.service -from dbus.mainloop.glib import DBusGMainLoop -from dbus.exceptions import * -import ABRTExceptions -from abrt_utils import _, log, log1, log2 - -ABRTD_DBUS_NAME = 'com.redhat.abrt' -ABRTD_DBUS_PATH = '/com/redhat/abrt' -ABRTD_DBUS_IFACE = 'com.redhat.abrt' - -APP_NAME = 'com.redhat.abrt.gui' -APP_PATH = '/com/redhat/abrt/gui' -APP_IFACE = 'com.redhat.abrt.gui' - -class DBusManager(gobject.GObject): - """ Class to provide communication with daemon over dbus """ - # and later with policyKit - bus = None - def __init__(self): - session = None - # binds the dbus to glib mainloop - DBusGMainLoop(set_as_default=True) - class DBusInterface(dbus.service.Object): - def __init__(self, dbusmanager): - self.dbusmanager = dbusmanager - dbus.service.Object.__init__(self, dbus.SessionBus(), APP_PATH) - - @dbus.service.method(dbus_interface=APP_IFACE) - def show(self): - self.dbusmanager.emit("show") - try: - session = dbus.SessionBus() - except Exception: - # probably run after "$ su" - pass - - if session: - try: - app_proxy = session.get_object(APP_NAME, APP_PATH) - app_iface = dbus.Interface(app_proxy, dbus_interface=APP_IFACE) - # app is running, so make it show it self - app_iface.show() - raise ABRTExceptions.IsRunning() - except DBusException, e: - # cannot create proxy or call the method => gui is not running - pass - - gobject.GObject.__init__(self) - # signal emited when new crash is detected - gobject.signal_new("crash", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()) - # signal emited when new analyze is complete - gobject.signal_new("analyze-complete", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) - # signal emited when smth fails - gobject.signal_new("abrt-error", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) - gobject.signal_new("warning", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) - # signal emited to update gui with current status - gobject.signal_new("update", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) - # signal emited to show gui if user try to run it again - gobject.signal_new("show", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()) - gobject.signal_new("daemon-state-changed", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) - gobject.signal_new("report-done", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) - - # export the app dbus interface - if session: - session.request_name(APP_NAME) - iface = DBusInterface(self) - - self.bus = dbus.SystemBus() - if not self.bus: - raise Exception(_("Cannot connect to system dbus.")) - self.bus.add_signal_receiver(self.owner_changed_cb, "NameOwnerChanged", dbus_interface="org.freedesktop.DBus") - # new crash notification - self.bus.add_signal_receiver(self.crash_cb, "Crash", dbus_interface=ABRTD_DBUS_IFACE) - # watch for updates - self.bus.add_signal_receiver(self.update_cb, "Update", dbus_interface=ABRTD_DBUS_IFACE) - # watch for warnings - self.bus.add_signal_receiver(self.warning_cb, "Warning", dbus_interface=ABRTD_DBUS_IFACE) - # watch for job-done signals - self.bus.add_signal_receiver(self.jobdone_cb, "JobDone", dbus_interface=ABRTD_DBUS_IFACE) - - # We use this function instead of caching and reusing of - # dbus.Interface(proxy, dbus_interface=ABRTD_DBUS_IFACE) because we want - # to restart abrtd in this scenario: - # (1) abrt-gui was run - # (2) user generated the report, then left for coffee break - # (3) abrtd exited on inactivity timeout - # (4) user returned and wants to submit the report - # for (4) to restart abrtd, we must recreate proxy and daemon - def daemon(self): - if not self.bus: - self.bus = dbus.SystemBus() - if not self.bus: - raise Exception(_("Cannot connect to system dbus.")) - # Autostart hack - # Theoretically, this is not needed, the first dbus call - # should autostart daemon if needed. In practice, - # without this code autostart is not reliable. - # Update: fixed the problem on daemon side, - # but retaining this code for now. More stability won't hurt us... - try: - (always_true, rc) = self.bus.start_service_by_name(ABRTD_DBUS_NAME, flags=0) - # rc is either self.bus.START_REPLY_SUCCESS or self.bus.START_REPLY_ALREADY_RUNNING - if rc == self.bus.START_REPLY_SUCCESS: - # Better solution may be to have daemon emit a signal and wait for it - log1("dbus auto-started abrt daemon, giving it 1 sec to initialize") - time.sleep(1) - except DBusException: - raise Exception("abrt daemon is not running, and DBus can't start it") - # End of autostart hack - try: - # follow_name_owner_changes=True: switch to new daemon if daemon is restarted - proxy = self.bus.get_object(ABRTD_DBUS_NAME, ABRTD_DBUS_PATH, introspect=False, follow_name_owner_changes=True) - except DBusException: - raise Exception("Can't connect to abrt daemon") - if not proxy: - raise Exception(_("Please check if the abrt daemon is running.")) - daemon = dbus.Interface(proxy, dbus_interface=ABRTD_DBUS_IFACE) - if not daemon: - raise Exception(_("Please check if the abrt daemon is running.")) - return daemon - -# # disconnect callback -# def disconnected(self, *args): -# print "disconnect" - - def error_handler_cb(self, error): - self.emit("abrt-error", error) - - def warning_handler_cb(self, arg): - self.emit("warning", arg) - - def error_handler(self, arg): - # used to silently ingore dbus timeouts - pass - - def dummy(self, *args): - # dummy function for async method call to workaround the timeout - pass - - def crash_cb(self,*args): - #FIXME "got another crash, gui should reload!" - #for arg in args: - # print arg - #emit a signal - #print "crash" - self.emit("crash") - - def update_cb(self, message): - log1("Update:%s", message) - self.emit("update", message) - - def warning_cb(self, message): - log1("Warning:%s", message) - self.emit("warning", message) - - def owner_changed_cb(self, name, old_owner, new_owner): - if name == ABRTD_DBUS_NAME: - if new_owner: - self.emit("daemon-state-changed", "up") - else: - self.emit("daemon-state-changed", "down") - - def jobdone_cb(self): - log1("Our job for crash_id %s is done", self.job_crash_id) - report = self.daemon().CreateReport(self.job_crash_id) - if report: - self.emit("analyze-complete", report) - else: - # FIXME: BUG: BarWindow remains. (how2reproduce: delete "component" in a dump dir and try to report it) - self.emit("abrt-error", _("Daemon did not return a valid report info.\nIs debuginfo missing?")) - - def report_done(self, result): - self.emit("report-done", result) - - def start_job(self, crash_id, force=0): - # 2nd param is "force recreating of backtrace etc" - self.daemon().StartJob(crash_id, force, timeout=60) - self.job_crash_id = crash_id - - def Report(self, report, reporters, reporters_settings = None): - # map < Plugin_name vec > - # daemon expects plugin names, not the objects - reporters_names = [str(reporter) for reporter in reporters] - if reporters_settings: - self.daemon().Report(report, reporters_names, reporters_settings, reply_handler=self.report_done, error_handler=self.error_handler_cb, timeout=60) - else: - self.daemon().Report(report, reporters_names, reply_handler=self.report_done, error_handler=self.error_handler_cb, timeout=60) - - def DeleteDebugDump(self, crash_id): - return self.daemon().DeleteDebugDump(crash_id) - - def getDumps(self): - row_dict = None - rows = [] - # FIXME check the arguments - for row in self.daemon().GetCrashInfos(): - row_dict = {} - for column in row: - row_dict[column] = row[column] - rows.append(row_dict) - return rows - - def getPluginsInfo(self): - return self.daemon().GetPluginsInfo() - - def getPluginSettings(self, plugin_name): - settings = self.daemon().GetPluginSettings(plugin_name) - return settings - -# "Enable" toggling in GUI is disabled for now. Grep for PLUGIN_DYNAMIC_LOAD_UNLOAD -# def registerPlugin(self, plugin_name): -# return self.daemon().RegisterPlugin(plugin_name) -# -# def unRegisterPlugin(self, plugin_name): -# return self.daemon().UnRegisterPlugin(plugin_name) - - def setPluginSettings(self, plugin_name, plugin_settings): - return self.daemon().SetPluginSettings(plugin_name, plugin_settings) - - def getSettings(self): - return self.daemon().GetSettings() - - def setSettings(self, settings): - # FIXME: STUB!!!! - log1("setSettings stub") - retval = self.daemon().SetSettings(self.daemon().GetSettings()) - print ">>>", retval diff --git a/src/Gui/CCDump.py b/src/Gui/CCDump.py deleted file mode 100644 index d8a15f9a..00000000 --- a/src/Gui/CCDump.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- coding: utf-8 -*- -from datetime import datetime - -from abrt_utils import _, init_logging, log, log1, log2 - -# Should match CrashTypes.h! -CD_TYPE = 0 -CD_EDITABLE = 1 -CD_CONTENT = 2 - -CD_SYS = "s" -CD_BIN = "b" -CD_TXT = "t" - -FILENAME_ARCHITECTURE = "architecture" -FILENAME_KERNEL = "kernel" -FILENAME_TIME = "time" -FILENAME_PACKAGE = "package" -FILENAME_COMPONENT = "component" -FILENAME_DESCRIPTION = "description" -FILENAME_ANALYZER = "analyzer" -FILENAME_RELEASE = "release" -FILENAME_EXECUTABLE = "executable" -FILENAME_REASON = "reason" -FILENAME_COMMENT = "comment" -FILENAME_REPRODUCE = "reproduce" -FILENAME_RATING = "rating" -FILENAME_CMDLINE = "cmdline" -FILENAME_COREDUMP = "coredump" -FILENAME_BACKTRACE = "backtrace" -FILENAME_MEMORYMAP = "memorymap" - -CD_UID = "uid" -CD_UUID = "UUID" -CD_INFORMALL = "InformAll" -CD_DUPHASH = "DUPHASH" -CD_DUMPDIR = "DumpDir" -CD_COUNT = "Count" -CD_REPORTED = "Reported" -CD_MESSAGE = "Message" - -# FIXME - create method or smth that returns type|editable|content - - -class Dump(): - """Class for mapping the debug dump to python object""" - not_required_fields = ["comment", "Message"] - def __init__(self): - # we set all attrs dynamically, so no need to have it in init - for field in self.not_required_fields: - self.__dict__[field] = None - - def __setattr__(self, name, value): - if value != None: - if name == "time": - try: - self.__dict__["date"] = datetime.fromtimestamp(int(value[CD_CONTENT])).strftime("%c") - except Exception, ex: - self.__dict__["date"] = value[CD_CONTENT] - log2("can't convert timestamp to date: %s" % ex) - self.__dict__[name] = value[CD_CONTENT] - else: - self.__dict__[name] = value - - def __str__(self): - return "Dump instance" - - def getUUID(self): - return self.UUID - - def getUID(self): - return self.uid - - def getCount(self): - return int(self.Count) - - def getExecutable(self): - return self.executable - - def getPackage(self): - return self.package - - def isReported(self): - return self.Reported == "1" - - def getMessage(self): - if not self.Message: - return "" #[] - #return self.Message[CD_CONTENT].split('\n') - return self.Message - - def getTime(self, fmt=None): - if self.time: - if fmt: - try: - return datetime.fromtimestamp(int(self.time)).strftime(fmt) - except Exception, ex: - log1(ex) - return int(self.time) - return self.time - - def getPackageName(self): - name_delimiter_pos = self.package[:self.package.rfind("-")].rfind("-") - # fix for kerneloops - if name_delimiter_pos > 0: - return self.package[:name_delimiter_pos] - return self.package - - def getDescription(self): - return self.description - - def getAnalyzerName(self): - return self.analyzer - - def get_release(self): - return self.release - - def get_reason(self): - return self.reason - - def get_comment(self): - return self.comment - - def get_component(self): - return self.component - - def get_cmdline(self): - return self.cmdline - - def get_arch(self): - return self.architecture - - def get_kernel(self): - return self.kernel - - def get_backtrace(self): - try: - return self.backtrace - except AttributeError: - return None - - def get_rating(self): - try: - return self.rating - except AttributeError: - return None - - def get_hostname(self): - try: - return self.hostname - except AttributeError: - return None diff --git a/src/Gui/CCDumpList.py b/src/Gui/CCDumpList.py deleted file mode 100644 index 3c555d84..00000000 --- a/src/Gui/CCDumpList.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -from CCDump import Dump - -from abrt_utils import _, init_logging, log, log1, log2 - -class DumpList(list): - """Class to store list of debug dumps""" - def __init__(self,dbus_manager=None): - list.__init__(self) - self.dm = dbus_manager - - def load(self): - if self.dm: - #print "loading DumpList" - try: - rows = self.dm.getDumps() - #print rows - for row in rows: - entry = Dump() - for column in row: - log2(" Dump.%s='%s'", column, row[column]) - entry.__setattr__(column, row[column]) - self.append(entry) - except Exception: - # FIXME handle exception better - # this is just temporary workaround for rhbz#543725 - raise - else: - print "db == None!" - - def getDumpByCrashID(self, crashid): - for dump in self: - # crashid can be either hash or uid:hash - if crashid in (dump.getUUID(),dump.getUID()+":"+dump.getUUID()): - return dump - -__PFList = None -__PFList_dbmanager = None - -def getDumpList(dbmanager,refresh=None): - global __PFList - global __PFList_dbmanager - - if __PFList == None or refresh or __PFList_dbmanager != dbmanager: - __PFList = DumpList(dbus_manager=dbmanager) - __PFList.load() - __PFList_dbmanager = dbmanager - return __PFList - -__PFList = None diff --git a/src/Gui/CCMainWindow.py b/src/Gui/CCMainWindow.py deleted file mode 100644 index 4ee14768..00000000 --- a/src/Gui/CCMainWindow.py +++ /dev/null @@ -1,457 +0,0 @@ -# -*- coding: utf-8 -*- -import sys -import pwd -import getopt - -from abrt_utils import _, init_logging, log, log1, log2 -import gobject -gobject.set_prgname(_("Automatic Bug Reporting Tool")) -import pygtk -pygtk.require("2.0") -try: - import gtk -except RuntimeError,e: - # rhbz#552039 - print e - sys.exit() -import gtk.glade - -from ConfBackend import getCurrentConfBackend, ConfBackendInitError -import CCDBusBackend -from CC_gui_functions import * -from CCDumpList import getDumpList -from CCDump import * # FILENAME_xxx, CD_xxx -from CCReporterDialog import ReporterDialog, ReporterSelector -from CReporterAssistant import ReporterAssistant -from PluginsSettingsDialog import PluginsSettingsDialog -from SettingsDialog import SettingsDialog -from PluginList import getPluginInfoList -import ABRTExceptions - - -class MainWindow(): - ccdaemon = None - def __init__(self, daemon): - self.theme = gtk.icon_theme_get_default() - self.updates = "" - self.ccdaemon = daemon - #Set the Glade file - self.gladefile = "%s/ccgui.glade" % sys.path[0] - self.wTree = gtk.glade.XML(self.gladefile) - - #Get the Main Window, and connect the "destroy" event - self.window = self.wTree.get_widget("main_window") - if self.window: - self.window.set_default_size(600, 700) - self.window.connect("delete_event", self.delete_event_cb) - self.window.connect("destroy", self.destroy) - self.window.connect("focus-in-event", self.focus_in_cb) - self.wTree.get_widget("vp_details").modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse("#FFFFFF")) - #init the dumps treeview - self.dlist = self.wTree.get_widget("tvDumps") - #rows of items with: - STATUS_COL = 0 - APP_NAME_COL = 1 - TIME_STR_COL = 2 - HOSTNAME_COL = 3 - UNIX_TIME_COL = 4 - DUMP_OBJECT_COL = 5 - #is_reported, application_name, hostname, date, time_in_sec ?object? - self.dumpsListStore = gtk.ListStore(str, str, str, str, int, object) - self.dlist.set_model(self.dumpsListStore) - # add pixbuff separatelly - icon_column = gtk.TreeViewColumn(_("Reported")) - icon_column.cell = gtk.CellRendererPixbuf() - #icon_column.cell.set_property('cell-background', "#C9C9C9") - n = self.dlist.append_column(icon_column) - icon_column.pack_start(icon_column.cell, True) - icon_column.set_attributes(icon_column.cell, stock_id=(n-1))# cell_background_set=6) - # =============================================== - columns = [] - columns.append(gtk.TreeViewColumn(_("Application"))) - columns[-1].set_sort_column_id(APP_NAME_COL) - columns.append(gtk.TreeViewColumn(_("Hostname"))) - columns[-1].set_sort_column_id(HOSTNAME_COL) - columns.append(gtk.TreeViewColumn(_("Latest Crash"))) - columns[-1].set_sort_column_id(UNIX_TIME_COL) - # add cells to colums and bind cells to the liststore values - for column in columns: - n = self.dlist.append_column(column) - column.cell = gtk.CellRendererText() - column.pack_start(column.cell, False) - column.set_attributes(column.cell, text=(n-1)) - column.set_resizable(True) - #connect signals - self.dlist.connect("cursor-changed", self.on_tvDumps_cursor_changed) - self.dlist.connect("row-activated", self.on_dumpRowActivated) - self.dlist.connect("button-press-event", self.on_popupActivate) - self.wTree.get_widget("bDelete").connect("clicked", self.on_bDelete_clicked, self.dlist) - self.wTree.get_widget("bReport").connect("clicked", self.on_bReport_clicked) - self.wTree.get_widget("b_close").connect("clicked", self.on_bQuit_clicked) - self.wTree.get_widget("b_copy").connect("clicked", self.on_b_copy_clicked) - self.wTree.get_widget("b_help").connect("clicked", self.on_miAbout_clicked) - self.wTree.get_widget("miQuit").connect("activate", self.on_bQuit_clicked) - self.wTree.get_widget("miAbout").connect("activate", self.on_miAbout_clicked) - self.wTree.get_widget("miPlugins").connect("activate", self.on_miPreferences_clicked) - self.wTree.get_widget("miPreferences").connect("activate", self.on_miSettings_clicked) - self.wTree.get_widget("miReport").connect("activate", self.on_bReport_clicked) - self.wTree.get_widget("miDelete").connect("activate", self.on_bDelete_clicked, self.dlist) - # connect handlers for daemon signals - self.ccdaemon.connect("crash", self.on_data_changed_cb, None) - self.ccdaemon.connect("abrt-error", self.error_cb) - #self.ccdaemon.connect("update", self.update_cb) - # for now, just treat them the same (w/o this, we don't even see daemon warnings in logs!): - #self.ccdaemon.connect("warning", self.update_cb) - self.ccdaemon.connect("show", self.show_cb) - self.ccdaemon.connect("daemon-state-changed", self.on_daemon_state_changed_cb) - self.ccdaemon.connect("report-done", self.on_report_done_cb) - - self.pluginlist = None - - def on_report_done_cb(self, daemon, result): - self.hydrate() - - def on_daemon_state_changed_cb(self, widget, state): - if state == "up": - self.hydrate() # refresh crash list - #self.window.set_sensitive(True) - # abrtd might just die on timeout, it's not fatal - #elif state == "down": - # self.window.set_sensitive(False) - - def on_popupActivate(self, widget, event): - menu = self.wTree.get_widget("popup_menu") - # 3 == right mouse button - if event.button == 3: - menu.popup(None, None, None, event.button, event.time) - - def on_miAbout_clicked(self, widget): - dialog = self.wTree.get_widget("about") - result = dialog.run() - dialog.hide() - - def on_miPreferences_clicked(self, widget): - dialog = PluginsSettingsDialog(self.window,self.ccdaemon) - dialog.hydrate() - dialog.show() - - def on_miSettings_clicked(self, widget): - dialog = SettingsDialog(self.window, self.ccdaemon) - try: - dialog.hydrate() - except Exception, ex: - gui_error_message(_("Cannot show the settings dialog.\n%s" % ex)) - return - dialog.show() - - def error_cb(self, daemon, message=None): - gui_error_message(_("Unable to finish the current task!\n%s" % message), parent_dialog=self.window) - - def update_cb(self, daemon, message): - self.updates += message - if self.updates[-1] != '\n': - self.updates += '\n' - message = message.replace('\n',' ') - self.wTree.get_widget("lStatus").set_text(message) - buff = gtk.TextBuffer() - buff.set_text(self.updates) - end = buff.get_insert() - tvUpdates = self.wTree.get_widget("tvUpdates") - tvUpdates.set_buffer(buff) - tvUpdates.scroll_mark_onscreen(end) - - def get_username_from_uid(self, uid): - # if uid == None or "" return it back - if not uid: - return uid - user = "N/A" - if uid != "-1": # compat: only abrt <= 1.0.9 used UID = -1 - try: - user = pwd.getpwuid(int(uid))[0] - except Exception, ex: - user = "UID: %s" % uid - return user - - - def hydrate(self): - n = None - self.dumpsListStore.clear() - try: - dumplist = getDumpList(self.ccdaemon, refresh=True) - except Exception, ex: - # there is something wrong with the daemon if we cant get the dumplist - gui_error_message(_("Error while loading the dumplist.\n%s" % ex)) - # so we shouldn't continue.. - sys.exit() - for entry in dumplist[::-1]: - n = self.dumpsListStore.append([["gtk-no","gtk-yes"][entry.isReported()], - entry.getExecutable(), - entry.get_hostname(), - entry.getTime("%c"), - entry.getTime(), - entry]) - # activate the first row if any.. - if n: - # we can use (0,) as path for the first row, but what if API changes? - self.dlist.set_cursor(self.dumpsListStore.get_path(self.dumpsListStore.get_iter_first())) - - def filter_dumps(self, model, miter, data): - # for later.. - return True - - def dumplist_get_selected(self): - selection = self.dlist.get_selection() - if selection: - # returns (dumpsListStore, path) tuple - dumpsListStore, path = selection.get_selected_rows() - return dumpsListStore, path - else: - return None, None - - def on_tvDumps_cursor_changed(self, treeview): - dumpsListStore, path = self.dumplist_get_selected() - if not path: - self.wTree.get_widget("bDelete").set_sensitive(False) - self.wTree.get_widget("bReport").set_sensitive(False) - self.wTree.get_widget("b_copy").set_sensitive(False) - # create an empty dump to fill the labels with empty strings - self.wTree.get_widget("sw_details").hide() - return - else: - self.wTree.get_widget("sw_details").show() - self.wTree.get_widget("bDelete").set_sensitive(True) - self.wTree.get_widget("bReport").set_sensitive(True) - self.wTree.get_widget("b_copy").set_sensitive(True) - # this should work until we keep the row object in the last position - dump = dumpsListStore.get_value(dumpsListStore.get_iter(path[0]), - dumpsListStore.get_n_columns()-1) - - try: - icon = get_icon_for_package(self.theme, dump.getPackageName()) - except: - icon = None - - i_package_icon = self.wTree.get_widget("i_package_icon") - if icon: - i_package_icon.set_from_pixbuf(icon) - else: - i_package_icon.set_from_icon_name("application-x-executable", gtk.ICON_SIZE_DIALOG) - - l_heading = self.wTree.get_widget("l_detail_heading") - l_heading.set_markup(_("%s Crash\n%s") % (dump.getPackageName().title(),dump.getPackage())) - - # process the labels in sw_details - # hide the fields that are not filled by daemon - e.g. comments - # and how to reproduce - for field in dump.not_required_fields: - self.wTree.get_widget("l_%s" % field.lower()).hide() - self.wTree.get_widget("l_%s_heading" % field.lower()).hide() - - # fill the details - # read attributes from CCDump object and if a corresponding label is - # found, then the label text is set to the attribute's value - # field names in glade file: - # heading label: l__heading - # text label: l_ - for att in dump.__dict__: - label = self.wTree.get_widget("l_%s" % str(att).lower()) - if label: - label.show() - if att in dump.not_required_fields: - try: - lbl_heading = self.wTree.get_widget("l_%s_heading" % str(att).lower()) - lbl_heading.show() - except: - # we don't care if we fail to show the heading, it will - # break the gui a little, but it's better then exit - log2("failed to show the heading for >%s< : %s" % (att,e)) - pass - if dump.__dict__[att] != None: - label.set_text(dump.__dict__[att]) - else: - label.set_text("") - self.wTree.get_widget("l_date").set_text(dump.getTime("%c")) - self.wTree.get_widget("l_user").set_text(self.get_username_from_uid(dump.getUID())) - - #move this to Dump class - hb_reports = self.wTree.get_widget("hb_reports") - lReported = self.wTree.get_widget("l_message") - if dump.isReported(): - hb_reports.show() - report_label_raw = "" - report_label = "" - # plugin message follows, but at least in case of kerneloops, - # it is not informative (no URL to the report) - for message in dump.getMessage().split(';'): - if message: - report_message = tag_urls_in_text(message) - report_label += "%s\n" % report_message - report_label_raw += "%s\n" % message - log2("setting markup '%s'", report_label) - # Sometimes (!) set_markup() fails with - # "GtkWarning: Failed to set text from markup due to error parsing - # markup: Unknown tag 'a'" If it does, then set_text() - # in "fill the details" above acts as a fallback - lReported.set_markup(report_label) - else: - hb_reports.hide() - - def mark_last_selected_row(self, dump_list_store, path, iter, last_selected_uuid): - # Get dump object from list (in our list it's in last col) - dump = dump_list_store.get_value(iter, dump_list_store.get_n_columns()-1) - if dump.getUUID() == last_selected_uuid: - self.dlist.set_cursor(dump_list_store.get_path(iter)[0]) - return True # done, stop iteration - return False - - def on_bDelete_clicked(self, button, treeview): - dumpsListStore, path = self.dumplist_get_selected() - if not path: - return - # this should work until we keep the dump object in the last position - dump = dumpsListStore.get_value(dumpsListStore.get_iter(path[0]), dumpsListStore.get_n_columns()-1) - next_iter = dumpsListStore.iter_next(dumpsListStore.get_iter(path[0])) - last_dump = None - if next_iter: - last_dump = dumpsListStore.get_value(next_iter, dumpsListStore.get_n_columns()-1) - try: - self.ccdaemon.DeleteDebugDump("%s:%s" % (dump.getUID(), dump.getUUID())) - self.hydrate() - if last_dump: - # we deleted the selected line, so we want to select the next one - dumpsListStore.foreach(self.mark_last_selected_row, last_dump.getUUID()) - treeview.emit("cursor-changed") - except Exception, ex: - print ex - - def dumplist_get_selected_values(self): - dumpsListStore, path = self.dumplist_get_selected() - if path and dumpsListStore: - return dumpsListStore.get_value(dumpsListStore.get_iter(path[0]), dumpsListStore.get_n_columns()-1) - return None - - def on_b_copy_clicked(self, button): - clipboard = gtk.clipboard_get() - dump = self.dumplist_get_selected_values() - if not dump: - gui_info_dialog(_("You have to select a crash to copy."), parent=self.window) - return - # dictionaries are not sorted, so we need this as a workaround - dumpinfo = [("Package:", dump.package), - ("Latest Crash:", dump.date), - ("Command:", dump.cmdline), - ("Reason:", dump.reason), - ("Comment:", dump.comment), - ("Bug Reports:", dump.Message), - ] - dumpinfo_text = "" - for line in dumpinfo: - dumpinfo_text += ("%-12s\t%s" % (line[0], line[1])).replace('\n','\n\t\t') - dumpinfo_text += '\n' - clipboard.set_text(dumpinfo_text) - - def destroy(self, widget, data=None): - gtk.main_quit() - - def on_data_changed_cb(self, *_args): - # FIXME mark the new entry somehow.... - # remember the selected row - last_dump = None - dumpsListStore, path = self.dumplist_get_selected() - if path and dumpsListStore: - last_dump = dumpsListStore.get_value(dumpsListStore.get_iter(path[0]), dumpsListStore.get_n_columns()-1) - self.hydrate() - if last_dump: - # re-select the line that was selected before a new crash happened - dumpsListStore.foreach(self.mark_last_selected_row, last_dump.getUUID()) - - - def on_bReport_clicked(self, button): - dumpsListStore, path = self.dumplist_get_selected() - self.on_dumpRowActivated(self.dlist, None, path, None) - - def on_dumpRowActivated(self, treeview, it, path, user_data=None): - dumpsListStore, path = self.dumplist_get_selected() - if not path: - return - dump = dumpsListStore.get_value(dumpsListStore.get_iter(path[0]), dumpsListStore.get_n_columns()-1) - - # Do we want to let user decide which UI they want to use? - #rs = ReporterSelector(dump, self.ccdaemon, parent=self.window) - #rs.show() - assistant = ReporterAssistant(dump, self.ccdaemon, parent=self.window) - assistant.hydrate() - - def delete_event_cb(self, widget, event, data=None): - gtk.main_quit() - - def focus_in_cb(self, widget, event, data=None): - self.window.set_urgency_hint(False) - - def on_bQuit_clicked(self, widget): - try: - gtk.main_quit() - except: # prevent "RuntimeError: called outside of a mainloop" - sys.exit() - - def show(self): - self.window.show() - - def show_cb(self, daemon): - if self.window: - if self.window.is_active(): - return - self.window.set_urgency_hint(True) - self.window.present() - -if __name__ == "__main__": - verbose = 0 - crashid = None - try: - opts, args = getopt.getopt(sys.argv[1:], "vh", ["help", "report="]) - except getopt.GetoptError, err: - print str(err) # prints something like "option -a not recognized" - sys.exit(2) - - for opt, arg in opts: - if opt == "-v": - verbose += 1 - elif opt == "--report": - crashid=arg - elif opt in ("-h", "--help"): - print _("Usage: abrt-gui [OPTIONS]" - "\n\t-v[vv]\t\t\tVerbose" - "\n\t--report=CRASH_ID\tDirectly report crash with CRASH_ID" - ) - sys.exit() - - init_logging("abrt-gui", verbose) - log1("log level:%d", verbose) - - try: - daemon = CCDBusBackend.DBusManager() - except ABRTExceptions.IsRunning: - # another instance is running, so exit quietly - sys.exit() - except Exception, ex: - # show error message if connection fails - gui_error_message("%s" % ex) - sys.exit() - - if crashid: - dumplist = getDumpList(daemon) - crashdump = dumplist.getDumpByCrashID(crashid) - if not crashdump: - gui_error_message(_("No such crash in the database, probably wrong crashid." - "\ncrashid=%s" % crashid)) - sys.exit() - assistant = ReporterAssistant(crashdump, daemon, parent=None) - assistant.hydrate() - # Do we want to let the users to decide which UI to use? -# rs = ReporterSelector(crashdump, daemon, parent=None) -# rs.show() - else: - cc = MainWindow(daemon) - cc.hydrate() - cc.show() - gtk.main() diff --git a/src/Gui/CCReporterDialog.py b/src/Gui/CCReporterDialog.py deleted file mode 100644 index 947a2582..00000000 --- a/src/Gui/CCReporterDialog.py +++ /dev/null @@ -1,578 +0,0 @@ -# -*- coding: utf-8 -*- -import pygtk -pygtk.require("2.0") -import gtk -import gobject -import sys -from CC_gui_functions import * -import CellRenderers -from ABRTPlugin import PluginInfo -from PluginSettingsUI import PluginSettingsUI -from PluginList import getPluginInfoList -from CCDump import * # FILENAME_xxx, CD_xxx -from abrt_utils import _, log, log1, log2, get_verbose_level, g_verbose - -# FIXME - create method or smth that returns type|editable|content - -# response -REFRESH = -50 -SHOW_LOG = -60 - -# default texts -COMMENT_HINT_TEXT = _("Brief description of how to reproduce this or what you did...") -HOW_TO_HINT_TEXT = "1.\n2.\n3.\n" - -class ReporterDialog(): - """Reporter window""" - def __init__(self, report, daemon, log=None, parent=None): - self.editable = [] - self.row_dict = {} - self.report = report - #Set the Glade file - # FIXME add to path - builderfile = "%s/report.glade" % sys.path[0] - self.builder = gtk.Builder() - self.builder.add_from_file(builderfile) - #Get the Main Window, and connect the "destroy" event - self.window = self.builder.get_object("reporter_dialog") - self.window.set_default_size(-1, 800) - self.window.connect("response", self.on_response, daemon) - if parent: - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.window.set_transient_for(parent) - self.window.set_modal(True) - else: - self.window.set_position(gtk.WIN_POS_CENTER) - - # comment textview - self.tvComment = self.builder.get_object("tvComment") - self.tvComment.connect("focus-in-event", self.on_comment_focus_cb) - self.show_hint_comment = 1 - - # "how to reproduce" textview - self.tevHowToReproduce = self.builder.get_object("tevHowToReproduce") - - self.builder.get_object("fErrors").hide() - bLog = self.builder.get_object("bLog") - #if g_verbose > 0: - doesn't work! why?! - if get_verbose_level() > 0: - bLog.connect("clicked", self.show_log_cb, log) - else: - bLog.unset_flags(gtk.VISIBLE) - tb_send_bt = self.builder.get_object("cbSendBacktrace") - tb_send_bt.connect("toggled", self.on_send_backtrace_toggled) - try: - tb_send_bt.get_child().modify_fg(gtk.STATE_NORMAL,gtk.gdk.color_parse("red")) - except Exception, ex: - # we don't want gui to die if it fails to set the button color - log(ex) - self.allow_send() - self.hydrate() - - def check_backtrace(self): - print "checking backtrace" - - def warn_user(self, warnings): - # FIXME: show in lError - fErrors = self.builder.get_object("fErrors") - lErrors = self.builder.get_object("lErrors") - warning_lbl = None - for warning in warnings: - if warning_lbl: - warning_lbl += "\n* %s" % warning - else: - warning_lbl = "* %s" % warning - lErrors.set_label(warning_lbl) - fErrors.show_all() - - def hide_warning(self): - fErrors = self.builder.get_object("fErrors") - lErrors = self.builder.get_object("lErrors") - fErrors.hide() - - def allow_send(self): - self.hide_warning() - bSend = self.builder.get_object("bSend") - SendBacktrace = self.builder.get_object("cbSendBacktrace").get_active() - send = True - error_msgs = [] - try: - rating = int(self.report[FILENAME_RATING][CD_CONTENT]) - except: - rating = None - # active buttons acording to required fields - # if an backtrace has rating use it - if not SendBacktrace: - send = False - error_msgs.append(_("You must check the backtrace for sensitive data.")) - # we have both SendBacktrace and rating - if rating != None: - try: - package = self.report[FILENAME_PACKAGE][CD_CONTENT] - # if we don't have package for some reason - except: - package = None - # not usable report - if int(self.report[FILENAME_RATING][CD_CONTENT]) < 3: - if package: - error_msgs.append(_("Reporting disabled because the backtrace is unusable.\nPlease try to install debuginfo manually using the command: debuginfo-install %s \nthen use the Refresh button to regenerate the backtrace." % package[0:package.rfind('-',0,package.rfind('-'))])) - else: - error_msgs.append(_("The backtrace is unusable, you cannot report this!")) - send = False - # probably usable 3 - elif int(self.report[FILENAME_RATING][CD_CONTENT]) < 4: - error_msgs.append(_("The backtrace is incomplete, please make sure you provide the steps to reproduce.")) - - if error_msgs: - self.warn_user(error_msgs) - bSend.set_sensitive(send) - if not send: - bSend.set_tooltip_text(_("Reporting disabled, please fix the problems shown above.")) - else: - bSend.set_tooltip_text(_("Sends the report using the selected plugin.")) - - def on_send_backtrace_toggled(self, toggle_button): - self.allow_send() - - def show_log_cb(self, widget, log): - show_log(log, parent=self.window) - - # this callback is called when user press Cancel or Report button in Report dialog - def on_response(self, dialog, response_id, daemon): - # the button has been pressed (probably) - if response_id == gtk.RESPONSE_APPLY: - if not (self.check_report()): - dialog.stop_emission("response") - self.builder.get_object("bSend").stop_emission("clicked") - if response_id == SHOW_LOG: - # prevent the report dialog from quitting the run() and closing itself - dialog.stop_emission("response") - - def on_send_toggled(self, cell, path, model): - model[path][3] = not model[path][3] - - def on_comment_focus_cb(self, widget, event): - if self.show_hint_comment: - # clear "hint" text by supplying a fresh, empty TextBuffer - widget.set_buffer(gtk.TextBuffer()) - self.show_hint_comment = 0 - - def set_label(self, label_widget, text): - if len(text) > label_widget.get_max_width_chars(): - label_widget.set_tooltip_text(text) - label_widget.set_text(text) - - def hydrate(self): - self.editable = [] - self.old_comment = COMMENT_HINT_TEXT - self.old_how_to_reproduce = HOW_TO_HINT_TEXT - for item in self.report: - try: - log2("report[%s]:%s/%s/%s", item, self.report[item][0], self.report[item][1], self.report[item][2][0:20]) - except: - pass - - if item == FILENAME_BACKTRACE: - buff = gtk.TextBuffer() - tvBacktrace = self.builder.get_object("tvBacktrace") - buff.set_text(self.report[item][CD_CONTENT]) - tvBacktrace.set_buffer(buff) - continue - - if item == FILENAME_COMMENT: - try: - if self.report[item][CD_CONTENT]: - self.old_comment = self.report[item][CD_CONTENT] - except Exception, e: - pass - continue - - if item == FILENAME_REPRODUCE: - try: - if self.report[item][CD_CONTENT]: - self.old_how_to_reproduce = self.report[item][CD_CONTENT] - except Exception, e: - pass - continue - - if self.report[item][CD_TYPE] == CD_SYS: - continue - - # item name 0| value 1| editable? 2| toggled? 3| visible?(attachment)4 - # FIXME: handle editable fields - if self.report[item][CD_TYPE] == CD_BIN: - self.builder.get_object("fAttachment").show() - vbAttachments = self.builder.get_object("vbAttachments") - toggle = gtk.CheckButton(self.report[item][CD_CONTENT]) - vbAttachments.pack_start(toggle) - # bind item to checkbox - toggle.item = item - #FIXME: temporary workaround, in 1.0.4 reporters don't care - # about this, they just send what they want to - # TicketUploader even sends coredump!! - #toggle.show() - continue - - # It must be CD_TXT field - item_label = self.builder.get_object("l%s" % item) - if item_label: - self.set_label(item_label, self.report[item][CD_CONTENT]) - else: - # no widget to show this item - # probably some new item need to adjust the GUI! - # FIXME: add some window+button to show all the info - # in raw form (smth like the old report dialog) - pass - #end for - - buff = gtk.TextBuffer() - self.show_hint_comment = (self.old_comment == COMMENT_HINT_TEXT) - if self.show_hint_comment: - buff.set_text(COMMENT_HINT_TEXT) - else: - buff.set_text(self.old_comment) - self.tvComment.set_buffer(buff) - - buff = gtk.TextBuffer() - if self.old_how_to_reproduce == "": - buff.set_text(HOW_TO_HINT_TEXT) - else: - buff.set_text(self.old_how_to_reproduce) - self.tevHowToReproduce.set_buffer(buff) - - def dehydrate(self): - ## # handle attachments - ## vbAttachments = self.builder.get_object("vbAttachments") - ## for attachment in vbAttachments.get_children(): - ## #print "%s file %s" % (["not sending","sending"][attachment.get_active()], attachment.get_label()) - ## del self.report[attachment.item] - - # handle comment - buff = self.tvComment.get_buffer() - text = buff.get_text(buff.get_start_iter(), buff.get_end_iter()) - if self.old_comment != text: - self.report[FILENAME_COMMENT] = [CD_TXT, 'y', text] - # handle how to reproduce - buff = self.tevHowToReproduce.get_buffer() - text = buff.get_text(buff.get_start_iter(), buff.get_end_iter()) - if self.old_how_to_reproduce != text: - self.report[FILENAME_REPRODUCE] = [CD_TXT, 'y', text] - # handle backtrace - tev_backtrace = self.builder.get_object("tvBacktrace") - buff = tev_backtrace.get_buffer() - text = buff.get_text(buff.get_start_iter(), buff.get_end_iter()) - self.report[FILENAME_BACKTRACE] = [CD_TXT, 'y', text] - - def check_report(self): - # FIXME: check the report for passwords and some other potentially - # sensitive info - self.dehydrate() - return True - - def run(self): - result = self.window.run() - self.window.destroy() - return (result, self.report) - -class ReporterSelector(): - def __init__(self, crashdump, daemon, log=None, parent=None): - self.connected_signals = [] - self.updates = "" - self.daemon = daemon - self.dump = crashdump - self.selected_reporters = [] - #FIXME: cache settings! Create some class to represent it like PluginList - self.settings = daemon.getSettings() - pluginlist = getPluginInfoList(daemon) - self.reporters = [] - AnalyzerActionsAndReporters = self.settings["AnalyzerActionsAndReporters"] - try: - reporters = None - try: - reporters = AnalyzerActionsAndReporters[self.dump.getAnalyzerName()+":"+self.dump.getPackageName()] - except KeyError: - pass - if not reporters: - reporters = AnalyzerActionsAndReporters[crashdump.getAnalyzerName()] - for reporter_name in reporters.split(','): - reporter = pluginlist.getReporterByName(reporter_name) - if reporter: - self.reporters.append(reporter) - except KeyError: - # Analyzer has no associated reporters. - pass - - builderfile = "%s/report.glade" % sys.path[0] - self.builder = gtk.Builder() - self.builder.add_from_file(builderfile) - self.window = self.builder.get_object("w_reporters") - b_cancel = self.builder.get_object("b_close") - - if parent: - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.window.set_transient_for(parent) - self.window.set_modal(True) - self.connect_signal(self.window, "delete-event", self.on_window_delete) - self.connect_signal(self.window, "destroy-event", self.on_window_delete) - self.connect_signal(b_cancel, "clicked", self.on_close_clicked) - else: - # if we don't have parent we want to quit the mainloop on close - self.window.set_position(gtk.WIN_POS_CENTER) - self.connect_signal(self.window, "delete-event", gtk.main_quit) - self.connect_signal(self.window, "destroy-event", gtk.main_quit) - self.connect_signal(b_cancel, "clicked", gtk.main_quit) - - - self.pBarWindow = self.builder.get_object("pBarWindow") - self.pBarWindow.set_transient_for(self.window) - - reporters_vbox = self.builder.get_object("vb_reporters") - for reporter in self.reporters: - button = gtk.Button(str(reporter)) - self.connect_signal(button, "clicked", self.on_reporter_clicked, data=reporter) - reporters_vbox.pack_start(button) - - # progress bar window to show while bt is being extracted - self.pBarWindow = self.builder.get_object("pBarWindow") - if self.pBarWindow: - self.connect_signal(self.pBarWindow, "delete_event", self.sw_delete_event_cb) - if parent: - self.pBarWindow.set_transient_for(parent) - else: - self.pBarWindow.set_transient_for(self.window) - self.pBar = self.builder.get_object("pBar") - - # connect handlers for daemon signals - #self.ccdaemon.connect("abrt-error", self.error_cb) - self.connect_signal(daemon, "update", self.update_cb) - # for now, just treat them the same (w/o this, we don't even see daemon warnings in logs!): - #self.ccdaemon.connect("warning", self.update_cb) - #self.ccdaemon.connect("show", self.show_cb) - #self.ccdaemon.connect("daemon-state-changed", self.on_daemon_state_changed_cb) - self.connect_signal(daemon, "report-done", self.on_report_done_cb) - self.connect_signal(daemon, "analyze-complete", self.on_analyze_complete_cb, self.pBarWindow) - - def connect_signal(self, obj, signal, callback, data=None): - if data: - signal_id = obj.connect(signal, callback, data) - else: - signal_id = obj.connect(signal, callback) - self.connected_signals.append((obj, signal_id)) - - def disconnect_signals(self): - # we need to disconnect all signals in order to break all references - # to this object, otherwise python won't destroy this object and the - # signals emmited by daemon will get caught by multiple instances of - # this class - for obj, signal_id in self.connected_signals: - obj.disconnect(signal_id) - - def cleanup_and_exit(self): - if not self.window.get_property("visible"): - self.disconnect_signals() - # if the reporter selector doesn't have a parent - if not self.window.get_transient_for(): - gtk.main_quit() - - def update_cb(self, daemon, message): - self.updates += message - if self.updates[-1] != '\n': - self.updates += '\n' - message = message.replace('\n',' ') - self.builder.get_object("lStatus").set_text(message) - buff = gtk.TextBuffer() - buff.set_text(self.updates) - end = buff.get_insert() - tvUpdates = self.builder.get_object("tvUpdates") - tvUpdates.set_buffer(buff) - tvUpdates.scroll_mark_onscreen(end) - - def sw_delete_event_cb(self, widget, event, data=None): - if self.timer: - gobject.source_remove(self.timer) - widget.hide() - return True - - def show(self): - if not self.reporters: - gui_error_message(_("No reporter plugin available for this type of crash.\n" - "Please check abrt.conf.")) - elif len(self.reporters) > 1: - self.builder.get_object("vb_reporters").show_all() - self.window.show() - else: - # we have only one reporter in the list - self.selected_reporters = [str(self.reporters[0])] - self.show_report() - - def on_config_plugin_clicked(self, button, plugin, image): - ui = PluginSettingsUI(plugin, parent=self.window) - ui.hydrate() - response = ui.run() - if response == gtk.RESPONSE_APPLY: - ui.dehydrate() - if plugin.Settings.check(): - try: - plugin.save_settings_on_client_side() - except Exception, e: - gui_error_message(_("Cannot save plugin settings:\n %s" % e)) - box = image.get_parent() - im = gtk.Image() - im.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_MENU) - box.remove(image) - box.pack_start(im, expand = False, fill = False) - im.show() - image.destroy() - button.set_sensitive(False) - elif response == gtk.RESPONSE_CANCEL: - log1("cancel") - ui.destroy() - - def check_settings(self, reporters): - wrong_conf_plugs = [] - for reporter in reporters: - if reporter.Settings.check() == False: - wrong_conf_plugs.append(reporter) - - if wrong_conf_plugs: - gladefile = "%s%ssettings_wizard.glade" % (sys.path[0],"/") - builder = gtk.Builder() - builder.add_from_file(gladefile) - dialog = builder.get_object("WrongSettings") - vbWrongSettings = builder.get_object("vbWrongSettings") - for plugin in wrong_conf_plugs: - hbox = gtk.HBox() - hbox.set_spacing(6) - image = gtk.Image() - image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_MENU) - button = gtk.Button(_("Configure %s options" % plugin.getName())) - button.connect("clicked", self.on_config_plugin_clicked, plugin, image) - hbox.pack_start(button) - hbox.pack_start(image, expand = False, fill = False) - vbWrongSettings.pack_start(hbox) - vbWrongSettings.show_all() - dialog.set_transient_for(self.window) - dialog.set_modal(True) - response = dialog.run() - dialog.destroy() - if response != gtk.RESPONSE_YES: - # user cancelled reporting - return False - return True - - def on_reporter_clicked(self, widget, reporter): - self.selected_reporters = [reporter] - if self.check_settings(self.selected_reporters): - self.show_report() - - def on_close_clicked(self, widget): - self.disconnect_signals() - self.window.destroy() - - def on_window_delete(self, window, event): - self.disconnect_signals() - return False - - def on_report_done_cb(self, daemon, result): - try: - gobject.source_remove(self.timer) - except: - pass - self.pBarWindow.hide() - gui_report_dialog(result, self.window) - - self.cleanup_and_exit() - - def on_analyze_complete_cb(self, daemon, report, pBarWindow): - try: - gobject.source_remove(self.timer) - except: - pass - self.pBarWindow.hide() -#FIXME - why we need this?? -> timeout warnings -# try: -# dumplist = getDumpList(self.daemon) -# except Exception, e: -# print e - if not report: - gui_error_message(_("Unable to get report!\nIs debuginfo missing?")) - return - - # if we have only one reporter enabled, the window with - # the selection is not shown, so we can't use it as a parent - # and we use the mainwindow instead - if self.window.get_property("visible"): - parent_window = self.window - else: - parent_window = self.window.get_transient_for() - - report_dialog = ReporterDialog(report, self.daemon, log=self.updates, parent=parent_window) - # (response, report) - response, result = report_dialog.run() - - if response == gtk.RESPONSE_APPLY: - try: - self.pBarWindow.show_all() - self.timer = gobject.timeout_add(100, self.progress_update_cb) - pluginlist = getPluginInfoList(self.daemon) - reporters_settings = pluginlist.getReporterPluginsSettings() - log2("Report(result,reporters,settings):") - log2(" result:%s", str(result)) - # Careful, this will print reporters_settings["Password"] too - log2(" settings:%s", str(reporters_settings)) - self.daemon.Report(result, self.selected_reporters, reporters_settings) - log2("Report() returned") - #self.hydrate() - except Exception, ex: - gui_error_message(_("Reporting failed!\n%s" % ex)) - # -50 == REFRESH - elif response == -50: - self.refresh_report(report) - else: - self.cleanup_and_exit() - - # call to update the progressbar - def progress_update_cb(self, *args): - self.pBar.pulse() - return True - - def refresh_report(self, report): - self.updates = "" - self.pBarWindow.show_all() - self.timer = gobject.timeout_add(100, self.progress_update_cb) - - # show the report window with selected report - try: - self.daemon.start_job("%s:%s" % (report[CD_UID][CD_CONTENT], report[CD_UUID][CD_CONTENT]), force=1) - except Exception, ex: - # FIXME #3 dbus.exceptions.DBusException: org.freedesktop.DBus.Error.NoReply: Did not receive a reply - # do this async and wait for yum to end with debuginfoinstal - if self.timer: - gobject.source_remove(self.timer) - self.pBarWindow.hide() - gui_error_message(_("Error acquiring the report: %s" % ex)) - return - - def show_report(self): - self.updates = "" - # FIXME don't duplicate the code, move to function - #self.pBar.show() - self.pBarWindow.show_all() - self.timer = gobject.timeout_add(100, self.progress_update_cb) - - # show the report window with selected dump - # when getReport is done it emits "analyze-complete" and on_analyze_complete_cb is called - # FIXME: does it make sense to change it to use callback rather then signal emitting? - try: - self.daemon.start_job("%s:%s" % (self.dump.getUID(), self.dump.getUUID())) - except Exception, ex: - # FIXME #3 dbus.exceptions.DBusException: org.freedesktop.DBus.Error.NoReply: Did not receive a reply - # do this async and wait for yum to end with debuginfoinstal - if self.timer: - gobject.source_remove(self.timer) - self.pBarWindow.hide() - gui_error_message(_("Error acquiring the report: %s" % ex)) - return - - def __del__(self): - log1("ReporterSelector: instance is about to be garbage-collected") diff --git a/src/Gui/CC_gui_functions.py b/src/Gui/CC_gui_functions.py deleted file mode 100644 index 56abae1b..00000000 --- a/src/Gui/CC_gui_functions.py +++ /dev/null @@ -1,253 +0,0 @@ -# -*- coding: utf-8 -*- -from glib import markup_escape_text -import gtk -import pango -import subprocess -import sys - -try: - # we don't want to add dependency to rpm, but if we have it, we can use it - import rpm -except ImportError: - rpm = None -from abrt_utils import _, log, log1, log2 - - -def tag_urls_in_text(text): - url_marks = ["http://", "https://", "ftp://", "ftps://", "file://"] - text = markup_escape_text(text) - lines = text.split('\n') - lines_dict = {} - for index in xrange(len(lines)): - lines_dict[index] = lines[index] - - for mark in url_marks: - for ix,line in lines_dict.items(): - last_mark = line.find(mark) - if last_mark != -1: - url_end = line.find(' ',last_mark) - if url_end == -1: - url_end = len(line) - url = line[last_mark:url_end] - tagged_url = "%s" % (url, url) - lines_dict[ix] = line.replace(url, tagged_url) - retval = "" - for line in lines_dict.itervalues(): - retval += line - retval +='\n' - # strip the trailing \n - return retval[:-1] - -def on_label_resize(label, allocation): - label.set_size_request(allocation.width,-1) - -def gui_report_dialog ( report_status_dict, parent_dialog, - message_type=gtk.MESSAGE_INFO, - widget=None, page=0, broken_widget=None ): - MAX_WIDTH = 50 - builder = gtk.Builder() - builderfile = "%s%sdialogs.glade" % (sys.path[0],"/") - builder.add_from_file(builderfile) - dialog = builder.get_object("ReportDialog") - dialog.set_geometry_hints(dialog, min_width=450, min_height=150) - dialog.set_resizable(True) - main_hbox = builder.get_object("main_hbox") - - STATUS = 0 - MESSAGE = 1 - status_vbox = gtk.VBox() - for plugin, res in report_status_dict.iteritems(): - plugin_status_vbox = gtk.VBox() - plugin_label = gtk.Label() - plugin_label.set_markup("%s: " % plugin) - plugin_label.set_justify(gtk.JUSTIFY_RIGHT) - plugin_label.set_alignment(0, 0) - status_label = gtk.Label() - status_label.connect("size-allocate",on_label_resize) - status_label.set_max_width_chars(MAX_WIDTH) - status_label.set_size_request(400,-1) - status_label.set_selectable(True) - status_label.set_line_wrap(True) - status_label.set_line_wrap_mode(pango.WRAP_CHAR) - status_label.set_alignment(0, 0) - plugin_status_vbox.pack_start(plugin_label, expand=False) - plugin_status_vbox.pack_start(status_label, fill=True, expand=True) - # 0 means not succesfull - #if report_status_dict[plugin][STATUS] == '0': - # this first one is actually a fallback to set at least - # a raw text in case when set_markup() fails - status_label.set_text(report_status_dict[plugin][MESSAGE]) - status_label.set_markup("%s" % markup_escape_text(report_status_dict[plugin][MESSAGE])) - # if the report was not succesful then this won't pass so this runs only - # if report succeds and gets overwriten by the status message - if report_status_dict[plugin][STATUS] == '1': - status_label.set_markup(tag_urls_in_text(report_status_dict[plugin][MESSAGE])) - - if len(report_status_dict[plugin][1]) > MAX_WIDTH: - status_label.set_tooltip_text(report_status_dict[plugin][1]) - status_vbox.pack_start(plugin_status_vbox, fill=True, expand=False) - main_hbox.pack_start(status_vbox) - - if widget != None: - if isinstance (widget, gtk.CList): - widget.select_row (page, 0) - elif isinstance (widget, gtk.Notebook): - widget.set_current_page (page) - if broken_widget != None: - broken_widget.grab_focus () - if isinstance (broken_widget, gtk.Entry): - broken_widget.select_region (0, -1) - - if parent_dialog: - dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT) - dialog.set_transient_for(parent_dialog) - else: - dialog.set_position (gtk.WIN_POS_CENTER) - - main_hbox.show_all() - ret = dialog.run() - dialog.destroy() - return ret - -def gui_info_dialog ( message, parent=None, - message_type=gtk.MESSAGE_INFO, - widget=None, page=0, broken_widget=None ): - - dialog = gtk.MessageDialog( parent, - gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, - message_type, gtk.BUTTONS_OK, - message ) - dialog.set_markup(message) - if widget != None: - if isinstance (widget, gtk.CList): - widget.select_row (page, 0) - elif isinstance (widget, gtk.Notebook): - widget.set_current_page (page) - if broken_widget != None: - broken_widget.grab_focus () - if isinstance (broken_widget, gtk.Entry): - broken_widget.select_region (0, -1) - - if parent: - dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT) - dialog.set_transient_for(parent) - else: - dialog.set_position (gtk.WIN_POS_CENTER) - - ret = dialog.run () - dialog.destroy() - return ret - -def gui_error_message ( message, parent_dialog=None, - message_type=gtk.MESSAGE_ERROR, - widget=None, page=0, broken_widget=None ): - - dialog = gtk.MessageDialog( parent_dialog, - gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, - message_type, gtk.BUTTONS_OK, message ) - dialog.set_markup(message) - if parent_dialog: - dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT) - dialog.set_transient_for(parent_dialog) - else: - dialog.set_position (gtk.WIN_POS_CENTER) - - ret = dialog.run () - dialog.destroy() - return ret - -def gui_question_dialog ( message, parent_dialog=None, - message_type=gtk.MESSAGE_QUESTION, - widget=None, page=0, broken_widget=None ): - - dialog = gtk.MessageDialog( parent_dialog, - gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, - message_type, gtk.BUTTONS_NONE, - message ) - dialog.add_button("gtk-cancel", gtk.RESPONSE_CANCEL) - dialog.add_button("gtk-no", gtk.RESPONSE_NO) - dialog.add_button("gtk-yes", gtk.RESPONSE_YES) - dialog.set_markup(message) - if parent_dialog: - dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT) - dialog.set_transient_for(parent_dialog) - else: - dialog.set_position (gtk.WIN_POS_CENTER) - - ret = dialog.run () - dialog.destroy() - return ret - -def get_icon_for_package(theme, package): - log2("get_icon_for_package('%s')", package) - package_icon = None - try: - package_icon = theme.load_icon(package, 48, gtk.ICON_LOOKUP_USE_BUILTIN) - except: - # try to find icon filename by manually - if not rpm: - return None - ts = rpm.TransactionSet() - mi = ts.dbMatch('name', package) - possible_icons = [] - icon_filename = "" - icon_name = "" - filenames = "" - for h in mi: - filenames = h['filenames'] - for filename in filenames: - # add check only for last 4 chars - if filename.rfind(".png") != -1: - possible_icons.append(filename) - if filename.rfind(".desktop") != -1: - log2("desktop file:'%s'", filename) - desktop_file = open(filename, 'r') - lines = desktop_file.readlines() - for line in lines: - if line.find("Icon=") != -1: - log2("Icon='%s'", line[5:-1]) - icon_name = line[5:-1] - break - desktop_file.close() - # .desktop file found - if icon_name: - try: - package_icon = theme.load_icon(icon_name, 48, gtk.ICON_LOOKUP_USE_BUILTIN) - except: - # we should get here only if the .desktop file is wrong.. - for filename in h['filenames']: - if filename.rfind("%s.png" % icon_name) != -1: - icon_filename = filename - # if we found size 48x48 we don't need to continue - if "48x48" in icon_filename: - log2("png file:'%s'", filename) - break - if icon_filename: - log1("icon created from %s", icon_filename) - package_icon = gtk.gdk.pixbuf_new_from_file_at_size(icon_filename, 48, 48) - return package_icon - -def show_log(message_log, parent=None): - builder = gtk.Builder() - builderfile = "%s%sdialogs.glade" % (sys.path[0],"/") - builder.add_from_file(builderfile) - dialog = builder.get_object("LogViewer") - tevLog = builder.get_object("tevLog") - - if parent: - dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT) - dialog.set_transient_for(parent) - else: - dialog.set_position (gtk.WIN_POS_CENTER) - - buff = gtk.TextBuffer() - buff.set_text(message_log) - tevLog.set_buffer(buff) - - dialog.run() - dialog.destroy() - -if __name__ == "__main__": - window = gtk.Window() - gui_report_dialog("Bugzilla: CReporterBugzilla::Report(): CReporterBugzilla::Login(): RPC response indicates failure. The username or password you entered is not valid.\nLogger: Report was stored into: /var/log/abrt.log", window) - gtk.main() diff --git a/src/Gui/CReporterAssistant.py b/src/Gui/CReporterAssistant.py deleted file mode 100644 index 0ddd18fa..00000000 --- a/src/Gui/CReporterAssistant.py +++ /dev/null @@ -1,894 +0,0 @@ -import gtk -from PluginList import getPluginInfoList -from abrt_utils import _, log, log1, log2, get_verbose_level, g_verbose, warn -from CCDump import * # FILENAME_xxx, CD_xxx -from PluginSettingsUI import PluginSettingsUI -import sys -import gobject -from CC_gui_functions import * -import pango - -# assistant pages -PAGE_REPORTER_SELECTOR = 0 -PAGE_BACKTRACE_APPROVAL = 1 -PAGE_EXTRA_INFO = 2 -PAGE_CONFIRM = 3 -PAGE_REPORT_DONE = 4 -NO_PROBLEMS_DETECTED = -50 -HOW_TO_HINT_TEXT = "1.\n2.\n3.\n" -COMMENT_HINT_TEXT = _("Brief description of how to reproduce this or what you did...") -MISSING_BACKTRACE_TEXT = _("Crash info doesn't contain a backtrace") - -DEFAULT_WIDTH = 800 -DEFAULT_HEIGHT = 500 - -class ReporterAssistant(): - def __init__(self, report, daemon, log=None, parent=None): - self.connected_signals = [] - self.plugins_cb = [] - self.daemon = daemon - self.updates = "" - self.pdict = {} - self.report = report - self.parent = parent - self.comment_changed = False - self.howto_changed = False - self.report_has_bt = False - self.selected_reporters = [] - """ create the assistant """ - self.assistant = gtk.Assistant() - self.assistant.set_icon_name("abrt") - self.assistant.set_default_size(DEFAULT_WIDTH,DEFAULT_HEIGHT) - self.assistant.set_size_request(DEFAULT_WIDTH,DEFAULT_HEIGHT) - self.connect_signal(self.assistant, "prepare",self.on_page_prepare) - self.connect_signal(self.assistant, "cancel",self.on_cancel_clicked) - self.connect_signal(self.assistant, "close",self.on_close_clicked) - self.connect_signal(self.assistant, "apply",self.on_apply_clicked) - if parent: - self.assistant.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.assistant.set_transient_for(parent) - else: - # if we don't have parent we want to quit the mainloop on close - self.assistant.set_position(gtk.WIN_POS_CENTER) - - ### progress bar window - self.builder = gtk.Builder() - builderfile = "%s/progress_window.glade" % sys.path[0] - self.builder.add_from_file(builderfile) - self.pBarWindow = self.builder.get_object("pBarWindow") - if self.pBarWindow: - self.connect_signal(self.pBarWindow, "delete_event", self.sw_delete_event_cb) - if parent: - self.pBarWindow.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.pBarWindow.set_transient_for(parent) - else: - self.pBarWindow.set_position(gtk.WIN_POS_CENTER) - self.pBar = self.builder.get_object("pBar") - else: - log1("Couldn't create the progressbar window") - - self.connect_signal(daemon, "analyze-complete", self.on_analyze_complete_cb, self.pBarWindow) - self.connect_signal(daemon, "report-done", self.on_report_done_cb) - self.connect_signal(daemon, "update", self.update_cb) - - # call to update the progressbar - def progress_update_cb(self, *args): - self.pBar.pulse() - return True - - def on_show_bt_clicked(self, button): - viewer = gtk.Window() - viewer.set_icon_name("abrt") - viewer.set_default_size(600,500) - viewer.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - viewer.set_transient_for(self.assistant) - vbox = gtk.VBox() - viewer.add(vbox) - bt_tev = gtk.TextView() - backtrace_scroll_w = gtk.ScrolledWindow() - backtrace_scroll_w.add(bt_tev) - backtrace_scroll_w.set_policy(gtk.POLICY_AUTOMATIC, - gtk.POLICY_AUTOMATIC) - bt_tev.set_buffer(self.backtrace_buff) - vbox.pack_start(backtrace_scroll_w) - b_close = gtk.Button(stock=gtk.STOCK_CLOSE) - b_close.connect("clicked",lambda *w: viewer.destroy()) - vbox.pack_start(b_close, False) - viewer.show_all() - - def on_report_done_cb(self, daemon, result): - self.hide_progress() - STATUS = 0 - MESSAGE = 1 - # 0 means not succesfull - #if report_status_dict[plugin][STATUS] == '0': - # this first one is actually a fallback to set at least - # a raw text in case when set_markup() fails - for plugin, res in result.iteritems(): - bug_report = gtk.Label() - bug_report.set_selectable(True) - bug_report.set_line_wrap(True) - bug_report.set_alignment(0.0, 0.0) - bug_report.set_justify(gtk.JUSTIFY_LEFT) - bug_report.set_size_request(DEFAULT_WIDTH-50, -1) - bug_report.set_text(result[plugin][MESSAGE]) - bug_report.set_markup("%s" % markup_escape_text(result[plugin][MESSAGE])) - # if the report was not succesful then this won't pass so this runs only - # if report succeds and gets overwriten by the status message - if result[plugin][STATUS] == '1': - bug_report.set_markup(tag_urls_in_text(result[plugin][MESSAGE])) - self.bug_reports_vbox.pack_start(bug_report, expand=False) - bug_report.show() - - - #if len(result[plugin][1]) > MAX_WIDTH: - # self.bug_reports.set_tooltip_text(result[plugin][1]) - #gui_report_dialog(result, self.parent) - - def cleanup_and_exit(self): - self.disconnect_signals() - self.assistant.destroy() - if not self.parent: - gtk.main_quit() - - def update_cb(self, daemon, message): - self.updates += message - if self.updates[-1] != '\n': - self.updates += '\n' - message = message.replace('\n',' ') - self.builder.get_object("lStatus").set_text(message) - buff = gtk.TextBuffer() - buff.set_text(self.updates) - end = buff.get_insert() - tvUpdates = self.builder.get_object("tvUpdates") - tvUpdates.set_buffer(buff) - tvUpdates.scroll_mark_onscreen(end) - - def sw_delete_event_cb(self, widget, event, data=None): - if self.timer: - gobject.source_remove(self.timer) - widget.hide() - return True - - def connect_signal(self, obj, signal, callback, data=None): - if data: - signal_id = obj.connect(signal, callback, data) - else: - signal_id = obj.connect(signal, callback) - log1("connected signal %s:%s" % (signal, signal_id)) - self.connected_signals.append((obj, signal_id)) - - def disconnect_signals(self): - # we need to disconnect all signals in order to break all references - # to this object, otherwise python won't destroy this object and the - # signals emmited by daemon will get caught by multiple instances of - # this class - for obj, signal_id in self.connected_signals: - log1("disconnect %s:%s" % (obj, signal_id)) - obj.disconnect(signal_id) - - def on_cancel_clicked(self, assistant, user_data=None): - self.cleanup_and_exit() - - def on_close_clicked(self, assistant, user_data=None): - self.cleanup_and_exit() - - def on_apply_clicked(self, assistant, user_data=None): - self.send_report(self.result) - - def hide_progress(self): - try: - gobject.source_remove(self.timer) - except: - pass - self.pBarWindow.hide() - - def on_config_plugin_clicked(self, button, parent, plugin, image): - try: - ui = PluginSettingsUI(plugin, parent=parent) - except Exception, ex: - gui_error_message(str(ex)) - return - - ui.hydrate() - response = ui.run() - if response == gtk.RESPONSE_APPLY: - ui.dehydrate() - if plugin.Settings.check(): - try: - plugin.save_settings_on_client_side() - except Exception, e: - gui_error_message(_("Cannot save plugin settings:\n %s" % e)) - box = image.get_parent() - im = gtk.Image() - im.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_MENU) - box.remove(image) - box.pack_start(im, expand = False, fill = False) - im.show() - image.destroy() - button.set_sensitive(False) - elif response == gtk.RESPONSE_CANCEL: - log1("cancel") - ui.destroy() - - def check_settings(self, reporters): - wrong_conf_plugs = [] - for reporter in reporters: - if reporter.Settings.check() == False: - wrong_conf_plugs.append(reporter) - - if wrong_conf_plugs: - gladefile = "%s%ssettings_wizard.glade" % (sys.path[0],"/") - builder = gtk.Builder() - builder.add_from_file(gladefile) - dialog = builder.get_object("WrongSettings") - vbWrongSettings = builder.get_object("vbWrongSettings") - for plugin in wrong_conf_plugs: - hbox = gtk.HBox() - hbox.set_spacing(6) - image = gtk.Image() - image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_MENU) - button = gtk.Button(_("Configure %s options" % plugin.getName())) - button.connect("clicked", self.on_config_plugin_clicked, dialog, plugin, image) - hbox.pack_start(button) - hbox.pack_start(image, expand = False, fill = False) - vbWrongSettings.pack_start(hbox) - vbWrongSettings.show_all() - dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - dialog.set_transient_for(self.assistant) - dialog.set_modal(True) - response = dialog.run() - dialog.destroy() - if response == gtk.RESPONSE_YES: - return True - else: - # user cancelled reporting - return False - else: - return NO_PROBLEMS_DETECTED - - def warn_user(self, warnings): - # FIXME: show in lError - #self.lErrors = self.builder.get_object("lErrors") - warning_lbl = None - for warning in warnings: - if warning_lbl: - warning_lbl += "\n* %s" % warning - else: - warning_lbl = "* %s" % warning - # fallback - self.lbl_errors.set_label(warning_lbl) - self.lbl_errors.set_markup(warning_lbl) - self.errors_hbox.show_all() - #fErrors.show_all() - - def hide_warning(self): - self.errors_hbox.hide() - - def allow_send(self, send_toggle): - self.hide_warning() - #bSend = self.builder.get_object("bSend") - SendBacktrace = send_toggle.get_active() - send = True - error_msgs = [] - rating_required = False - - for reporter in self.selected_reporters: - if "RatingRequired" in reporter.Settings.keys(): - if reporter.Settings["RatingRequired"] == "yes": - rating_required = True - log1(_("Rating is required by the %s plugin") % reporter) - if self.selected_reporters and not rating_required: - log1(_("Rating is not required by any plugin, skipping the check...")) - - try: - rating = int(self.result[FILENAME_RATING][CD_CONTENT]) - log1(_("Rating is %s" % rating)) - except Exception, ex: - rating = None - log1(_("Crashdump doesn't have rating => we suppose it's not required")) - # active buttons acording to required fields - # if an backtrace has rating use it - if not SendBacktrace: - send = False - error_msgs.append(_("You should check the backtrace for sensitive data.")) - error_msgs.append(_("You must agree with sending the backtrace.")) - # we have both SendBacktrace and rating - # if analyzer doesn't provide the rating, then we suppose that it's - # not required e.g.: kerneloops, python - if rating_required and rating != None: - try: - package = self.result[FILENAME_PACKAGE][CD_CONTENT] - # if we don't have package for some reason - except: - package = None - # not usable report - if rating < 3: - if package: - error_msgs.append(_("Reporting disabled because the backtrace is unusable.\nPlease try to install debuginfo manually using the command: debuginfo-install %s \nthen use the Refresh button to regenerate the backtrace." % self.report.getPackageName())) - else: - error_msgs.append(_("Reporting disabled because the backtrace is unusable.")) - send = False - # probably usable 3 - elif rating < 4: - error_msgs.append(_("The backtrace is incomplete, please make sure you provide the steps to reproduce.")) - - if error_msgs: - self.warn_user(error_msgs) - #bSend.set_sensitive(send) - self.assistant.set_page_complete(self.pdict_get_page(PAGE_BACKTRACE_APPROVAL), send) - - def on_page_prepare(self, assistant, page): - if page == self.pdict_get_page(PAGE_REPORTER_SELECTOR): - pass - - # this is where dehydrate happens - elif page == self.pdict_get_page(PAGE_EXTRA_INFO): - if not self.howto_changed: - # howto - buff = gtk.TextBuffer() - try: - buff.set_text(self.result[FILENAME_REPRODUCE][CD_CONTENT]) - self.howto_changed = True - except KeyError: - buff.set_text(HOW_TO_HINT_TEXT) - self.howto_tev.set_buffer(buff) - # don't refresh the comment if user changed it - if not self.comment_changed: - # comment - buff = gtk.TextBuffer() - try: - buff.set_text(self.result[FILENAME_COMMENT][CD_CONTENT]) - self.comment_changed = True - except KeyError: - buff.set_text(COMMENT_HINT_TEXT) - self.comment_tev.set_buffer(buff) - elif page == self.pdict_get_page(PAGE_CONFIRM): - # howto - if self.howto_changed: - howto_buff = self.howto_tev.get_buffer() - howto_text = howto_buff.get_text(howto_buff.get_start_iter(), howto_buff.get_end_iter()) - # user has changed the steps to reproduce - self.steps.set_text(howto_text) - self.result[FILENAME_REPRODUCE] = [CD_TXT, 'y', howto_text] - else: - self.steps.set_text(_("You did not provide any steps to reproduce.")) - try: - del self.result[FILENAME_REPRODUCE] - except KeyError: - # if this is a first time, then we don't have key FILENAME_REPRODUCE - pass - #comment - if self.comment_changed: - comment_buff = self.comment_tev.get_buffer() - comment_text = comment_buff.get_text(comment_buff.get_start_iter(), comment_buff.get_end_iter()) - # user has changed the comment - self.comments.set_text(comment_text) - self.result[FILENAME_COMMENT] = [CD_TXT, 'y', comment_text] - else: - self.comments.set_text(_("You did not provide any comments.")) - try: - del self.result[FILENAME_COMMENT] - except KeyError: - # if this is a first time, then we don't have key FILENAME_COMMENT - pass - - # backtrace - backtrace_text = self.backtrace_buff.get_text(self.backtrace_buff.get_start_iter(), self.backtrace_buff.get_end_iter()) - if self.report_has_bt: - self.result[FILENAME_BACKTRACE] = [CD_TXT, 'y', backtrace_text] - if page == self.pdict_get_page(PAGE_BACKTRACE_APPROVAL): - self.allow_send(self.backtrace_cb) - - def send_report(self, report): - try: - self.pBarWindow.show_all() - self.timer = gobject.timeout_add(100, self.progress_update_cb) - pluginlist = getPluginInfoList(self.daemon) - reporters_settings = pluginlist.getReporterPluginsSettings() - log2("Report(report, reporters, settings):") - log2(" result:%s", str(report)) - # Careful, this will print reporters_settings["Password"] too - log2(" settings:%s", str(reporters_settings)) - self.daemon.Report(report, self.selected_reporters, reporters_settings) - log2("Report() returned") - #self.hydrate() - except Exception, ex: - self.hide_progress() - gui_error_message(_("Reporting failed!\n%s" % ex)) - - def on_plugin_toggled(self, plugin, plugins, reporter, page): - complete = False - if plugin.get_active(): - log1("Plugin >>%s<< activated" % reporter) - self.selected_reporters.append(reporter) - check_result = self.check_settings([reporter]) - if check_result == NO_PROBLEMS_DETECTED: - pass - elif check_result: - page_n = self.assistant.get_current_page() - self.assistant.set_page_complete(page, True) - self.assistant.set_current_page(page_n+1) - else: - plugin.set_active(False) - else: - self.selected_reporters.remove(reporter) - log1("Plugin >>%s<< de-activated" % reporter) - if self.selected_reporters: - complete = True - log1("Selected reporters: %s" % [str(x) for x in self.selected_reporters]) - self.assistant.set_page_complete(page, complete) - - def on_bt_toggled(self, togglebutton, page): - self.allow_send(togglebutton) - - def pdict_add_page(self, page, name): - # FIXME try, except?? - if name not in self.pdict: - self.pdict[name] = page - else: - warn("The page %s is already in the dictionary" % name) - #raise Exception("The page %s is already in the dictionary" % name) - - def pdict_get_page(self, name): - try: - return self.pdict[name] - except Exception, e: - log2(e) - return None - - def prepare_page_1(self): - plugins_cb = [] - page = gtk.VBox(spacing=10) - page.set_border_width(10) - self.assistant.insert_page(page, PAGE_REPORTER_SELECTOR) - lbl_default_info = gtk.Label() - lbl_default_info.set_line_wrap(True) - lbl_default_info.set_alignment(0.0, 0.0) - lbl_default_info.set_justify(gtk.JUSTIFY_LEFT) - lbl_default_info.set_size_request(DEFAULT_WIDTH-50, -1) - lbl_default_info.set_markup(_("It looks like an application from the " - "package %s has crashed " - "on your system. It is a good idea to send " - "a bug report about this issue. The report " - "will provide software maintainers with " - "information essential in figuring out how " - "to provide a bug fix for you.\n\n" - "Please review the information that follows " - "and modify it as needed to ensure your bug " - "report does not contain any sensitive data " - "you would rather not share.\n\n" - "Select where you would like to report the " - "bug, and press 'Forward' to continue.") - % self.report.getPackageName()) - page.pack_start(lbl_default_info, expand=True, fill=True) - vbox_plugins = gtk.VBox() - page.pack_start(vbox_plugins) - - # add checkboxes for enabled reporters - self.selected_reporters = [] - #FIXME: cache settings! Create some class to represent it like PluginList - self.settings = self.daemon.getSettings() - pluginlist = getPluginInfoList(self.daemon) - self.reporters = [] - AnalyzerActionsAndReporters = self.settings["AnalyzerActionsAndReporters"] - try: - reporters = None - try: - reporters = AnalyzerActionsAndReporters[self.report.getAnalyzerName()+":"+self.report.getPackageName()] - log1("Found per-package reporters, " - "using it instead of the common reporter") - except KeyError: - pass - # the package specific reporter has higher priority, - # so don't overwrite it if it's set - if not reporters: - reporters = AnalyzerActionsAndReporters[self.report.getAnalyzerName()] - # FIXME: split(',') doesn't work for RunApp("date", "date.txt") - # but since we don't have reporters with parameters, it will find - # the reporter plugins anyway, but it should be more clever... - for reporter_name in reporters.split(','): - reporter = pluginlist.getReporterByName(reporter_name) - if reporter: - log1("Adding >>%s<< to reporters", reporter) - self.reporters.append(reporter) - except KeyError: - # Analyzer has no associated reporters. - # but we don't care, maybe user just want to read the backtrace?? - pass - for reporter in self.reporters: - cb = gtk.CheckButton(str(reporter)) - cb.connect("toggled", self.on_plugin_toggled, plugins_cb, reporter, page) - plugins_cb.append(cb) - vbox_plugins.pack_start(cb, fill=True, expand=False) - # automatically select the reporter if we have only one reporter plugin - if len(self.reporters) == 1: - # we want to skip it only if the plugin is properly configured - if self.reporters[0].Settings.check(): - #self.selected_reporters.append(self.reporters[0]) - self.assistant.set_page_complete(page, True) - log1(_("Only one reporter plugin is configured.")) - # this is safe, because in python the variable is visible even - # outside the for loop - cb.set_active(True) - self.pdict_add_page(page, PAGE_REPORTER_SELECTOR) - self.assistant.set_page_type(page, gtk.ASSISTANT_PAGE_INTRO) - self.assistant.set_page_title(page, _("Send a bug report")) - page.show_all() - - def on_bt_copy(self, button, bt_text_view): - buff = bt_text_view.get_buffer() - bt_text = buff.get_text(buff.get_start_iter(), buff.get_end_iter()) - clipboard = gtk.clipboard_get() - clipboard.set_text(bt_text) - - def tv_text_changed(self, textview, default_text): - buff = textview.get_buffer() - text = buff.get_text(buff.get_start_iter(), buff.get_end_iter()) - if text: - if text and text != default_text: - return True - return False - else: - buff.set_text(default_text) - - def on_howto_focusout_cb(self, textview, event): - self.howto_changed = self.tv_text_changed(textview, HOW_TO_HINT_TEXT) - - def on_comment_focusin_cb(self, textview, event): - if not self.comment_changed: - # clear "hint" text by supplying a fresh, empty TextBuffer - textview.set_buffer(gtk.TextBuffer()) - - def on_comment_focusout_cb(self, textview, event): - self.comment_changed = self.tv_text_changed(textview, COMMENT_HINT_TEXT) - - def prepare_page_2(self): - page = gtk.VBox(spacing=10) - page.set_border_width(10) - lbl_default_info = gtk.Label() - lbl_default_info.set_line_wrap(True) - lbl_default_info.set_alignment(0.0, 0.0) - lbl_default_info.set_justify(gtk.JUSTIFY_FILL) - lbl_default_info.set_size_request(DEFAULT_WIDTH-50, -1) - lbl_default_info.set_text(_("Below is the backtrace associated with your " - "crash. A crash backtrace provides developers with details about " - "how the crash happened, helping them track down the source of the " - "problem.\n\n" - "Please review the backtrace below and modify it as needed to " - "ensure your bug report does not contain any sensitive data you would " - "rather not share:") - ) - page.pack_start(lbl_default_info, expand=False, fill=True) - self.backtrace_tev = gtk.TextView() - # global? - self.backtrace_buff = gtk.TextBuffer() - #self.backtrace_buff.set_text(self.report[FILENAME_BACKTRACE][CD_CONTENT]) - self.backtrace_tev.set_buffer(self.backtrace_buff) - backtrace_scroll_w = gtk.ScrolledWindow() - backtrace_scroll_w.add(self.backtrace_tev) - backtrace_scroll_w.set_policy(gtk.POLICY_AUTOMATIC, - gtk.POLICY_AUTOMATIC) - # backtrace - hbox_bt = gtk.HBox() - vbox_bt = gtk.VBox(homogeneous=False, spacing=5) - hbox_bt.pack_start(vbox_bt) - backtrace_alignment = gtk.Alignment() - hbox_bt.pack_start(backtrace_alignment, expand=False, padding=10) - # bad backtrace, reporting disabled - vbox_bt.pack_start(backtrace_scroll_w) - # warnings about wrong bt - self.errors_hbox = gtk.HBox() - self.warning_image = gtk.Image() - self.warning_image.set_from_stock(gtk.STOCK_DIALOG_WARNING,gtk.ICON_SIZE_DIALOG) - self.lbl_errors = gtk.Label() - self.lbl_errors.set_line_wrap(True) - #self.lbl_errors.set_alignment(0.0, 0.0) - self.lbl_errors.set_justify(gtk.JUSTIFY_FILL) - self.lbl_errors.set_size_request(DEFAULT_WIDTH-50, -1) - self.errors_hbox.pack_start(self.warning_image, False, False) - self.errors_hbox.pack_start(self.lbl_errors) - ### - vbox_bt.pack_start(self.errors_hbox, False, False) - hbox_buttons = gtk.HBox(homogeneous=True) - button_alignment = gtk.Alignment() - b_refresh = gtk.Button(_("Refresh")) - b_refresh.connect("clicked", self.hydrate, 1) - b_copy = gtk.Button(_("Copy")) - b_copy.connect("clicked", self.on_bt_copy, self.backtrace_tev) - hbox_buttons.pack_start(button_alignment) - hbox_buttons.pack_start(b_refresh, expand=False, fill=True) - hbox_buttons.pack_start(b_copy, expand=False, fill=True) - vbox_bt.pack_start(hbox_buttons, expand=False, fill=False) - self.backtrace_cb = gtk.CheckButton(_("I agree with submitting the backtrace")) - self.backtrace_cb.connect("toggled", self.on_bt_toggled, page) - self.assistant.insert_page(page, PAGE_BACKTRACE_APPROVAL) - self.pdict_add_page(page, PAGE_BACKTRACE_APPROVAL) - self.assistant.set_page_type(page, gtk.ASSISTANT_PAGE_CONTENT) - self.assistant.set_page_title(page, _("Approve the backtrace")) - page.pack_start(hbox_bt) - page.pack_start(self.backtrace_cb, expand=False, fill=False) - page.show_all() - - def prepare_page_3(self): - page = gtk.VBox(spacing=10) - page.set_border_width(10) - #lbl_default_info = gtk.Label() - #lbl_default_info.set_line_wrap(True) - #lbl_default_info.set_alignment(0.0, 0.0) - #lbl_default_info.set_justify(gtk.JUSTIFY_FILL) - #lbl_default_info.set_size_request(600, -1) - #page.pack_start(lbl_default_info, expand=False, fill=True) - details_hbox = gtk.HBox() - details_hbox.set_border_width(10) - details_alignment = gtk.Alignment() - details_vbox = gtk.VBox(spacing=10) - details_hbox.pack_start(details_vbox) - details_hbox.pack_start(details_alignment, expand=False, padding=30) - - # how to reproduce - howto_vbox = gtk.VBox(spacing=5) - howto_lbl = gtk.Label(_("How did this crash happen (step-by-step)? " - "How would you reproduce it?")) - howto_lbl.set_alignment(0.0, 0.0) - howto_lbl.set_justify(gtk.JUSTIFY_FILL) - self.howto_tev = gtk.TextView() - self.howto_tev.set_accepts_tab(False) - self.howto_tev.connect("focus-out-event", self.on_howto_focusout_cb) - howto_buff = gtk.TextBuffer() - howto_buff.set_text(HOW_TO_HINT_TEXT) - self.howto_tev.set_buffer(howto_buff) - howto_scroll_w = gtk.ScrolledWindow() - howto_scroll_w.add(self.howto_tev) - howto_scroll_w.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - howto_vbox.pack_start(howto_lbl, expand=False, fill=True) - howto_vbox.pack_start(howto_scroll_w) - - # comment - comment_vbox = gtk.VBox(spacing=5) - comment_lbl = gtk.Label(_("Are there any comments you would like to share " - "with the software maintainers?")) - comment_lbl.set_alignment(0.0, 0.0) - comment_lbl.set_justify(gtk.JUSTIFY_FILL) - self.comment_tev = gtk.TextView() - self.comment_tev.set_accepts_tab(False) - self.comment_tev.connect("focus-in-event", self.on_comment_focusin_cb) - self.comment_tev.connect("focus-out-event", self.on_comment_focusout_cb) - comment_scroll_w = gtk.ScrolledWindow() - comment_scroll_w.add(self.comment_tev) - comment_scroll_w.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - comment_vbox.pack_start(comment_lbl, expand=False, fill=True) - comment_vbox.pack_start(comment_scroll_w) - - details_vbox.pack_start(howto_vbox) - details_vbox.pack_start(comment_vbox) - self.assistant.insert_page(page, PAGE_EXTRA_INFO) - self.pdict_add_page(page, PAGE_EXTRA_INFO) - self.assistant.set_page_type(page, gtk.ASSISTANT_PAGE_CONTENT) - self.assistant.set_page_title(page, _("Provide additional details")) - self.assistant.set_page_complete(page, True) - tip_hbox = gtk.HBox() - tip_image = gtk.Image() - tip_lbl = gtk.Label("") - tip_lbl.set_alignment(0.0, 0.0) - tip_lbl.set_justify(gtk.JUSTIFY_FILL) - tip_lbl.set_markup(_("Tip: Your comments are not private. " - "Please watch what you say accordingly.")) - #tip_hbox.pack_start(tip_image) - tip_hbox.pack_start(tip_lbl, expand=False) - page.pack_start(details_hbox) - tip_alignment = gtk.Alignment() - #page.pack_start(tip_alignment, padding=10) - page.pack_start(tip_hbox, expand=False) - page.show_all() - - def prepare_page_4(self): - lines_in_table = {} - width, height = self.assistant.get_size() - def add_info_to_table(table, heading, text): - line = 0 - if table in lines_in_table: - line = lines_in_table[table] - - heading_lbl = gtk.Label() - heading_lbl.set_alignment(0.0, 0.0) - heading_lbl.set_justify(gtk.JUSTIFY_LEFT) - heading_lbl.set_markup("%s:" % heading) - table.attach(heading_lbl, 0, 1, line, line+1, - xoptions=gtk.FILL, yoptions=gtk.EXPAND|gtk.FILL, - xpadding=5, ypadding=5) - lbl = gtk.Label(text) - lbl.set_line_wrap(True) - lbl.set_size_request(width/4, -1) - lbl.set_alignment(0.0, 0.0) - lbl.set_justify(gtk.JUSTIFY_LEFT) - table.attach(lbl, 1, 2, line, line+1, - xoptions=gtk.FILL, yoptions=gtk.EXPAND|gtk.FILL, - xpadding=5, ypadding=5) - - lines_in_table[table] = line+1 - - page = gtk.VBox(spacing=20) - page.set_border_width(10) - self.assistant.insert_page(page, PAGE_CONFIRM) - self.pdict_add_page(page, PAGE_CONFIRM) - self.assistant.set_page_type(page, gtk.ASSISTANT_PAGE_CONFIRM) - self.assistant.set_page_title(page, _("Confirm and send the report")) - self.assistant.set_page_complete(page, True) - summary_lbl = gtk.Label(_("Below is a summary of your bug report. " - "Please click 'Apply' to submit it.")) - summary_lbl.set_alignment(0.0, 0.0) - summary_lbl.set_justify(gtk.JUSTIFY_FILL) - basic_details_lbl = gtk.Label() - basic_details_lbl.set_markup(_("Basic details")) - basic_details_lbl.set_alignment(0.0, 0.0) - basic_details_lbl.set_justify(gtk.JUSTIFY_FILL) - - summary_table_left = gtk.Table(rows=4, columns=2) - summary_table_right = gtk.Table(rows=4, columns=2) - # left table - add_info_to_table(summary_table_left, _("Component"), "%s" % self.report.get_component()) - add_info_to_table(summary_table_left, _("Package"), "%s" % self.report.getPackageName()) - add_info_to_table(summary_table_left, _("Executable"), "%s" % self.report.getExecutable()) - add_info_to_table(summary_table_left, _("Cmdline"), "%s" % self.report.get_cmdline()) - #right table - add_info_to_table(summary_table_right, _("Architecture"), "%s" % self.report.get_arch()) - add_info_to_table(summary_table_right, _("Kernel"), "%s" % self.report.get_kernel()) - add_info_to_table(summary_table_right, _("Release"),"%s" % self.report.get_release()) - add_info_to_table(summary_table_right, _("Reason"), "%s" % self.report.get_reason()) - - summary_hbox = gtk.HBox(spacing=5, homogeneous=True) - left_table_vbox = gtk.VBox() - left_table_vbox.pack_start(summary_table_left, expand=False, fill=False) - left_table_vbox.pack_start(gtk.Alignment()) - summary_hbox.pack_start(left_table_vbox, expand=False, fill=True) - summary_hbox.pack_start(summary_table_right, expand=False, fill=True) - - # backtrace - backtrace_lbl = gtk.Label() - backtrace_lbl.set_markup(_("Backtrace")) - backtrace_lbl.set_alignment(0.0, 0.5) - backtrace_lbl.set_justify(gtk.JUSTIFY_LEFT) - backtrace_show_btn = gtk.Button(_("Click to view...")) - backtrace_show_btn.connect("clicked", self.on_show_bt_clicked) - backtrace_hbox = gtk.HBox(homogeneous=True) - hb = gtk.HBox() - hb.pack_start(backtrace_lbl) - hb.pack_start(backtrace_show_btn, expand=False) - backtrace_hbox.pack_start(hb) - alignment = gtk.Alignment() - backtrace_hbox.pack_start(alignment) - - # steps to reporoduce - reproduce_lbl = gtk.Label() - reproduce_lbl.set_markup(_("Steps to reproduce:")) - reproduce_lbl.set_alignment(0.0, 0.0) - reproduce_lbl.set_justify(gtk.JUSTIFY_LEFT) - self.steps = gtk.Label() - self.steps.set_alignment(0.0, 0.0) - self.steps.set_justify(gtk.JUSTIFY_LEFT) - self.steps.set_line_wrap(True) - self.steps.set_line_wrap_mode(pango.WRAP_CHAR) - self.steps.set_size_request(int(DEFAULT_WIDTH*0.8), -1) - #self.steps_lbl.set_text("1. Fill in information about step 1.\n" - # "2. Fill in information about step 2.\n" - # "3. Fill in information about step 3.\n") - steps_aligned_hbox = gtk.HBox() - self.steps_hbox = gtk.HBox(spacing=10) - self.steps_hbox.pack_start(reproduce_lbl) - self.steps_hbox.pack_start(self.steps) - steps_aligned_hbox.pack_start(self.steps_hbox, expand=False) - steps_aligned_hbox.pack_start(gtk.Alignment()) - - # comments - comments_lbl = gtk.Label() - comments_lbl.set_markup(_("Comments:")) - comments_lbl.set_alignment(0.0, 0.0) - comments_lbl.set_justify(gtk.JUSTIFY_LEFT) - self.comments = gtk.Label(_("No comment provided!")) - self.comments.set_line_wrap(True) - self.comments.set_line_wrap_mode(pango.WRAP_CHAR) - self.comments.set_size_request(int(DEFAULT_WIDTH*0.8), -1) - comments_hbox = gtk.HBox(spacing=10) - comments_hbox.pack_start(comments_lbl) - comments_hbox.pack_start(self.comments) - comments_aligned_hbox = gtk.HBox() - comments_aligned_hbox.pack_start(comments_hbox, expand=False) - comments_aligned_hbox.pack_start(gtk.Alignment()) - - # pack all into the page - - summary_vbox = gtk.VBox(spacing=20) - summary_vbox.pack_start(summary_hbox, expand=False) - summary_vbox.pack_start(backtrace_hbox, expand=False) - summary_vbox.pack_start(steps_aligned_hbox, expand=False) - summary_vbox.pack_start(comments_aligned_hbox, expand=False) - summary_scroll = gtk.ScrolledWindow() - summary_scroll.set_shadow_type(gtk.SHADOW_NONE) - summary_scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) - scroll_viewport = gtk.Viewport() - scroll_viewport.set_shadow_type(gtk.SHADOW_NONE) - scroll_viewport.add(summary_vbox) - summary_scroll.add(scroll_viewport) - page.pack_start(summary_lbl, expand=False) - page.pack_start(basic_details_lbl, expand=False) - page.pack_start(summary_scroll) - page.show_all() - - def prepare_page_5(self): - page = gtk.VBox(spacing=20) - page.set_border_width(10) - self.assistant.insert_page(page, PAGE_REPORT_DONE) - self.pdict_add_page(page, PAGE_REPORT_DONE) - self.assistant.set_page_type(page, gtk.ASSISTANT_PAGE_SUMMARY) - self.assistant.set_page_title(page, _("Finished sending the bug report")) - bug_reports_lbl = gtk.Label() - bug_reports_lbl.set_alignment(0.0, 0.0) - bug_reports_lbl.set_justify(gtk.JUSTIFY_LEFT) - bug_reports_lbl.set_markup(_("Bug reports:")) - width, height = self.assistant.get_size() - self.bug_reports_vbox = gtk.VBox(spacing=5) - self.bug_reports_vbox.pack_start(bug_reports_lbl, expand=False) - page.pack_start(self.bug_reports_vbox) - page.show_all() - - def __del__(self): - log1("wizard: about to be deleted") - - def on_analyze_complete_cb(self, daemon, result, pBarWindow): - try: - gobject.source_remove(self.timer) - except: - pass - self.pBarWindow.hide() - if not result: - gui_error_message(_("Unable to get report!\nIs debuginfo missing?")) - return - self.result = result - # set the backtrace text - try: - self.backtrace_buff.set_text(self.result[FILENAME_BACKTRACE][CD_CONTENT]) - self.report_has_bt = True - except: - self.backtrace_buff.set_text(MISSING_BACKTRACE_TEXT) - self.backtrace_cb.set_active(True) - log1("Crash info doesn't contain a backtrace, is it disabled?") - - self.allow_send(self.backtrace_cb) - self.show() - - def hydrate(self, button=None, force=0): - if not force: - self.prepare_page_1() - self.prepare_page_2() - self.prepare_page_3() - self.prepare_page_4() - self.prepare_page_5() - self.updates = "" - # FIXME don't duplicate the code, move to function - #self.pBar.show() - self.pBarWindow.show_all() - self.timer = gobject.timeout_add(100, self.progress_update_cb) - - # show the report window with selected report - # when getReport is done it emits "analyze-complete" and on_analyze_complete_cb is called - # FIXME: does it make sense to change it to use callback rather then signal emitting? - try: - self.daemon.start_job("%s:%s" % (self.report.getUID(), self.report.getUUID()), force) - except Exception, ex: - # FIXME #3 dbus.exceptions.DBusException: org.freedesktop.DBus.Error.NoReply: Did not receive a reply - # do this async and wait for yum to end with debuginfoinstal - if self.timer: - gobject.source_remove(self.timer) - self.pBarWindow.hide() - gui_error_message(_("Error acquiring the report: %s" % ex)) - return - - def show(self): - self.assistant.show() - - -if __name__ == "__main__": - wiz = ReporterAssistant() - wiz.show() - gtk.main() diff --git a/src/Gui/CellRenderers.py b/src/Gui/CellRenderers.py deleted file mode 100644 index fe17b3ed..00000000 --- a/src/Gui/CellRenderers.py +++ /dev/null @@ -1,65 +0,0 @@ -import gtk - -class CellTextView(gtk.TextView, gtk.CellEditable): - - __gtype_name__ = "CellTextView" - - def do_editing_done(self, *args): - self.remove_widget() - - def do_remove_widget(self, *args): - pass - - def do_start_editing(self, *args): - pass - - def get_text(self): - text_buffer = self.get_buffer() - bounds = text_buffer.get_bounds() - return text_buffer.get_text(*bounds) - - def set_text(self, text): - self.get_buffer().set_text(text) - - -class MultilineCellRenderer(gtk.CellRendererText): - - __gtype_name__ = "MultilineCellRenderer" - - def __init__(self): - gtk.CellRendererText.__init__(self) - self._in_editor_menu = False - self.old_text = "" - - def _on_editor_focus_out_event(self, editor, event): - if self._in_editor_menu: return - editor.remove_widget() - self.emit("edited", editor.get_data("path"), editor.get_text()) - - def _on_editor_key_press_event(self, editor, event): - if event.state & (gtk.gdk.SHIFT_MASK | gtk.gdk.CONTROL_MASK): return - if event.keyval == gtk.keysyms.Escape: - editor.set_text(self.old_text) - editor.remove_widget() - self.emit("editing-canceled") - - def _on_editor_populate_popup(self, editor, menu): - self._in_editor_menu = True - def on_menu_unmap(menu, self): - self._in_editor_menu = False - menu.connect("unmap", on_menu_unmap, self) - - def do_start_editing(self, event, widget, path, bg_area, cell_area, flags): - editor = CellTextView() - editor.modify_font(self.props.font_desc) - self.old_text = self.props.text - editor.set_text(self.props.text) - editor.set_size_request(cell_area.width, cell_area.height) - editor.set_border_width(min(self.props.xpad, self.props.ypad)) - editor.set_data("path", path) - editor.connect("focus-out-event", self._on_editor_focus_out_event) - editor.connect("key-press-event", self._on_editor_key_press_event) - editor.connect("populate-popup", self._on_editor_populate_popup) - editor.show() - return editor - diff --git a/src/Gui/ConfBackend.py b/src/Gui/ConfBackend.py deleted file mode 100644 index d3def183..00000000 --- a/src/Gui/ConfBackend.py +++ /dev/null @@ -1,246 +0,0 @@ -from abrt_utils import _, log, log1, log2 - -# Doc on Gnome keyring API: -# http://library.gnome.org/devel/gnome-keyring/stable/ -# Python bindings are in gnome-python2-desktop package - -try: - import gnomekeyring as gkey -except ImportError, e: - gkey = None - -# Exceptions -class ConfBackendInitError(Exception): - def __init__(self, msg): - Exception.__init__(self) - self.what = msg - - def __str__(self): - return self.what - -class ConfBackendSaveError(Exception): - def __init__(self, msg): - Exception.__init__(self) - self.what = msg - - def __str__(self): - return self.what - -class ConfBackendLoadError(Exception): - def __init__(self, msg): - Exception.__init__(self) - self.what = msg - - def __str__(self): - return self.what - - -class ConfBackend(object): - def __init__(self): - pass - - def save(self, name, settings): - """ Default save method has to be implemented in derived class """ - raise NotImplementedError - - def load(self, name): - """ Default load method has to be implemented in derived class """ - raise NotImplementedError - - -# We use Gnome keyring in the following way: -# we store passwords for each plugin in a key named "abrt:". -# The value of the key becomes the value of "Password" setting. -# Other settings (if plugin has them) are stored as attributes of this key. -# -# Example: Key "abrt:Bugzilla" with bugzilla password as value, and with attributes: -# -# Application: abrt -# AbrtPluginInfo: Bugzilla -# NoSSLVerify: yes -# Login: user@host.com -# BugzillaURL: https://host.with.bz.com/ -# -# Attributes "Application" and "AbrtPluginInfo" are special, they are used -# for efficient key retrieval via keyring API find_items_sync() function. - -g_default_key_ring = None - -class ConfBackendGnomeKeyring(ConfBackend): - def __init__(self): - global g_default_key_ring - - ConfBackend.__init__(self) - if g_default_key_ring: - return - if not gkey or not gkey.is_available(): - raise ConfBackendInitError(_("Cannot connect to the Gnome Keyring daemon.")) - try: - g_default_key_ring = gkey.get_default_keyring_sync() - except: - # could happen if keyring daemon is running, but we run gui under - # user who is not the owner of the running session - using su - raise ConfBackendInitError(_("Cannot get the default keyring.")) - - def save(self, name, settings): - settings_tmp = settings.copy() - settings_tmp["Application"] = "abrt" - settings_tmp["AbrtPluginInfo"] = name - - # delete all keyring items containg "AbrtPluginInfo":"", - # so we always have only 1 item per plugin - try: - item_list = gkey.find_items_sync(gkey.ITEM_GENERIC_SECRET, { "AbrtPluginInfo": str(name) }) - for item in item_list: - log2("found old keyring item: ring:'%s' item_id:%s attrs:%s", item.keyring, item.item_id, str(item.attributes)) - log2("deleting it from keyring '%s'", g_default_key_ring) - gkey.item_delete_sync(g_default_key_ring, item.item_id) - except gkey.NoMatchError: - # nothing found - pass - except gkey.DeniedError: - raise ConfBackendSaveError(_("Access to gnome-keyring has been denied, plugins settings will not be saved.")) - # if plugin has a "Password" setting, we handle it specially: in keyring, - # it is stored as item.secret, not as one of attributes - password = "" - if "Password" in settings_tmp: - password = settings_tmp["Password"] - del settings_tmp["Password"] - # store new settings for this plugin as one keyring item - try: - gkey.item_create_sync(g_default_key_ring, - gkey.ITEM_GENERIC_SECRET, - "abrt:%s" % name, # display_name - settings_tmp, # attrs - password, # secret - True) - except gkey.DeniedError: - raise ConfBackendSaveError(_("Access to gnome-keyring has been denied, plugins settings will not be saved.")) - - def load(self, name): - item_list = None - #FIXME: make this configurable - # this actually makes GUI to ask twice per every plugin - # which have it's settings stored in keyring - attempts = 2 - while attempts: - try: - log2("looking for keyring items with 'AbrtPluginInfo:%s' attr", str(name)) - item_list = gkey.find_items_sync(gkey.ITEM_GENERIC_SECRET, {"AbrtPluginInfo":str(name)}) - for item in item_list: - # gnome keyring is weeeeird. why display_name, type, mtime, ctime - # aren't available in find_items_sync() results? why we need to - # get them via additional call, item_get_info_sync()? - # internally, item has GNOME_KEYRING_TYPE_FOUND type, - # and info has GNOME_KEYRING_TYPE_ITEM_INFO type. - # why not use the same type for both? - # - # and worst of all, this information took four hours of googling... - # - #info = gkey.item_get_info_sync(item.keyring, item.item_id) - log2("found keyring item: ring:'%s' item_id:%s attrs:%s", # "secret:'%s' display_name:'%s'" - item.keyring, item.item_id, str(item.attributes) #, item.secret, info.get_display_name() - ) - except gkey.NoMatchError: - # nothing found - pass - except gkey.DeniedError: - attempts -= 1 - log2("gk-authorization has failed %i time(s)", 2-attempts) - if attempts == 0: - # we tried 2 times, so giving up the authorization - raise ConfBackendLoadError(_("Access to gnome-keyring has been denied, cannot load the settings for %s!" % name)) - continue - break - - if item_list: - retval = item_list[0].attributes.copy() - retval["Password"] = item_list[0].secret - return retval - return {} - - # This routine loads setting for all plugins. It doesn't need plugin name. - # Thus we can avoid talking to abrtd just in order to get plugin names. - def load_all(self): - retval = {} - item_list = {} - - # UGLY compat cludge for users who has saved items without "Application" attr - # (abrt <= 1.0.3 was saving those) - item_ids = gkey.list_item_ids_sync(g_default_key_ring) - log2("all keyring item ids:%s", item_ids) - for item_id in item_ids: - info = gkey.item_get_info_sync(g_default_key_ring, item_id) - attrs = gkey.item_get_attributes_sync(g_default_key_ring, item_id) - log2("keyring item %s: attrs:%s", item_id, str(attrs)) - if "AbrtPluginInfo" in attrs: - if not "Application" in attrs: - log2("updating old-style keyring item") - attrs["Application"] = "abrt" - try: - gkey.item_set_attributes_sync(g_default_key_ring, item_id, attrs) - except: - log2("error updating old-style keyring item") - plugin_name = attrs["AbrtPluginInfo"] - # If plugin has a "Password" setting, we handle it specially: in keyring, - # it is stored as item.secret, not as one of attributes - if info.get_secret(): - attrs["Password"] = info.get_secret() - # avoiding sending useless duplicate info over dbus... - del attrs["AbrtPluginInfo"] - try: - del attrs["Application"] - except: - pass - retval[plugin_name] = attrs; - # end of UGLY compat cludge - - try: - log2("looking for keyring items with 'Application:abrt' attr") - item_list = gkey.find_items_sync(gkey.ITEM_GENERIC_SECRET, { "Application": "abrt" }) - except gkey.NoMatchError: - # nothing found - pass - except gkey.DeniedError: - raise ConfBackendLoadError(_("Access to gnome-keyring has been denied, cannot load settings.")) - - for item in item_list: - # gnome keyring is weeeeird. why display_name, type, mtime, ctime - # aren't available in find_items_sync() results? why we need to - # get them via additional call, item_get_info_sync()? - # internally, item has GNOME_KEYRING_TYPE_FOUND type, - # and info has GNOME_KEYRING_TYPE_ITEM_INFO type. - # why not use the same type for both? - # - # and worst of all, this information took four hours of googling... - # - #info = gkey.item_get_info_sync(item.keyring, item.item_id) - log2("found keyring item: ring:%s item_id:%s attrs:%s", # "secret:%s display_name:'%s'" - item.keyring, item.item_id, str(item.attributes) #, item.secret, info.get_display_name() - ) - attrs = item.attributes.copy() - if "AbrtPluginInfo" in attrs: - plugin_name = attrs["AbrtPluginInfo"] - # If plugin has a "Password" setting, we handle it specially: in keyring, - # it is stored as item.secret, not as one of attributes - if item.secret: - attrs["Password"] = item.secret - # avoiding sending useless duplicate info over dbus... - del attrs["AbrtPluginInfo"] - try: - del attrs["Application"] - except: - pass - retval[plugin_name] = attrs - return retval - - -# Rudimentary backend factory - -currentConfBackend = None - -def getCurrentConfBackend(): - global currentConfBackend - if not currentConfBackend: - currentConfBackend = ConfBackendGnomeKeyring() - return currentConfBackend diff --git a/src/Gui/Makefile.am b/src/Gui/Makefile.am deleted file mode 100644 index 002b3bc7..00000000 --- a/src/Gui/Makefile.am +++ /dev/null @@ -1,33 +0,0 @@ -bin_SCRIPTS = abrt-gui - -PYTHON_FILES = CCDBusBackend.py CCDumpList.py CCDump.py CC_gui_functions.py \ - CCReporterDialog.py abrt_utils.py \ - CCMainWindow.py CellRenderers.py ABRTExceptions.py \ - SettingsDialog.py ABRTPlugin.py PluginList.py PluginSettingsUI.py \ - PluginsSettingsDialog.py ConfBackend.py CReporterAssistant.py - -GLADE_FILES = ccgui.glade report.glade settings.glade dialogs.glade \ - settings_wizard.glade progress_window.glade - -pkgdata_PYTHON = $(PYTHON_FILES) -pkgdata_DATA = $(GLADE_FILES) - -@INTLTOOL_DESKTOP_RULE@ - -desktopdir = $(datadir)/applications -desktop_in_files = abrt.desktop.in - -desktop_DATA = $(desktop_in_files:.desktop.in=.desktop) - -EXTRA_DIST = $(PYTHON_FILES) $(GLADE_FILES) abrt-gui $(desktop_in_files) - -CLEANFILES := $(notdir $(wildcard *~)) $(notdir $(wildcard *\#)) $(notdir $(wildcard \.\#*)) $(notdir $(wildcard *.pyc)) - -install-exec-hook: - for b in $(bin_SCRIPTS); do \ - sed 's:/usr/share:$(datadir):g' -i $(DESTDIR)$(bindir)/$$b || exit $$?; \ - sed 's:VERSION:@VERSION@:g' -i $(DESTDIR)$(bindir)/$$b || exit $$?; \ - done - -install-data-hook: - sed 's:@VER@:$(VERSION):g' -i $(DESTDIR)$(pkgdatadir)/ccgui.glade || exit $$? diff --git a/src/Gui/PluginList.py b/src/Gui/PluginList.py deleted file mode 100644 index f93e64e4..00000000 --- a/src/Gui/PluginList.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -import CCDBusBackend -from ABRTPlugin import PluginInfo, PluginSettings -from abrt_utils import _, log, log1, log2 - -class PluginInfoList(list): - """Class to store list of PluginInfos""" - def __init__(self,dbus_manager=None): - list.__init__(self) - self.dm = dbus_manager - - def load(self): - if self.dm: - rows = self.dm.getPluginsInfo() - for plugin_name in rows: - row = rows[plugin_name] - entry = PluginInfo() - for attr_name in row: - log2("PluginInfoList: adding %s[%s]:%s", plugin_name, attr_name, row[attr_name]) - entry.__dict__[attr_name] = row[attr_name] - daemon_settings = self.dm.getPluginSettings(plugin_name) - entry.load_daemon_settings(daemon_settings) - self.append(entry) - else: - log("PluginInfoList: db == None") - - def getReporterByName(self, name): - try: - return [x for x in self if x["Name"] == name and x.Type == "Reporter" ][0] - except: - # if such reporter doesnt't exist return None - return None - - def getEnabledPlugins(self): - return [x for x in self if x["Enabled"] == 'yes'] - - def getActionPlugins(self): - return [x for x in self if x["Enabled"] == 'yes' and x["Type"] == 'Action'] - - def getDatabasePlugins(self): - return [x for x in self if x["Enabled"] == 'yes' and x["Type"] == 'Database'] - - def getAnalyzerPlugins(self): - return [x for x in self if x["Enabled"] == 'yes' and x["Type"] == 'Analyzer'] - - def getReporterPlugins(self): - return [x for x in self if x["Enabled"] == 'yes' and x["Type"] == 'Reporter'] - - def getReporterPluginsSettings(self): - reporters_settings = {} - for plugin in self.getReporterPlugins(): - reporters_settings[str(plugin)] = plugin.Settings - return reporters_settings - - -__PFList = None -__PFList_dbmanager = None - -def getPluginInfoList(dbmanager,refresh=None): - global __PFList - global __PFList_dbmanager - - if __PFList == None or refresh or __PFList_dbmanager != dbmanager: - __PFList = PluginInfoList(dbus_manager=dbmanager) - __PFList.load() - __PFList_dbmanager = dbmanager - return __PFList - -__PFList = None diff --git a/src/Gui/PluginSettingsUI.py b/src/Gui/PluginSettingsUI.py deleted file mode 100644 index db4c92de..00000000 --- a/src/Gui/PluginSettingsUI.py +++ /dev/null @@ -1,91 +0,0 @@ -import gtk -from abrt_utils import _, log, log1, log2 - -class PluginSettingsUI(): - def __init__(self, pluginfo, parent=None): - #print "Init PluginSettingsUI" - self.plugin_name = pluginfo.Name - self.Settings = pluginfo.Settings - self.pluginfo = pluginfo - self.plugin_gui = None - - if pluginfo.getGUI(): - self.plugin_gui = gtk.Builder() - self.plugin_gui.add_from_file(pluginfo.getGUI()) - self.dialog = self.plugin_gui.get_object("PluginDialog") - if not self.dialog: - raise Exception(_("Cannot find PluginDialog widget in the UI description!")) - self.dialog.set_title("%s" % pluginfo.getName()) - if parent: - self.dialog.set_transient_for(parent) - else: - # we shouldn't get here, but just to be safe - log1("No UI for plugin %s" % pluginfo) - raise Exception(_("No UI for the plugin %s, this is probably a bug.\n" - "Please report it at " - "" - "https://fedorahosted.org/abrt/newticket") % pluginfo) - return - - if parent: - self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.dialog.set_transient_for(parent) - self.dialog.set_modal(True) - - def on_show_pass_toggled(self, button, entry=None): - if entry: - entry.set_visibility(button.get_active()) - - def hydrate(self): - if self.plugin_gui: - if self.pluginfo.Enabled == "yes": - if self.Settings: - #print "Hydrating %s" % self.plugin_name - for key, value in self.Settings.iteritems(): - #print "%s:%s" % (key,value) - widget = self.plugin_gui.get_object("conf_%s" % key) - if type(widget) == gtk.Entry: - widget.set_text(value) - if widget.get_visibility() == False: - # if we find toggle button called the same name as entry and entry has - # visibility set to False, connect set_visible to it - # coz I guess it's toggle for revealing the password - button = self.plugin_gui.get_object("cb_%s" % key) - if type(button) == gtk.CheckButton: - button.connect("toggled", self.on_show_pass_toggled, widget) - elif type(widget) == gtk.CheckButton: - widget.set_active(value == "yes") - elif type(widget) == gtk.ComboBox: - print _("Combo box is not implemented") - else: - #print "Plugin %s has no configuration." % self.plugin_name - pass - else: - #print "Plugin %s is disabled." % self.plugin_name - pass - - else: - print _("Nothing to hydrate!") - - def dehydrate(self): - #print "dehydrating %s" % self.pluginfo.getName() - if self.Settings: - for key in self.Settings.keys(): - #print key - #print "%s:%s" % (key,value) - widget = self.plugin_gui.get_object("conf_%s" % key) - if type(widget) == gtk.Entry: - self.Settings[key] = widget.get_text() - elif type(widget) == gtk.CheckButton: - if widget.get_active(): - self.Settings[key] = "yes" - else: - self.Settings[key] = "no" - elif type(widget) == gtk.ComboBox: - print _("Combo box is not implemented") - - def destroy(self): - self.dialog.destroy() - - def run(self): - return self.dialog.run() diff --git a/src/Gui/PluginsSettingsDialog.py b/src/Gui/PluginsSettingsDialog.py deleted file mode 100644 index 2951320e..00000000 --- a/src/Gui/PluginsSettingsDialog.py +++ /dev/null @@ -1,195 +0,0 @@ -import sys -import gtk -from PluginList import getPluginInfoList, PluginInfoList -from CC_gui_functions import * -from PluginSettingsUI import PluginSettingsUI -from ABRTPlugin import PluginSettings, PluginInfo -from abrt_utils import _, log, log1, log2 - - -class PluginsSettingsDialog: - def __init__(self, parent, daemon): - #print "Settings dialog init" - self.ccdaemon = daemon - - self.builder = gtk.Builder() - builderfile = "%s%ssettings.glade" % (sys.path[0], "/") - #print builderfile - try: - self.builder.add_from_file(builderfile) - except Exception, e: - print e - self.window = self.builder.get_object("wPluginsSettings") - if not self.window: - raise Exception(_("Cannot load the GUI description for SettingsDialog!")) - - if parent: - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.window.set_transient_for(parent) - self.window.set_modal(True) - - self.pluginlist = self.builder.get_object("tvSettings") # a TreeView - # cell_text, toggle_active, toggle_visible, group_name_visible, color, plugin - self.pluginsListStore = gtk.TreeStore(str, bool, bool, bool, str, object) - # set filter - modelfilter = self.pluginsListStore.filter_new() - modelfilter.set_visible_func(self.filter_plugins, None) - self.pluginlist.set_model(modelfilter) - - # Create/configure columns and add them to pluginlist - # column "name" has two kind of cells: - column = gtk.TreeViewColumn(_("Name")) - # cells for individual plugins (white) - cell_name = gtk.CellRendererText() - column.pack_start(cell_name, True) - column.set_attributes(cell_name, markup=0, visible=2) # show 0th field (plugin name) from data items if 2th field is true - # cells for plugin types (gray) - cell_plugin_type = gtk.CellRendererText() - column.pack_start(cell_plugin_type, True) - column.add_attribute(cell_plugin_type, "visible", 3) - column.add_attribute(cell_plugin_type, "markup", 0) - column.add_attribute(cell_plugin_type, "cell_background", 4) - # column "name" is ready, insert - column.set_resizable(True) - self.pluginlist.append_column(column) - -# "Enable" toggle column is disabled for now. Grep for PLUGIN_DYNAMIC_LOAD_UNLOAD -# column = gtk.TreeViewColumn(_("Enabled")) -# # column "enabled" has one kind of cells: -# cell_toggle_enable = gtk.CellRendererToggle() -# cell_toggle_enable.set_property("activatable", True) -# cell_toggle_enable.connect("toggled", self.on_enabled_toggled, self.pluginsListStore) -# column.pack_start(cell_toggle_enable, True) -# column.add_attribute(cell_toggle_enable, "active", 1) -# column.add_attribute(cell_toggle_enable, "visible", 2) -# self.pluginlist.append_column(column) - - #connect signals - self.pluginlist.connect("cursor-changed", self.on_tvDumps_cursor_changed) - self.builder.get_object("bConfigurePlugin").connect("clicked", self.on_bConfigurePlugin_clicked, self.pluginlist) - self.builder.get_object("bClose").connect("clicked", self.on_bClose_clicked) - self.builder.get_object("bConfigurePlugin").set_sensitive(False) - -# "Enable" toggle column is disabled for now. Grep for PLUGIN_DYNAMIC_LOAD_UNLOAD -# def on_enabled_toggled(self,cell, path, model): -# plugin = model[path][model.get_n_columns()-1] -# if plugin: -# if model[path][1]: -# #print "self.ccdaemon.UnRegisterPlugin(%s)" % (plugin.getName()) -# self.ccdaemon.unRegisterPlugin(plugin.getName()) -# # FIXME: create class plugin and move this into method Plugin.Enable() -# plugin.Enabled = "no" -# plugin.Settings = None -# else: -# #print "self.ccdaemon.RegisterPlugin(%s)" % (model[path][model.get_n_columns()-1]) -# self.ccdaemon.registerPlugin(plugin.getName()) -# # FIXME: create class plugin and move this into method Plugin.Enable() -# plugin.Enabled = "yes" -# default_settings = self.ccdaemon.getPluginSettings(plugin.getName()) -# plugin.Settings = PluginSettings() -# plugin.Settings.load(plugin.getName(), default_settings) -# model[path][1] = not model[path][1] - - def filter_plugins(self, model, miter, data): - return True - - def hydrate(self): - #print "settings hydrate" - self.pluginsListStore.clear() - try: - #pluginlist = getPluginInfoList(self.ccdaemon, refresh=True) - # don't force refresh as it will overwrite settings if g-k is not available - pluginlist = getPluginInfoList(self.ccdaemon) - except Exception, e: - log("Error while loading plugins info: %s", e) - #gui_error_message("Error while loading plugins info, please check if abrt daemon is running\n %s" % e) - return - plugin_rows = {} - group_empty = {} - for plugin_type in PluginInfo.types.keys(): - it = self.pluginsListStore.append(None, - # cell_text, toggle_active, toggle_visible, group_name_visible, color, plugin - ["%s" % PluginInfo.types[plugin_type], 0, 0, 1, "gray", None]) - plugin_rows[plugin_type] = it - group_empty[plugin_type] = it - for entry in pluginlist: - if entry.Description: - text = "%s\n%s" % (entry.getName(), entry.Description) - else: - # non-loaded plugins have empty description - text = "%s" % entry.getName() - plugin_type = entry.getType() - self.pluginsListStore.append(plugin_rows[plugin_type], - # cell_text, toggle_active, toggle_visible, group_name_visible, color, plugin - [text, entry.Enabled == "yes", 1, 0, "white", entry]) - if group_empty.has_key(plugin_type): - del group_empty[plugin_type] - # rhbz#560971 "Don't show empty 'Not loaded plugins' section" - # don't show any empty groups - for it in group_empty.values(): - self.pluginsListStore.remove(it) - - self.pluginlist.expand_all() - - def dehydrate(self): - # we have nothing to save, plugin's does the work - pass - - def show(self): - self.window.show() - #if result == gtk.RESPONSE_APPLY: - # self.dehydrate() - #self.window.destroy() - #return result - - def on_bConfigurePlugin_clicked(self, button, pluginview): - pluginsListStore, path = pluginview.get_selection().get_selected_rows() - if not path: - gui_info_dialog(_("Please select a plugin from the list to edit it's options."), parent=self.window) - return - # this should work until we keep the row object in the last position - pluginfo = pluginsListStore.get_value(pluginsListStore.get_iter(path[0]), pluginsListStore.get_n_columns()-1) - if pluginfo: - try: - ui = PluginSettingsUI(pluginfo, parent=self.window) - except Exception, e: - gui_error_message(_("Error while opening the plugin settings UI: \n\n%s" % e)) - return - ui.hydrate() - response = ui.run() - if response == gtk.RESPONSE_APPLY: - ui.dehydrate() - if pluginfo.Settings: - try: - pluginfo.save_settings_on_client_side() - # FIXME: do we need to call this? all reporters set their settings - # when Report() is called - self.ccdaemon.setPluginSettings(pluginfo.getName(), pluginfo.Settings) - except Exception, e: - gui_error_message(_("Cannot save plugin settings:\n %s" % e)) - #for key, val in pluginfo.Settings.iteritems(): - # print "%s:%s" % (key, val) - elif response == gtk.RESPONSE_CANCEL: - pass - else: - log("unknown response from settings dialog:%d", response) - ui.destroy() - - def on_bClose_clicked(self, button): - self.window.destroy() - - def on_tvDumps_cursor_changed(self, treeview): - pluginsListStore, path = treeview.get_selection().get_selected_rows() - if not path: - self.builder.get_object("lDescription").set_label("No description") - return - # this should work until we keep the row object in the last position - pluginfo = pluginsListStore.get_value(pluginsListStore.get_iter(path[0]), pluginsListStore.get_n_columns()-1) - if pluginfo: - self.builder.get_object("lPluginAuthor").set_text(pluginfo.Email) - self.builder.get_object("lPluginVersion").set_text(pluginfo.Version) - self.builder.get_object("lPluginWebSite").set_text(pluginfo.WWW) - self.builder.get_object("lPluginName").set_text(pluginfo.Name) - self.builder.get_object("lPluginDescription").set_text(pluginfo.Description) - # print (pluginfo.Enabled == "yes" and pluginfo.GTKBuilder != "") - self.builder.get_object("bConfigurePlugin").set_sensitive(pluginfo != None and pluginfo.Enabled == "yes" and pluginfo.GTKBuilder != "") diff --git a/src/Gui/SettingsDialog.py b/src/Gui/SettingsDialog.py deleted file mode 100644 index a69a68ec..00000000 --- a/src/Gui/SettingsDialog.py +++ /dev/null @@ -1,242 +0,0 @@ -import sys -import gtk -from PluginList import getPluginInfoList -from CC_gui_functions import * -#from PluginSettingsUI import PluginSettingsUI -from abrt_utils import _, log, log1, log2 - - -#FIXME: create a better struct, to automatize hydrate/dehydrate process -settings_dict = { "Common": - {"OpenGPGCheck":bool, - "Database":object, - "EnabledPlugins": list, - "BlackList": list, - "MaxCrashReportsSize": int, - "OpenGPGPublicKeys": list, - }, - } - -class SettingsDialog: - def __init__(self, parent, daemon): - builderfile = "%s%ssettings.glade" % (sys.path[0],"/") - self.ccdaemon = daemon - self.builder = gtk.Builder() - self.builder.add_from_file(builderfile) - self.window = self.builder.get_object("wGlobalSettings") - self.builder.get_object("bSaveSettings").connect("clicked", self.on_ok_clicked) - self.builder.get_object("bCancelSettings").connect("clicked", self.on_cancel_clicked) - self.builder.get_object("bAddCronJob").connect("clicked", self.on_bAddCronJob_clicked) - - # action plugin list for Cron tab - self.actionPluginsListStore = gtk.ListStore(str, object) - self.actionPluginsListStore.append([_("Select plugin"), None]) - # database plugin list - self.databasePluginsListStore = gtk.ListStore(str, object) - self.databasePluginsListStore.append([_("Select database backend"), None]) - - self.dbcombo = self.builder.get_object("cbDatabase") - self.dbcombo.set_model(self.databasePluginsListStore) - cell = gtk.CellRendererText() - self.dbcombo.pack_start(cell) - self.dbcombo.add_attribute(cell, "markup", 0) - # blacklist edit - self.builder.get_object("bEditBlackList").connect("clicked", self.on_blacklistEdit_clicked) - - self.builder.get_object("bOpenGPGPublicKeys").connect("clicked", self.on_GPGKeysEdit_clicked) - self.builder.get_object("bAddAction").connect("clicked", self.on_bAddAction_clicked) - # AnalyzerActionsAndReporters - self.analyzerPluginsListStore = gtk.ListStore(str, object) - self.analyzerPluginsListStore.append([_("Select plugin"), None]) - # GPG keys - self.wGPGKeys = self.builder.get_object("wGPGKeys") - self.GPGKeysListStore = gtk.ListStore(str) - self.tvGPGKeys = self.builder.get_object("tvGPGKeys") - self.tvGPGKeys.set_model(self.GPGKeysListStore) - self.builder.get_object("bCancelGPGKeys").connect("clicked", self.on_bCancelGPGKeys_clicked) - self.builder.get_object("bSaveGPGKeys").connect("clicked", self.on_bSaveGPGKeys_clicked) - - gpg_column = gtk.TreeViewColumn() - cell = gtk.CellRendererText() - gpg_column.pack_start(cell) - gpg_column.add_attribute(cell, "text", 0) - self.tvGPGKeys.append_column(gpg_column) - - def filter_settings(self, model, miter, data): - return True - - def hydrate(self): - try: - self.settings = self.ccdaemon.getSettings() - except Exception, e: - # FIXME: this should be error gui message! - print e - try: - self.pluginlist = getPluginInfoList(self.ccdaemon, refresh=True) - except Exception, e: - raise Exception("Comunication with daemon has failed, have you restarted the daemon after update?") - - ## hydrate cron jobs: - for key,val in self.settings["Cron"].iteritems(): - # actions are separated by ',' - actions = val.split(',') - self.settings["Cron"][key] = actions - for plugin in self.pluginlist.getActionPlugins(): - it = self.actionPluginsListStore.append([plugin.getName(), plugin]) - for key,val in self.settings["Cron"].iteritems(): - if plugin.getName() in val: - cron_job = (key,it) - self.add_CronJob(cron_job) - self.settings["Cron"][key].remove(plugin.getName()) - # hydrate common - common = self.settings["Common"] - # ensure that all expected keys exist: - if "OpenGPGCheck" not in common: - common["OpenGPGCheck"] = "no" # check unsigned pkgs too - ## gpgcheck - self.builder.get_object("cbOpenGPGCheck").set_active(common["OpenGPGCheck"] == 'yes') - ## database - for dbplugin in self.pluginlist.getDatabasePlugins(): - it = self.databasePluginsListStore.append([dbplugin.getName(), dbplugin]) - if common["Database"] == dbplugin.getName(): - self.dbcombo.set_active_iter(it) - ## MaxCrashSize - self.builder.get_object("sbMaxCrashReportsSize").set_value(float(common["MaxCrashReportsSize"])) - ## GPG keys - try: - self.builder.get_object("eOpenGPGPublicKeys").set_text(common["OpenGPGPublicKeys"]) - self.gpgkeys = common["OpenGPGPublicKeys"].split(',') - for gpgkey in self.gpgkeys: - self.GPGKeysListStore.append([gpgkey]) - except: - pass - - ## blacklist - self.builder.get_object("eBlacklist").set_text(common["BlackList"]) - # hydrate AnalyzerActionsAndReporters - AnalyzerActionsAndReporters = self.settings["AnalyzerActionsAndReporters"] - for analplugin in self.pluginlist.getAnalyzerPlugins(): - it = self.analyzerPluginsListStore.append([analplugin.getName(), analplugin]) - if analplugin.getName() in AnalyzerActionsAndReporters: - action = (AnalyzerActionsAndReporters[analplugin.getName()], it) - self.add_AnalyzerAction(action) - - def on_bCancelGPGKeys_clicked(self, button): - self.wGPGKeys.hide() - - def on_bSaveGPGKeys_clicked(self, button): - self.wGPGKeys.hide() - - def on_bAddGPGKey_clicked(self, button): - print "add GPG key" - - def on_bRemoveGPGKey_clicked(self, button): - print "add GPG key" - - def on_blacklistEdit_clicked(self, button): - print "edit blacklist" - - def on_GPGKeysEdit_clicked(self, button): - self.wGPGKeys.show() - - def on_ok_clicked(self, button): - self.dehydrate() - self.window.hide() - - def on_cancel_clicked(self, button): - self.window.hide() - - def on_remove_CronJob_clicked(self, button, job_hbox): - self.removeHBoxWihtChildren(job_hbox) - - def on_remove_Action_clicked(self, button, binding_hbox): - self.removeHBoxWihtChildren(binding_hbox) - - def removeHBoxWihtChildren(self, job_hbox): - job_hbox.get_parent().remove(job_hbox) - for child in job_hbox.get_children(): - child.destroy() - job_hbox.destroy() - - def add_CronJob(self, job=None): - hbox = gtk.HBox() - hbox.set_spacing(6) - time = gtk.Entry() - remove_image = gtk.Image() - remove_image.set_from_stock("gtk-remove",gtk.ICON_SIZE_MENU) - remove_button = gtk.Button() - remove_button.set_image(remove_image) - remove_button.set_tooltip_text(_("Remove this job")) - remove_button.connect("clicked", self.on_remove_CronJob_clicked, hbox) - plugins = gtk.ComboBox() - cell = gtk.CellRendererText() - - plugins.pack_start(cell) - plugins.add_attribute(cell, 'markup', 0) - plugins.set_model(self.actionPluginsListStore) - - if job: - time.set_text(job[0]) - plugins.set_active_iter(job[1]) - else: - plugins.set_active(0) - hbox.pack_start(plugins,True) - hbox.pack_start(time,True) - hbox.pack_start(remove_button,False) - self.builder.get_object("vbCronJobs").pack_start(hbox,False) - - hbox.show_all() - - def on_bAddCronJob_clicked(self, button): - self.add_CronJob() - print "add" - - def on_bEditAction_clicked(self, button, data=None): - print "edit action" - - def add_AnalyzerAction(self, action=None): - #print "add_AnalyzerAction" - hbox = gtk.HBox() - hbox.set_spacing(6) - action_list = gtk.Entry() - edit_actions = gtk.Button() - edit_actions.set_tooltip_text("Edit actions") - edit_image = gtk.Image() - edit_image.set_from_stock("gtk-edit", gtk.ICON_SIZE_MENU) - edit_actions.set_image(edit_image) - edit_actions.connect("clicked", self.on_bEditAction_clicked) - - remove_image = gtk.Image() - remove_image.set_from_stock("gtk-remove",gtk.ICON_SIZE_MENU) - remove_button = gtk.Button() - remove_button.set_image(remove_image) - remove_button.set_tooltip_text(_("Remove this action")) - remove_button.connect("clicked", self.on_remove_Action_clicked, hbox) - - reporters = gtk.ComboBox() - cell = gtk.CellRendererText() - reporters.pack_start(cell) - reporters.add_attribute(cell, 'markup', 0) - reporters.set_model(self.analyzerPluginsListStore) - - if action: - action_list.set_text(action[0]) - reporters.set_active_iter(action[1]) - else: - reporters.set_active(0) - - hbox.pack_start(reporters,True) - hbox.pack_start(action_list,True) - hbox.pack_start(edit_actions,False) - hbox.pack_start(remove_button,False) - self.builder.get_object("vbActions").pack_start(hbox,False) - hbox.show_all() - - def on_bAddAction_clicked(self, button): - self.add_AnalyzerAction() - - def dehydrate(self): - self.ccdaemon.setSettings(self.settings) - - def show(self): - self.window.show() diff --git a/src/Gui/abrt-gui b/src/Gui/abrt-gui deleted file mode 100755 index 059d8676..00000000 --- a/src/Gui/abrt-gui +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -if test x"$1" = x"--version"; then - echo "abrt-gui VERSION" - exit 0 -fi -export PYTHONPATH=/usr/share/abrt -export XLOGNAME=$LOGNAME -exec /usr/bin/python /usr/share/abrt/CCMainWindow.py "$@" diff --git a/src/Gui/abrt.desktop.in b/src/Gui/abrt.desktop.in deleted file mode 100644 index 99d7f8f8..00000000 --- a/src/Gui/abrt.desktop.in +++ /dev/null @@ -1,11 +0,0 @@ -[Desktop Entry] -Encoding=UTF-8 -_Name=Automatic Bug Reporting Tool -_Comment=View and report application crashes -Exec=abrt-gui -Icon=abrt -Terminal=false -Type=Application -Categories=System;X-Red-Hat-Base; -StartupNotify=true -X-Desktop-File-Install-Version=0.15 diff --git a/src/Gui/abrt.png b/src/Gui/abrt.png deleted file mode 100644 index dc24865e..00000000 Binary files a/src/Gui/abrt.png and /dev/null differ diff --git a/src/Gui/abrt_utils.py b/src/Gui/abrt_utils.py deleted file mode 100644 index f9c60b52..00000000 --- a/src/Gui/abrt_utils.py +++ /dev/null @@ -1,46 +0,0 @@ -import sys - -GETTEXT_PROGNAME = "abrt" -PROGNAME = "abrt-gui" -g_verbose = 0 - -import locale -import gettext - -_ = lambda x: gettext.lgettext(x) - -def init_logging(progname, v): - import gtk.glade - global PROGNAME, g_verbose - PROGNAME = progname - g_verbose = v - try: - locale.setlocale(locale.LC_ALL, "") - except locale.Error: - import os - os.environ['LC_ALL'] = 'C' - locale.setlocale(locale.LC_ALL, "") - gettext.bind_textdomain_codeset(GETTEXT_PROGNAME, locale.nl_langinfo(locale.CODESET)) - gettext.bindtextdomain(GETTEXT_PROGNAME, '/usr/share/locale') - gtk.glade.bindtextdomain(GETTEXT_PROGNAME, '/usr/share/locale') - gtk.glade.textdomain(GETTEXT_PROGNAME) - gettext.textdomain(GETTEXT_PROGNAME) - -def get_verbose_level(): - # Just importing g_verbose from another module doesn't work (why!?), - # need to use a function - return g_verbose - -def log(fmt, *args): - sys.stderr.write("%s: %s\n" % (PROGNAME, fmt % args)) - -def log1(fmt, *args): - if g_verbose >= 1: - sys.stderr.write("%s: %s\n" % (PROGNAME, fmt % args)) - -def log2(fmt, *args): - if g_verbose >= 2: - sys.stderr.write("%s: %s\n" % (PROGNAME, fmt % args)) - -def warn(fmt, *args): - sys.stderr.write("WARNING: %s: %s\n" % (PROGNAME, fmt % args)) diff --git a/src/Gui/ccgui.glade b/src/Gui/ccgui.glade deleted file mode 100644 index 7d46c298..00000000 --- a/src/Gui/ccgui.glade +++ /dev/null @@ -1,697 +0,0 @@ - - - - - - 5 - About ABRT - False - center-on-parent - abrt - dialog - False - ABRT - @VER@ - (C) 2009, 2010 Red Hat, Inc. - http://fedorahosted.org/abrt/ - This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. - Anton Arapov <aarapov@redhat.com> -Karel Klic <kklic@redhat.com> -Jiri Moskovcak <jmoskovc@redhat.com> -Nikola Pajkovsky <npajkovs@redhat.com> -Zdenek Prikryl <zprikryl@redhat.com> -Denys Vlasenko <dvlasenk@redhat.com> - translator-credits - Patrick Connelly <pcon@fedoraproject.org> -Lapo Calamandrei - -UI Design: -Máirín Duffy <duffy@redhat.com> - abrt - True - - - True - 2 - - - - - - True - end - - - False - end - 0 - - - - - - - Automatic Bug Reporting Tool - center - abrt - - - True - vertical - - - True - - - True - _File - True - - - True - - - gtk-quit - True - True - True - True - - - - - - - - - True - _Edit - True - - - True - - - True - Plugins - - - - - gtk-preferences - True - True - True - True - - - - - - - - - True - _Help - True - - - True - - - View log - True - - - - - gtk-about - True - True - True - True - - - - - - - - - False - 0 - - - - - True - True - 12 - vertical - - - True - True - automatic - automatic - in - - - True - True - True - True - 1 - - - - - False - False - - - - - False - never - automatic - in - - - True - queue - none - - - True - vertical - - - True - 10 - - - True - 5 - gtk-missing-image - 6 - - - False - False - 0 - - - - - True - 0 - True - - - 1 - - - - - False - False - 10 - 0 - - - - - True - 5 - - - True - 0 - 0 - 5 - <b>Bug Reports:</b> - True - - - False - 0 - - - - - True - 0 - 0 - True - - - 1 - - - - - False - False - 1 - - - - - True - - - True - 5 - 2 - 5 - 5 - - - True - 0 - 0 - <b>Latest Crash:</b> - True - - - GTK_FILL - - - - - - True - 0 - 0 - <b>Command:</b> - True - - - 1 - 2 - GTK_FILL - GTK_FILL - - - - - True - 0 - 0 - <b>User:</b> - True - - - 2 - 3 - GTK_FILL - - - - - - True - 0 - 0 - <b>Crash Count:</b> - True - - - 3 - 4 - GTK_FILL - - - - - - True - 0 - 0 - True - 30 - - - 1 - 2 - GTK_FILL - - - - - - True - 0 - 0 - True - 30 - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - - - True - 1.862645149230957e-09 - 0 - - - 1 - 2 - 2 - 3 - GTK_FILL - GTK_FILL - - - - - True - 0 - 0 - - - 1 - 2 - 3 - 4 - GTK_FILL - GTK_FILL - - - - - True - - - - - - 4 - 5 - - - - - True - - - - - - 1 - 2 - 4 - 5 - - - - - False - False - 5 - 0 - - - - - True - vertical - 5 - - - True - 0 - 0 - <b>Reason:</b> - True - - - False - False - 0 - - - - - True - 0 - 0 - True - 40 - - - 1 - - - - - True - 0 - 0 - <b>Comment:</b> - True - - - False - False - 2 - - - - - True - 0 - 0 - True - 40 - - - 3 - - - - - 1 - - - - - False - False - 2 - - - - - - - - - False - False - - - - - 1 - - - - - True - 10 - 5 - True - - - True - - - - - - 0 - - - - - gtk-delete - True - False - True - True - True - - - 1 - - - - - Copy to Clipboard - True - False - True - True - - - 2 - - - - - Report - True - False - True - True - - - 3 - - - - - False - False - 2 - - - - - True - - - - - - False - 10 - 3 - - - - - True - True - - - gtk-help - True - True - True - True - - - 10 - 0 - - - - - True - - - - - - 1 - - - - - True - - - - - - 2 - - - - - True - - - - - - 3 - - - - - gtk-close - True - True - True - True - - - 10 - 4 - - - - - False - 4 - - - - - True - - - - - - False - 10 - 5 - - - - - - - True - - - gtk-delete - True - True - True - True - - - - - Report - True - False - True - - - True - gtk-go-up - 1 - - - - - - diff --git a/src/Gui/dialogs.glade b/src/Gui/dialogs.glade deleted file mode 100644 index 1f251de5..00000000 --- a/src/Gui/dialogs.glade +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - 6 - Report done - True - center-on-parent - True - abrt - normal - False - - - True - vertical - 12 - - - True - 6 - 12 - - - True - 0 - 0 - gtk-dialog-info - 6 - - - False - False - 0 - - - - - - - - 1 - - - - - True - end - - - gtk-ok - True - True - True - True - - - False - False - 0 - - - - - False - end - 0 - - - - - - button1 - - - - 5 - Log - True - 450 - 260 - normal - False - - - True - vertical - 2 - - - True - True - automatic - automatic - - - True - True - False - - - - - 1 - - - - - True - end - - - gtk-close - True - True - True - True - - - False - False - 0 - - - - - False - end - 0 - - - - - - bClose - - - diff --git a/src/Gui/progress_window.glade b/src/Gui/progress_window.glade deleted file mode 100644 index af48ee55..00000000 --- a/src/Gui/progress_window.glade +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - 500 - 12 - Please wait... - True - center-on-parent - abrt - - - True - vertical - 12 - - - True - 0 - - - False - 0 - - - - - True - 0 - - - False - 1 - - - - - True - True - - - True - True - automatic - automatic - etched-in - - - True - True - - - - - - - True - Details - - - - - 2 - - - - - - diff --git a/src/Gui/report.glade b/src/Gui/report.glade deleted file mode 100644 index 2f79b39f..00000000 --- a/src/Gui/report.glade +++ /dev/null @@ -1,827 +0,0 @@ - - - - - - 5 - Automatic Bug Reporting Tool - True - abrt - normal - False - - - True - vertical - 2 - - - True - True - never - automatic - - - True - queue - none - - - True - vertical - 5 - - - True - 0 - in - - - True - 12 - - - True - - - True - vertical - True - - - True - 0 - <span fgcolor="blue">Package:</span> - True - - - 0 - - - - - True - 0 - <span fgcolor="blue">Component:</span> - True - - - 1 - - - - - True - 0 - <span fgcolor="blue">Executable:</span> - True - - - 2 - - - - - True - 0 - <span fgcolor="blue">Cmdline:</span> - True - - - 3 - - - - - False - False - 0 - - - - - True - vertical - True - - - True - 0 - 5 - N/A - True - 40 - - - 0 - - - - - True - 0 - 5 - N/A - True - 40 - - - 1 - - - - - True - 0 - 5 - N/A - True - 40 - - - 2 - - - - - True - 0 - 5 - N/A - True - 40 - - - 3 - - - - - 1 - - - - - True - vertical - True - - - True - 0 - <span fgcolor="blue">Architecture:</span> - True - - - 0 - - - - - True - 0 - <span fgcolor="blue">Kernel:</span> - True - - - 1 - - - - - True - 0 - <span fgcolor="blue">Release:</span> - True - - - 2 - - - - - True - 0 - <span fgcolor="blue">Reason:</span> - True - - - 3 - - - - - False - False - 2 - - - - - True - vertical - - - True - 0 - 5 - N/A - True - 40 - - - 0 - - - - - True - 0 - 5 - N/A - True - 40 - - - 1 - - - - - True - 0 - 5 - N/A - True - 40 - - - 2 - - - - - True - 0 - 5 - N/A - True - 40 - - - 3 - - - - - 3 - - - - - - - - - - - - False - False - 0 - - - - - True - 0 - none - - - True - 12 - - - True - vertical - - - True - True - automatic - automatic - - - 200 - True - True - False - - - - - 0 - - - - - True - - - I checked the backtrace and removed sensitive data (passwords, etc) - True - True - False - True - - - False - False - 0 - - - - - True - - - - - - 1 - - - - - False - False - 1 - - - - - - - - - True - 5 - <b>Backtrace</b> - True - - - - - 1 - - - - - True - 0 - none - - - True - 12 - - - True - vertical - 12 - - - True - vertical - 12 - - - True - 0 - none - - - True - - - True - True - automatic - automatic - - - True - True - word-char - False - - - - - - - - - True - <b>How to reproduce (in a few simple steps)</b> - True - - - - - 0 - - - - - True - 0 - none - - - True - - - True - True - automatic - automatic - - - True - True - word-char - False - - - - - - - - - True - <b>Comment</b> - True - - - - - 1 - - - - - 0 - - - - - - - - - - - - 2 - - - - - 0 - none - - - True - 12 - - - True - vertical - - - - - - - - - - True - <b>Attachments</b> - True - - - - - False - False - 3 - - - - - True - 0 - in - - - True - - - True - - - - - - 0 - - - - - True - gtk-dialog-warning - 6 - - - False - False - 1 - - - - - True - vertical - - - True - <b>Please fix the following problems:</b> - True - - - 0 - - - - - True - - True - True - - - 1 - - - - - 2 - - - - - True - - - - - - 3 - - - - - - - - - - False - False - 4 - - - - - - - - - 1 - - - - - True - end - - - Show log - True - True - True - - - False - False - 0 - True - - - - - gtk-cancel - True - True - True - True - - - False - False - 1 - - - - - gtk-refresh - True - True - True - Forces ABRT to regenerate the backtrace. - True - - - False - False - 2 - - - - - Send report - True - True - True - - - False - False - 3 - - - - - False - end - 0 - - - - - - bLog - bCancel - bRefresh - bSend - - - - Reporter Selector - True - center-on-parent - abrt - - - True - vertical - - - True - 5 - 10 - <b>Where do you want to report this incident?</b> - True - - - 0 - - - - - True - vertical - - - - - - 1 - - - - - True - - - - - - 9 - 2 - - - - - True - - - gtk-close - True - True - True - True - - - 0 - - - - - False - 3 - - - - - - - 500 - 12 - Please wait... - True - center-on-parent - abrt - w_reporters - - - True - vertical - 12 - - - True - 0 - - - False - 0 - - - - - True - 0 - - - False - 1 - - - - - True - True - - - True - True - automatic - automatic - etched-in - - - True - True - - - - - - - True - Details - - - - - 2 - - - - - - diff --git a/src/Gui/settings.glade b/src/Gui/settings.glade deleted file mode 100644 index b83e6617..00000000 --- a/src/Gui/settings.glade +++ /dev/null @@ -1,855 +0,0 @@ - - - - - - Plugins - True - center-on-parent - 450 - 400 - abrt - - - True - 12 - vertical - 12 - - - True - vertical - 6 - - - True - True - immediate - never - automatic - in - - - True - True - - - - - 0 - - - - - True - True - - - True - 12 - - - True - - - True - 5 - 2 - 12 - 6 - - - True - 0 - Web Site: - - - 4 - 5 - GTK_FILL - - - - - True - 2.2351741291171123e-10 - Author: - - - 2 - 3 - GTK_FILL - - - - - True - 0 - Version: - - - 3 - 4 - GTK_FILL - - - - - True - 2.2351741291171123e-10 - True - - - 1 - 2 - 3 - 4 - GTK_FILL - - - - - True - 2.2351741291171123e-10 - True - - - 1 - 2 - 2 - 3 - GTK_FILL - - - - - True - 0 - True - - - 1 - 2 - 4 - 5 - GTK_FILL - - - - - True - 0 - Description: - - - 1 - 2 - GTK_FILL - - - - - True - 0 - Name: - - - - - True - 0 - True - - - 1 - 2 - 1 - 2 - - - - - True - 0 - True - - - 1 - 2 - - - - - False - False - 0 - - - - - - - - - - - - True - <b>Plugin details</b> - True - - - - - False - False - 1 - - - - - 0 - - - - - True - 12 - end - - - C_onfigure Plugin - True - True - True - True - - - False - False - 0 - - - - - gtk-close - True - True - True - True - - - False - False - 1 - - - - - False - False - 1 - - - - - - - Preferences - True - center-on-parent - 450 - 400 - abrt - - - True - 12 - vertical - 6 - - - True - True - False - - - True - 6 - 5 - 2 - 12 - 6 - - - Check package GPG signature - True - True - False - True - - - 2 - GTK_FILL - - - - - True - 0 - 5 - Database backend: - - - 1 - 2 - GTK_FILL - GTK_FILL - - - - - True - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - - True - 0 - 5 - Blacklisted packages: - - - 2 - 3 - GTK_FILL - GTK_FILL - - - - - True - 2.2351741291171123e-10 - 5 - Max coredump storage size (MB): - - - 3 - 4 - GTK_FILL - GTK_FILL - - - - - True - 0 - 5 - GPG keys: - - - 4 - 5 - GTK_FILL - GTK_FILL - - - - - True - True - - adjMaxRepSize - True - - - 1 - 2 - 3 - 4 - GTK_FILL - - - - - True - 6 - - - True - True - - - - 0 - - - - - True - True - True - imEdit - - - False - False - 1 - - - - - 1 - 2 - 2 - 3 - GTK_FILL - - - - - True - 6 - - - True - True - False - - - - 0 - - - - - True - True - True - imEdit1 - - - False - False - 1 - - - - - 1 - 2 - 4 - 5 - GTK_FILL - - - - - - - True - Common - - - False - - - - - True - 6 - vertical - 6 - - - True - True - automatic - automatic - - - True - queue - none - - - True - vertical - - - True - - - True - <b>Plugin</b> - True - - - 0 - - - - - True - <b>Time (or period)</b> - True - - - 1 - - - - - False - 0 - - - - - True - vertical - - - - - - 1 - - - - - - - - - 0 - - - - - True - 12 - end - - - gtk-add - True - True - True - True - - - False - False - 0 - - - - - False - False - 1 - - - - - 1 - - - - - True - Cron - - - 1 - False - - - - - True - 6 - vertical - 6 - - - True - True - automatic - automatic - - - True - queue - none - - - True - vertical - - - True - - - True - <b>Analyzer plugin</b> - True - - - 0 - - - - - True - <b>Associated action</b> - True - - - 1 - - - - - True - - - - - - 2 - - - - - False - 0 - - - - - True - vertical - - - - - - 1 - - - - - - - - - 0 - - - - - True - 12 - end - - - gtk-add - True - True - True - True - - - False - False - 0 - - - - - False - False - 1 - - - - - 2 - - - - - True - Analyzers, Actions, Reporters - - - 2 - False - - - - - 0 - - - - - True - 12 - end - - - gtk-cancel - True - True - True - True - - - False - False - 0 - - - - - gtk-ok - True - False - True - True - True - - - False - False - 1 - - - - - False - False - 1 - - - - - - - 1000000 - 1 - - - GPG Keys - True - center-on-parent - 400 - 400 - abrt - wGlobalSettings - - - True - vertical - - - True - True - - - 0 - - - - - True - 12 - 12 - end - - - gtk-add - True - True - True - True - - - False - False - 0 - - - - - gtk-remove - True - True - True - True - - - False - False - 1 - - - - - gtk-ok - True - True - True - True - - - False - False - 2 - - - - - gtk-cancel - True - True - True - True - - - False - False - 3 - - - - - False - False - 1 - - - - - - - True - gtk-edit - 1 - - - True - gtk-edit - 1 - - diff --git a/src/Gui/settings_wizard.glade b/src/Gui/settings_wizard.glade deleted file mode 100644 index 24cb200a..00000000 --- a/src/Gui/settings_wizard.glade +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - 5 - Wrong Settings Detected - False - center-on-parent - abrt - normal - False - - - True - vertical - 6 - - - True - vertical - 6 - - - True - 12 - - - True - gtk-dialog-warning - 6 - - - 0 - - - - - True - Wrong settings were detected for some of the enabled reporter plugins. Please use the buttons below to open the respective configuration and fix it before you proceed, otherwise, the reporting process may fail. - - True - fill - True - - - 1 - - - - - 0 - - - - - True - vertical - 6 - - - - - - 1 - - - - - True - <b>Do you want to continue?</b> - True - - - 2 - - - - - 1 - - - - - True - end - - - gtk-no - True - True - True - True - - - False - False - 0 - - - - - gtk-yes - True - True - True - True - - - False - False - 1 - - - - - False - end - 0 - - - - - - bCancel - bContinue - - - diff --git a/src/Hooks/Makefile.am b/src/Hooks/Makefile.am deleted file mode 100644 index eec2cd11..00000000 --- a/src/Hooks/Makefile.am +++ /dev/null @@ -1,44 +0,0 @@ -libexec_PROGRAMS = abrt-hook-ccpp -bin_PROGRAMS = dumpoops - -# abrt-hook-ccpp -abrt_hook_ccpp_SOURCES = abrt-hook-ccpp.cpp -abrt_hook_ccpp_CPPFLAGS = \ - -I$(srcdir)/../../inc \ - -I$(srcdir)/../../lib/Utils \ - -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ - -DCONF_DIR=\"$(CONF_DIR)\" \ - -DVAR_RUN=\"$(VAR_RUN)\" \ - -D_GNU_SOURCE -abrt_hook_ccpp_LDADD = \ - ../../lib/Utils/libABRTUtils.la - -# dumpoops -dumpoops_SOURCES = dumpoops.cpp -dumpoops_CPPFLAGS = \ - -I$(srcdir)/../../inc \ - -I$(srcdir)/../../lib/Utils \ - -I$(srcdir)/../../lib/Plugins \ - -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ - -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ - -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ - -DCONF_DIR=\"$(CONF_DIR)\" \ - -DVAR_RUN=\"$(VAR_RUN)\" \ - -D_GNU_SOURCE -# build will succeed, but at runtime plugins do need ABRT*d*Utils -dumpoops_LDADD = \ - ../../lib/Utils/libABRTUtils.la \ - ../../lib/Utils/libABRTdUtils.la - -python_PYTHON = abrt.pth abrt_exception_handler.py -EXTRA_DIST = abrt_exception_handler.py.in $(man_MANS) - -CLEANFILES := $(notdir $(wildcard *~)) $(notdir $(wildcard *\#)) $(notdir $(wildcard \.\#*)) $(notdir $(wildcard *.pyc)) - -# Must be synchronized with another sed call below. -abrt_exception_handler.py: - sed s,\@VAR_RUN\@,\"$(VAR_RUN)\",g abrt_exception_handler.py.in > abrt_exception_handler.py - -# RPM fix: we need to regenerate abrt_exception_handler.py, because it has the default ddir -install-data-local: - sed s,\@VAR_RUN\@,\"$(VAR_RUN)\",g abrt_exception_handler.py.in > abrt_exception_handler.py diff --git a/src/Hooks/abrt-hook-ccpp.cpp b/src/Hooks/abrt-hook-ccpp.cpp deleted file mode 100644 index e53007a4..00000000 --- a/src/Hooks/abrt-hook-ccpp.cpp +++ /dev/null @@ -1,553 +0,0 @@ -/* - abrt-hook-ccpp.cpp - the hook for C/C++ crashing program - - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#include "abrtlib.h" -#include "hooklib.h" -#include "debug_dump.h" -#include "crash_types.h" -#include "abrt_exception.h" -#include - -using namespace std; - -static char* malloc_readlink(const char *linkname) -{ - char buf[PATH_MAX + 1]; - int len; - - len = readlink(linkname, buf, sizeof(buf)-1); - if (len >= 0) - { - buf[len] = '\0'; - return xstrdup(buf); - } - return NULL; -} - -/* Custom version of copyfd_xyz, - * one which is able to write into two descriptors at once. - */ -#define CONFIG_FEATURE_COPYBUF_KB 4 -static off_t copyfd_sparse(int src_fd, int dst_fd1, int dst_fd2, off_t size2) -{ - off_t total = 0; - int last_was_seek = 0; -#if CONFIG_FEATURE_COPYBUF_KB <= 4 - char buffer[CONFIG_FEATURE_COPYBUF_KB * 1024]; - enum { buffer_size = sizeof(buffer) }; -#else - char *buffer; - int buffer_size; - - /* We want page-aligned buffer, just in case kernel is clever - * and can do page-aligned io more efficiently */ - buffer = mmap(NULL, CONFIG_FEATURE_COPYBUF_KB * 1024, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON, - /* ignored: */ -1, 0); - buffer_size = CONFIG_FEATURE_COPYBUF_KB * 1024; - if (buffer == MAP_FAILED) { - buffer = alloca(4 * 1024); - buffer_size = 4 * 1024; - } -#endif - - while (1) { - ssize_t rd = safe_read(src_fd, buffer, buffer_size); - if (!rd) { /* eof */ - if (last_was_seek) { - if (lseek(dst_fd1, -1, SEEK_CUR) < 0 - || safe_write(dst_fd1, "", 1) != 1 - || (dst_fd2 >= 0 - && (lseek(dst_fd2, -1, SEEK_CUR) < 0 - || safe_write(dst_fd2, "", 1) != 1 - ) - ) - ) { - perror_msg("write error"); - total = -1; - goto out; - } - } - /* all done */ - goto out; - } - if (rd < 0) { - perror_msg("read error"); - total = -1; - goto out; - } - - /* checking sparseness */ - ssize_t cnt = rd; - while (--cnt >= 0) { - if (buffer[cnt] != 0) { - /* not sparse */ - errno = 0; - ssize_t wr1 = full_write(dst_fd1, buffer, rd); - ssize_t wr2 = (dst_fd2 >= 0 ? full_write(dst_fd2, buffer, rd) : rd); - if (wr1 < rd || wr2 < rd) { - perror_msg("write error"); - total = -1; - goto out; - } - last_was_seek = 0; - goto adv; - } - } - /* sparse */ - xlseek(dst_fd1, rd, SEEK_CUR); - if (dst_fd2 >= 0) - xlseek(dst_fd2, rd, SEEK_CUR); - last_was_seek = 1; - adv: - total += rd; - size2 -= rd; - if (size2 < 0) - dst_fd2 = -1; - } - out: - -#if CONFIG_FEATURE_COPYBUF_KB > 4 - if (buffer_size != 4 * 1024) - munmap(buffer, buffer_size); -#endif - return total; -} - -static char* get_executable(pid_t pid, int *fd_p) -{ - char buf[sizeof("/proc/%lu/exe") + sizeof(long)*3]; - - sprintf(buf, "/proc/%lu/exe", (long)pid); - *fd_p = open(buf, O_RDONLY); /* might fail and return -1, it's ok */ - char *executable = malloc_readlink(buf); - /* find and cut off " (deleted)" from the path */ - char *deleted = executable + strlen(executable) - strlen(" (deleted)"); - if (deleted > executable && strcmp(deleted, " (deleted)") == 0) - { - *deleted = '\0'; - log("file %s seems to be deleted", executable); - } - /* find and cut off prelink suffixes from the path */ - char *prelink = executable + strlen(executable) - strlen(".#prelink#.XXXXXX"); - if (prelink > executable && strncmp(prelink, ".#prelink#.", strlen(".#prelink#.")) == 0) - { - log("file %s seems to be a prelink temporary file", executable); - *prelink = '\0'; - } - return executable; -} - -static char* get_cwd(pid_t pid) -{ - char buf[sizeof("/proc/%lu/cwd") + sizeof(long)*3]; - - sprintf(buf, "/proc/%lu/cwd", (long)pid); - return malloc_readlink(buf); -} - -static char core_basename[sizeof("core.%lu") + sizeof(long)*3] = "core"; - -static int open_user_core(const char *user_pwd, uid_t uid, pid_t pid) -{ - struct passwd* pw = getpwuid(uid); - gid_t gid = pw ? pw->pw_gid : uid; - xsetegid(gid); - xseteuid(uid); - - errno = 0; - if (user_pwd == NULL - || chdir(user_pwd) != 0 - ) { - perror_msg("can't cd to %s", user_pwd); - return 0; - } - - /* Mimic "core.PID" if requested */ - char buf[] = "0\n"; - int fd = open("/proc/sys/kernel/core_uses_pid", O_RDONLY); - if (fd >= 0) - { - read(fd, buf, sizeof(buf)); - close(fd); - } - if (strcmp(buf, "1\n") == 0) - { - sprintf(core_basename, "core.%lu", (long)pid); - } - - /* man core: - * There are various circumstances in which a core dump file - * is not produced: - * - * [skipped obvious ones] - * The process does not have permission to write the core file. - * ...if a file with the same name exists and is not writable - * or is not a regular file (e.g., it is a directory or a symbolic link). - * - * A file with the same name already exists, but there is more - * than one hard link to that file. - * - * The file system where the core dump file would be created is full; - * or has run out of inodes; or is mounted read-only; - * or the user has reached their quota for the file system. - * - * The RLIMIT_CORE or RLIMIT_FSIZE resource limits for the process - * are set to zero. - * [shouldn't it be checked by kernel? 2.6.30.9-96 doesn't, still - * calls us even if "ulimit -c 0"] - * - * The binary being executed by the process does not have - * read permission enabled. [how we can check it here?] - * - * The process is executing a set-user-ID (set-group-ID) program - * that is owned by a user (group) other than the real - * user (group) ID of the process. [TODO?] - * (However, see the description of the prctl(2) PR_SET_DUMPABLE operation, - * and the description of the /proc/sys/fs/suid_dumpable file in proc(5).) - */ - - /* Do not O_TRUNC: if later checks fail, we do not want to have file already modified here */ - struct stat sb; - errno = 0; - int user_core_fd = open(core_basename, O_WRONLY | O_CREAT | O_NOFOLLOW, 0600); /* kernel makes 0600 too */ - xsetegid(0); - xseteuid(0); - if (user_core_fd < 0 - || fstat(user_core_fd, &sb) != 0 - || !S_ISREG(sb.st_mode) - || sb.st_nlink != 1 - /* kernel internal dumper checks this too: if (inode->i_uid != current->fsuid) , need to mimic? */ - ) { - perror_msg("%s/%s is not a regular file with link count 1", user_pwd, core_basename); - return -1; - } - if (ftruncate(user_core_fd, 0) != 0) { - /* perror first, otherwise unlink may trash errno */ - perror_msg("truncate %s/%s", user_pwd, core_basename); - return -1; - } - - return user_core_fd; -} - -int main(int argc, char** argv) -{ - int i; - struct stat sb; - - if (argc < 5) - { - error_msg_and_die("Usage: %s: DUMPDIR PID SIGNO UID CORE_SIZE_LIMIT", argv[0]); - } - - /* Not needed on 2.6.30. - * At least 2.6.18 has a bug where - * argv[1] = "DUMPDIR PID SIGNO UID CORE_SIZE_LIMIT" - * argv[2] = "PID SIGNO UID CORE_SIZE_LIMIT" - * and so on. Fixing it: - */ - for (i = 1; argv[i]; i++) - { - strchrnul(argv[i], ' ')[0] = '\0'; - } - - openlog("abrt", LOG_PID, LOG_DAEMON); - logmode = LOGMODE_SYSLOG; - - errno = 0; - const char* dddir = argv[1]; - pid_t pid = xatoi_u(argv[2]); - const char* signal_str = argv[3]; - int signal_no = xatoi_u(argv[3]); - uid_t uid = xatoi_u(argv[4]); - off_t ulimit_c = strtoull(argv[5], NULL, 10); - if (ulimit_c < 0) /* unlimited? */ - { - /* set to max possible >0 value */ - ulimit_c = ~((off_t)1 << (sizeof(off_t)*8-1)); - } - if (errno || pid <= 0) - { - error_msg_and_die("pid '%s' or limit '%s' is bogus", argv[2], argv[5]); - } - - int src_fd_binary; - char* executable = get_executable(pid, &src_fd_binary); - if (executable == NULL) - { - perror_msg_and_die("can't read /proc/%lu/exe link", (long)pid); - } - if (strstr(executable, "/abrt-hook-ccpp")) - { - error_msg_and_die("pid %lu is '%s', not dumping it to avoid recursion", - (long)pid, executable); - } - - char *user_pwd = get_cwd(pid); /* may be NULL on error */ - - /* Parse abrt.conf and plugins/CCpp.conf */ - unsigned setting_MaxCrashReportsSize = 0; - bool setting_MakeCompatCore = false; - bool setting_SaveBinaryImage = false; - parse_conf(CONF_DIR"/plugins/CCpp.conf", &setting_MaxCrashReportsSize, &setting_MakeCompatCore, &setting_SaveBinaryImage); - if (!setting_SaveBinaryImage && src_fd_binary >= 0) - { - close(src_fd_binary); - src_fd_binary = -1; - } - - /* Open a fd to compat coredump, if requested and is possible */ - int user_core_fd = -1; - if (setting_MakeCompatCore && ulimit_c != 0) - user_core_fd = open_user_core(user_pwd, uid, pid); - - const char *signame = NULL; - /* Tried to use array for this but C++ does not support v[] = { [IDX] = "str" } */ - switch (signal_no) - { - case SIGILL : signame = "ILL" ; break; - case SIGFPE : signame = "FPE" ; break; - case SIGSEGV: signame = "SEGV"; break; - case SIGBUS : signame = "BUS" ; break; //Bus error (bad memory access) - case SIGABRT: signame = "ABRT"; break; //usually when abort() was called - //case SIGQUIT: signame = "QUIT"; break; //Quit from keyboard - //case SIGSYS : signame = "SYS" ; break; //Bad argument to routine (SVr4) - //case SIGTRAP: signame = "TRAP"; break; //Trace/breakpoint trap - //case SIGXCPU: signame = "XCPU"; break; //CPU time limit exceeded (4.2BSD) - //case SIGXFSZ: signame = "XFSZ"; break; //File size limit exceeded (4.2BSD) - default: goto create_user_core; // not a signal we care about - } - - if (!daemon_is_ok()) - { - /* not an error, exit with exitcode 0 */ - log("abrt daemon is not running. If it crashed, " - "/proc/sys/kernel/core_pattern contains a stale value, " - "consider resetting it to 'core'" - ); - goto create_user_core; - } - - try - { - if (setting_MaxCrashReportsSize > 0) - { - check_free_space(setting_MaxCrashReportsSize); - } - - char path[PATH_MAX]; - - /* Check /var/spool/abrt/last-ccpp marker, do not dump repeated crashes - * if they happen too often. Else, write new marker value. - */ - snprintf(path, sizeof(path), "%s/last-ccpp", dddir); - int fd = open(path, O_RDWR | O_CREAT, 0600); - if (fd >= 0) - { - int sz; - fstat(fd, &sb); /* !paranoia. this can't fail. */ - - if (sb.st_size != 0 /* if it wasn't created by us just now... */ - && (unsigned)(time(NULL) - sb.st_mtime) < 20 /* and is relatively new [is 20 sec ok?] */ - ) { - sz = read(fd, path, sizeof(path)-1); /* (ab)using path as scratch buf */ - if (sz > 0) - { - path[sz] = '\0'; - if (strcmp(executable, path) == 0) - { - error_msg("not dumping repeating crash in '%s'", executable); - if (setting_MakeCompatCore) - goto create_user_core; - return 1; - } - } - lseek(fd, 0, SEEK_SET); - } - sz = write(fd, executable, strlen(executable)); - if (sz >= 0) - ftruncate(fd, sz); - close(fd); - } - - if (strstr(executable, "/abrtd")) - { - /* If abrtd crashes, we don't want to create a _directory_, - * since that can make new copy of abrtd to process it, - * and maybe crash again... - * Unlike dirs, mere files are ignored by abrtd. - */ - snprintf(path, sizeof(path), "%s/abrtd-coredump", dddir); - int abrt_core_fd = xopen3(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); - off_t core_size = copyfd_eof(STDIN_FILENO, abrt_core_fd, COPYFD_SPARSE); - if (core_size < 0 || fsync(abrt_core_fd) != 0) - { - unlink(path); - /* copyfd_eof logs the error including errno string, - * but it does not log file name */ - error_msg_and_die("error saving coredump to %s", path); - } - log("saved core dump of pid %lu (%s) to %s (%llu bytes)", (long)pid, executable, path, (long long)core_size); - return 0; - } - - unsigned path_len = snprintf(path, sizeof(path), "%s/ccpp-%ld-%lu.new", - dddir, (long)time(NULL), (long)pid); - if (path_len >= (sizeof(path) - sizeof("/"FILENAME_COREDUMP))) - return 1; - - CDebugDump dd; - char *cmdline = get_cmdline(pid); /* never NULL */ - char *reason = xasprintf("Process %s was killed by signal %s (SIG%s)", executable, signal_str, signame ? signame : signal_str); - dd.Create(path, uid); - dd.SaveText(FILENAME_ANALYZER, "CCpp"); - dd.SaveText(FILENAME_EXECUTABLE, executable); - dd.SaveText(FILENAME_CMDLINE, cmdline); - dd.SaveText(FILENAME_REASON, reason); - free(cmdline); - free(reason); - - if (src_fd_binary > 0) - { - strcpy(path + path_len, "/"FILENAME_BINARY); - int dst_fd_binary = xopen3(path, O_WRONLY | O_CREAT | O_TRUNC, 0600); - off_t sz = copyfd_eof(src_fd_binary, dst_fd_binary, COPYFD_SPARSE); - if (sz < 0 || fsync(dst_fd_binary) != 0) - { - unlink(path); - error_msg_and_die("error saving binary image to %s", path); - } - close(dst_fd_binary); - close(src_fd_binary); - } - - /* We need coredumps to be readable by all, because - * when abrt daemon processes coredump, - * process producing backtrace is run under the same UID - * as the crashed process. - * Thus 644, not 600 */ - strcpy(path + path_len, "/"FILENAME_COREDUMP); - int abrt_core_fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); - if (abrt_core_fd < 0) - { - int sv_errno = errno; - dd.Delete(); - dd.Close(); - if (user_core_fd >= 0) - { - xchdir(user_pwd); - unlink(core_basename); - } - errno = sv_errno; - perror_msg_and_die("can't open '%s'", path); - } - - /* We write both coredumps at once. - * We can't write user coredump first, since it might be truncated - * and thus can't be copied and used as abrt coredump; - * and if we write abrt coredump first and then copy it as user one, - * then we have a race when process exits but coredump does not exist yet: - * $ echo -e '#include\nmain(){raise(SIGSEGV);}' | gcc -o test -x c - - * $ rm -f core*; ulimit -c unlimited; ./test; ls -l core* - * 21631 Segmentation fault (core dumped) ./test - * ls: cannot access core*: No such file or directory <=== BAD - */ -//TODO: fchown abrt_core_fd to uid:abrt? -//Currently it is owned by 0:0 but is readable by anyone, so the owner -//of the crashed binary still can access it, as he has -//r-x access to the dump dir. - off_t core_size = copyfd_sparse(STDIN_FILENO, abrt_core_fd, user_core_fd, ulimit_c); - if (core_size < 0 || fsync(abrt_core_fd) != 0) - { - unlink(path); - dd.Delete(); - dd.Close(); - if (user_core_fd >= 0) - { - xchdir(user_pwd); - unlink(core_basename); - } - /* copyfd_sparse logs the error including errno string, - * but it does not log file name */ - error_msg_and_die("error writing %s", path); - } - log("saved core dump of pid %lu (%s) to %s (%llu bytes)", (long)pid, executable, path, (long long)core_size); - if (user_core_fd >= 0 && core_size >= ulimit_c) - { - /* user coredump is too big, nuke it */ - xchdir(user_pwd); - unlink(core_basename); - } - - /* We close dumpdir before we start catering for crash storm case. - * Otherwise, delete_debug_dump_dir's from other concurrent - * CCpp's won't be able to delete our dump (their delete_debug_dump_dir - * will wait for us), and we won't be able to delete their dumps. - * Classic deadlock. - */ - dd.Close(); - path[path_len] = '\0'; /* path now contains only directory name */ - char *newpath = xstrndup(path, path_len - (sizeof(".new")-1)); - if (rename(path, newpath) == 0) - strcpy(path, newpath); - free(newpath); - - /* rhbz#539551: "abrt going crazy when crashing process is respawned" */ - if (setting_MaxCrashReportsSize > 0) - { - trim_debug_dumps(setting_MaxCrashReportsSize, path); - } - - return 0; - } - catch (CABRTException& e) - { - error_msg_and_die("%s", e.what()); - } - catch (std::exception& e) - { - error_msg_and_die("%s", e.what()); - } - - /* We didn't create abrt dump, but may need to create compat coredump */ - create_user_core: - if (user_core_fd < 0) - return 0; - - off_t core_size = copyfd_size(STDIN_FILENO, user_core_fd, ulimit_c, COPYFD_SPARSE); - if (core_size < 0 || fsync(user_core_fd) != 0) { - /* perror first, otherwise unlink may trash errno */ - perror_msg("error writing %s/%s", user_pwd, core_basename); - xchdir(user_pwd); - unlink(core_basename); - return 1; - } - if (core_size >= ulimit_c) - { - xchdir(user_pwd); - unlink(core_basename); - return 1; - } - log("saved core dump of pid %lu to %s/%s (%llu bytes)", (long)pid, user_pwd, core_basename, (long long)core_size); - - return 0; -} diff --git a/src/Hooks/abrt.pth b/src/Hooks/abrt.pth deleted file mode 100644 index 39fc292e..00000000 --- a/src/Hooks/abrt.pth +++ /dev/null @@ -1 +0,0 @@ -import abrt_exception_handler diff --git a/src/Hooks/abrt_exception_handler.py.in b/src/Hooks/abrt_exception_handler.py.in deleted file mode 100644 index dd6fbaed..00000000 --- a/src/Hooks/abrt_exception_handler.py.in +++ /dev/null @@ -1,167 +0,0 @@ -#:mode=python: -# -*- coding: utf-8 -*- -## Copyright (C) 2001-2005 Red Hat, Inc. -## Copyright (C) 2001-2005 Harald Hoyer -## Copyright (C) 2009 Jiri Moskovcak - -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2 of the License, or -## (at your option) any later version. - -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. - -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -""" -Module for the ABRT exception handling hook -""" - -import sys -import os -import syslog -import subprocess -import socket - -def write_dump(pid, tb): - executable = "Exception raised from python shell" - if sys.argv[0]: - executable = os.path.abspath(sys.argv[0]) - - # Open ABRT daemon's socket and write data to it. - try: - s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - s.connect(@VAR_RUN@ + "/abrt/abrt.socket") - s.sendall("PID=%s\0" % pid) - s.sendall("EXECUTABLE=%s\0" % executable) - s.sendall("ANALYZER=Python\0") - s.sendall("BASENAME=pyhook\0") - # This handler puts a short(er) crash descr in 1st line of the backtrace. - # Example: - # CCMainWindow.py:1::ZeroDivisionError: integer division or modulo by zero - s.sendall("REASON=%s\0" % tb.splitlines()[0]) - s.sendall("BACKTRACE=%s\0" % tb) - s.sendall("DONE\0") - s.close() - except Exception, ex: - syslog.syslog("can't communicate with ABRT daemon, is it running? %s", str(ex)) - -def handleMyException((etype, value, tb)): - """ - The exception handling function. - - progname - the name of the application - version - the version of the application - """ - - # restore original exception handler - sys.excepthook = sys.__excepthook__ # pylint: disable-msg=E1101 - # ignore uncaught ctrl-c - if etype == KeyboardInterrupt: - return sys.__excepthook__(etype, value, tb) - - try: - import os - import os.path - import traceback - import errno - - # EPIPE is not a crash, it happens all the time - # Testcase: script.py | true, where script.py is: - ## #!/usr/bin/python - ## import os - ## import time - ## time.sleep(1) - ## os.write(1, "Hello\n") # print "Hello" wouldn't be the same - # - if etype == IOError or etype == OSError: - if value.errno == errno.EPIPE: - return sys.__excepthook__(etype, value, tb) - - # "-c" appears in this case: - # $ python -c 'import sys; print "argv0 is:%s" % sys.argv[0]' - # argv0 is:-c - if not sys.argv[0] or sys.argv[0] == "-c": - # Looks like interactive Python - abort dumping - syslog.syslog("abrt: detected unhandled Python exception") - raise Exception - syslog.syslog("abrt: detected unhandled Python exception in %s" % sys.argv[0]) - if sys.argv[0][0] != "/": - # Relative path - can't reliably determine package - # this script belongs to - abort dumping - # TODO: check abrt.conf and abort only if - # ProcessUnpackaged = no? - raise Exception - - elist = traceback.format_exception(etype, value, tb) - tblast = traceback.extract_tb(tb, limit=None) - if len(tblast): - tblast = tblast[len(tblast)-1] - extxt = traceback.format_exception_only(etype, value) - if tblast and len(tblast) > 3: - ll = [] - ll.extend(tblast[:3]) - ll[0] = os.path.basename(tblast[0]) - tblast = ll - - ntext = "" - for t in tblast: - ntext += str(t) + ":" - - text = ntext - text += extxt[0] - text += "\n" - text += "".join(elist) - - trace = tb - while trace.tb_next: - trace = trace.tb_next - frame = trace.tb_frame - text += ("\nLocal variables in innermost frame:\n") - try: - for (key, val) in frame.f_locals.items(): - text += "%s: %s\n" % (key, repr(val)) - except: - pass - - # add coredump saving - write_dump(os.getpid(), text) - - except: - # silently ignore any error in this hook, - # to not interfere with the python scripts - pass - - return sys.__excepthook__(etype, value, tb) - - -def installExceptionHandler(): - """ - Install the exception handling function. - """ - sys.excepthook = lambda etype, value, tb: handleMyException((etype, value, tb)) - -# install the exception handler when the abrt_exception_handler -# module is imported -try: - installExceptionHandler() -except Exception, e: - # TODO: log errors? - # OTOH, if abrt is deinstalled uncleanly - # and this file (sitecustomize.py) exists but - # abrt_exception_handler module does not exist, we probably - # don't want to irritate admins... - pass - -if __name__ == '__main__': - # test exception raised to show the effect - div0 = 1 / 0 # pylint: disable-msg=W0612 - sys.exit(0) - - -__author__ = "Harald Hoyer " diff --git a/src/Hooks/dumpoops.cpp b/src/Hooks/dumpoops.cpp deleted file mode 100644 index a2d2353a..00000000 --- a/src/Hooks/dumpoops.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 RedHat Inc - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - - Authors: - Denys Vlasenko - Zdenek Prikryl -*/ - -#include "abrtlib.h" -#include "abrt_types.h" -#include "abrt_exception.h" -#include "KerneloopsScanner.h" -#include - -#define LOADSYM(fp, name) \ -do { \ - fp = (typeof(fp)) (dlsym(handle, name)); \ - if (!fp) \ - perror_msg_and_die(PLUGINS_LIB_DIR"/libKerneloopsScanner.so has no %s", name); \ -} while (0) - - -int main(int argc, char **argv) -{ - char *program_name = strrchr(argv[0], '/'); - program_name = program_name ? program_name + 1 : argv[0]; - - /* Parse options */ - bool opt_d = 0, opt_s = 0; - int opt; - while ((opt = getopt(argc, argv, "dsv")) != -1) { - switch (opt) { - case 'd': - opt_d = 1; - break; - case 's': - opt_s = 1; - break; - case 'v': - /* Kerneloops code uses VERB3, thus: */ - g_verbose = 3; - break; - default: - usage: - error_msg_and_die( - "Usage: %s [-dsv] FILE\n\n" - "Options:\n" - "\t-d\tCreate ABRT dump for every oops found\n" - "\t-s\tPrint found oopses on standard output\n" - "\t-v\tVerbose\n" - , program_name - ); - } - } - argv += optind; - if (!argv[0]) - goto usage; - - msg_prefix = xasprintf("%s: ", program_name); - - /* Load KerneloopsScanner plugin */ -// const plugin_info_t *plugin_info; - CPlugin* (*plugin_newf)(void); - int (*scan_syslog_file)(vector_string_t& oopsList, const char *filename, time_t *last_changed_p); - void (*save_oops_to_debug_dump)(const vector_string_t& oopsList); - void *handle; - - errno = 0; -//TODO: use it directly, not via dlopen? - handle = dlopen(PLUGINS_LIB_DIR"/libKerneloopsScanner.so", RTLD_NOW); - if (!handle) - perror_msg_and_die("can't load %s", PLUGINS_LIB_DIR"/libKerneloopsScanner.so"); - -// LOADSYM(plugin_info, "plugin_info"); - LOADSYM(plugin_newf, "plugin_new"); - LOADSYM(scan_syslog_file, "scan_syslog_file"); - LOADSYM(save_oops_to_debug_dump, "save_oops_to_debug_dump"); - -// CKerneloopsScanner* scanner = (CKerneloopsScanner*) plugin_newf(); -// scanner->Init(); -// scanner->LoadSettings(path); - - /* Use it: parse and dump the oops */ - vector_string_t oopsList; - int cnt = scan_syslog_file(oopsList, argv[0], NULL); - log("found oopses: %d", cnt); - - if (cnt > 0) { - if (opt_s) { - int i = 0; - while (i < oopsList.size()) { - printf("\nVersion: %s", oopsList[i].c_str()); - i++; - } - } - if (opt_d) { - log("dumping oopses"); - try { - save_oops_to_debug_dump(oopsList); - } - catch (CABRTException& e) { - fprintf(stderr, "Error: %s\n", e.what()); - return 1; - } - } - } - - /*dlclose(handle); - why bother? */ - return 0; -} diff --git a/src/Makefile.am b/src/Makefile.am index 42266b31..c2576c4e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1 +1 @@ -SUBDIRS = Hooks Daemon Applet Gui CLI utils +SUBDIRS = hooks daemon applet gui cli utils diff --git a/src/applet/Applet.cpp b/src/applet/Applet.cpp new file mode 100644 index 00000000..85ee0db8 --- /dev/null +++ b/src/applet/Applet.cpp @@ -0,0 +1,302 @@ +/* + Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include +#if HAVE_CONFIG_H +# include +#endif +#if HAVE_LOCALE_H +# include +#endif +#if ENABLE_NLS +# include +# define _(S) gettext(S) +#else +# define _(S) (S) +#endif +#include "abrtlib.h" +#include "abrt_dbus.h" +#include "dbus_common.h" +#include "CCApplet.h" + + +static CApplet* applet; + + +static void Crash(DBusMessage* signal) +{ + int r; + DBusMessageIter in_iter; + dbus_message_iter_init(signal, &in_iter); + + /* 1st param: package */ + const char* package_name; + r = load_val(&in_iter, package_name); + + /* 2nd param: crash_id */ + const char* crash_id = NULL; + if (r != ABRT_DBUS_MORE_FIELDS) + { + error_msg("dbus signal %s: parameter type mismatch", __func__); + return; + } + r = load_val(&in_iter, crash_id); + + /* Optional 3rd param: uid */ + const char* uid_str = NULL; + if (r == ABRT_DBUS_MORE_FIELDS) + { + r = load_val(&in_iter, uid_str); + } + if (r != ABRT_DBUS_LAST_FIELD) + { + error_msg("dbus signal %s: parameter type mismatch", __func__); + return; + } + + if (uid_str != NULL) + { + char *end; + errno = 0; + unsigned long uid_num = strtoul(uid_str, &end, 10); + if (errno || *end != '\0' || uid_num != getuid()) + { + return; + } + } + + const char* message = _("A crash in the %s package has been detected"); + if (package_name[0] == '\0') + message = _("A crash has been detected"); + //applet->AddEvent(uid, package_name); + applet->SetIconTooltip(message, package_name); + applet->ShowIcon(); + + /* If this crash seems to be repeating, do not annoy user with popup dialog. + * (The icon in the tray is not suppressed) + */ + static time_t last_time = 0; + static char* last_package_name = NULL; + static char* last_crash_id = NULL; + time_t cur_time = time(NULL); + if (last_package_name && strcmp(last_package_name, package_name) == 0 + && last_crash_id && strcmp(last_crash_id, crash_id) == 0 + && (unsigned)(cur_time - last_time) < 2 * 60 * 60 + ) { + log_msg("repeated crash in %s, not showing the notification", package_name); + return; + } + last_time = cur_time; + free(last_package_name); + last_package_name = xstrdup(package_name); + free(last_crash_id); + last_crash_id = xstrdup(crash_id); + + applet->CrashNotify(crash_id, message, package_name); +} + +static void QuotaExceed(DBusMessage* signal) +{ + int r; + DBusMessageIter in_iter; + dbus_message_iter_init(signal, &in_iter); + const char* str; + r = load_val(&in_iter, str); + if (r != ABRT_DBUS_LAST_FIELD) + { + error_msg("dbus signal %s: parameter type mismatch", __func__); + return; + } + + //if (m_pSessionDBus->has_name("com.redhat.abrt.gui")) + // return; + applet->ShowIcon(); + applet->MessageNotify("%s", str); +} + +static void NameOwnerChanged(DBusMessage* signal) +{ + int r; + DBusMessageIter in_iter; + dbus_message_iter_init(signal, &in_iter); + const char* name; + r = load_val(&in_iter, name); + if (r != ABRT_DBUS_MORE_FIELDS) + { + error_msg("dbus signal %s: parameter type mismatch", __func__); + return; + } + + /* We are only interested in (dis)appearances of our daemon */ + if (strcmp(name, "com.redhat.abrt") != 0) + return; + + const char* old_owner; + r = load_val(&in_iter, old_owner); + if (r != ABRT_DBUS_MORE_FIELDS) + { + error_msg("dbus signal %s: parameter type mismatch", __func__); + return; + } + const char* new_owner; + r = load_val(&in_iter, new_owner); + if (r != ABRT_DBUS_LAST_FIELD) + { + error_msg("dbus signal %s: parameter type mismatch", __func__); + return; + } + +// hide icon if it's visible - as NM and don't show it, if it's not + if (!new_owner[0]) + applet->HideIcon(); +} + +static DBusHandlerResult handle_message(DBusConnection* conn, DBusMessage* msg, void* user_data) +{ + const char* member = dbus_message_get_member(msg); + + VERB1 log("%s(member:'%s')", __func__, member); + + int type = dbus_message_get_type(msg); + if (type != DBUS_MESSAGE_TYPE_SIGNAL) + { + log("The message is not a signal. ignoring"); + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (strcmp(member, "NameOwnerChanged") == 0) + NameOwnerChanged(msg); + else if (strcmp(member, "Crash") == 0) + Crash(msg); + else if (strcmp(member, "QuotaExceed") == 0) + QuotaExceed(msg); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +//TODO: move to abrt_dbus.cpp +static void die_if_dbus_error(bool error_flag, DBusError* err, const char* msg) +{ + if (dbus_error_is_set(err)) + { + error_msg("dbus error: %s", err->message); + /*dbus_error_free(&err); - why bother, we will exit in a microsecond */ + error_flag = true; + } + if (!error_flag) + return; + error_msg_and_die("%s", msg); +} + +int main(int argc, char** argv) +{ + const char * app_name = "abrt-gui"; + /* I18n */ + setlocale(LC_ALL, ""); +#if ENABLE_NLS + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + + /* Need to be thread safe */ + g_thread_init(NULL); + gdk_threads_init(); + gdk_threads_enter(); + + /* Parse options */ + int opt; + while ((opt = getopt(argc, argv, "dv")) != -1) + { + switch (opt) + { + case 'v': + g_verbose++; + break; + default: + error_msg_and_die( + "Usage: abrt-applet [-v]\n" + "\nOptions:" + "\n\t-v\tVerbose" + ); + } + } + gtk_init(&argc, &argv); + + /* Prevent zombies when we spawn abrt-gui */ + signal(SIGCHLD, SIG_IGN); + + /* Initialize our (dbus_abrt) machinery: hook _system_ dbus to glib main loop. + * (session bus is left to be handled by libnotify, see below) */ + DBusError err; + dbus_error_init(&err); + DBusConnection* system_conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); + die_if_dbus_error(system_conn == NULL, &err, "Can't connect to system dbus"); + attach_dbus_conn_to_glib_main_loop(system_conn); + if (!dbus_connection_add_filter(system_conn, handle_message, NULL, NULL)) + error_msg_and_die("Can't add dbus filter"); + /* which messages do we want to be fed to handle_message()? */ + //signal sender=org.freedesktop.DBus -> path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged + // string "com.redhat.abrt" + // string "" + // string ":1.70" + dbus_bus_add_match(system_conn, "type='signal',member='NameOwnerChanged'", &err); + die_if_dbus_error(false, &err, "Can't add dbus match"); + //signal sender=:1.73 -> path=/com/redhat/abrt; interface=com.redhat.abrt; member=Crash + // string "coreutils-7.2-3.fc11" + // string "0" + dbus_bus_add_match(system_conn, "type='signal',path='/com/redhat/abrt'", &err); + die_if_dbus_error(false, &err, "Can't add dbus match"); + + /* Initialize GUI stuff. + * Note: inside CApplet ctor, libnotify hooks session dbus + * to glib main loop */ + applet = new CApplet(app_name); + /* dbus_abrt cannot handle more than one bus, and we don't really need to. + * The only thing we want to do is to announce ourself on session dbus */ + DBusConnection* session_conn = dbus_bus_get(DBUS_BUS_SESSION, &err); + die_if_dbus_error(session_conn == NULL, &err, "Can't connect to session dbus"); + int r = dbus_bus_request_name(session_conn, + "com.redhat.abrt.applet", + /* flags */ DBUS_NAME_FLAG_DO_NOT_QUEUE, &err); + die_if_dbus_error(r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER, &err, + "Problem connecting to dbus, or applet is already running"); + + /* show the warning in terminal, as nm-applet does */ + if (!dbus_bus_name_has_owner(system_conn, ABRTD_DBUS_NAME, &err)) + { + const char* msg = _("ABRT service is not running"); + puts(msg); + } + + /* dbus_bus_request_name can already read some data. Thus while dbus fd hasn't + * any data anymore, dbus library can buffer a message or two. + * If we don't do this, the data won't be processed until next dbus data arrives. + */ + int cnt = 10; + while (dbus_connection_dispatch(system_conn) != DBUS_DISPATCH_COMPLETE && --cnt) + continue; + + /* Enter main loop */ + gtk_main(); + + gdk_threads_leave(); + delete applet; + return 0; +} diff --git a/src/applet/CCApplet.cpp b/src/applet/CCApplet.cpp new file mode 100644 index 00000000..5349c7a5 --- /dev/null +++ b/src/applet/CCApplet.cpp @@ -0,0 +1,456 @@ +/* + Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#if HAVE_CONFIG_H +# include +#endif +#if ENABLE_NLS +# include +# define _(S) gettext(S) +#else +# define _(S) (S) +#endif +#include "abrtlib.h" +#include "CCApplet.h" + +static void on_notify_close(NotifyNotification *notification, gpointer user_data) +{ + g_object_unref(notification); +} + +static NotifyNotification *new_warn_notification() +{ + NotifyNotification *notification; + notification = notify_notification_new(_("Warning"), NULL, NULL, NULL); + g_signal_connect(notification, "closed", G_CALLBACK(on_notify_close), NULL); + + GdkPixbuf *pixbuf = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), + GTK_STOCK_DIALOG_WARNING, 48, GTK_ICON_LOOKUP_USE_BUILTIN, NULL); + + if (pixbuf) + notify_notification_set_icon_from_pixbuf(notification, pixbuf); + notify_notification_set_urgency(notification, NOTIFY_URGENCY_NORMAL); + notify_notification_set_timeout(notification, NOTIFY_EXPIRES_DEFAULT); + + return notification; +} + + +static void on_hide_cb(GtkMenuItem *menuitem, gpointer applet) +{ + if (applet) + ((CApplet*)applet)->HideIcon(); +} + +static void on_about_cb(GtkMenuItem *menuitem, gpointer dialog) +{ + if (dialog) + { + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_hide(GTK_WIDGET(dialog)); + } +} + +static GtkWidget *create_about_dialog() +{ + const char *copyright_str = "Copyright © 2009 Red Hat, Inc\nCopyright © 2010 Red Hat, Inc"; + const char *license_str = "This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version." + "\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details." + "\n\nYou should have received a copy of the GNU General Public License along with this program. If not, see ."; + + const char *website_url = "https://fedorahosted.org/abrt/"; + const char *authors[] = {"Anton Arapov ", + "Karel Klic ", + "Jiri Moskovcak ", + "Nikola Pajkovsky ", + "Zdenek Prikryl ", + "Denys Vlasenko ", + NULL}; + + const char *artists[] = {"Patrick Connelly ", + "Lapo Calamandrei", + NULL}; + + const char *comments = _("Notification area applet that notifies users about " + "issues detected by ABRT"); + GtkWidget *about_d = gtk_about_dialog_new(); + if (about_d) + { + gtk_window_set_default_icon_name("abrt"); + gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(about_d), VERSION); + gtk_about_dialog_set_logo_icon_name(GTK_ABOUT_DIALOG(about_d), "abrt"); + gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(about_d), comments); + gtk_about_dialog_set_program_name(GTK_ABOUT_DIALOG(about_d), "ABRT"); + gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(about_d), copyright_str); + gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(about_d), license_str); + gtk_about_dialog_set_wrap_license(GTK_ABOUT_DIALOG(about_d),true); + gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(about_d), website_url); + gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(about_d), authors); + gtk_about_dialog_set_artists(GTK_ABOUT_DIALOG(about_d), artists); + gtk_about_dialog_set_translator_credits(GTK_ABOUT_DIALOG(about_d), _("translator-credits")); + } + return about_d; +} + +static GtkWidget *create_menu(CApplet *applet) +{ + GtkWidget *menu = gtk_menu_new(); + GtkWidget *b_quit = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL); + g_signal_connect(b_quit, "activate", gtk_main_quit, NULL); + GtkWidget *b_hide = gtk_menu_item_new_with_label(_("Hide")); + g_signal_connect(b_hide, "activate", G_CALLBACK(on_hide_cb), applet); + GtkWidget *b_about = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, NULL); + GtkWidget *about_dialog = create_about_dialog(); + g_signal_connect(b_about, "activate", G_CALLBACK(on_about_cb), about_dialog); + GtkWidget *separator = gtk_separator_menu_item_new(); + if (menu) + { + gtk_menu_shell_append(GTK_MENU_SHELL(menu),b_hide); + gtk_widget_show(b_hide); + gtk_menu_shell_append(GTK_MENU_SHELL(menu),b_about); + gtk_widget_show(b_about); + gtk_menu_shell_append(GTK_MENU_SHELL(menu),separator); + gtk_widget_show(separator); + gtk_menu_shell_append(GTK_MENU_SHELL(menu),b_quit); + gtk_widget_show(b_quit); + } + return menu; +} + +CApplet::CApplet(const char* app_name) +{ + m_bDaemonRunning = true; + /* set-up icon buffers */ + m_iAnimator = 0; + m_iAnimationStage = ICON_DEFAULT; + m_bIconsLoaded = load_icons(); + /* - animation - */ + if (m_bIconsLoaded == true) + { + //FIXME: animation is disabled for now + m_pStatusIcon = gtk_status_icon_new_from_pixbuf(icon_stages_buff[ICON_DEFAULT]); + } + else + { + m_pStatusIcon = gtk_status_icon_new_from_icon_name("abrt"); + } + notify_init(app_name); + + gtk_status_icon_set_visible(m_pStatusIcon, FALSE); + + g_signal_connect(G_OBJECT(m_pStatusIcon), "activate", GTK_SIGNAL_FUNC(CApplet::OnAppletActivate_CB), this); + g_signal_connect(G_OBJECT(m_pStatusIcon), "popup_menu", GTK_SIGNAL_FUNC(CApplet::OnMenuPopup_cb), this); + +// SetIconTooltip(_("Pending events: %i"), m_mapEvents.size()); + + m_pMenu = create_menu(this); +} + +CApplet::~CApplet() +{ + if (notify_is_initted()) + notify_uninit(); +} + +void CApplet::SetIconTooltip(const char *format, ...) +{ + va_list args; + int n; + char *buf; + + va_start(args, format); + buf = NULL; + n = vasprintf(&buf, format, args); + va_end(args); + + gtk_status_icon_set_tooltip_text(m_pStatusIcon, (n >= 0 && buf) ? buf : ""); + free(buf); +} + +void CApplet::action_report(NotifyNotification *notification, gchar *action, gpointer user_data) +{ + CApplet *applet = (CApplet *)user_data; + if (applet->m_bDaemonRunning) + { + pid_t pid = vfork(); + if (pid < 0) + perror_msg("vfork"); + if (pid == 0) + { /* child */ + char *buf = xasprintf("--report=%s", applet->m_pLastCrashID); + signal(SIGCHLD, SIG_DFL); /* undo SIG_IGN in abrt-applet */ + execl(BIN_DIR"/abrt-gui", "abrt-gui", buf, (char*) NULL); + /* Did not find abrt-gui in installation directory. Oh well */ + /* Trying to find it in PATH */ + execlp("abrt-gui", "abrt-gui", buf, (char*) NULL); + perror_msg_and_die("Can't execute abrt-gui"); + } + GError *err = NULL; + notify_notification_close(notification, &err); + if (err != NULL) + { + error_msg("%s", err->message); + g_error_free(err); + } + gtk_status_icon_set_visible(applet->m_pStatusIcon, false); + applet->stop_animate_icon(); + } +} + +void CApplet::action_open_gui(NotifyNotification *notification, gchar *action, gpointer user_data) +{ + CApplet *applet = (CApplet *)user_data; + if (applet->m_bDaemonRunning) + { + pid_t pid = vfork(); + if (pid < 0) + perror_msg("vfork"); + if (pid == 0) + { /* child */ + signal(SIGCHLD, SIG_DFL); /* undo SIG_IGN in abrt-applet */ + execl(BIN_DIR"/abrt-gui", "abrt-gui", (char*) NULL); + /* Did not find abrt-gui in installation directory. Oh well */ + /* Trying to find it in PATH */ + execlp("abrt-gui", "abrt-gui", (char*) NULL); + perror_msg_and_die("Can't execute abrt-gui"); + } + GError *err = NULL; + notify_notification_close(notification, &err); + if (err != NULL) + { + error_msg("%s", err->message); + g_error_free(err); + } + gtk_status_icon_set_visible(applet->m_pStatusIcon, false); + applet->stop_animate_icon(); + } +} + +void CApplet::CrashNotify(const char* crash_id, const char *format, ...) +{ + m_pLastCrashID = crash_id; + va_list args; + va_start(args, format); + char *buf = xvasprintf(format, args); + va_end(args); + + NotifyNotification *notification = new_warn_notification(); + notify_notification_add_action(notification, "REPORT", _("Report"), + NOTIFY_ACTION_CALLBACK(CApplet::action_report), + this, NULL); + notify_notification_add_action(notification, "OPEN_MAIN_WINDOW", _("Open ABRT"), + NOTIFY_ACTION_CALLBACK(CApplet::action_open_gui), + this, NULL); + + notify_notification_update(notification, _("Warning"), buf, NULL); + free(buf); + GError *err = NULL; + notify_notification_show(notification, &err); + if (err != NULL) + { + error_msg("%s", err->message); + g_error_free(err); + } +} + +void CApplet::MessageNotify(const char *format, ...) +{ + va_list args; + + va_start(args, format); + char *buf = xvasprintf(format, args); + va_end(args); + + /* we don't want to show any buttons now, + maybe later we can add action binded to message + like >>Clear old dumps<< for quota exceeded + */ + NotifyNotification *notification = new_warn_notification(); + notify_notification_add_action(notification, "OPEN_MAIN_WINDOW", _("Open ABRT"), + NOTIFY_ACTION_CALLBACK(CApplet::action_open_gui), + this, NULL); + notify_notification_update(notification, _("Warning"), buf, NULL); + free(buf); + GError *err = NULL; + notify_notification_show(notification, &err); + if (err != NULL) + { + error_msg("%s", err->message); + g_error_free(err); + } +} + +void CApplet::OnAppletActivate_CB(GtkStatusIcon *status_icon, gpointer user_data) +{ + CApplet *applet = (CApplet *)user_data; + if (applet->m_bDaemonRunning) + { + pid_t pid = vfork(); + if (pid < 0) + perror_msg("vfork"); + if (pid == 0) + { /* child */ + signal(SIGCHLD, SIG_DFL); /* undo SIG_IGN in abrt-applet */ + execl(BIN_DIR"/abrt-gui", "abrt-gui", (char*) NULL); + /* Did not find abrt-gui in installation directory. Oh well */ + /* Trying to find it in PATH */ + execlp("abrt-gui", "abrt-gui", (char*) NULL); + perror_msg_and_die("Can't execute abrt-gui"); + } + gtk_status_icon_set_visible(applet->m_pStatusIcon, false); + applet->stop_animate_icon(); + } +} + +void CApplet::OnMenuPopup_cb(GtkStatusIcon *status_icon, + guint button, + guint activate_time, + gpointer user_data) +{ + CApplet *applet = (CApplet *)user_data; + /* stop the animation */ + applet->stop_animate_icon(); + + if (applet->m_pMenu != NULL) + { + gtk_menu_popup(GTK_MENU(((CApplet *)user_data)->m_pMenu), + NULL, NULL, + gtk_status_icon_position_menu, + status_icon, button, activate_time); + } +} + +void CApplet::ShowIcon() +{ + gtk_status_icon_set_visible(m_pStatusIcon, true); + /* only animate if all icons are loaded, use the "gtk-warning" instead */ + if (m_bIconsLoaded) + animate_icon(); +} + +void CApplet::HideIcon() +{ + gtk_status_icon_set_visible(m_pStatusIcon, false); + stop_animate_icon(); +} + +void CApplet::Disable(const char *reason) +{ + /* + FIXME: once we have our icon + */ + m_bDaemonRunning = false; + GdkPixbuf *gray_scaled; + GdkPixbuf *pixbuf = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), + GTK_STOCK_DIALOG_WARNING, 24, GTK_ICON_LOOKUP_USE_BUILTIN, NULL); + if (pixbuf) + { + gray_scaled = gdk_pixbuf_copy(pixbuf); + gdk_pixbuf_saturate_and_pixelate(pixbuf, gray_scaled, 0.0, false); + gtk_status_icon_set_from_pixbuf(m_pStatusIcon, gray_scaled); +//do we need to free pixbufs nere? + } + else + error_msg("Can't load icon"); + SetIconTooltip(reason); + ShowIcon(); +} + +void CApplet::Enable(const char *reason) +{ + /* restore the original icon */ + m_bDaemonRunning = true; + SetIconTooltip(reason); + gtk_status_icon_set_from_stock(m_pStatusIcon, GTK_STOCK_DIALOG_WARNING); + ShowIcon(); +} + +gboolean CApplet::update_icon(void *user_data) +{ + CApplet* applet = (CApplet*)user_data; + if (applet->m_pStatusIcon && applet->m_iAnimationStage < ICON_STAGE_LAST) + { + gtk_status_icon_set_from_pixbuf(applet->m_pStatusIcon, + applet->icon_stages_buff[applet->m_iAnimationStage++]); + } + if (applet->m_iAnimationStage == ICON_STAGE_LAST) + { + applet->m_iAnimationStage = 0; + } + if (--applet->m_iAnimCountdown == 0) + { + applet->stop_animate_icon(); + } + return true; +} + +void CApplet::animate_icon() +{ + if (m_iAnimator == 0) + { + m_iAnimator = g_timeout_add(100, update_icon, this); + m_iAnimCountdown = 10 * 3; /* 3 sec */ + } +} + +void CApplet::stop_animate_icon() +{ + /* animator should be 0 if icons are not loaded, so this should be safe */ + if (m_iAnimator != 0) + { + g_source_remove(m_iAnimator); + gtk_status_icon_set_from_pixbuf(m_pStatusIcon, icon_stages_buff[ICON_DEFAULT]); + m_iAnimator = 0; + } +} + +bool CApplet::load_icons() +{ + //FIXME: just a tmp workaround + return false; + int stage; + for (stage = ICON_DEFAULT; stage < ICON_STAGE_LAST; stage++) + { + char name[sizeof(ICON_DIR"/abrt%02d.png")]; + GError *error = NULL; + if (snprintf(name, sizeof(ICON_DIR"/abrt%02d.png"), ICON_DIR"/abrt%02d.png", stage) > 0) + { + icon_stages_buff[stage] = gdk_pixbuf_new_from_file(name, &error); + if (error != NULL) + { + error_msg("Can't load pixbuf from %s, animation is disabled", name); + return false; + } + } + } + return true; +} + + +//int CApplet::AddEvent(int pUUID, const char *pProgname) +//{ +// m_mapEvents[pUUID] = "pProgname"; +// SetIconTooltip(_("Pending events: %i"), m_mapEvents.size()); +// return 0; +//} +// +//int CApplet::RemoveEvent(int pUUID) +//{ +// m_mapEvents.erase(pUUID); +// return 0; +//} diff --git a/src/applet/CCApplet.h b/src/applet/CCApplet.h new file mode 100644 index 00000000..a58ec687 --- /dev/null +++ b/src/applet/CCApplet.h @@ -0,0 +1,88 @@ +/* + Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef CC_APPLET_H_ +#define CC_APPLET_H_ + +#include +#include +#include +#include + +class CApplet +{ + private: + GtkStatusIcon* m_pStatusIcon; + GtkWidget *m_pMenu; + +// std::map m_mapEvents; + bool m_bDaemonRunning; + int m_iAnimationStage; + guint m_iAnimator; + unsigned m_iAnimCountdown; + bool m_bIconsLoaded; + const char *m_pLastCrashID; + + enum ICON_STAGES + { + ICON_DEFAULT, + ICON_STAGE1, + ICON_STAGE2, + ICON_STAGE3, + ICON_STAGE4, + ICON_STAGE5, + /* this must be always the last */ + ICON_STAGE_LAST + } icon_stages; + GdkPixbuf *icon_stages_buff[ICON_STAGE_LAST]; + + public: + CApplet(const char* app_name); + ~CApplet(); + void ShowIcon(); + void HideIcon(); + void SetIconTooltip(const char *format, ...); + void CrashNotify(const char* crash_id, const char *format, ...); + void MessageNotify(const char *format, ...); + void Disable(const char *reason); + void Enable(const char *reason); + // create some event storage, to let user choose + // or ask the daemon every time? + // maybe just events which occured during current session + // map:: +// int AddEvent(int pUUID, const char *pProgname); +// int RemoveEvent(int pUUID); + + protected: + //@@TODO applet menus + static void OnAppletActivate_CB(GtkStatusIcon *status_icon, gpointer user_data); + //this action should open the reporter dialog directly, without showing the main window + static void action_report(NotifyNotification *notification, gchar *action, gpointer user_data); + //this action should open the main window + static void action_open_gui(NotifyNotification *notification, gchar *action, gpointer user_data); + static void OnMenuPopup_cb(GtkStatusIcon *status_icon, + guint button, + guint activate_time, + gpointer user_data); + static gboolean update_icon(void *data); + void animate_icon(); + void stop_animate_icon(); + bool load_icons(); +}; + +#endif diff --git a/src/applet/Makefile.am b/src/applet/Makefile.am new file mode 100644 index 00000000..743c18df --- /dev/null +++ b/src/applet/Makefile.am @@ -0,0 +1,43 @@ +bin_PROGRAMS = abrt-applet + +abrt_applet_SOURCES = \ + Applet.cpp \ + CCApplet.h CCApplet.cpp +abrt_applet_CPPFLAGS = \ + -Wall -Werror \ + -I$(srcdir)/../../inc \ + -I$(srcdir)/../../lib/utils \ + -I/usr/include/glib-2.0 \ + -I/usr/lib/glib-2.0/include \ + -DBIN_DIR=\"$(bindir)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -DICON_DIR=\"${datadir}/abrt/icons/hicolor/48x48/status\" \ + $(GTK_CFLAGS) \ + $(DBUS_CFLAGS) \ + -D_GNU_SOURCE +# $(LIBNOTIFY_CFLAGS) +# $(DBUS_GLIB_CFLAGS) +abrt_applet_LDADD = \ + ../../lib/utils/libABRTUtils.la \ + -lglib-2.0 \ + -lgthread-2.0 \ + $(DBUS_LIBS) \ + $(LIBNOTIFY_LIBS) \ + $(GTK_LIBS) +# ../../lib/Utils/libABRTdUtils.la +# $(DL_LIBS) + +DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ + +@INTLTOOL_DESKTOP_RULE@ + +autostartdir = $(sysconfdir)/xdg/autostart +autostart_in_files = abrt-applet.desktop.in + +autostart_DATA = $(autostart_in_files:.desktop.in=.desktop) + +EXTRA_DIST = $(autostart_in_files) diff --git a/src/applet/abrt-applet.desktop.in b/src/applet/abrt-applet.desktop.in new file mode 100644 index 00000000..57a6278e --- /dev/null +++ b/src/applet/abrt-applet.desktop.in @@ -0,0 +1,9 @@ +[Desktop Entry] +Encoding=UTF-8 +_Name=Automatic Bug Reporting Tool +_Comment=ABRT notification applet +Icon=abrt +Exec=abrt-applet +Terminal=false +Type=Application +X-GNOME-Autostart-enabled=true diff --git a/src/cli/CLI.cpp b/src/cli/CLI.cpp new file mode 100644 index 00000000..276703dc --- /dev/null +++ b/src/cli/CLI.cpp @@ -0,0 +1,436 @@ +/* + Copyright (C) 2009, 2010 Red Hat, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include "abrt_exception.h" +#include "abrtlib.h" +#include "abrt_dbus.h" +#include "dbus_common.h" +#include "report.h" +#include "dbus.h" +#if HAVE_CONFIG_H +# include +#endif +#if HAVE_LOCALE_H +# include +#endif +#if ENABLE_NLS +# include +# define _(S) gettext(S) +#else +# define _(S) (S) +#endif + +/** Creates a localized string from crash time. */ +static char *localize_crash_time(const char *timestr) +{ + long time = xatou(timestr); + char timeloc[256]; + int success = strftime(timeloc, 128, "%c", localtime(&time)); + if (!success) + error_msg_and_die("Error while converting time to string"); + return xasprintf("%s", timeloc); +} + +/** Prints basic information about a crash to stdout. */ +static void print_crash(const map_crash_data_t &crash) +{ + /* Create a localized string from crash time. */ + const char *timestr = get_crash_data_item_content(crash, FILENAME_TIME).c_str(); + const char *timeloc = localize_crash_time(timestr); + + printf(_("\tUID : %s\n" + "\tUUID : %s\n" + "\tPackage : %s\n" + "\tExecutable : %s\n" + "\tCrash Time : %s\n" + "\tCrash Count: %s\n"), + get_crash_data_item_content(crash, CD_UID).c_str(), + get_crash_data_item_content(crash, CD_UUID).c_str(), + get_crash_data_item_content(crash, FILENAME_PACKAGE).c_str(), + get_crash_data_item_content(crash, FILENAME_EXECUTABLE).c_str(), + timeloc, + get_crash_data_item_content(crash, CD_COUNT).c_str()); + + free((void *)timeloc); + + /* Print the hostname if it's available. */ + const char *hostname = get_crash_data_item_content_or_NULL(crash, FILENAME_HOSTNAME); + if (hostname) + printf(_("\tHostname : %s\n"), hostname); +} + +/** + * Prints a list containing "crashes" to stdout. + * @param include_reported + * Do not skip entries marked as already reported. + */ +static void print_crash_list(const vector_map_crash_data_t& crash_list, bool include_reported) +{ + for (unsigned i = 0; i < crash_list.size(); ++i) + { + const map_crash_data_t& crash = crash_list[i]; + if (get_crash_data_item_content(crash, CD_REPORTED) == "1" && !include_reported) + continue; + + printf("%u.\n", i); + print_crash(crash); + } +} + +/** + * Prints full information about a crash + */ +static void print_crash_info(const map_crash_data_t& crash, bool show_backtrace) +{ + const char *timestr = get_crash_data_item_content(crash, FILENAME_TIME).c_str(); + const char *timeloc = localize_crash_time(timestr); + + printf(_("Crash ID: %s:%s\n" + "Last crash: %s\n" + "Analyzer: %s\n" + "Component: %s\n" + "Package: %s\n" + "Command: %s\n" + "Executable: %s\n" + "System: %s, kernel %s\n" + "Rating: %s\n" + "Coredump file: %s\n" + "Reason: %s\n"), + get_crash_data_item_content(crash, CD_UID).c_str(), + get_crash_data_item_content(crash, CD_UUID).c_str(), + timeloc, + get_crash_data_item_content(crash, FILENAME_ANALYZER).c_str(), + get_crash_data_item_content(crash, FILENAME_COMPONENT).c_str(), + get_crash_data_item_content(crash, FILENAME_PACKAGE).c_str(), + get_crash_data_item_content(crash, FILENAME_CMDLINE).c_str(), + get_crash_data_item_content(crash, FILENAME_EXECUTABLE).c_str(), + get_crash_data_item_content(crash, FILENAME_RELEASE).c_str(), + get_crash_data_item_content(crash, FILENAME_KERNEL).c_str(), + get_crash_data_item_content(crash, FILENAME_RATING).c_str(), + get_crash_data_item_content(crash, FILENAME_COREDUMP).c_str(), + get_crash_data_item_content(crash, FILENAME_REASON).c_str()); + + free((void *)timeloc); + + /* print only if available */ + const char *crash_function = get_crash_data_item_content_or_NULL(crash, FILENAME_CRASH_FUNCTION); + if (crash_function) + printf(_("Crash function: %s\n"), crash_function); + + const char *hostname = get_crash_data_item_content_or_NULL(crash, FILENAME_HOSTNAME); + if (hostname) + printf(_("Hostname: %s\n"), hostname); + + const char *reproduce = get_crash_data_item_content_or_NULL(crash, FILENAME_REPRODUCE); + if (reproduce) + printf(_("\nHow to reproduce:\n%s\n"), reproduce); + + const char *comment = get_crash_data_item_content_or_NULL(crash, FILENAME_COMMENT); + if (comment) + printf(_("\nComment:\n%s\n"), comment); + + if (show_backtrace) + { + const char *backtrace = get_crash_data_item_content_or_NULL(crash, FILENAME_BACKTRACE); + if (backtrace) + printf(_("\nBacktrace:\n%s\n"), backtrace); + } +} + +/** + * Converts crash reference from user's input to unique crash identification + * in form UID:UUID. + * The returned string must be released by caller. + */ +static char *guess_crash_id(const char *str) +{ + vector_map_crash_data_t ci = call_GetCrashInfos(); + unsigned num_crashinfos = ci.size(); + if (str[0] == '@') /* "--report @N" syntax */ + { + unsigned position = xatoi_u(str + 1); + if (position >= num_crashinfos) + error_msg_and_die("There are only %u crash infos", num_crashinfos); + map_crash_data_t& info = ci[position]; + return xasprintf("%s:%s", + get_crash_data_item_content(info, CD_UID).c_str(), + get_crash_data_item_content(info, CD_UUID).c_str() + ); + } + + unsigned len = strlen(str); + unsigned ii; + char *result = NULL; + for (ii = 0; ii < num_crashinfos; ii++) + { + map_crash_data_t& info = ci[ii]; + const char *this_uuid = get_crash_data_item_content(info, CD_UUID).c_str(); + if (strncmp(str, this_uuid, len) == 0) + { + if (result) + error_msg_and_die("Crash prefix '%s' is not unique", str); + result = xasprintf("%s:%s", + get_crash_data_item_content(info, CD_UID).c_str(), + this_uuid + ); + } + } + if (!result) + error_msg_and_die("Crash '%s' not found", str); + return result; +} + +/* Program options */ +enum +{ + OPT_GET_LIST, + OPT_REPORT, + OPT_DELETE, + OPT_INFO +}; + +/** + * Long options. + * Do not use the has_arg field. Arguments are handled after parsing all options. + * The reason is that we want to use all the following combinations: + * --report ID + * --report ID --always + * --report --always ID + */ +static const struct option longopts[] = +{ + /* name, has_arg, flag, val */ + { "help" , no_argument, NULL, '?' }, + { "version" , no_argument, NULL, 'V' }, + { "list" , no_argument, NULL, 'l' }, + { "full" , no_argument, NULL, 'f' }, + { "always" , no_argument, NULL, 'y' }, + { "report" , no_argument, NULL, 'r' }, + { "delete" , no_argument, NULL, 'd' }, + { "info" , no_argument, NULL, 'i' }, + { "backtrace", no_argument, NULL, 'b' }, + { 0, 0, 0, 0 } /* prevents crashes for unknown options*/ +}; + +/* Gets the program name from the first command line argument. */ +static const char *progname(const char *argv0) +{ + const char* name = strrchr(argv0, '/'); + if (name) + return ++name; + return argv0; +} + +/** + * Prints abrt-cli version and some help text. + * Then exits the program with return value 1. + */ +static void usage(char *argv0) +{ + const char *name = progname(argv0); + printf("%s "VERSION"\n\n", name); + + /* Message has embedded tabs. */ + printf(_("Usage: %s [OPTION]\n\n" + "Startup:\n" + " -V, --version display the version of %s and exit\n" + " -?, --help print this help\n\n" + "Actions:\n" + " -l, --list print a list of all crashes which are not yet reported\n" + " -f, --full print a list of all crashes, including the already reported ones\n" + " -r, --report CRASH_ID create and send a report\n" + " -y, --always create and send a report without asking\n" + " -d, --delete CRASH_ID remove a crash\n" + " -i, --info CRASH_ID print detailed information about a crash\n" + " -b, --backtrace print detailed information about a crash including backtrace\n" + "CRASH_ID can be:\n" + " UID:UUID pair,\n" + " unique UUID prefix - the crash with matching UUID will be acted upon\n" + " @N - N'th crash (as displayed by --list --full) will be acted upon\n" + ), + name, name); + + exit(1); +} + +int main(int argc, char** argv) +{ + const char* crash_id = NULL; + int op = -1; + bool full = false; + bool always = false; + bool backtrace = false; + + setlocale(LC_ALL, ""); +#if ENABLE_NLS + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + + while (1) + { + int option_index; + /* Do not use colons, arguments are handled after parsing all options. */ + int c = getopt_long_only(argc, argv, "?Vrdlfyib", + longopts, &option_index); + +#define SET_OP(newop) \ + if (op != -1 && op != newop) \ + { \ + error_msg(_("You must specify exactly one operation")); \ + return 1; \ + } \ + op = newop; + + switch (c) + { + case 'r': SET_OP(OPT_REPORT); break; + case 'd': SET_OP(OPT_DELETE); break; + case 'l': SET_OP(OPT_GET_LIST); break; + case 'i': SET_OP(OPT_INFO); break; + case 'f': full = true; break; + case 'y': always = true; break; + case 'b': backtrace = true; break; + case -1: /* end of options */ break; + default: /* some error */ + case '?': + usage(argv[0]); /* exits app */ + case 'V': + printf("%s "VERSION"\n", progname(argv[0])); + return 0; + } +#undef SET_OP + if (c == -1) + break; + } + + /* Handle option arguments. */ + int arg_count = argc - optind; + switch (arg_count) + { + case 0: + if (op == OPT_REPORT || op == OPT_DELETE || op == OPT_INFO) + usage(argv[0]); + break; + case 1: + if (op != OPT_REPORT && op != OPT_DELETE && op != OPT_INFO) + usage(argv[0]); + crash_id = argv[optind]; + break; + default: + usage(argv[0]); + } + + /* Check if we have an operation. + * Limit --full and --always to certain operations. + */ + if ((full && op != OPT_GET_LIST) || + (always && op != OPT_REPORT) || + (backtrace && op != OPT_INFO) || + op == -1) + { + usage(argv[0]); + return 1; + } + + DBusError err; + dbus_error_init(&err); + s_dbus_conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); + handle_dbus_err(s_dbus_conn == NULL, &err); + + /* Do the selected operation. */ + int exitcode = 0; + switch (op) + { + case OPT_GET_LIST: + { + vector_map_crash_data_t ci = call_GetCrashInfos(); + print_crash_list(ci, full); + break; + } + case OPT_REPORT: + { + int flags = CLI_REPORT_SILENT_IF_NOT_FOUND; + if (always) + flags |= CLI_REPORT_BATCH; + exitcode = report(crash_id, flags); + if (exitcode == -1) /* no such crash_id */ + { + crash_id = guess_crash_id(crash_id); + exitcode = report(crash_id, always ? CLI_REPORT_BATCH : 0); + if (exitcode == -1) + { + error_msg("Crash '%s' not found", crash_id); + free((void *)crash_id); + xfunc_die(); + } + + free((void *)crash_id); + } + break; + } + case OPT_DELETE: + { + exitcode = call_DeleteDebugDump(crash_id); + if (exitcode == ENOENT) + { + crash_id = guess_crash_id(crash_id); + exitcode = call_DeleteDebugDump(crash_id); + if (exitcode == ENOENT) + { + error_msg("Crash '%s' not found", crash_id); + free((void *)crash_id); + xfunc_die(); + } + + free((void *)crash_id); + } + if (exitcode != 0) + error_msg_and_die("Can't delete debug dump '%s'", crash_id); + break; + } + case OPT_INFO: + { + int old_logmode = logmode; + logmode = 0; + + map_crash_data_t crashData = call_CreateReport(crash_id); + if (crashData.empty()) /* no such crash_id */ + { + crash_id = guess_crash_id(crash_id); + crashData = call_CreateReport(crash_id); + if (crashData.empty()) + { + error_msg("Crash '%s' not found", crash_id); + free((void *)crash_id); + xfunc_die(); + } + + free((void *)crash_id); + } + + logmode = old_logmode; + + print_crash_info(crashData, backtrace); + + break; + } + } + + return exitcode; +} diff --git a/src/cli/Makefile.am b/src/cli/Makefile.am new file mode 100644 index 00000000..14d98a01 --- /dev/null +++ b/src/cli/Makefile.am @@ -0,0 +1,28 @@ +bin_PROGRAMS = abrt-cli + +abrt_cli_SOURCES = \ + CLI.cpp \ + run-command.h run-command.cpp \ + report.h report.cpp \ + dbus.h dbus.cpp + +abrt_cli_CPPFLAGS = \ + -I$(srcdir)/../../inc \ + -I$(srcdir)/../../lib/utils \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + $(ENABLE_SOCKET_OR_DBUS) \ + $(DBUS_CFLAGS) \ + -D_GNU_SOURCE +# $(GTK_CFLAGS) + +abrt_cli_LDADD = \ + ../../lib/utils/libABRTUtils.la \ + ../../lib/utils/libABRTdUtils.la + +man_MANS = abrt-cli.1 +EXTRA_DIST = $(man_MANS) + +completiondir = $(sysconfdir)/bash_completion.d +dist_completion_DATA = abrt-cli.bash + +DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ diff --git a/src/cli/abrt-cli.1 b/src/cli/abrt-cli.1 new file mode 100644 index 00000000..3ad858b3 --- /dev/null +++ b/src/cli/abrt-cli.1 @@ -0,0 +1,58 @@ +.TH abrt\-cli "1" "12 Oct 2009" "" +.SH NAME +abrt\-cli \- a command line interface to abrt +.SH SYNOPSIS +.B abrt\-cli +[option] +.SH DESCRIPTION +.I abrt\-cli +is a command line tool that manages application crashes catched by +.I abrtd +daemon. It enables access to crash data, and allows to report +crashes depending on active abrt plugins. +.SH OPTIONS +.B Basic startup options +.IP "\-V, \-\-version" +Displays version of abrt\-cli. +.IP "\-?, \-\-help" +Print a help message describing all of abrt-cli’s command-line options. + +.PP +.B Crash action options +.IP "\-l, \-\-list" +Prints list of crashes which are not reported yet. +.IP "\-r, \-\-report \fIUUID\fR" +Creates a crash report and then the text editor is invoked on that +report. When you are done with editing the report just exit the editor +and then you will be asked if you want to send the report. +.IP "\-d, \-\-delete \fIUUID\fR" +Removes data about particular crash. +.IP "\-i, \-\-info \fIUUID\fR" +Prints detailed information about particular crash. + +.PP +.B Listing options +.IP "\-f, \-\-full" +List all crashes, including already reported. + +.PP +.B Report options +.IP "\-y, \-\-always" +Creates and sends the crash report automatically, without asking +any questions. + +.PP +.B Info options +.IP "\-b, \-\-backtrace" +Includes the crash backtrace in the info output if the backtrace is +available. + +.SH ENVIRONMENT VARIABLES +The editor used to edit the crash report is chosen from the +ABRT_EDITOR environment variable, the VISUAL environment variable, or +the EDITOR environment variable, in that order. + +.SH SEE ALSO +.IR abrtd (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) diff --git a/src/cli/abrt-cli.bash b/src/cli/abrt-cli.bash new file mode 100644 index 00000000..fd0a85f3 --- /dev/null +++ b/src/cli/abrt-cli.bash @@ -0,0 +1,50 @@ +# bash-completion add-on for abrt-cli(1) +# http://bash-completion.alioth.debian.org/ + +# $1 = additional options for abrt-cli +_abrt_list() +{ + echo $(abrt-cli --list $1 | grep UUID | awk '{print $3}') + return 0 +} + +_abrt_cli() +{ + local cur prev opts + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + opts="--help --version --list --report --delete" + + # + # Complete the arguments to some of the basic commands. + # + case "${prev}" in + --list) + opts="--full" + ;; + --report) + # Include only not-yet-reported crashes. + opts="--always $(_abrt_list)" + ;; + --always) # This is for --report --always + # Include only not-yet-reported crashes. + opts=$(_abrt_list) + ;; + --delete) + opts=$(_abrt_list "--full") + ;; + esac + + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 +} +complete -F _abrt_cli abrt-cli + +# Local variables: +# mode: shell-script +# sh-basic-offset: 4 +# sh-indent-comment: t +# indent-tabs-mode: nil +# End: +# ex: ts=4 sw=4 et filetype=sh \ No newline at end of file diff --git a/src/cli/dbus.cpp b/src/cli/dbus.cpp new file mode 100644 index 00000000..7565d5bc --- /dev/null +++ b/src/cli/dbus.cpp @@ -0,0 +1,282 @@ +/* + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "dbus.h" +#include "dbus_common.h" + +DBusConnection* s_dbus_conn; + +/* + * DBus member calls + */ + +/* helpers */ +static DBusMessage* new_call_msg(const char* method) +{ + DBusMessage* msg = dbus_message_new_method_call(ABRTD_DBUS_NAME, ABRTD_DBUS_PATH, ABRTD_DBUS_IFACE, method); + if (!msg) + die_out_of_memory(); + return msg; +} + +static DBusMessage* send_get_reply_and_unref(DBusMessage* msg) +{ + dbus_uint32_t serial; + if (TRUE != dbus_connection_send(s_dbus_conn, msg, &serial)) + error_msg_and_die("Error sending DBus message"); + dbus_message_unref(msg); + + while (true) + { + DBusMessage *received = dbus_connection_pop_message(s_dbus_conn); + if (!received) + { + if (FALSE == dbus_connection_read_write(s_dbus_conn, -1)) + error_msg_and_die("dbus connection closed"); + continue; + } + + int tp = dbus_message_get_type(received); + const char *error_str = dbus_message_get_error_name(received); +#if 0 + /* Debugging */ + printf("type:%u (CALL:%u, RETURN:%u, ERROR:%u, SIGNAL:%u)\n", tp, + DBUS_MESSAGE_TYPE_METHOD_CALL, + DBUS_MESSAGE_TYPE_METHOD_RETURN, + DBUS_MESSAGE_TYPE_ERROR, + DBUS_MESSAGE_TYPE_SIGNAL + ); + const char *sender = dbus_message_get_sender(received); + if (sender) + printf("sender: %s\n", sender); + const char *path = dbus_message_get_path(received); + if (path) + printf("path: %s\n", path); + const char *member = dbus_message_get_member(received); + if (member) + printf("member: %s\n", member); + const char *interface = dbus_message_get_interface(received); + if (interface) + printf("interface: %s\n", interface); + const char *destination = dbus_message_get_destination(received); + if (destination) + printf("destination: %s\n", destination); + if (error_str) + printf("error: '%s'\n", error_str); +#endif + + DBusError err; + dbus_error_init(&err); + + if (dbus_message_is_signal(received, ABRTD_DBUS_IFACE, "Update")) + { + const char *update_msg; + if (!dbus_message_get_args(received, &err, + DBUS_TYPE_STRING, &update_msg, + DBUS_TYPE_INVALID)) + { + error_msg_and_die("dbus Update message: arguments mismatch"); + } + printf(">> %s\n", update_msg); + } + else if (dbus_message_is_signal(received, ABRTD_DBUS_IFACE, "Warning")) + { + const char *warning_msg; + if (!dbus_message_get_args(received, &err, + DBUS_TYPE_STRING, &warning_msg, + DBUS_TYPE_INVALID)) + { + error_msg_and_die("dbus Warning message: arguments mismatch"); + } + log(">! %s\n", warning_msg); + } + else + if (tp == DBUS_MESSAGE_TYPE_METHOD_RETURN + && dbus_message_get_reply_serial(received) == serial + ) { + return received; + } + else + if (tp == DBUS_MESSAGE_TYPE_ERROR + && dbus_message_get_reply_serial(received) == serial + ) { + error_msg_and_die("dbus call returned error: '%s'", error_str); + } + + dbus_message_unref(received); + } +} + +vector_map_crash_data_t call_GetCrashInfos() +{ + DBusMessage* msg = new_call_msg(__func__ + 5); + DBusMessage *reply = send_get_reply_and_unref(msg); + + DBusMessageIter in_iter; + dbus_message_iter_init(reply, &in_iter); + + vector_map_crash_data_t argout; + int r = load_val(&in_iter, argout); + if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */ + error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5); + + dbus_message_unref(reply); + return argout; +} + +map_crash_data_t call_CreateReport(const char* crash_id) +{ + DBusMessage* msg = new_call_msg(__func__ + 5); + dbus_message_append_args(msg, + DBUS_TYPE_STRING, &crash_id, + DBUS_TYPE_INVALID); + + DBusMessage *reply = send_get_reply_and_unref(msg); + + DBusMessageIter in_iter; + dbus_message_iter_init(reply, &in_iter); + + map_crash_data_t argout; + int r = load_val(&in_iter, argout); + if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */ + error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5); + + dbus_message_unref(reply); + return argout; +} + +report_status_t call_Report(const map_crash_data_t& report, + const vector_string_t& reporters, + const map_map_string_t &plugins) +{ + DBusMessage* msg = new_call_msg(__func__ + 5); + DBusMessageIter out_iter; + dbus_message_iter_init_append(msg, &out_iter); + + /* parameter #1: report data */ + store_val(&out_iter, report); + /* parameter #2: reporters to use */ + store_val(&out_iter, reporters); + /* parameter #3 (opt): plugin config */ + if (!plugins.empty()) + store_val(&out_iter, plugins); + + DBusMessage *reply = send_get_reply_and_unref(msg); + + DBusMessageIter in_iter; + dbus_message_iter_init(reply, &in_iter); + + report_status_t result; + int r = load_val(&in_iter, result); + if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */ + error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5); + + dbus_message_unref(reply); + return result; +} + +int32_t call_DeleteDebugDump(const char* crash_id) +{ + DBusMessage* msg = new_call_msg(__func__ + 5); + dbus_message_append_args(msg, + DBUS_TYPE_STRING, &crash_id, + DBUS_TYPE_INVALID); + + DBusMessage *reply = send_get_reply_and_unref(msg); + + DBusMessageIter in_iter; + dbus_message_iter_init(reply, &in_iter); + + int32_t result; + int r = load_val(&in_iter, result); + if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */ + error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5); + + dbus_message_unref(reply); + return result; +} + +map_map_string_t call_GetPluginsInfo() +{ + DBusMessage *msg = new_call_msg(__func__ + 5); + DBusMessage *reply = send_get_reply_and_unref(msg); + + DBusMessageIter in_iter; + dbus_message_iter_init(reply, &in_iter); + + map_map_string_t argout; + int r = load_val(&in_iter, argout); + if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */ + error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5); + + dbus_message_unref(reply); + return argout; +} + +map_plugin_settings_t call_GetPluginSettings(const char *name) +{ + DBusMessage *msg = new_call_msg(__func__ + 5); + dbus_message_append_args(msg, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + + DBusMessage *reply = send_get_reply_and_unref(msg); + + DBusMessageIter in_iter; + dbus_message_iter_init(reply, &in_iter); + + map_string_t argout; + int r = load_val(&in_iter, argout); + if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */ + error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5); + + dbus_message_unref(reply); + return argout; +} + +map_map_string_t call_GetSettings() +{ + DBusMessage *msg = new_call_msg(__func__ + 5); + DBusMessage *reply = send_get_reply_and_unref(msg); + + DBusMessageIter in_iter; + dbus_message_iter_init(reply, &in_iter); + map_map_string_t argout; + int r = load_val(&in_iter, argout); + if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */ + error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5); + + dbus_message_unref(reply); + return argout; +} + +void handle_dbus_err(bool error_flag, DBusError *err) +{ + if (dbus_error_is_set(err)) + { + error_msg("dbus error: %s", err->message); + /* dbus_error_free(&err); */ + error_flag = true; + } + if (!error_flag) + return; + error_msg_and_die( + "error requesting DBus name %s, possible reasons: " + "abrt run by non-root; dbus config is incorrect; " + "or dbus daemon needs to be restarted to reload dbus config", + ABRTD_DBUS_NAME); +} diff --git a/src/cli/dbus.h b/src/cli/dbus.h new file mode 100644 index 00000000..8b56b152 --- /dev/null +++ b/src/cli/dbus.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef ABRT_CLI_DBUS_H +#define ABRT_CLI_DBUS_H + +#include "abrt_dbus.h" +#include "crash_types.h" + +extern DBusConnection* s_dbus_conn; + +vector_map_crash_data_t call_GetCrashInfos(); + +map_crash_data_t call_CreateReport(const char *crash_id); + +/** Sends report using enabled Reporter plugins. + * @param report + * The report sent to Reporter plugins. + * @param reporters + * List of names of Reporters which should be called. + * @param plugins + * Optional settings for Reporter plugins, can be empty. + * Format: plugins["PluginName"]["SettingsKey"] = "SettingsValue" + * If it contains settings for some plugin, it must contain _all fields_ + * obtained by call_GetPluginSettings, otherwise the plugin might ignore + * the settings. + */ +report_status_t call_Report(const map_crash_data_t& report, + const vector_string_t& reporters, + const map_map_string_t &plugins); + +int32_t call_DeleteDebugDump(const char* crash_id); + +/* Gets basic data about all installed plugins. + * @todo + * Return more semantically structured output - maybe a struct instead of a map. + */ +map_map_string_t call_GetPluginsInfo(); + +/** Gets default plugin settings. + * @param name + * Corresponds to name obtained from call_GetPluginsInfo. + */ +map_plugin_settings_t call_GetPluginSettings(const char *name); + +/** Gets global daemon settings. + * @todo + * Return more semantically structured output - maybe a struct instead of a map. + */ +map_map_string_t call_GetSettings(); + +void handle_dbus_err(bool error_flag, DBusError *err); + +#endif diff --git a/src/cli/report.cpp b/src/cli/report.cpp new file mode 100644 index 00000000..96dec553 --- /dev/null +++ b/src/cli/report.cpp @@ -0,0 +1,743 @@ +/* + Copyright (C) 2009, 2010 Red Hat, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "report.h" +#include "run-command.h" +#include "dbus.h" +#include "abrtlib.h" +#include "debug_dump.h" +#include "crash_types.h" // FILENAME_* defines +#include "plugin.h" // LoadPluginSettings +#include +#include +#include +#if HAVE_CONFIG_H +# include +#endif +#if ENABLE_NLS +# include +# define _(S) gettext(S) +#else +# define _(S) (S) +#endif + +#include "abrt_packages.h" + +/* Field separator for the crash report file that is edited by user. */ +#define FIELD_SEP "%----" + +/* + * Trims whitespace characters both from left and right side of a string. + * Modifies the string in-place. Returns the trimmed string. + */ +static char *trim(char *str) +{ + if (!str) + return NULL; + + // Remove leading spaces. + char *ibuf; + ibuf = skip_whitespace(str); + int i = strlen(ibuf); + if (str != ibuf) + memmove(str, ibuf, i + 1); + + // Remove trailing spaces. + while (--i >= 0) + { + if (!isspace(str[i])) + break; + } + str[++i] = '\0'; + return str; +} + +/* + * Escapes the field content string to avoid confusion with file comments. + * Returned field must be free()d by caller. + */ +static char *escape(const char *str) +{ + // Determine the size of resultant string. + // Count the required number of escape characters. + // 1. NEWLINE followed by # + // 2. NEWLINE followed by \# (escaped version) + const char *ptr = str; + bool newline = true; + int count = 0; + while (*ptr) + { + if (newline) + { + if (*ptr == '#') + ++count; + if (*ptr == '\\' && ptr[1] == '#') + ++count; + } + + newline = (*ptr == '\n'); + ++ptr; + } + + // Copy the input string to the resultant string, and escape all + // occurences of \# and #. + char *result = (char*)xmalloc(strlen(str) + 1 + count); + + const char *src = str; + char *dest = result; + newline = true; + while (*src) + { + if (newline) + { + if (*src == '#') + *dest++ = '\\'; + else if (*src == '\\' && *(src + 1) == '#') + *dest++ = '\\'; + } + + newline = (*src == '\n'); + *dest++ = *src++; + } + *dest = '\0'; + return result; +} + +/* + * Removes all comment lines, and unescapes the string previously escaped + * by escape(). Works in-place. + */ +static void remove_comments_and_unescape(char *str) +{ + char *src = str, *dest = str; + bool newline = true; + while (*src) + { + if (newline) + { + if (*src == '#') + { // Skip the comment line! + while (*src && *src != '\n') + ++src; + + if (*src == '\0') + break; + + ++src; + continue; + } + if (*src == '\\' + && (src[1] == '#' || (src[1] == '\\' && src[2] == '#')) + ) { + ++src; // Unescape escaped char. + } + } + + newline = (*src == '\n'); + *dest++ = *src++; + } + *dest = '\0'; +} + +/* + * Writes a field of crash report to a file. + * Field must be writable. + */ +static void write_crash_report_field(FILE *fp, const map_crash_data_t &report, + const char *field, const char *description) +{ + const map_crash_data_t::const_iterator it = report.find(field); + if (it == report.end()) + { + // exit silently, all fields are optional for now + //error_msg("Field %s not found", field); + return; + } + + if (it->second[CD_TYPE] == CD_SYS) + { + error_msg("Cannot update field %s because it is a system value", field); + return; + } + + fprintf(fp, "%s%s\n", FIELD_SEP, it->first.c_str()); + + fprintf(fp, "%s\n", description); + if (it->second[CD_EDITABLE] != CD_ISEDITABLE) + fprintf(fp, _("# This field is read only\n")); + + char *escaped_content = escape(it->second[CD_CONTENT].c_str()); + fprintf(fp, "%s\n", escaped_content); + free(escaped_content); +} + +/* + * Saves the crash report to a file. + * Parameter 'fp' must be opened before write_crash_report is called. + * Returned value: + * If the report is successfully stored to the file, a zero value is returned. + * On failure, nonzero value is returned. + */ +static void write_crash_report(const map_crash_data_t &report, FILE *fp) +{ + fprintf(fp, "# Please check this report. Lines starting with '#' will be ignored.\n" + "# Lines starting with '%%----' separate fields, please do not delete them.\n\n"); + + write_crash_report_field(fp, report, FILENAME_COMMENT, + _("# Describe the circumstances of this crash below")); + write_crash_report_field(fp, report, FILENAME_REPRODUCE, + _("# How to reproduce the crash?")); + write_crash_report_field(fp, report, FILENAME_BACKTRACE, + _("# Backtrace\n# Check that it does not contain any sensitive data (passwords, etc.)")); + write_crash_report_field(fp, report, CD_DUPHASH, "# DUPHASH"); + write_crash_report_field(fp, report, FILENAME_ARCHITECTURE, _("# Architecture")); + write_crash_report_field(fp, report, FILENAME_CMDLINE, _("# Command line")); + write_crash_report_field(fp, report, FILENAME_COMPONENT, _("# Component")); + write_crash_report_field(fp, report, FILENAME_COREDUMP, _("# Core dump")); + write_crash_report_field(fp, report, FILENAME_EXECUTABLE, _("# Executable")); + write_crash_report_field(fp, report, FILENAME_KERNEL, _("# Kernel version")); + write_crash_report_field(fp, report, FILENAME_PACKAGE, _("# Package")); + write_crash_report_field(fp, report, FILENAME_REASON, _("# Reason of crash")); + write_crash_report_field(fp, report, FILENAME_RELEASE, _("# Release string of the operating system")); +} + +/* + * Updates appropriate field in the report from the text. The text can + * contain multiple fields. + * Returns: + * 0 if no change to the field was detected. + * 1 if the field was changed. + * Changes to read-only fields are ignored. + */ +static int read_crash_report_field(const char *text, map_crash_data_t &report, + const char *field) +{ + char separator[sizeof("\n" FIELD_SEP)-1 + strlen(field) + 2]; // 2 = '\n\0' + sprintf(separator, "\n%s%s\n", FIELD_SEP, field); + const char *textfield = strstr(text, separator); + if (!textfield) + return 0; // exit silently because all fields are optional + + textfield += strlen(separator); + int length = 0; + const char *end = strstr(textfield, "\n" FIELD_SEP); + if (!end) + length = strlen(textfield); + else + length = end - textfield; + + const map_crash_data_t::iterator it = report.find(field); + if (it == report.end()) + { + error_msg("Field %s not found", field); + return 0; + } + + if (it->second[CD_TYPE] == CD_SYS) + { + error_msg("Cannot update field %s because it is a system value", field); + return 0; + } + + // Do not change noneditable fields. + if (it->second[CD_EDITABLE] != CD_ISEDITABLE) + return 0; + + // Compare the old field contents with the new field contents. + char newvalue[length + 1]; + strncpy(newvalue, textfield, length); + newvalue[length] = '\0'; + trim(newvalue); + + char oldvalue[it->second[CD_CONTENT].length() + 1]; + strcpy(oldvalue, it->second[CD_CONTENT].c_str()); + trim(oldvalue); + + // Return if no change in the contents detected. + int cmp = strcmp(newvalue, oldvalue); + if (!cmp) + return 0; + + it->second[CD_CONTENT].assign(newvalue); + return 1; +} + +/* + * Updates the crash report 'report' from the text. The text must not contain + * any comments. + * Returns: + * 0 if no field was changed. + * 1 if any field was changed. + * Changes to read-only fields are ignored. + */ +static int read_crash_report(map_crash_data_t &report, const char *text) +{ + int result = 0; + result |= read_crash_report_field(text, report, FILENAME_COMMENT); + result |= read_crash_report_field(text, report, FILENAME_REPRODUCE); + result |= read_crash_report_field(text, report, FILENAME_BACKTRACE); + result |= read_crash_report_field(text, report, CD_DUPHASH); + result |= read_crash_report_field(text, report, FILENAME_ARCHITECTURE); + result |= read_crash_report_field(text, report, FILENAME_CMDLINE); + result |= read_crash_report_field(text, report, FILENAME_COMPONENT); + result |= read_crash_report_field(text, report, FILENAME_COREDUMP); + result |= read_crash_report_field(text, report, FILENAME_EXECUTABLE); + result |= read_crash_report_field(text, report, FILENAME_KERNEL); + result |= read_crash_report_field(text, report, FILENAME_PACKAGE); + result |= read_crash_report_field(text, report, FILENAME_REASON); + result |= read_crash_report_field(text, report, FILENAME_RELEASE); + return result; +} + +/** + * Ensures that the fields needed for editor are present in the crash data. + * Fields: comments, how to reproduce. + */ +static void create_fields_for_editor(map_crash_data_t &crash_data) +{ + if (crash_data.find(FILENAME_COMMENT) == crash_data.end()) + add_to_crash_data_ext(crash_data, FILENAME_COMMENT, CD_TXT, CD_ISEDITABLE, ""); + + if (crash_data.find(FILENAME_REPRODUCE) == crash_data.end()) + add_to_crash_data_ext(crash_data, FILENAME_REPRODUCE, CD_TXT, CD_ISEDITABLE, "1. \n2. \n3. \n"); +} + +/** + * Runs external editor. + * Returns: + * 0 if the launch was successful + * 1 if it failed. The error reason is logged using error_msg() + */ +static int launch_editor(const char *path) +{ + const char *editor, *terminal; + + editor = getenv("ABRT_EDITOR"); + if (!editor) + editor = getenv("VISUAL"); + if (!editor) + editor = getenv("EDITOR"); + + terminal = getenv("TERM"); + if (!editor && (!terminal || strcmp(terminal, "dumb") == 0)) + { + error_msg(_("Cannot run vi: $TERM, $VISUAL and $EDITOR are not set")); + return 1; + } + + if (!editor) + editor = "vi"; + + char *args[3]; + args[0] = (char*)editor; + args[1] = (char*)path; + args[2] = NULL; + run_command(args); + + return 0; +} + +/** + * Returns: + * 0 on success, crash data has been updated + * 2 on failure, unable to create, open, or close temporary file + * 3 on failure, cannot launch text editor + */ +static int run_report_editor(map_crash_data_t &cr) +{ + /* Open a temporary file and write the crash report to it. */ + char filename[] = "/tmp/abrt-report.XXXXXX"; + int fd = mkstemp(filename); + if (fd == -1) /* errno is set */ + { + perror_msg("can't generate temporary file name"); + return 2; + } + + FILE *fp = fdopen(fd, "w"); + if (!fp) /* errno is set */ + { + perror_msg("can't open '%s' to save the crash report", filename); + return 2; + } + + write_crash_report(cr, fp); + + if (fclose(fp)) /* errno is set */ + { + perror_msg("can't close '%s'", filename); + return 2; + } + + // Start a text editor on the temporary file. + if (launch_editor(filename) != 0) + return 3; /* exit with error */ + + // Read the file back and update the report from the file. + fp = fopen(filename, "r"); + if (!fp) /* errno is set */ + { + perror_msg("can't open '%s' to read the crash report", filename); + return 2; + } + + fseek(fp, 0, SEEK_END); + long size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + char *text = (char*)xmalloc(size + 1); + if (fread(text, 1, size, fp) != size) + { + error_msg("can't read '%s'", filename); + return 2; + } + text[size] = '\0'; + if (fclose(fp) != 0) /* errno is set */ + { + perror_msg("can't close '%s'", filename); + return 2; + } + + // Delete the tempfile. + if (unlink(filename) == -1) /* errno is set */ + { + perror_msg("can't unlink %s", filename); + } + + remove_comments_and_unescape(text); + // Updates the crash report from the file text. + int report_changed = read_crash_report(cr, text); + free(text); + if (report_changed) + puts(_("\nThe report has been updated")); + else + puts(_("\nNo changes were detected in the report")); + + return 0; +} + +/** + * Asks user for a text response. + * @param question + * Question displayed to user. + * @param result + * Output array. + * @param result_size + * Maximum byte count to be written. + */ +static void read_from_stdin(const char *question, char *result, int result_size) +{ + assert(result_size > 1); + printf("%s", question); + fflush(NULL); + if (NULL == fgets(result, result_size, stdin)) + result[0] = '\0'; + // Remove the newline from the login. + strchrnul(result, '\n')[0] = '\0'; +} + +/** Splits a string into substrings using chosen delimiters. + * @param delim + * Specifies a set of characters that delimit the + * tokens in the parsed string + */ +static vector_string_t split(const char *s, const char delim) +{ + std::vector elems; + while (1) + { + const char *end = strchrnul(s, delim); + elems.push_back(std::string(s, end - s)); + if (*end == '\0') + break; + s = end + 1; + } + return elems; +} + +/** Returns a list of enabled Reporter plugins, that are used to report + * a particular crash. + * @todo + * Very similar code is used in the GUI, and also in the Daemon. + * It should be shared. + */ +static vector_string_t get_enabled_reporters(map_crash_data_t &crash_data) +{ + vector_string_t result; + + /* Get global daemon settings. Analyzer->Reporters mapping is stored there. */ + map_map_string_t settings = call_GetSettings(); + /* Reporters are separated by comma in the following map. */ + map_string_t &analyzer_to_reporters = settings["AnalyzerActionsAndReporters"]; + + /* Get the analyzer from the crash. */ + const char *analyzer = get_crash_data_item_content_or_NULL(crash_data, FILENAME_ANALYZER); + if (!analyzer) + return result; /* No analyzer found in the crash data. */ + + /* First try to find package name dependent analyzer. + * nvr = name-version-release + * TODO: Similar code is in MiddleWare.cpp. It should not be duplicated. + */ + const char *package_nvr = get_crash_data_item_content_or_NULL(crash_data, FILENAME_PACKAGE); + if (!package_nvr) + return result; /* No package name found in the crash data. */ + char * package_name = get_package_name_from_NVR_or_NULL(package_nvr); + // analyzer with package name (CCpp:xorg-x11-app) has higher priority + map_string_t::const_iterator reporters_iter; + if (package_name != NULL) + { + char* package_specific_analyzer = xasprintf("%s:%s", analyzer, package_name); + reporters_iter = analyzer_to_reporters.find(package_specific_analyzer); + free(package_specific_analyzer); + free(package_name); + } + + if (analyzer_to_reporters.end() == reporters_iter) + { + reporters_iter = analyzer_to_reporters.find(analyzer); + if (analyzer_to_reporters.end() == reporters_iter) + return result; /* No reporters found for the analyzer. */ + } + + /* Reporters found, now parse the list. */ + vector_string_t reporter_vec = split(reporters_iter->second.c_str(), ','); + + // Get informations about all plugins. + map_map_string_t plugins = call_GetPluginsInfo(); + // Check the configuration of each enabled Reporter plugin. + map_map_string_t::iterator it, itend = plugins.end(); + for (it = plugins.begin(); it != itend; ++it) + { + // Skip disabled plugins. + if (string_to_bool(it->second["Enabled"].c_str()) != true) + continue; + // Skip nonReporter plugins. + if (0 != strcmp(it->second["Type"].c_str(), "Reporter")) + continue; + // Skip plugins not used in this particular crash. + if (reporter_vec.end() == std::find(reporter_vec.begin(), reporter_vec.end(), std::string(it->first))) + continue; + result.push_back(it->first); + } + return result; +} + +/* Asks a [y/n] question on stdin/stdout. + * Returns true if the answer is yes, false otherwise. + */ +static bool ask_yesno(const char *question) +{ + printf(question); + fflush(NULL); + char answer[16] = "n"; + fgets(answer, sizeof(answer), stdin); + /* TODO: localize 'y' */ + return ((answer[0] | 0x20) == 'y'); +} + +/* Returns true if echo has been changed from another state. */ +static bool set_echo(bool enabled) +{ + if (isatty(STDIN_FILENO) == 0) + { + /* Clean errno, which is set by isatty. */ + errno = 0; + return false; + } + + struct termios t; + if (tcgetattr(STDIN_FILENO, &t) < 0) + return false; + + /* No change needed. */ + if ((bool)(t.c_lflag & ECHO) == enabled) + return false; + + if (enabled) + t.c_lflag |= ECHO; + else + t.c_lflag &= ~ECHO; + + if (tcsetattr(STDIN_FILENO, TCSANOW, &t) < 0) + perror_msg_and_die("tcsetattr"); + + return true; +} + +/** + * Gets reporter plugin settings. + * @param reporters + * List of reporter names. Settings of these reporters are handled. + * @param settings + * A structure filled with reporter plugin settings. + * @param ask_user + * If it's set to true and some reporter plugin settings are found to be missing + * (like login name or password), user is asked to provide the missing parts. + */ +static void get_reporter_plugin_settings(const vector_string_t& reporters, + map_map_string_t &settings, + bool ask_user) +{ + /* First of all, load system-wide report plugin settings. */ + for (vector_string_t::const_iterator it = reporters.begin(); it != reporters.end(); ++it) + { + map_string_t single_plugin_settings = call_GetPluginSettings(it->c_str()); + // Copy the received settings as defaults. + // Plugins won't work without it, if some value is missing + // they use their default values for all fields. + settings[it->c_str()] = single_plugin_settings; + } + + /* Second, load user-specific settings, which override + the system-wide settings. */ + struct passwd* pw = getpwuid(geteuid()); + const char* homedir = pw ? pw->pw_dir : NULL; + if (homedir) + { + map_map_string_t::const_iterator itend = settings.end(); + for (map_map_string_t::iterator it = settings.begin(); it != itend; ++it) + { + map_string_t single_plugin_settings; + std::string path = std::string(homedir) + "/.abrt/" + + it->first + "."PLUGINS_CONF_EXTENSION; + /* Load plugin config in the home dir. Do not skip lines with empty value (but containing a "key="), + because user may want to override password from /etc/abrt/plugins/\*.conf, but he prefers to + enter it every time he reports. */ + bool success = LoadPluginSettings(path.c_str(), single_plugin_settings, false); + if (!success) + continue; + // Merge user's plugin settings into already loaded settings. + map_string_t::const_iterator valit, valitend = single_plugin_settings.end(); + for (valit = single_plugin_settings.begin(); valit != valitend; ++valit) + it->second[valit->first] = valit->second; + } + } + + if (!ask_user) + return; + + /* Third, check if a login or password is missing, and ask for it. */ + map_map_string_t::const_iterator itend = settings.end(); + for (map_map_string_t::iterator it = settings.begin(); it != itend; ++it) + { + map_string_t &single_plugin_settings = it->second; + // Login information is missing. + bool loginMissing = single_plugin_settings.find("Login") != single_plugin_settings.end() + && 0 == strcmp(single_plugin_settings["Login"].c_str(), ""); + bool passwordMissing = single_plugin_settings.find("Password") != single_plugin_settings.end() + && 0 == strcmp(single_plugin_settings["Password"].c_str(), ""); + if (!loginMissing && !passwordMissing) + continue; + + // Read the missing information and push it to plugin settings. + printf(_("Wrong settings were detected for plugin %s\n"), it->first.c_str()); + char result[64]; + if (loginMissing) + { + read_from_stdin(_("Enter your login: "), result, 64); + single_plugin_settings["Login"] = std::string(result); + } + if (passwordMissing) + { + bool changed = set_echo(false); + read_from_stdin(_("Enter your password: "), result, 64); + if (changed) + set_echo(true); + + // Newline was not added by pressing Enter because ECHO was disabled, so add it now. + puts(""); + single_plugin_settings["Password"] = std::string(result); + } + } +} + +/* Reports the crash with corresponding crash_id over DBus. */ +int report(const char *crash_id, int flags) +{ + int old_logmode = logmode; + if (flags & CLI_REPORT_SILENT_IF_NOT_FOUND) + logmode = 0; + // Ask for an initial report. + map_crash_data_t cr = call_CreateReport(crash_id); + logmode = old_logmode; + if (cr.size() == 0) + { + return -1; + } + + /* Open text editor and give a chance to review the backtrace etc. */ + if (!(flags & CLI_REPORT_BATCH)) + { + create_fields_for_editor(cr); + int result = run_report_editor(cr); + if (result != 0) + return result; + } + + /* Get enabled reporters associated with this particular crash. */ + vector_string_t reporters = get_enabled_reporters(cr); + + int errors = 0; + int plugins = 0; + if (flags & CLI_REPORT_BATCH) + { + map_map_string_t reporters_settings; /* to be filled on the next line */ + get_reporter_plugin_settings(reporters, reporters_settings, false); + + puts(_("Reporting...")); + report_status_t r = call_Report(cr, reporters, reporters_settings); + report_status_t::iterator it = r.begin(); + while (it != r.end()) + { + vector_string_t &v = it->second; + printf("%s: %s\n", it->first.c_str(), v[REPORT_STATUS_IDX_MSG].c_str()); + plugins++; + if (v[REPORT_STATUS_IDX_FLAG] == "0") + errors++; + it++; + } + } + else + { + /* For every reporter, ask if user really wants to report using it. */ + for (vector_string_t::const_iterator it = reporters.begin(); it != reporters.end(); ++it) + { + char question[255]; + snprintf(question, 255, _("Report using %s? [y/N]: "), it->c_str()); + if (!ask_yesno(question)) + { + puts(_("Skipping...")); + continue; + } + + vector_string_t cur_reporter(1, *it); + map_map_string_t reporters_settings; /* to be filled on the next line */ + get_reporter_plugin_settings(cur_reporter, reporters_settings, true); + report_status_t r = call_Report(cr, cur_reporter, reporters_settings); + assert(r.size() == 1); /* one reporter --> one report status */ + vector_string_t &v = r.begin()->second; + printf("%s: %s\n", r.begin()->first.c_str(), v[REPORT_STATUS_IDX_MSG].c_str()); + plugins++; + if (v[REPORT_STATUS_IDX_FLAG] == "0") + errors++; + } + } + + printf(_("Crash reported via %d plugins (%d errors)\n"), plugins, errors); + return errors != 0; +} diff --git a/src/cli/report.h b/src/cli/report.h new file mode 100644 index 00000000..8a851b8e --- /dev/null +++ b/src/cli/report.h @@ -0,0 +1,28 @@ +/* + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef ABRT_CLI_REPORT_H +#define ABRT_CLI_REPORT_H + +/* Reports the crash with corresponding uuid over DBus. */ +enum { + CLI_REPORT_BATCH = 1 << 0, + CLI_REPORT_SILENT_IF_NOT_FOUND = 1 << 1, +}; +int report(const char *uuid, int flags); + +#endif diff --git a/src/cli/run-command.cpp b/src/cli/run-command.cpp new file mode 100644 index 00000000..1f6836d6 --- /dev/null +++ b/src/cli/run-command.cpp @@ -0,0 +1,73 @@ +/* + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "run-command.h" +#include "abrtlib.h" + +/* + Inspired by git code. + http://git.kernel.org/?p=git/git.git;a=blob;f=run-command.c;hb=HEAD +*/ + +static pid_t start_command(char **argv) +{ + pid_t pid = vfork(); + if (pid < 0) + { + perror_msg_and_die("vfork"); + } + if (pid == 0) + { // new process + execvp(argv[0], argv); + exit(127); + } + return pid; +} + +static int finish_command(pid_t pid, char **argv) +{ + pid_t waiting; + int status; + while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) + continue; + if (waiting < 0) + perror_msg_and_die("waitpid"); + + int code = -1; + if (WIFSIGNALED(status)) + { + code = WTERMSIG(status); + error_msg("'%s' killed by signal %d", argv[0], code); + code += 128; /* shells use this convention for deaths by signal */ + } + else /* if (WIFEXITED(status)) */ + { + code = WEXITSTATUS(status); + if (code == 127) + { + error_msg_and_die("Can't run '%s'", argv[0]); + } + } + + return code; +} + +int run_command(char **argv) +{ + pid_t pid = start_command(argv); + return finish_command(pid, argv); +} diff --git a/src/cli/run-command.h b/src/cli/run-command.h new file mode 100644 index 00000000..71391579 --- /dev/null +++ b/src/cli/run-command.h @@ -0,0 +1,23 @@ +/* + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef ABRT_CLI_RUN_COMMAND_H +#define ABRT_CLI_RUN_COMMAND_H + +int run_command(char **argv); + +#endif diff --git a/src/daemon/CommLayerServer.cpp b/src/daemon/CommLayerServer.cpp new file mode 100644 index 00000000..5e250121 --- /dev/null +++ b/src/daemon/CommLayerServer.cpp @@ -0,0 +1,29 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 RedHat Inc + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "CommLayerServer.h" +#include "CrashWatcher.h" + +CCommLayerServer::CCommLayerServer() +{ + m_init_error = 0; +} + +CCommLayerServer::~CCommLayerServer() +{ +} diff --git a/src/daemon/CommLayerServer.h b/src/daemon/CommLayerServer.h new file mode 100644 index 00000000..c6bf71ee --- /dev/null +++ b/src/daemon/CommLayerServer.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 RedHat Inc + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef COMMLAYERSERVER_H_ +#define COMMLAYERSERVER_H_ + +#include "abrtlib.h" +#include "crash_types.h" + +class CCommLayerServer { + public: + int m_init_error; + + CCommLayerServer(); + virtual ~CCommLayerServer(); + + /* just stubs to be called when not implemented in specific comm layer */ + virtual void Crash(const char *package_name, const char* crash_id, const char *uid_str) {} + virtual void JobDone(const char* peer) = 0; + virtual void QuotaExceed(const char* str) {} + + virtual void Update(const char* pMessage, const char* peer) {}; + virtual void Warning(const char* pMessage, const char* peer) {}; +}; + +#endif diff --git a/src/daemon/CommLayerServerDBus.cpp b/src/daemon/CommLayerServerDBus.cpp new file mode 100644 index 00000000..4b11fc04 --- /dev/null +++ b/src/daemon/CommLayerServerDBus.cpp @@ -0,0 +1,624 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 RedHat Inc + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include "abrtlib.h" +#include "abrt_dbus.h" +#include "abrt_exception.h" +#include "CrashWatcher.h" +#include "Settings.h" +#include "Daemon.h" +#include "CommLayerServerDBus.h" + +// 16kB message limit +#define LIMIT_MESSAGE 16384 + +#if HAVE_CONFIG_H + #include +#endif +#if ENABLE_NLS + #include + #define _(S) gettext(S) +#else + #define _(S) (S) +#endif + +/* + * DBus signal emitters + */ + +/* helpers */ +static DBusMessage* new_signal_msg(const char* member, const char* peer = NULL) +{ + /* path, interface, member name */ + DBusMessage* msg = dbus_message_new_signal(ABRTD_DBUS_PATH, ABRTD_DBUS_IFACE, member); + if (!msg) + die_out_of_memory(); + /* Send unicast dbus signal if peer is known */ + if (peer && !dbus_message_set_destination(msg, peer)) + die_out_of_memory(); + return msg; +} +static void send_flush_and_unref(DBusMessage* msg) +{ + if (!dbus_connection_send(g_dbus_conn, msg, NULL /* &serial */)) + error_msg_and_die("Error sending DBus message"); + dbus_connection_flush(g_dbus_conn); + VERB3 log("DBus message sent"); + dbus_message_unref(msg); +} + +/* Notify the clients (UI) about a new crash */ +void CCommLayerServerDBus::Crash(const char *package_name, + const char* crash_id, + const char *uid_str) +{ + DBusMessage* msg = new_signal_msg("Crash"); + if (uid_str) + { + dbus_message_append_args(msg, + DBUS_TYPE_STRING, &package_name, + DBUS_TYPE_STRING, &crash_id, + DBUS_TYPE_STRING, &uid_str, + DBUS_TYPE_INVALID); + VERB2 log("Sending signal Crash('%s','%s','%s')", package_name, crash_id, uid_str); + } + else + { + dbus_message_append_args(msg, + DBUS_TYPE_STRING, &package_name, + DBUS_TYPE_STRING, &crash_id, + DBUS_TYPE_INVALID); + VERB2 log("Sending signal Crash('%s','%s')", package_name, crash_id); + } + send_flush_and_unref(msg); +} + +void CCommLayerServerDBus::QuotaExceed(const char* str) +{ + DBusMessage* msg = new_signal_msg("QuotaExceed"); + dbus_message_append_args(msg, + DBUS_TYPE_STRING, &str, + DBUS_TYPE_INVALID); + VERB2 log("Sending signal QuotaExceed('%s')", str); + send_flush_and_unref(msg); +} + +void CCommLayerServerDBus::JobDone(const char* peer) +{ + DBusMessage* msg = new_signal_msg("JobDone", peer); + VERB2 log("Sending signal JobDone() to peer %s", peer); + send_flush_and_unref(msg); +} + +void CCommLayerServerDBus::Update(const char* pMessage, const char* peer) +{ + DBusMessage* msg = new_signal_msg("Update", peer); + dbus_message_append_args(msg, + DBUS_TYPE_STRING, &pMessage, + DBUS_TYPE_INVALID); + send_flush_and_unref(msg); +} + +void CCommLayerServerDBus::Warning(const char* pMessage, const char* peer) +{ + DBusMessage* msg = new_signal_msg("Warning", peer); + dbus_message_append_args(msg, + DBUS_TYPE_STRING, &pMessage, + DBUS_TYPE_INVALID); + send_flush_and_unref(msg); +} + + +/* + * DBus call handlers + */ + +static long get_remote_uid(DBusMessage* call, const char** ppSender = NULL) +{ + DBusError err; + dbus_error_init(&err); + const char* sender = dbus_message_get_sender(call); + if (ppSender) + *ppSender = sender; + long uid = dbus_bus_get_unix_user(g_dbus_conn, sender, &err); + if (dbus_error_is_set(&err)) + { + dbus_error_free(&err); + error_msg("Can't determine remote uid, assuming 0"); + return 0; + } + return uid; +} + +static int handle_GetCrashInfos(DBusMessage* call, DBusMessage* reply) +{ + long unix_uid = get_remote_uid(call); + vector_map_crash_data_t argout1 = GetCrashInfos(unix_uid); + + DBusMessageIter out_iter; + dbus_message_iter_init_append(reply, &out_iter); + store_val(&out_iter, argout1); + + send_flush_and_unref(reply); + return 0; +} + +static int handle_StartJob(DBusMessage* call, DBusMessage* reply) +{ + int r; + DBusMessageIter in_iter; + dbus_message_iter_init(call, &in_iter); + const char* crash_id; + r = load_val(&in_iter, crash_id); + if (r != ABRT_DBUS_MORE_FIELDS) + { + error_msg("dbus call %s: parameter type mismatch", __func__ + 7); + return -1; + } + int32_t force; + r = load_val(&in_iter, force); + if (r != ABRT_DBUS_LAST_FIELD) + { + error_msg("dbus call %s: parameter type mismatch", __func__ + 7); + return -1; + } + + const char* sender; + long unix_uid = get_remote_uid(call, &sender); + if (CreateReportThread(crash_id, unix_uid, force, sender) != 0) + return -1; /* can't create thread (err msg is already logged) */ + + send_flush_and_unref(reply); + return 0; +} + +static int handle_CreateReport(DBusMessage* call, DBusMessage* reply) +{ + int r; + DBusMessageIter in_iter; + dbus_message_iter_init(call, &in_iter); + const char* crash_id; + r = load_val(&in_iter, crash_id); + if (r != ABRT_DBUS_LAST_FIELD) + { + error_msg("dbus call %s: parameter type mismatch", __func__ + 7); + return -1; + } + + long unix_uid = get_remote_uid(call); + map_crash_data_t report; + CreateReport(crash_id, unix_uid, /*force:*/ 0, report); + + DBusMessageIter out_iter; + dbus_message_iter_init_append(reply, &out_iter); + store_val(&out_iter, report); + + send_flush_and_unref(reply); + return 0; +} + +static int handle_Report(DBusMessage* call, DBusMessage* reply) +{ + int r; + DBusMessageIter in_iter; + dbus_message_iter_init(call, &in_iter); + + map_crash_data_t argin1; + r = load_val(&in_iter, argin1); + if (r != ABRT_DBUS_MORE_FIELDS) + { + error_msg("dbus call %s: parameter type mismatch", __func__ + 7); + return -1; + } + const char* comment = get_crash_data_item_content_or_NULL(argin1, FILENAME_COMMENT) ? : ""; + const char* reproduce = get_crash_data_item_content_or_NULL(argin1, FILENAME_REPRODUCE) ? : ""; + const char* errmsg = NULL; + if (strlen(comment) > LIMIT_MESSAGE) + { + errmsg = _("Comment is too long"); + } + else if (strlen(reproduce) > LIMIT_MESSAGE) + { + errmsg = _("'How to reproduce' is too long"); + } + if (errmsg) + { + dbus_message_unref(reply); + reply = dbus_message_new_error(call, DBUS_ERROR_FAILED, errmsg); + if (!reply) + die_out_of_memory(); + send_flush_and_unref(reply); + return 0; + } + + /* Second parameter: list of reporters to use */ + vector_string_t reporters; + r = load_val(&in_iter, reporters); + if (r == ABRT_DBUS_ERROR) + { + error_msg("dbus call %s: parameter type mismatch", __func__ + 7); + return -1; + } + + /* Third parameter (optional): configuration data for plugins */ + map_map_string_t user_conf_data; + if (r == ABRT_DBUS_MORE_FIELDS) + { + r = load_val(&in_iter, user_conf_data); + if (r != ABRT_DBUS_LAST_FIELD) + { + error_msg("dbus call %s: parameter type mismatch", __func__ + 7); + return -1; + } + } + +#if 0 + //const char * sender = dbus_message_get_sender(call); + if (!user_conf_data.empty()) + { + map_map_string_t::const_iterator it_user_conf_data = user_conf_data.begin(); + for (; it_user_conf_data != user_conf_data.end(); it_user_conf_data++) + { + //std::string PluginName = it_user_conf_data->first; + log("plugin:'%s'", it_user_conf_data->first.c_str()); + map_string_t::const_iterator it_plugin_config; + for (it_plugin_config = it_user_conf_data->second.begin(); + it_plugin_config != it_user_conf_data->second.end(); + it_plugin_config++) + { + log("key:'%s' val:'%s'", it_plugin_config->first.c_str(), it_plugin_config->second.c_str()); + } + // this would overwrite the default settings + //g_pPluginManager->SetPluginSettings(PluginName, sender, plugin_settings); + } + } +#endif + + long unix_uid = get_remote_uid(call); + report_status_t argout1; + try + { + argout1 = Report(argin1, reporters, user_conf_data, unix_uid); + } + catch (CABRTException &e) + { + dbus_message_unref(reply); + reply = dbus_message_new_error(call, DBUS_ERROR_FAILED, e.what()); + if (!reply) + die_out_of_memory(); + send_flush_and_unref(reply); + return 0; + } + + DBusMessageIter out_iter; + dbus_message_iter_init_append(reply, &out_iter); + store_val(&out_iter, argout1); + + send_flush_and_unref(reply); + return 0; +} + +static int handle_DeleteDebugDump(DBusMessage* call, DBusMessage* reply) +{ + int r; + DBusMessageIter in_iter; + dbus_message_iter_init(call, &in_iter); + const char* crash_id; + r = load_val(&in_iter, crash_id); + if (r != ABRT_DBUS_LAST_FIELD) + { + error_msg("dbus call %s: parameter type mismatch", __func__ + 7); + return -1; + } + + long unix_uid = get_remote_uid(call); + int32_t result = DeleteDebugDump(crash_id, unix_uid); + + DBusMessageIter out_iter; + dbus_message_iter_init_append(reply, &out_iter); + store_val(&out_iter, result); + + send_flush_and_unref(reply); + return 0; +} + +static int handle_GetPluginsInfo(DBusMessage* call, DBusMessage* reply) +{ + DBusMessageIter out_iter; + dbus_message_iter_init_append(reply, &out_iter); + store_val(&out_iter, g_pPluginManager->GetPluginsInfo()); + + send_flush_and_unref(reply); + return 0; +} + +static int handle_GetPluginSettings(DBusMessage* call, DBusMessage* reply) +{ + int r; + DBusMessageIter in_iter; + dbus_message_iter_init(call, &in_iter); + const char* PluginName; + r = load_val(&in_iter, PluginName); + if (r != ABRT_DBUS_LAST_FIELD) + { + error_msg("dbus call %s: parameter type mismatch", __func__ + 7); + return -1; + } + + //long unix_uid = get_remote_uid(call); + //VERB1 log("got %s('%s') call from uid %ld", "GetPluginSettings", PluginName, unix_uid); + map_plugin_settings_t plugin_settings = g_pPluginManager->GetPluginSettings(PluginName); //, to_string(unix_uid).c_str()); + + DBusMessageIter out_iter; + dbus_message_iter_init_append(reply, &out_iter); + store_val(&out_iter, plugin_settings); + + send_flush_and_unref(reply); + return 0; +} + +static int handle_SetPluginSettings(DBusMessage* call, DBusMessage* reply) +{ + int r; + DBusMessageIter in_iter; + dbus_message_iter_init(call, &in_iter); + std::string PluginName; + r = load_val(&in_iter, PluginName); + if (r != ABRT_DBUS_MORE_FIELDS) + { + error_msg("dbus call %s: parameter type mismatch", __func__ + 7); + return -1; + } + map_plugin_settings_t plugin_settings; + r = load_val(&in_iter, plugin_settings); + if (r != ABRT_DBUS_LAST_FIELD) + { + error_msg("dbus call %s: parameter type mismatch", __func__ + 7); + return -1; + } + + long unix_uid = get_remote_uid(call); + VERB1 log("got %s('%s',...) call from uid %ld", "SetPluginSettings", PluginName.c_str(), unix_uid); + /* Disabled, as we don't use it, we use only temporary user settings while reporting + this method should be used to change the default setting and thus should + be protected by polkit + */ + //FIXME: protect with polkit +// g_pPluginManager->SetPluginSettings(PluginName, to_string(unix_uid), plugin_settings); + + send_flush_and_unref(reply); + return 0; +} + +#ifdef PLUGIN_DYNAMIC_LOAD_UNLOAD +static int handle_RegisterPlugin(DBusMessage* call, DBusMessage* reply) +{ + int r; + DBusMessageIter in_iter; + dbus_message_iter_init(call, &in_iter); + const char* PluginName; + r = load_val(&in_iter, PluginName); + if (r != ABRT_DBUS_LAST_FIELD) + { + error_msg("dbus call %s: parameter type mismatch", __func__ + 7); + return -1; + } + + const char * sender = dbus_message_get_sender(call); + g_pPluginManager->RegisterPluginDBUS(PluginName, sender); + + send_flush_and_unref(reply); + return 0; +} + +static int handle_UnRegisterPlugin(DBusMessage* call, DBusMessage* reply) +{ + int r; + DBusMessageIter in_iter; + dbus_message_iter_init(call, &in_iter); + const char* PluginName; + r = load_val(&in_iter, PluginName); + if (r != ABRT_DBUS_LAST_FIELD) + { + error_msg("dbus call %s: parameter type mismatch", __func__ + 7); + return -1; + } + + const char * sender = dbus_message_get_sender(call); + g_pPluginManager->UnRegisterPluginDBUS(PluginName, sender); + + send_flush_and_unref(reply); + return 0; +} +#endif + +static int handle_GetSettings(DBusMessage* call, DBusMessage* reply) +{ + map_abrt_settings_t result = GetSettings(); + + DBusMessageIter out_iter; + dbus_message_iter_init_append(reply, &out_iter); + store_val(&out_iter, result); + + send_flush_and_unref(reply); + return 0; +} + +static int handle_SetSettings(DBusMessage* call, DBusMessage* reply) +{ + int r; + DBusMessageIter in_iter; + dbus_message_iter_init(call, &in_iter); + map_abrt_settings_t param1; + r = load_val(&in_iter, param1); + if (r != ABRT_DBUS_LAST_FIELD) + { + error_msg("dbus call %s: parameter type mismatch", __func__ + 7); + return -1; + } + + const char * sender = dbus_message_get_sender(call); + SetSettings(param1, sender); + + send_flush_and_unref(reply); + return 0; +} + + +/* + * Glib integration machinery + */ + +/* Callback: "a message is received to a registered object path" */ +static DBusHandlerResult message_received(DBusConnection* conn, DBusMessage* msg, void* data) +{ + const char* member = dbus_message_get_member(msg); + VERB1 log("%s(method:'%s')", __func__, member); + + set_client_name(dbus_message_get_sender(msg)); + + DBusMessage* reply = dbus_message_new_method_return(msg); + int r = -1; + if (strcmp(member, "GetCrashInfos") == 0) + r = handle_GetCrashInfos(msg, reply); + else if (strcmp(member, "StartJob") == 0) + r = handle_StartJob(msg, reply); + else if (strcmp(member, "Report") == 0) + r = handle_Report(msg, reply); + else if (strcmp(member, "DeleteDebugDump") == 0) + r = handle_DeleteDebugDump(msg, reply); + else if (strcmp(member, "CreateReport") == 0) + r = handle_CreateReport(msg, reply); + else if (strcmp(member, "GetPluginsInfo") == 0) + r = handle_GetPluginsInfo(msg, reply); + else if (strcmp(member, "GetPluginSettings") == 0) + r = handle_GetPluginSettings(msg, reply); + else if (strcmp(member, "SetPluginSettings") == 0) + r = handle_SetPluginSettings(msg, reply); +#ifdef PLUGIN_DYNAMIC_LOAD_UNLOAD + else if (strcmp(member, "RegisterPlugin") == 0) + r = handle_RegisterPlugin(msg, reply); + else if (strcmp(member, "UnRegisterPlugin") == 0) + r = handle_UnRegisterPlugin(msg, reply); +#endif + else if (strcmp(member, "GetSettings") == 0) + r = handle_GetSettings(msg, reply); + else if (strcmp(member, "SetSettings") == 0) + r = handle_SetSettings(msg, reply); +// NB: C++ binding also handles "Introspect" method, which returns a string. +// It was sending "dummy" introspection answer whick looks like this: +// "\n" +// "\n" +// "\n" +// Apart from a warning from abrt-gui, just sending error back works as well. +// NB2: we may want to handle "Disconnected" here too. + + if (r < 0) + { + /* handle_XXX experienced an error (and did not send any reply) */ + dbus_message_unref(reply); + if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_CALL) + { + /* Create and send error reply */ + reply = dbus_message_new_error(msg, DBUS_ERROR_FAILED, "not supported"); + if (!reply) + die_out_of_memory(); + send_flush_and_unref(reply); + } + } + + set_client_name(NULL); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static void handle_dbus_err(bool error_flag, DBusError *err) +{ + if (dbus_error_is_set(err)) + { + error_msg("dbus error: %s", err->message); + /* dbus_error_free(&err); */ + error_flag = true; + } + if (!error_flag) + return; + error_msg_and_die( + "Error requesting DBus name %s, possible reasons: " + "abrt run by non-root; dbus config is incorrect; " + "or dbus daemon needs to be restarted to reload dbus config", + ABRTD_DBUS_NAME); +} + +CCommLayerServerDBus::CCommLayerServerDBus() +{ + DBusConnection* conn; + DBusError err; + + dbus_error_init(&err); + VERB3 log("dbus_bus_get"); + conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); + handle_dbus_err(conn == NULL, &err); + // dbus api says: + // "If dbus_bus_get obtains a new connection object never before returned + // from dbus_bus_get(), it will call dbus_connection_set_exit_on_disconnect(), + // so the application will exit if the connection closes. You can undo this + // by calling dbus_connection_set_exit_on_disconnect() yourself after you get + // the connection." + // ... + // "When a connection is disconnected, you are guaranteed to get a signal + // "Disconnected" from the interface DBUS_INTERFACE_LOCAL, path DBUS_PATH_LOCAL" + // + // dbus-daemon drops connections if it recvs a malformed message + // (we actually observed this when we sent bad UTF-8 string). + // Currently, in this case abrtd just exits with exitcode 1. + // (symptom: last two log messages are "abrtd: remove_watch()") + // If we want to have better logging or other nontrivial handling, + // here we need to do: + // + //dbus_connection_set_exit_on_disconnect(conn, FALSE); + //dbus_connection_add_filter(conn, handle_message, NULL, NULL); + // + // and need to code up handle_message to check for "Disconnected" dbus signal + + /* Also sets g_dbus_conn to conn. */ + attach_dbus_conn_to_glib_main_loop(conn, "/com/redhat/abrt", message_received); + + VERB3 log("dbus_bus_request_name"); + int rc = dbus_bus_request_name(conn, ABRTD_DBUS_NAME, DBUS_NAME_FLAG_REPLACE_EXISTING, &err); +//maybe check that r == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER instead? + handle_dbus_err(rc < 0, &err); + VERB3 log("dbus init done"); + + /* dbus_bus_request_name can already read some data. For example, + * if we were autostarted, the call which caused autostart arrives + * at this moment. Thus while dbus fd hasn't any data anymore, + * dbus library can buffer a message or two. + * If we don't do this, the data won't be processed + * until next dbus data arrives. + */ + int cnt = 10; + while (dbus_connection_dispatch(conn) != DBUS_DISPATCH_COMPLETE && --cnt) + VERB3 log("processed initial buffered dbus message"); +} + +CCommLayerServerDBus::~CCommLayerServerDBus() +{ + dbus_connection_unref(g_dbus_conn); +} diff --git a/src/daemon/CommLayerServerDBus.h b/src/daemon/CommLayerServerDBus.h new file mode 100644 index 00000000..7ccad083 --- /dev/null +++ b/src/daemon/CommLayerServerDBus.h @@ -0,0 +1,42 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 RedHat Inc + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef COMMLAYERSERVERDBUS_H_ +#define COMMLAYERSERVERDBUS_H_ + +#include "CommLayerServer.h" + +class CCommLayerServerDBus +: public CCommLayerServer +{ + public: + CCommLayerServerDBus(); + virtual ~CCommLayerServerDBus(); + + /* DBus signal senders */ + virtual void Crash(const char *package_name, + const char *crash_id, + const char *uid_str); + virtual void JobDone(const char* peer); + virtual void QuotaExceed(const char* str); + + virtual void Update(const char* pMessage, const char* peer); + virtual void Warning(const char* pMessage, const char* peer); +}; + +#endif diff --git a/src/daemon/CrashWatcher.cpp b/src/daemon/CrashWatcher.cpp new file mode 100644 index 00000000..dad43815 --- /dev/null +++ b/src/daemon/CrashWatcher.cpp @@ -0,0 +1,236 @@ +/* + Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "abrtlib.h" +#include "Daemon.h" +#include "abrt_exception.h" +#include "debug_dump.h" +#include "CrashWatcher.h" + +void CCrashWatcher::Status(const char *pMessage, const char* peer) +{ + VERB1 log("Update('%s'): %s", peer, pMessage); + if (g_pCommLayer != NULL) + g_pCommLayer->Update(pMessage, peer); +} + +void CCrashWatcher::Warning(const char *pMessage, const char* peer) +{ + VERB1 log("Warning('%s'): %s", peer, pMessage); + if (g_pCommLayer != NULL) + g_pCommLayer->Warning(pMessage, peer); +} + +CCrashWatcher::CCrashWatcher() +{ +} + +CCrashWatcher::~CCrashWatcher() +{ +} + +vector_map_crash_data_t GetCrashInfos(long caller_uid) +{ + vector_map_crash_data_t retval; + log("Getting crash infos..."); + try + { + vector_string_t crash_ids; + GetUUIDsOfCrash(caller_uid, crash_ids); + + unsigned int ii; + for (ii = 0; ii < crash_ids.size(); ii++) + { + const char *crash_id = crash_ids[ii].c_str(); + + map_crash_data_t info; + mw_result_t res = FillCrashInfo(crash_id, info); + switch (res) + { + case MW_OK: + retval.push_back(info); + break; + case MW_ERROR: + error_msg("Dump directory for crash_id %s doesn't exist or misses crucial files, deleting", crash_id); + /* Deletes both DB record and dump dir */ + DeleteDebugDump(crash_id, /*caller_uid:*/ 0); + break; + default: + break; + } + } + } + catch (CABRTException& e) + { + error_msg("%s", e.what()); + } + + return retval; +} + +/* + * Called in two cases: + * (1) by StartJob dbus call -> CreateReportThread(), in the thread + * (2) by CreateReport dbus call + * In the second case, it finishes quickly, because previous + * StartJob dbus call already did all the processing, and we just retrieve + * the result from dump directory, which is fast. + */ +void CreateReport(const char* crash_id, long caller_uid, int force, map_crash_data_t& crashReport) +{ + /* FIXME: starting from here, any shared data must be protected with a mutex. + * For example, CreateCrashReport does: + * g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); + * which is unsafe wrt concurrent updates to g_pPluginManager state. + */ + mw_result_t res = CreateCrashReport(crash_id, caller_uid, force, crashReport); + switch (res) + { + case MW_OK: + VERB2 log_map_crash_data(crashReport, "crashReport"); + break; + case MW_IN_DB_ERROR: + error_msg("Can't find crash with id %s in database", crash_id); + break; + case MW_PLUGIN_ERROR: + error_msg("Particular analyzer plugin isn't loaded or there is an error within plugin(s)"); + break; + default: + error_msg("Corrupted crash with id %s, deleting", crash_id); + DeleteDebugDump(crash_id, /*caller_uid:*/ 0); + break; + } +} + +typedef struct thread_data_t { + pthread_t thread_id; + long caller_uid; + int force; + char* crash_id; + char* peer; +} thread_data_t; +static void* create_report(void* arg) +{ + thread_data_t *thread_data = (thread_data_t *) arg; + + /* Client name is per-thread, need to set it */ + set_client_name(thread_data->peer); + + try + { + log("Creating report..."); + map_crash_data_t crashReport; + CreateReport(thread_data->crash_id, thread_data->caller_uid, thread_data->force, crashReport); + g_pCommLayer->JobDone(thread_data->peer); + } + catch (CABRTException& e) + { + error_msg("%s", e.what()); + } + catch (...) {} + set_client_name(NULL); + + /* free strduped strings */ + free(thread_data->crash_id); + free(thread_data->peer); + free(thread_data); + + /* Bogus value. pthreads require us to return void* */ + return NULL; +} +int CreateReportThread(const char* crash_id, long caller_uid, int force, const char* pSender) +{ + thread_data_t *thread_data = (thread_data_t *)xzalloc(sizeof(thread_data_t)); + thread_data->crash_id = xstrdup(crash_id); + thread_data->caller_uid = caller_uid; + thread_data->force = force; + thread_data->peer = xstrdup(pSender); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + int r = pthread_create(&thread_data->thread_id, &attr, create_report, thread_data); + pthread_attr_destroy(&attr); + if (r != 0) + { + free(thread_data->crash_id); + free(thread_data->peer); + free(thread_data); + /* The only reason this may happen is system-wide resource starvation, + * or ulimit is exceeded (someone floods us with CreateReport() dbus calls?) + */ + error_msg("Can't create thread"); + return r; + } + VERB3 log("Thread %llx created", (unsigned long long)thread_data->thread_id); + return r; +} + + +/* Remove dump dir and its DB record */ +int DeleteDebugDump(const char *crash_id, long caller_uid) +{ + try + { + CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); + database->Connect(); + database_row_t row = database->GetRow(crash_id); + if (row.m_sUUID == "") + { + database->DisConnect(); + return ENOENT; + } + if (caller_uid != 0 /* not called by root */ + && row.m_sInformAll != "1" + && to_string(caller_uid) != row.m_sUID + ) { + database->DisConnect(); + return EPERM; + } + database->DeleteRow(crash_id); + database->DisConnect(); + const char *dump_dir = row.m_sDebugDumpDir.c_str(); + if (dump_dir[0] != '\0') + { + delete_debug_dump_dir(dump_dir); + return 0; /* success */ + } + } + catch (CABRTException& e) + { + error_msg("%s", e.what()); + } + return EIO; /* generic failure code */ +} + +void DeleteDebugDump_by_dir(const char *dump_dir) +{ + try + { + CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); + database->Connect(); + database->DeleteRows_by_dir(dump_dir); + database->DisConnect(); + + delete_debug_dump_dir(dump_dir); + } + catch (CABRTException& e) + { + error_msg("%s", e.what()); + } +} diff --git a/src/daemon/CrashWatcher.h b/src/daemon/CrashWatcher.h new file mode 100644 index 00000000..44c04137 --- /dev/null +++ b/src/daemon/CrashWatcher.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef CRASHWATCHER_H_ +#define CRASHWATCHER_H_ + +#include +#include +#include +#include +#include "MiddleWare.h" +#include "Settings.h" + +#include "CommLayerServerDBus.h" +#include "comm_layer_inner.h" + + +class CCrashWatcher +: public CObserver +{ + public: + CCrashWatcher(); + virtual ~CCrashWatcher(); + + public: + /* Observer methods */ + virtual void Status(const char *pMessage, const char* peer); + virtual void Warning(const char *pMessage, const char* peer); +}; + +vector_map_crash_data_t GetCrashInfos(long caller_uid); +int CreateReportThread(const char* crash_id, long caller_uid, int force, const char* pSender); +void CreateReport(const char* crash_id, long caller_uid, int force, map_crash_data_t&); +int DeleteDebugDump(const char *crash_id, long caller_uid); +void DeleteDebugDump_by_dir(const char *dump_dir); + +#endif diff --git a/src/daemon/Daemon.cpp b/src/daemon/Daemon.cpp new file mode 100644 index 00000000..735da5af --- /dev/null +++ b/src/daemon/Daemon.cpp @@ -0,0 +1,1000 @@ +/* + Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include /* res_init */ +#include +#include +#include /* ioctl(FIONREAD) */ +#include +#include +#include +#if HAVE_CONFIG_H + #include +#endif +#if HAVE_LOCALE_H + #include +#endif +#if ENABLE_NLS + #include + #define _(S) gettext(S) +#else + #define _(S) (S) +#endif +#include "abrtlib.h" +#include "abrt_exception.h" +#include "CrashWatcher.h" +#include "debug_dump.h" +#include "Daemon.h" +#include "dumpsocket.h" +#include "rpm.h" + +using namespace std; + + +/* Daemon initializes, then sits in glib main loop, waiting for events. + * Events can be: + * - inotify: something new appeared under /var/spool/abrt + * - DBus: dbus message arrived + * - signal: we got SIGTERM or SIGINT + * + * DBus methods we have: + * - GetCrashInfos(): returns a vector_map_crash_data_t (vector_map_vector_string_t) + * of crashes for given uid + * v[N]["executable"/"uid"/"kernel"/"backtrace"][N] = "contents" + * - StartJob(crash_id,force): starts creating a report for /var/spool/abrt/DIR with this UID:UUID. + * Returns job id (uint64). + * After thread returns, when report creation thread has finished, + * JobDone() dbus signal is emitted. + * - CreateReport(crash_id): returns map_crash_data_t (map_vector_string_t) + * - Report(map_crash_data_t (map_vector_string_t[, map_map_string_t])): + * "Please report this crash": calls Report() of all registered reporter plugins. + * Returns report_status_t (map_vector_string_t) - the status of each call. + * 2nd parameter is the contents of user's abrt.conf. + * - DeleteDebugDump(crash_id): delete it from DB and delete corresponding /var/spool/abrt/DIR + * - GetPluginsInfo(): returns map_map_string_t + * map["plugin"] = { "Name": "plugin", "Enabled": "yes" ... } + * - GetPluginSettings(PluginName): returns map_plugin_settings_t (map_string_t) + * - SetPluginSettings(PluginName, map_plugin_settings_t): returns void + * - RegisterPlugin(PluginName): returns void + * - UnRegisterPlugin(PluginName): returns void + * - GetSettings(): returns map_abrt_settings_t (map_map_string_t) + * - SetSettings(map_abrt_settings_t): returns void + * + * DBus signals we emit: + * - Crash(progname, crash_id, uid) - a new crash occurred (new /var/spool/abrt/DIR is found) + * - JobDone(client_dbus_ID) - see StartJob above. + * Sent as unicast to the client which did StartJob. + * - Warning(msg) + * - Update(msg) + * Both are sent as unicast to last client set by set_client_name(name). + * If set_client_name(NULL) was done, they are not sent. + */ + + +#define VAR_RUN_LOCK_FILE VAR_RUN"/abrt/abrtd.lock" +#define VAR_RUN_PIDFILE VAR_RUN"/abrtd.pid" + + +//FIXME: add some struct to be able to join all threads! +typedef struct cron_callback_data_t +{ + std::string m_sPluginName; + std::string m_sPluginArgs; + unsigned int m_nTimeout; + + cron_callback_data_t( + const std::string& pPluginName, + const std::string& pPluginArgs, + const unsigned int& pTimeout) : + m_sPluginName(pPluginName), + m_sPluginArgs(pPluginArgs), + m_nTimeout(pTimeout) + {} +} cron_callback_data_t; + + +static volatile sig_atomic_t s_sig_caught; +static int s_signal_pipe[2]; +static int s_signal_pipe_write = -1; +static int s_upload_watch = -1; +static unsigned s_timeout; +static bool s_exiting; + +CCommLayerServer* g_pCommLayer; + + +static void cron_delete_callback_data_cb(gpointer data) +{ + cron_callback_data_t* cronDeleteCallbackData = static_cast(data); + delete cronDeleteCallbackData; +} + +static gboolean cron_activation_periodic_cb(gpointer data) +{ + cron_callback_data_t* cronPeriodicCallbackData = static_cast(data); + VERB1 log("Activating plugin: %s", cronPeriodicCallbackData->m_sPluginName.c_str()); + RunAction(DEBUG_DUMPS_DIR, + cronPeriodicCallbackData->m_sPluginName.c_str(), + cronPeriodicCallbackData->m_sPluginArgs.c_str() + ); + return TRUE; +} +static gboolean cron_activation_one_cb(gpointer data) +{ + cron_callback_data_t* cronOneCallbackData = static_cast(data); + VERB1 log("Activating plugin: %s", cronOneCallbackData->m_sPluginName.c_str()); + RunAction(DEBUG_DUMPS_DIR, + cronOneCallbackData->m_sPluginName.c_str(), + cronOneCallbackData->m_sPluginArgs.c_str() + ); + return FALSE; +} +static gboolean cron_activation_reshedule_cb(gpointer data) +{ + cron_callback_data_t* cronResheduleCallbackData = static_cast(data); + VERB1 log("Rescheduling plugin: %s", cronResheduleCallbackData->m_sPluginName.c_str()); + cron_callback_data_t* cronPeriodicCallbackData = new cron_callback_data_t(cronResheduleCallbackData->m_sPluginName, + cronResheduleCallbackData->m_sPluginArgs, + cronResheduleCallbackData->m_nTimeout); + g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, + cronPeriodicCallbackData->m_nTimeout, + cron_activation_periodic_cb, + static_cast(cronPeriodicCallbackData), + cron_delete_callback_data_cb + ); + return FALSE; +} + +static int SetUpMW() +{ + set_string_t::iterator it_k = g_settings_setOpenGPGPublicKeys.begin(); + for (; it_k != g_settings_setOpenGPGPublicKeys.end(); it_k++) + { + LoadOpenGPGPublicKey(it_k->c_str()); + } + VERB1 log("Adding actions or reporters"); + vector_pair_string_string_t::iterator it_ar = g_settings_vectorActionsAndReporters.begin(); + for (; it_ar != g_settings_vectorActionsAndReporters.end(); it_ar++) + { + AddActionOrReporter(it_ar->first.c_str(), it_ar->second.c_str()); + } + VERB1 log("Adding analyzers, actions or reporters"); + map_analyzer_actions_and_reporters_t::iterator it_aar = g_settings_mapAnalyzerActionsAndReporters.begin(); + for (; it_aar != g_settings_mapAnalyzerActionsAndReporters.end(); it_aar++) + { + vector_pair_string_string_t::iterator it_ar = it_aar->second.begin(); + for (; it_ar != it_aar->second.end(); it_ar++) + { + AddAnalyzerActionOrReporter(it_aar->first.c_str(), it_ar->first.c_str(), it_ar->second.c_str()); + } + } + return 0; +} + +static int SetUpCron() +{ + map_cron_t::iterator it_c = g_settings_mapCron.begin(); + for (; it_c != g_settings_mapCron.end(); it_c++) + { + std::string::size_type pos = it_c->first.find(":"); + int timeout = 0; + int nH = -1; + int nM = -1; + int nS = -1; + +//TODO: rewrite using good old sscanf? + + if (pos != std::string::npos) + { + std::string sH; + std::string sM; + + sH = it_c->first.substr(0, pos); + nH = xatou(sH.c_str()); + nH = nH > 23 ? 23 : nH; + nH = nH < 0 ? 0 : nH; + timeout += nH * 60 * 60; + sM = it_c->first.substr(pos + 1); + nM = xatou(sM.c_str()); + nM = nM > 59 ? 59 : nM; + nM = nM < 0 ? 0 : nM; + timeout += nM * 60; + } + else + { + std::string sS; + + sS = it_c->first; + nS = xatou(sS.c_str()); + nS = nS <= 0 ? 1 : nS; + timeout = nS; + } + + if (nS != -1) + { + vector_pair_string_string_t::iterator it_ar = it_c->second.begin(); + for (; it_ar != it_c->second.end(); it_ar++) + { + cron_callback_data_t* cronPeriodicCallbackData = new cron_callback_data_t(it_ar->first, it_ar->second, timeout); + g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, + timeout, + cron_activation_periodic_cb, + static_cast(cronPeriodicCallbackData), + cron_delete_callback_data_cb); + } + } + else + { + time_t actTime = time(NULL); + struct tm locTime; + localtime_r(&actTime, &locTime); + locTime.tm_hour = nH; + locTime.tm_min = nM; + locTime.tm_sec = 0; + time_t nextTime = mktime(&locTime); + if (nextTime == ((time_t)-1)) + { + /* paranoia */ + perror_msg("Can't set up cron time"); + return -1; + } + if (actTime > nextTime) + { + timeout = 24*60*60 + (nextTime - actTime); + } + else + { + timeout = nextTime - actTime; + } + vector_pair_string_string_t::iterator it_ar = it_c->second.begin(); + for (; it_ar != it_c->second.end(); it_ar++) + { + cron_callback_data_t* cronOneCallbackData = new cron_callback_data_t(it_ar->first, it_ar->second, timeout); + g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, + timeout, + cron_activation_one_cb, + static_cast(cronOneCallbackData), + cron_delete_callback_data_cb); + cron_callback_data_t* cronResheduleCallbackData = new cron_callback_data_t(it_ar->first, it_ar->second, 24 * 60 * 60); + g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, + timeout, + cron_activation_reshedule_cb, + static_cast(cronResheduleCallbackData), + cron_delete_callback_data_cb); + } + } + } + return 0; +} + +static void FindNewDumps(const char* pPath) +{ + /* Get all debugdump directories in the pPath directory */ + vector_string_t dirs; + DIR *dp = opendir(pPath); + if (dp == NULL) + { + perror_msg("Can't open directory '%s'", pPath); + return; + } + struct dirent *ep; + while ((ep = readdir(dp))) + { + if (dot_or_dotdot(ep->d_name)) + continue; /* skip "." and ".." */ + std::string dname = concat_path_file(pPath, ep->d_name); + struct stat stats; + if (lstat(dname.c_str(), &stats) == 0) + { + if (S_ISDIR(stats.st_mode)) + { + VERB1 log("Will check directory '%s'", ep->d_name); + dirs.push_back(dname); + } + } + } + closedir(dp); + + unsigned size = dirs.size(); + if (size == 0) + return; + log("Checking for unsaved crashes (dirs to check:%u)", size); + + /* Get potentially non-processed debugdumps */ + vector_string_t::iterator itt = dirs.begin(); + for (; itt != dirs.end(); ++itt) + { + try + { + const char *dir_name = itt->c_str(); + map_crash_data_t crashinfo; + mw_result_t res = SaveDebugDump(dir_name, crashinfo); + switch (res) + { + case MW_OK: + /* Not VERB1: this is new, unprocessed crash dump. + * Last abrtd somehow missed it - need to inform user */ + log("Non-processed crash in %s, saving into database", dir_name); + /* Run automatic actions and reporters on it (if we have them configured) */ + RunActionsAndReporters(dir_name); + break; + case MW_IN_DB: + /* This debugdump was found in DB, nothing else was done + * by SaveDebugDump or needs to be done by us */ + VERB1 log("%s is already saved in database", dir_name); + break; + case MW_REPORTED: /* already reported dup */ + case MW_OCCURRED: /* not-yet-reported dup */ + VERB1 log("Duplicate crash %s, deleting", dir_name); + delete_debug_dump_dir(dir_name); + break; + default: + log("Corrupted or bad crash %s (res:%d), deleting", dir_name, (int)res); + delete_debug_dump_dir(dir_name); + break; + } + } + catch (CABRTException& e) + { + error_msg("%s", e.what()); + } + } + log("Done checking for unsaved crashes"); +} + +static int CreatePidFile() +{ + int fd; + + /* JIC */ + unlink(VAR_RUN_PIDFILE); + + /* open the pidfile */ + fd = open(VAR_RUN_PIDFILE, O_WRONLY|O_CREAT|O_EXCL, 0644); + if (fd >= 0) + { + /* write our pid to it */ + char buf[sizeof(long)*3 + 2]; + int len = sprintf(buf, "%lu\n", (long)getpid()); + write(fd, buf, len); + close(fd); + return 0; + } + + /* something went wrong */ + perror_msg("Can't open '%s'", VAR_RUN_PIDFILE); + return -1; +} + +static int Lock() +{ + int lfd = open(VAR_RUN_LOCK_FILE, O_RDWR|O_CREAT, 0640); + if (lfd < 0) + { + perror_msg("Can't open '%s'", VAR_RUN_LOCK_FILE); + return -1; + } + if (lockf(lfd, F_TLOCK, 0) < 0) + { + perror_msg("Can't lock file '%s'", VAR_RUN_LOCK_FILE); + return -1; + } + close_on_exec_on(lfd); + return 0; + /* we leak opened lfd intentionally */ +} + +static void handle_fatal_signal(int signo) +{ + // Enable for debugging only, malloc/printf are unsafe in signal handlers + //VERB3 log("Got signal %d", signo); + + uint8_t l_sig_caught; + s_sig_caught = l_sig_caught = signo; + /* Using local copy of s_sig_caught so that concurrent signal + * won't change it under us */ + if (s_signal_pipe_write >= 0) + write(s_signal_pipe_write, &l_sig_caught, 1); +} + +/* Signal pipe handler */ +static gboolean handle_signal_cb(GIOChannel *gio, GIOCondition condition, gpointer ptr_unused) +{ + char signo; + gsize len = 0; + g_io_channel_read(gio, &signo, 1, &len); + if (len == 1) + { + /* we did receive a signal */ + VERB3 log("Got signal %d through signal pipe", signo); + s_exiting = 1; + return TRUE; + } + return FALSE; +} + +/* Inotify handler */ +static gboolean handle_inotify_cb(GIOChannel *gio, GIOCondition condition, gpointer ptr_unused) +{ + /* Default size: 128 simultaneous actions (about 1/2 meg) */ +#define INOTIFY_BUF_SIZE ((sizeof(struct inotify_event) + FILENAME_MAX)*128) + /* Determine how much to read (it usually is much smaller) */ + /* NB: this variable _must_ be int-sized, ioctl expects that! */ + int inotify_bytes = INOTIFY_BUF_SIZE; + if (ioctl(g_io_channel_unix_get_fd(gio), FIONREAD, &inotify_bytes) != 0 + || inotify_bytes < sizeof(struct inotify_event) + || inotify_bytes > INOTIFY_BUF_SIZE + ) { + inotify_bytes = INOTIFY_BUF_SIZE; + } + VERB3 log("FIONREAD:%d", inotify_bytes); + + char *buf = (char*)xmalloc(inotify_bytes); + errno = 0; + gsize len; + GIOError err = g_io_channel_read(gio, buf, inotify_bytes, &len); + if (err != G_IO_ERROR_NONE) + { + perror_msg("Error reading inotify fd"); + free(buf); + return FALSE; + } + + /* Reconstruct each event and send message to the dbus */ + gsize i = 0; + while (i < len) + { + struct inotify_event *event = (struct inotify_event *) &buf[i]; + const char *name = NULL; + if (event->len) + name = event->name; + //log("i:%d len:%d event->mask:%x IN_ISDIR:%x IN_CLOSE_WRITE:%x event->len:%d", + // i, len, event->mask, IN_ISDIR, IN_CLOSE_WRITE, event->len); + i += sizeof(*event) + event->len; + + if (event->wd == s_upload_watch) + { + /* Was the (presumable newly created) file closed in upload dir, + * or a file moved to upload dir? */ + if (!(event->mask & IN_ISDIR) + && event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO) + && name + ) { + const char *ext = strrchr(name, '.'); + if (ext && strcmp(ext + 1, "working") == 0) + continue; + + const char *dir = g_settings_sWatchCrashdumpArchiveDir.c_str(); + log("Detected creation of file '%s' in upload directory '%s'", name, dir); + if (fork() == 0) + { + xchdir(dir); + execlp("abrt-handle-upload", "abrt-handle-upload", DEBUG_DUMPS_DIR, dir, name, (char*)NULL); + error_msg_and_die("Can't execute '%s'", "abrt-handle-upload"); + } + } + continue; + } + + if (!(event->mask & IN_ISDIR) || !name) + { + /* ignore lock files and such */ + // Happens all the time during normal run + //VERB3 log("File '%s' creation detected, ignoring", name); + continue; + } + if (strcmp(strchrnul(name, '.'), ".new") == 0) + { + //VERB3 log("Directory '%s' creation detected, ignoring", name); + continue; + } + log("Directory '%s' creation detected", name); + + if (g_settings_nMaxCrashReportsSize > 0) + { + std::string worst_dir; + while (g_settings_nMaxCrashReportsSize > 0 + && get_dirsize_find_largest_dir(DEBUG_DUMPS_DIR, &worst_dir, name) / (1024*1024) >= g_settings_nMaxCrashReportsSize + && worst_dir != "" + ) { + log("Size of '%s' >= %u MB, deleting '%s'", DEBUG_DUMPS_DIR, g_settings_nMaxCrashReportsSize, worst_dir.c_str()); + g_pCommLayer->QuotaExceed(_("The size of the report exceeded the quota. Please check system's MaxCrashReportsSize value in abrt.conf.")); + /* deletes both directory and DB record */ + DeleteDebugDump_by_dir(concat_path_file(DEBUG_DUMPS_DIR, worst_dir.c_str()).c_str()); + worst_dir = ""; + } + } + + try + { + std::string fullname = concat_path_file(DEBUG_DUMPS_DIR, name); + /* Note: SaveDebugDump does not save crashinfo, it _fetches_ crashinfo */ + map_crash_data_t crashinfo; + mw_result_t res = SaveDebugDump(fullname.c_str(), crashinfo); + switch (res) + { + case MW_OK: + log("New crash %s, processing", fullname.c_str()); + /* Run automatic actions and reporters on it (if we have them configured) */ + RunActionsAndReporters(fullname.c_str()); + /* Fall through */ + + case MW_REPORTED: /* already reported dup */ + case MW_OCCURRED: /* not-yet-reported dup */ + { + if (res != MW_OK) + { + const char *first = get_crash_data_item_content(crashinfo, CD_DUMPDIR).c_str(); + log("Deleting crash %s (dup of %s), sending dbus signal", + strrchr(fullname.c_str(), '/') + 1, + strrchr(first, '/') + 1); + delete_debug_dump_dir(fullname.c_str()); + } +#define fullname fullname_should_not_be_used_here + + const char *analyzer = get_crash_data_item_content(crashinfo, FILENAME_ANALYZER).c_str(); + const char *uid_str = get_crash_data_item_content(crashinfo, CD_UID).c_str(); + + /* Autoreport it if configured to do so */ + if (res != MW_REPORTED + && analyzer_has_AutoReportUIDs(analyzer, uid_str) + ) { + VERB1 log("Reporting the crash automatically"); + map_crash_data_t crash_report; + string crash_id = ssprintf("%s:%s", uid_str, get_crash_data_item_content(crashinfo, CD_UUID).c_str()); + mw_result_t crash_result = CreateCrashReport( + crash_id.c_str(), + /*caller_uid:*/ 0, + /*force:*/ 0, + crash_report + ); + if (crash_result == MW_OK) + { + map_analyzer_actions_and_reporters_t::const_iterator it = g_settings_mapAnalyzerActionsAndReporters.find(analyzer); + map_analyzer_actions_and_reporters_t::const_iterator end = g_settings_mapAnalyzerActionsAndReporters.end(); + if (it != end) + { + vector_pair_string_string_t keys = it->second; + unsigned size = keys.size(); + for (unsigned ii = 0; ii < size; ii++) + { + autoreport(keys[ii], crash_report); + } + } + } + } + /* Send dbus signal */ + if (analyzer_has_InformAllUsers(analyzer)) + uid_str = NULL; + char *crash_id = xasprintf("%s:%s", + get_crash_data_item_content(crashinfo, CD_UID).c_str(), + get_crash_data_item_content(crashinfo, CD_UUID).c_str() + ); + g_pCommLayer->Crash(get_crash_data_item_content(crashinfo, FILENAME_PACKAGE).c_str(), + crash_id, + uid_str); + free(crash_id); + break; +#undef fullname + } + case MW_IN_DB: + log("Huh, this crash is already in db?! Nothing to do"); + break; + case MW_BLACKLISTED: + case MW_CORRUPTED: + case MW_PACKAGE_ERROR: + case MW_GPG_ERROR: + case MW_FILE_ERROR: + default: + log("Corrupted or bad crash %s (res:%d), deleting", fullname.c_str(), (int)res); + delete_debug_dump_dir(fullname.c_str()); + break; + } + } + catch (CABRTException& e) + { + error_msg("%s", e.what()); + } + catch (...) + { + free(buf); + throw; + } + } /* while */ + + free(buf); + return TRUE; +} + +/* Run main loop with idle timeout. + * Basically, almost like glib's g_main_run(loop) + */ +static void run_main_loop(GMainLoop* loop) +{ + GMainContext *context = g_main_loop_get_context(loop); + time_t old_time = 0; + time_t dns_conf_hash = 0; + + while (!s_exiting) + { + /* we have just a handful of sources, 32 should be ample */ + const unsigned NUM_POLLFDS = 32; + GPollFD fds[NUM_POLLFDS]; + gboolean some_ready; + gint max_priority; + gint timeout; + + some_ready = g_main_context_prepare(context, &max_priority); + if (some_ready) + g_main_context_dispatch(context); + + gint nfds = g_main_context_query(context, max_priority, &timeout, fds, NUM_POLLFDS); + if (nfds > NUM_POLLFDS) + error_msg_and_die("Internal error"); + + if (s_timeout) + alarm(s_timeout); + g_poll(fds, nfds, timeout); + if (s_timeout) + alarm(0); + + /* res_init() makes glibc reread /etc/resolv.conf. + * I'd think libc should be clever enough to do it itself + * at every name resolution attempt, but no... + * We need to guess ourself whether we want to do it. + */ + time_t now = time(NULL) >> 2; + if (old_time != now) /* check once in 4 seconds */ + { + old_time = now; + + time_t hash = 0; + struct stat sb; + if (stat("/etc/resolv.conf", &sb) == 0) + hash = sb.st_mtime; + if (stat("/etc/host.conf", &sb) == 0) + hash += sb.st_mtime; + if (stat("/etc/hosts", &sb) == 0) + hash += sb.st_mtime; + if (stat("/etc/nsswitch.conf", &sb) == 0) + hash += sb.st_mtime; + if (dns_conf_hash != hash) + { + dns_conf_hash = hash; + res_init(); + } + } + + some_ready = g_main_context_check(context, max_priority, fds, nfds); + if (some_ready) + g_main_context_dispatch(context); + } + + g_main_context_unref(context); +} + +static void start_syslog_logging() +{ + /* Open stdin to /dev/null */ + xmove_fd(xopen("/dev/null", O_RDWR), STDIN_FILENO); + /* We must not leave fds 0,1,2 closed. + * Otherwise fprintf(stderr) dumps messages into random fds, etc. */ + xdup2(STDIN_FILENO, STDOUT_FILENO); + xdup2(STDIN_FILENO, STDERR_FILENO); + openlog("abrtd", 0, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; +} + +static void ensure_writable_dir(const char *dir, mode_t mode, const char *user) +{ + struct stat sb; + + if (mkdir(dir, mode) != 0 && errno != EEXIST) + perror_msg_and_die("Can't create '%s'", dir); + if (stat(dir, &sb) != 0 || !S_ISDIR(sb.st_mode)) + error_msg_and_die("'%s' is not a directory", dir); + + struct passwd *pw = getpwnam(user); + if (!pw) + perror_msg_and_die("Can't find user '%s'", user); + + if ((sb.st_uid != pw->pw_uid || sb.st_gid != pw->pw_gid) && chown(dir, pw->pw_uid, pw->pw_gid) != 0) + perror_msg_and_die("Can't set owner %u:%u on '%s'", (unsigned int)pw->pw_uid, (unsigned int)pw->pw_gid, dir); + if ((sb.st_mode & 07777) != mode && chmod(dir, mode) != 0) + perror_msg_and_die("Can't set mode %o on '%s'", mode, dir); +} + +static void sanitize_dump_dir_rights() +{ + /* We can't allow everyone to create dumps: otherwise users can flood + * us with thousands of bogus or malicious dumps */ + /* 07000 bits are setuid, setgit, and sticky, and they must be unset */ + /* 00777 bits are usual "rwxrwxrwx" access rights */ + ensure_writable_dir(DEBUG_DUMPS_DIR, 0755, "abrt"); + /* debuginfo cache */ + ensure_writable_dir(DEBUG_INFO_DIR, 0755, "root"); + /* temp dir */ + ensure_writable_dir(VAR_RUN"/abrt", 0755, "root"); +} + +int main(int argc, char** argv) +{ + bool daemonize = true; + int opt; + int parent_pid = getpid(); + + setlocale(LC_ALL, ""); + +#if ENABLE_NLS + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + + if (getuid() != 0) + error_msg_and_die("ABRT daemon must be run as root"); + + while ((opt = getopt(argc, argv, "dsvt:")) != -1) + { + unsigned long ul; + + switch (opt) + { + case 'd': + daemonize = false; + break; + case 's': + start_syslog_logging(); + break; + case 'v': + g_verbose++; + break; + case 't': + char *end; + errno = 0; + s_timeout = ul = strtoul(optarg, &end, 0); + if (errno == 0 && *end == '\0' && ul <= INT_MAX) + break; + /* fall through to error */ + default: + error_msg_and_die( + "Usage: abrtd [-dsv] [-t SEC]\n" + "\nOptions:" + "\n\t-d\tDo not daemonize" + "\n\t-s\tLog to syslog even with -d" + "\n\t-t SEC\tExit after SEC seconds of inactivity" + "\n\t-v\tVerbose" + ); + } + } + + msg_prefix = "abrtd: "; /* for log(), error_msg() and such */ + + xpipe(s_signal_pipe); + close_on_exec_on(s_signal_pipe[0]); + close_on_exec_on(s_signal_pipe[1]); + signal(SIGTERM, handle_fatal_signal); + signal(SIGINT, handle_fatal_signal); + if (s_timeout) + signal(SIGALRM, handle_fatal_signal); + + /* Daemonize unless -d */ + if (daemonize) + { + /* forking to background */ + pid_t pid = fork(); + if (pid < 0) + { + perror_msg_and_die("fork"); + } + if (pid > 0) + { + /* Parent */ + /* Wait for child to notify us via SIGTERM that it feels ok */ + int i = 20; /* 2 sec */ + while (s_sig_caught == 0 && --i) + { + usleep(100 * 1000); + } + if (s_sig_caught == SIGTERM) + { + exit(0); + } + if (s_sig_caught) + { + error_msg_and_die("Failed to start: got sig %d", s_sig_caught); + } + error_msg_and_die("Failed to start: timeout waiting for child"); + } + /* Child (daemon) continues */ + setsid(); /* never fails */ + if (g_verbose == 0 && logmode != LOGMODE_SYSLOG) + start_syslog_logging(); + } + + GMainLoop* pMainloop = NULL; + GIOChannel* channel_inotify = NULL; + guint channel_inotify_event_id = 0; + GIOChannel* channel_signal = NULL; + guint channel_signal_event_id = 0; + bool lockfile_created = false; + bool pidfile_created = false; + CCrashWatcher watcher; + + /* Initialization */ + try + { + init_daemon_logging(&watcher); + + VERB1 log("Loading settings"); + if (LoadSettings() != 0) + throw 1; + + VERB1 log("Initializing XML-RPC library"); + xmlrpc_env env; + xmlrpc_env_init(&env); + xmlrpc_client_setup_global_const(&env); + if (env.fault_occurred) + error_msg_and_die("XML-RPC Fault: %s(%d)", env.fault_string, env.fault_code); + + VERB1 log("Initializing rpm library"); + rpm_init(); + + VERB1 log("Creating glib main loop"); + pMainloop = g_main_loop_new(NULL, FALSE); + /* Watching DEBUG_DUMPS_DIR for new files... */ + + VERB1 log("Initializing inotify"); + sanitize_dump_dir_rights(); + errno = 0; + int inotify_fd = inotify_init(); + if (inotify_fd == -1) + perror_msg_and_die("inotify_init failed"); + close_on_exec_on(inotify_fd); + if (inotify_add_watch(inotify_fd, DEBUG_DUMPS_DIR, IN_CREATE | IN_MOVED_TO) < 0) + perror_msg_and_die("inotify_add_watch failed on '%s'", DEBUG_DUMPS_DIR); + if (!g_settings_sWatchCrashdumpArchiveDir.empty()) + { + s_upload_watch = inotify_add_watch(inotify_fd, g_settings_sWatchCrashdumpArchiveDir.c_str(), IN_CLOSE_WRITE|IN_MOVED_TO); + if (s_upload_watch < 0) + perror_msg_and_die("inotify_add_watch failed on '%s'", g_settings_sWatchCrashdumpArchiveDir.c_str()); + } + VERB1 log("Adding inotify watch to glib main loop"); + channel_inotify = g_io_channel_unix_new(inotify_fd); + channel_inotify_event_id = g_io_add_watch(channel_inotify, + G_IO_IN, + handle_inotify_cb, + NULL); + + VERB1 log("Loading plugins from "PLUGINS_LIB_DIR); + g_pPluginManager = new CPluginManager(); + g_pPluginManager->LoadPlugins(); + + if (SetUpMW() != 0) /* logging is inside */ + throw 1; + if (SetUpCron() != 0) + throw 1; + + /* Add an event source which waits for INT/TERM signal */ + VERB1 log("Adding signal pipe watch to glib main loop"); + channel_signal = g_io_channel_unix_new(s_signal_pipe[0]); + channel_signal_event_id = g_io_add_watch(channel_signal, + G_IO_IN, + handle_signal_cb, + NULL); + + /* Mark the territory */ + VERB1 log("Creating lock file"); + if (Lock() != 0) + throw 1; + lockfile_created = true; + VERB1 log("Creating pid file"); + if (CreatePidFile() != 0) + throw 1; + pidfile_created = true; + + /* Open socket to receive new crashes. */ + dumpsocket_init(); + + /* Note: this already may process a few dbus messages, + * therefore it should be the last thing to initialize. + */ + VERB1 log("Initializing dbus"); + g_pCommLayer = new CCommLayerServerDBus(); + if (g_pCommLayer->m_init_error) + throw 1; + } + catch (...) + { + /* Initialization error */ + error_msg("Error while initializing daemon"); + /* Inform parent that initialization failed */ + if (daemonize) + kill(parent_pid, SIGINT); + goto cleanup; + } + + /* Inform parent that we initialized ok */ + if (daemonize) + { + VERB1 log("Signalling parent"); + kill(parent_pid, SIGTERM); + if (logmode != LOGMODE_SYSLOG) + start_syslog_logging(); + } + + /* Only now we want signal pipe to work */ + s_signal_pipe_write = s_signal_pipe[1]; + + /* Enter the event loop */ + try + { + /* This may take a while, therefore we don't do it in init section */ + FindNewDumps(DEBUG_DUMPS_DIR); + log("Init complete, entering main loop"); + run_main_loop(pMainloop); + } + catch (CABRTException& e) + { + error_msg("Error: %s", e.what()); + } + catch (std::exception& e) + { + error_msg("Error: %s", e.what()); + } + + cleanup: + /* Error or INT/TERM. Clean up, in reverse order. + * Take care to not undo things we did not do. + */ + dumpsocket_shutdown(); + rpm_destroy(); + if (pidfile_created) + unlink(VAR_RUN_PIDFILE); + if (lockfile_created) + unlink(VAR_RUN_LOCK_FILE); + + if (channel_signal_event_id > 0) + g_source_remove(channel_signal_event_id); + if (channel_signal) + g_io_channel_unref(channel_signal); + if (channel_inotify_event_id > 0) + g_source_remove(channel_inotify_event_id); + if (channel_inotify) + g_io_channel_unref(channel_inotify); + + delete g_pCommLayer; + if (g_pPluginManager) + { + /* This restores /proc/sys/kernel/core_pattern, among other things: */ + g_pPluginManager->UnLoadPlugins(); + delete g_pPluginManager; + } + if (pMainloop) + g_main_loop_unref(pMainloop); + + /* Exiting */ + if (s_sig_caught && s_sig_caught != SIGALRM) + { + error_msg_and_die("Got signal %d, exiting", s_sig_caught); + signal(s_sig_caught, SIG_DFL); + raise(s_sig_caught); + } + error_msg_and_die("Exiting"); +} diff --git a/src/daemon/Daemon.h b/src/daemon/Daemon.h new file mode 100644 index 00000000..62088651 --- /dev/null +++ b/src/daemon/Daemon.h @@ -0,0 +1,36 @@ +/* + Copyright (C) 2009 Denys Vlasenko (dvlasenk@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef DAEMON_H_ +#define DAEMON_H_ + +#include +#include "abrt_types.h" +#include "crash_types.h" + +class CCrashWatcher; +class CCommLayerServer; +class CPluginManager; + +/* Used for sending dbus signals */ +extern CCommLayerServer *g_pCommLayer; + +/* Collection of loaded plugins */ +extern CPluginManager* g_pPluginManager; + +#endif diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am new file mode 100644 index 00000000..7dacc604 --- /dev/null +++ b/src/daemon/Makefile.am @@ -0,0 +1,65 @@ +bin_SCRIPTS = abrt-debuginfo-install abrt-handle-upload + +sbin_PROGRAMS = abrtd + +abrtd_SOURCES = \ + PluginManager.h PluginManager.cpp \ + rpm.h rpm.c \ + MiddleWare.h MiddleWare.cpp \ + CrashWatcher.h CrashWatcher.cpp \ + CommLayerServer.h CommLayerServer.cpp \ + CommLayerServerDBus.h CommLayerServerDBus.cpp \ + Daemon.h Daemon.cpp \ + Settings.h Settings.cpp \ + dumpsocket.h dumpsocket.cpp +abrtd_CPPFLAGS = \ + -I$(srcdir)/../../inc \ + -I$(srcdir)/../../lib/utils \ + -DBIN_DIR=\"$(bindir)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(XMLRPC_CFLAGS) $(XMLRPC_CLIENT_CFLAGS) \ + $(ENABLE_SOCKET_OR_DBUS) \ + -D_GNU_SOURCE \ + -Wall +abrtd_LDADD = \ + ../../lib/utils/libABRTUtils.la \ + ../../lib/utils/libABRTdUtils.la \ + $(DL_LIBS) \ + $(DBUS_LIBS) \ + $(RPM_LIBS) \ + $(XMLRPC_LIBS) $(XMLRPC_CLIENT_LIBS) + +dbusabrtconfdir = ${sysconfdir}/dbus-1/system.d/ +dist_dbusabrtconf_DATA = dbus-abrt.conf + +daemonconfdir = $(CONF_DIR) +dist_daemonconf_DATA = \ + abrt.conf \ + gpg_keys + +polkitconfdir = ${datadir}/polkit-1/actions +dist_polkitconf_DATA = org.fedoraproject.abrt.policy + +comredhatabrtservicedir = ${datadir}/dbus-1/system-services +dist_comredhatabrtservice_DATA = com.redhat.abrt.service + +man_MANS = abrtd.8 abrt.conf.5 + +EXTRA_DIST = $(man_MANS) abrt-debuginfo-install abrt-handle-upload + +if HAVE_SYSTEMD +dist_systemdsystemunit_DATA = \ + abrtd.service +endif + +DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ + +install-data-local: + $(mkdir_p) '$(DESTDIR)/$(VAR_RUN)' diff --git a/src/daemon/MiddleWare.cpp b/src/daemon/MiddleWare.cpp new file mode 100644 index 00000000..c7ed4df5 --- /dev/null +++ b/src/daemon/MiddleWare.cpp @@ -0,0 +1,1132 @@ +/* + MiddleWare.cpp + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include +#include "abrtlib.h" +#include "abrt_types.h" +#include "Daemon.h" +#include "Settings.h" +#include "rpm.h" +#include "debug_dump.h" +#include "abrt_exception.h" +#include "abrt_packages.h" +#include "comm_layer_inner.h" +#include "MiddleWare.h" + +using namespace std; + +/** + * An instance of CPluginManager. When MiddleWare wants to do something + * with plugins, it calls the plugin manager. + * @see PluginManager.h + */ +CPluginManager* g_pPluginManager; + +/** + * A map, which associates particular analyzer to one or more + * action or reporter plugins. These are activated when a crash, which + * is maintained by particular analyzer, occurs. + */ +typedef std::map map_analyzer_actions_and_reporters_t; +static map_analyzer_actions_and_reporters_t s_mapAnalyzerActionsAndReporters; +/** + * A vector of one or more action or reporter plugins. These are + * activated when any crash occurs. + */ +static vector_pair_string_string_t s_vectorActionsAndReporters; + + +static void RunAnalyzerActions(const char *pAnalyzer, const char* pPackageName, const char *pDebugDumpDir, int force); + + +static char* is_text_file(const char *name, ssize_t *sz) +{ + /* We were using magic.h API to check for file being text, but it thinks + * that file containing just "0" is not text (!!) + * So, we do it ourself. + */ + + int fd = open(name, O_RDONLY); + if (fd < 0) + return NULL; /* it's not text (because it does not exist! :) */ + + char *buf = (char*)xmalloc(*sz); + ssize_t r = *sz = full_read(fd, buf, *sz); + close(fd); + if (r < 0) + { + free(buf); + return NULL; /* it's not text (because we can't read it) */ + } + + /* Some files in our dump directories are known to always be textual */ + const char *base = strrchr(name, '/'); + if (base) + { + base++; + if (strcmp(base, FILENAME_BACKTRACE) == 0 + || strcmp(base, FILENAME_CMDLINE) == 0 + ) { + return buf; + } + } + + /* Every once in a while, even a text file contains a few garbled + * or unexpected non-ASCII chars. We should not declare it "binary". + */ + const unsigned RATIO = 50; + unsigned total_chars = r + RATIO; + unsigned bad_chars = 1; /* 1 prevents division by 0 later */ + while (--r >= 0) + { + if (buf[r] >= 0x7f + /* among control chars, only '\t','\n' etc are allowed */ + || (buf[r] < ' ' && !isspace(buf[r])) + ) { + if (buf[r] == '\0') + { + /* We don't like NULs very much. Not text for sure! */ + free(buf); + return NULL; + } + bad_chars++; + } + } + + if ((total_chars / bad_chars) >= RATIO) + return buf; /* looks like text to me */ + + free(buf); + return NULL; /* it's binary */ +} + +static void load_crash_data_from_debug_dump(CDebugDump& dd, map_crash_data_t& data) +{ + std::string short_name; + std::string full_name; + + dd.InitGetNextFile(); + while (dd.GetNextFile(&short_name, &full_name)) + { + ssize_t sz = 4*1024; + char *text = NULL; + bool editable = is_editable_file(short_name.c_str()); + + if (!editable) + { + text = is_text_file(full_name.c_str(), &sz); + if (!text) + { + add_to_crash_data_ext(data, + short_name.c_str(), + CD_BIN, + CD_ISNOTEDITABLE, + full_name.c_str() + ); + continue; + } + } + + std::string content; + if (sz < 4*1024) /* is_text_file did read entire file */ + content.assign(text, sz); + else /* no, need to read it all */ + dd.LoadText(short_name.c_str(), content); + free(text); + + add_to_crash_data_ext(data, + short_name.c_str(), + CD_TXT, + editable ? CD_ISEDITABLE : CD_ISNOTEDITABLE, + content.c_str() + ); + } +} + +/** + * Transforms a debugdump directory to inner crash + * report form. This form is used for later reporting. + * @param pDebugDumpDir A debugdump dir containing all necessary data. + * @param pCrashData A created crash report. + */ +static void DebugDumpToCrashReport(const char *pDebugDumpDir, map_crash_data_t& pCrashData) +{ + VERB3 log(" DebugDumpToCrashReport('%s')", pDebugDumpDir); + + CDebugDump dd; + dd.Open(pDebugDumpDir); + + const char *const *v = must_have_files; + while (*v) + { + if (!dd.Exist(*v)) + { + throw CABRTException(EXCEP_ERROR, "DebugDumpToCrashReport(): important file '%s' is missing", *v); + } + v++; + } + + load_crash_data_from_debug_dump(dd, pCrashData); +} + +/** + * Get a local UUID from particular analyzer plugin. + * @param pAnalyzer A name of an analyzer plugin. + * @param pDebugDumpDir A debugdump dir containing all necessary data. + * @return A local UUID. + */ +static std::string GetLocalUUID(const char *pAnalyzer, const char *pDebugDumpDir) +{ + CAnalyzer* analyzer = g_pPluginManager->GetAnalyzer(pAnalyzer); + if (analyzer) + { + return analyzer->GetLocalUUID(pDebugDumpDir); + } + throw CABRTException(EXCEP_PLUGIN, "Error running '%s'", pAnalyzer); +} + +/** + * Get a global UUID from particular analyzer plugin. + * @param pAnalyzer A name of an analyzer plugin. + * @param pDebugDumpDir A debugdump dir containing all necessary data. + * @return A global UUID. + */ +static std::string GetGlobalUUID(const char *pAnalyzer, + const char *pDebugDumpDir) +{ + CAnalyzer* analyzer = g_pPluginManager->GetAnalyzer(pAnalyzer); + if (analyzer) + { + return analyzer->GetGlobalUUID(pDebugDumpDir); + } + throw CABRTException(EXCEP_PLUGIN, "Error running '%s'", pAnalyzer); +} + +/** + * Take care of getting all additional data needed + * for computing UUIDs and creating a report for particular analyzer + * plugin. This report could be send somewhere afterwards. + * @param pAnalyzer A name of an analyzer plugin. + * @param pDebugDumpPath A debugdump dir containing all necessary data. + */ +static void run_analyser_CreateReport(const char *pAnalyzer, + const char *pDebugDumpDir, + int force) +{ + CAnalyzer* analyzer = g_pPluginManager->GetAnalyzer(pAnalyzer); + if (analyzer) + { + analyzer->CreateReport(pDebugDumpDir, force); + } + /* else: GetAnalyzer() already complained, no need to handle it here */ +} + +/* + * Called in three cases: + * (1) by StartJob dbus call -> CreateReportThread(), in the thread + * (2) by CreateReport dbus call + * (3) by daemon if AutoReportUID is set for this user's crashes + */ +mw_result_t CreateCrashReport(const char *crash_id, + long caller_uid, + int force, + map_crash_data_t& pCrashData) +{ + VERB2 log("CreateCrashReport('%s',%ld,result)", crash_id, caller_uid); + + database_row_t row; + CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); + database->Connect(); + row = database->GetRow(crash_id); + database->DisConnect(); + if (row.m_sUUID == "") + { + error_msg("crash '%s' is not in database", crash_id); + return MW_IN_DB_ERROR; + } + if (caller_uid != 0 /* not called by root */ + && row.m_sInformAll != "1" + && to_string(caller_uid) != row.m_sUID + ) { + error_msg("crash '%s' can't be accessed by user with uid %ld", crash_id, caller_uid); + return MW_IN_DB_ERROR; + } + + mw_result_t r = MW_OK; + try + { + { + CDebugDump dd; + dd.Open(row.m_sDebugDumpDir.c_str()); + load_crash_data_from_debug_dump(dd, pCrashData); + } + + std::string analyzer = get_crash_data_item_content(pCrashData, FILENAME_ANALYZER); + const char* package = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_PACKAGE); + char* package_name = get_package_name_from_NVR_or_NULL(package); + + // TODO: explain what run_analyser_CreateReport and RunAnalyzerActions are expected to do. + // Do they potentially add more files to dump dir? + // Why we calculate dup_hash after run_analyser_CreateReport but before RunAnalyzerActions? + // Why do we reload dump dir's data via DebugDumpToCrashReport? + + VERB3 log(" run_analyser_CreateReport('%s')", analyzer.c_str()); + run_analyser_CreateReport(analyzer.c_str(), row.m_sDebugDumpDir.c_str(), force); + + std::string dup_hash = GetGlobalUUID(analyzer.c_str(), row.m_sDebugDumpDir.c_str()); + VERB3 log(" DUPHASH:'%s'", dup_hash.c_str()); + + VERB3 log(" RunAnalyzerActions('%s','%s','%s',force=%d)", analyzer.c_str(), package_name, row.m_sDebugDumpDir.c_str(), force); + RunAnalyzerActions(analyzer.c_str(), package_name, row.m_sDebugDumpDir.c_str(), force); + free(package_name); + DebugDumpToCrashReport(row.m_sDebugDumpDir.c_str(), pCrashData); + add_to_crash_data_ext(pCrashData, CD_UUID , CD_SYS, CD_ISNOTEDITABLE, row.m_sUUID.c_str()); + add_to_crash_data_ext(pCrashData, CD_DUPHASH, CD_TXT, CD_ISNOTEDITABLE, dup_hash.c_str()); + } + catch (CABRTException& e) + { + r = MW_CORRUPTED; + error_msg("%s", e.what()); + if (e.type() == EXCEP_DD_OPEN) + { + r = MW_ERROR; + } + else if (e.type() == EXCEP_DD_LOAD) + { + r = MW_FILE_ERROR; + } + else if (e.type() == EXCEP_PLUGIN) + { + r = MW_PLUGIN_ERROR; + } + } + + VERB3 log("CreateCrashReport() returns %d", r); + return r; +} + +void RunAction(const char *pActionDir, + const char *pPluginName, + const char *pPluginArgs) +{ + CAction* action = g_pPluginManager->GetAction(pPluginName); + if (!action) + { + /* GetAction() already complained */ + return; + } + try + { + action->Run(pActionDir, pPluginArgs, /*force:*/ 0); + } + catch (CABRTException& e) + { + error_msg("Execution of '%s' was not successful: %s", pPluginName, e.what()); + } +} + +void RunActionsAndReporters(const char *pDebugDumpDir) +{ + vector_pair_string_string_t::iterator it_ar = s_vectorActionsAndReporters.begin(); + map_plugin_settings_t plugin_settings; + for (; it_ar != s_vectorActionsAndReporters.end(); it_ar++) + { + const char *plugin_name = it_ar->first.c_str(); + try + { + VERB3 log("RunActionsAndReporters: checking %s", plugin_name); + plugin_type_t tp = g_pPluginManager->GetPluginType(plugin_name); + if (tp == REPORTER) + { + CReporter* reporter = g_pPluginManager->GetReporter(plugin_name); /* can't be NULL */ + map_crash_data_t crashReport; + DebugDumpToCrashReport(pDebugDumpDir, crashReport); + VERB2 log("%s.Report(...)", plugin_name); + reporter->Report(crashReport, plugin_settings, it_ar->second.c_str()); + } + else if (tp == ACTION) + { + CAction* action = g_pPluginManager->GetAction(plugin_name); /* can't be NULL */ + VERB2 log("%s.Run('%s','%s')", plugin_name, pDebugDumpDir, it_ar->second.c_str()); + action->Run(pDebugDumpDir, it_ar->second.c_str(), /*force:*/ 0); + } + } + catch (CABRTException& e) + { + error_msg("Activation of plugin '%s' was not successful: %s", plugin_name, e.what()); + } + } +} + + +// Do not trust client_report here! +// dbus handler passes it from user without checking +report_status_t Report(const map_crash_data_t& client_report, + const vector_string_t &reporters, + map_map_string_t& settings, + long caller_uid) +{ + // Get ID fields + const char *UID = get_crash_data_item_content_or_NULL(client_report, CD_UID); + const char *UUID = get_crash_data_item_content_or_NULL(client_report, CD_UUID); + if (!UID || !UUID) + { + throw CABRTException(EXCEP_ERROR, "Report(): UID or UUID is missing in client's report data"); + } + string crash_id = ssprintf("%s:%s", UID, UUID); + + // Retrieve corresponding stored record + map_crash_data_t stored_report; + mw_result_t r = FillCrashInfo(crash_id.c_str(), stored_report); + if (r != MW_OK) + { + return report_status_t(); + } + + // Is it allowed for this user to report? + if (caller_uid != 0 // not called by root + && get_crash_data_item_content(stored_report, CD_INFORMALL) != "1" + && strcmp(to_string(caller_uid).c_str(), UID) != 0 + ) { + throw CABRTException(EXCEP_ERROR, "Report(): user with uid %ld can't report crash %s", + caller_uid, crash_id.c_str()); + } + + const std::string& pDumpDir = get_crash_data_item_content(stored_report, CD_DUMPDIR); + + // Save comment, "how to reproduce", backtrace +//TODO: we should iterate through stored_report and modify all +//modifiable fields which have new data in client_report + const char *comment = get_crash_data_item_content_or_NULL(client_report, FILENAME_COMMENT); + const char *reproduce = get_crash_data_item_content_or_NULL(client_report, FILENAME_REPRODUCE); + const char *backtrace = get_crash_data_item_content_or_NULL(client_report, FILENAME_BACKTRACE); + if (comment || reproduce || backtrace) + { + CDebugDump dd; + dd.Open(pDumpDir.c_str()); + if (comment) + { + dd.SaveText(FILENAME_COMMENT, comment); + add_to_crash_data_ext(stored_report, FILENAME_COMMENT, CD_TXT, CD_ISEDITABLE, comment); + } + if (reproduce) + { + dd.SaveText(FILENAME_REPRODUCE, reproduce); + add_to_crash_data_ext(stored_report, FILENAME_REPRODUCE, CD_TXT, CD_ISEDITABLE, reproduce); + } + if (backtrace) + { + dd.SaveText(FILENAME_BACKTRACE, backtrace); + add_to_crash_data_ext(stored_report, FILENAME_BACKTRACE, CD_TXT, CD_ISEDITABLE, backtrace); + } + } + + /* Remove BIN filenames from stored_report if they are not present in client's data */ + map_crash_data_t::const_iterator its = stored_report.begin(); + while (its != stored_report.end()) + { + if (its->second[CD_TYPE] == CD_BIN) + { + std::string key = its->first; + if (get_crash_data_item_content_or_NULL(client_report, key.c_str()) == NULL) + { + /* client does not have it -> does not want it passed to reporters */ + VERB3 log("Won't report BIN file %s:'%s'", key.c_str(), its->second[CD_CONTENT].c_str()); + its++; /* move off the element we will erase */ + stored_report.erase(key); + continue; + } + } + its++; + } + + const std::string& analyzer = get_crash_data_item_content(stored_report, FILENAME_ANALYZER); + + std::string dup_hash = GetGlobalUUID(analyzer.c_str(), pDumpDir.c_str()); + VERB3 log(" DUPHASH:'%s'", dup_hash.c_str()); + add_to_crash_data_ext(stored_report, CD_DUPHASH, CD_TXT, CD_ISNOTEDITABLE, dup_hash.c_str()); + + // Run reporters + + VERB3 { + log("Run reporters"); + log_map_crash_data(client_report, " client_report"); + log_map_crash_data(stored_report, " stored_report"); + } +#define client_report client_report_must_not_be_used_below + + map_crash_data_t::const_iterator its_PACKAGE = stored_report.find(FILENAME_PACKAGE); + std::string packageNVR = its_PACKAGE->second[CD_CONTENT]; + char * packageName = get_package_name_from_NVR_or_NULL(packageNVR.c_str()); + + // analyzer with package name (CCpp:xorg-x11-app) has higher priority + char* key = xasprintf("%s:%s",analyzer.c_str(),packageName); + free(packageName); + map_analyzer_actions_and_reporters_t::iterator end = s_mapAnalyzerActionsAndReporters.end(); + map_analyzer_actions_and_reporters_t::iterator keyPtr = s_mapAnalyzerActionsAndReporters.find(key); + if (keyPtr == end) + { + VERB3 log("'%s' not found, looking for '%s'", key, analyzer.c_str()); + // if there is no such settings, then try default analyzer + keyPtr = s_mapAnalyzerActionsAndReporters.find(analyzer); + } + free(key); + + bool at_least_one_reporter_succeeded = false; + report_status_t ret; + std::string message; + if (keyPtr != end) + { + VERB2 log("Found AnalyzerActionsAndReporters for '%s'", analyzer.c_str()); + + vector_pair_string_string_t::iterator it_r = keyPtr->second.begin(); + for (; it_r != keyPtr->second.end(); it_r++) + { + const char *plugin_name = it_r->first.c_str(); + + /* Check if the reporter is in the input list of allowed reporters. */ + if (reporters.end() == std::find(reporters.begin(), reporters.end(), plugin_name)) + { + continue; + } + + try + { + if (g_pPluginManager->GetPluginType(plugin_name) == REPORTER) + { + CReporter* reporter = g_pPluginManager->GetReporter(plugin_name); /* can't be NULL */ + map_plugin_settings_t plugin_settings = settings[plugin_name]; + std::string res = reporter->Report(stored_report, plugin_settings, it_r->second.c_str()); + ret[plugin_name].push_back("1"); // REPORT_STATUS_IDX_FLAG + ret[plugin_name].push_back(res); // REPORT_STATUS_IDX_MSG + if (message != "") + message += ";"; + message += res; + at_least_one_reporter_succeeded = true; + } + } + catch (CABRTException& e) + { + ret[plugin_name].push_back("0"); // REPORT_STATUS_IDX_FLAG + ret[plugin_name].push_back(e.what()); // REPORT_STATUS_IDX_MSG + update_client("Reporting via '%s' was not successful: %s", plugin_name, e.what()); + } + } // for + } // if + + if (at_least_one_reporter_succeeded) + { + CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); + database->Connect(); + report_status_t::iterator ret_it = ret.begin(); + while (ret_it != ret.end()) + { + const string &plugin_name = ret_it->first; + const vector_string_t &v = ret_it->second; + if (v[REPORT_STATUS_IDX_FLAG] == "1") + { + database->SetReportedPerReporter(crash_id.c_str(), plugin_name.c_str(), v[REPORT_STATUS_IDX_MSG].c_str()); + } + ret_it++; + } + database->SetReported(crash_id.c_str(), message.c_str()); + database->DisConnect(); + } + + return ret; +#undef client_report +} + +/** + * Check whether particular debugdump directory is saved + * in database. This check is done together with an UID of an user. + * @param uid + * An UID of an user. + * @param debug_dump_dir + * A debugdump dir containing all necessary data. + * @return + * It returns true if debugdump dir is already saved, otherwise + * it returns false. + * @todo + * Use database query instead of dumping all rows and searching in them. + */ +static bool is_debug_dump_saved(long uid, const char *debug_dump_dir) +{ + if (g_settings_sDatabase.empty()) + error_msg_and_die(_("Database plugin not specified. Please check abrtd settings.")); + + CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); + database->Connect(); + vector_database_rows_t rows = database->GetUIDData(uid); + database->DisConnect(); + + size_t ii; + bool found = false; + for (ii = 0; ii < rows.size(); ii++) + { + if (0 == strcmp(rows[ii].m_sDebugDumpDir.c_str(), debug_dump_dir)) + { + found = true; + break; + } + } + + return found; +} + +void LoadOpenGPGPublicKey(const char* key) +{ + VERB1 log("Loading GPG key '%s'", key); + rpm_load_gpgkey(key); +} + +/** + * Returns the first full path argument in the command line or NULL. + * Skips options are in form "-XXX". + * Caller must delete the returned string using free(). + */ +static char *get_argv1_if_full_path(const char* cmdline) +{ + const char *argv1 = strpbrk(cmdline, " \t"); + while (argv1 != NULL) + { + /* we found space in cmdline, so it might contain + * path to some script like: + * /usr/bin/python [-XXX] /usr/bin/system-control-network + */ + argv1++; /* skip the space */ + if (*argv1 == '-') /* skip arguments */ + { + /* looks like -XXX in "perl -XXX /usr/bin/script.pl", skip */ + argv1 = strpbrk(argv1, " \t"); + continue; + } + else if (*argv1 == ' ' || *argv1 == '\t') /* skip multiple spaces */ + continue; + else if (*argv1 != '/') + { + /* if the string following the space doesn't start + * with '/' it's probably not a full path to script + * and we can't use it to determine the package name + */ + break; + } + + /* cut the rest of cmdline arguments */ + int len = strchrnul(argv1, ' ') - argv1; + return xstrndup(argv1, len); + } + return NULL; +} + +static bool is_path_blacklisted(const char *path) +{ + set_string_t::iterator it = g_settings_setBlackListedPaths.begin(); + while (it != g_settings_setBlackListedPaths.end()) + { + if (fnmatch(it->c_str(), path, /*flags:*/ 0) == 0) + { + return true; + } + it++; + } + return false; +} + + +/** + * Get a package name from executable name and save + * package description to particular debugdump directory of a crash. + * @param pExecutable A name of crashed application. + * @param pDebugDumpDir A debugdump dir containing all necessary data. + * @return It return results of operation. See mw_result_t. + */ +static mw_result_t SavePackageDescriptionToDebugDump( + const char *pExecutable, + const char *cmdline, + bool remote, + const char *pDebugDumpDir) +{ + char* rpm_pkg = NULL; + char* packageName = NULL; + char* component = NULL; + std::string scriptName; /* only if "interpreter /path/to/script" */ + + if (strcmp(pExecutable, "kernel") == 0) + { + component = xstrdup("kenel"); + rpm_pkg = xstrdup("kernel"); + packageName = xstrdup("kernel"); + } + else + { + if (is_path_blacklisted(pExecutable)) + { + log("Blacklisted executable '%s'", pExecutable); + return MW_BLACKLISTED; + } + + rpm_pkg = rpm_get_package_nvr(pExecutable); + if (rpm_pkg == NULL) + { + if (g_settings_bProcessUnpackaged || remote) + { + VERB2 log("Crash in unpackaged executable '%s', proceeding without packaging information", pExecutable); + try + { + CDebugDump dd; + dd.Open(pDebugDumpDir); + dd.SaveText(FILENAME_PACKAGE, ""); + dd.SaveText(FILENAME_COMPONENT, ""); + dd.SaveText(FILENAME_DESCRIPTION, "Crashed executable does not belong to any installed package"); + return MW_OK; + } + catch (CABRTException& e) + { + error_msg("%s", e.what()); + return MW_ERROR; + } + } + else + { + log("Executable '%s' doesn't belong to any package", pExecutable); + return MW_PACKAGE_ERROR; + } + } + + /* Check well-known interpreter names */ + + const char *basename = strrchr(pExecutable, '/'); + if (basename) basename++; else basename = pExecutable; + + /* Add more interpreters as needed */ + if (strcmp(basename, "python") == 0 + || strcmp(basename, "perl") == 0 + ) { +// TODO: we don't verify that python executable is not modified +// or that python package is properly signed +// (see CheckFingerprint/CheckHash below) + + /* Try to find package for the script by looking at argv[1]. + * This will work only if the cmdline contains the whole path. + * Example: python /usr/bin/system-control-network + */ + bool knownOrigin = false; + char *script_name = get_argv1_if_full_path(cmdline); + if (script_name) + { + char *script_pkg = rpm_get_package_nvr(script_name); + if (script_pkg) + { + /* There is a well-formed script name in argv[1], + * and it does belong to some package. + * Replace interpreter's rpm_pkg and pExecutable + * with data pertaining to the script. + */ + free(rpm_pkg); + rpm_pkg = script_pkg; + scriptName = script_name; + pExecutable = scriptName.c_str(); + knownOrigin = true; + /* pExecutable has changed, check it again */ + if (is_path_blacklisted(pExecutable)) + { + log("Blacklisted executable '%s'", pExecutable); + return MW_BLACKLISTED; + } + } + free(script_name); + } + + if (!knownOrigin && !g_settings_bProcessUnpackaged && !remote) + { + log("Interpreter crashed, but no packaged script detected: '%s'", cmdline); + return MW_PACKAGE_ERROR; + } + } + + packageName = get_package_name_from_NVR_or_NULL(rpm_pkg); + VERB2 log("Package:'%s' short:'%s'", rpm_pkg, packageName); + + if (g_settings_setBlackListedPkgs.find(packageName) != g_settings_setBlackListedPkgs.end()) + { + log("Blacklisted package '%s'", packageName); + free(packageName); + return MW_BLACKLISTED; + } + if (g_settings_bOpenGPGCheck && !remote) + { + if (rpm_chk_fingerprint(packageName)) + { + log("Package '%s' isn't signed with proper key", packageName); + free(packageName); + return MW_GPG_ERROR; + } + /* + Checking the MD5 sum requires to run prelink to "un-prelink" the + binaries - this is considered potential security risk so we don't + use it, until we find some non-intrusive way + + Delete? + */ + /* + if (!CheckHash(packageName.c_str(), pExecutable)) + { + error_msg("Executable '%s' seems to be modified, " + "doesn't match one from package '%s'", + pExecutable, packageName.c_str()); + return MW_GPG_ERROR; + } + */ + } + component = rpm_get_component(pExecutable); + } + + char *dsc = rpm_get_description(packageName); + free(packageName); + + char host[HOST_NAME_MAX + 1]; + if (!remote) + { + // HOST_NAME_MAX is defined in limits.h + int ret = gethostname(host, HOST_NAME_MAX); + host[HOST_NAME_MAX] = '\0'; + if (ret < 0) + { + perror_msg("gethostname"); + host[0] = '\0'; + } + } + + try + { + CDebugDump dd; + dd.Open(pDebugDumpDir); + if (rpm_pkg) + { + dd.SaveText(FILENAME_PACKAGE, rpm_pkg); + free(rpm_pkg); + } + + if (dsc) + { + dd.SaveText(FILENAME_DESCRIPTION, dsc); + free(dsc); + } + + if (component) + { + dd.SaveText(FILENAME_COMPONENT, component); + free(component); + } + + if (!remote) + dd.SaveText(FILENAME_HOSTNAME, host); + } + catch (CABRTException& e) + { + error_msg("%s", e.what()); + return MW_ERROR; + } + + return MW_OK; +} + +bool analyzer_has_InformAllUsers(const char *analyzer_name) +{ + CAnalyzer* analyzer = g_pPluginManager->GetAnalyzer(analyzer_name); + if (!analyzer) + { + return false; + } + map_plugin_settings_t settings = analyzer->GetSettings(); + map_plugin_settings_t::const_iterator it = settings.find("InformAllUsers"); + if (it == settings.end()) + return false; + return string_to_bool(it->second.c_str()); +} + +bool analyzer_has_AutoReportUIDs(const char *analyzer_name, const char *uid_str) +{ + CAnalyzer* analyzer = g_pPluginManager->GetAnalyzer(analyzer_name); + if (!analyzer) + { + return false; + } + map_plugin_settings_t settings = analyzer->GetSettings(); + map_plugin_settings_t::const_iterator it = settings.find("AutoReportUIDs"); + if (it == settings.end()) + return false; + + vector_string_t logins; + parse_args(it->second.c_str(), logins); + + uid_t uid = xatoi_u(uid_str); + unsigned size = logins.size(); + for (unsigned ii = 0; ii < size; ii++) + { + struct passwd* pw = getpwnam(logins[ii].c_str()); + if (!pw) + continue; + if (pw->pw_uid == uid) + return true; + } + + return false; +} + +void autoreport(const pair_string_string_t& reporter_options, const map_crash_data_t& crash_report) +{ + CReporter* reporter = g_pPluginManager->GetReporter(reporter_options.first.c_str()); + if (!reporter) + { + return; + } + map_plugin_settings_t plugin_settings; + /*std::string res =*/ reporter->Report(crash_report, plugin_settings, reporter_options.second.c_str()); +} + +/** + * Execute all action plugins, which are associated to + * particular analyzer plugin. + * @param pAnalyzer A name of an analyzer plugin. + * @param pDebugDumpPath A debugdump dir containing all necessary data. + */ +static void RunAnalyzerActions(const char *pAnalyzer, const char *pPackageName, const char *pDebugDumpDir, int force) +{ + map_analyzer_actions_and_reporters_t::iterator analyzer; + if (pPackageName != NULL) + { + /*try to find analyzer:component first*/ + char *analyzer_component = xasprintf("%s:%s", pAnalyzer, pPackageName); + analyzer = s_mapAnalyzerActionsAndReporters.find(analyzer_component); + /* if we didn't find an action for specific package, use the generic one */ + if (analyzer == s_mapAnalyzerActionsAndReporters.end()) + { + VERB2 log("didn't find action for %s, trying just %s", analyzer_component, pAnalyzer); + map_analyzer_actions_and_reporters_t::iterator analyzer = s_mapAnalyzerActionsAndReporters.find(pAnalyzer); + } + free(analyzer_component); + } + else + { + VERB2 log("no package name specified, trying to find action for: %s", pAnalyzer); + analyzer = s_mapAnalyzerActionsAndReporters.find(pAnalyzer); + } + if (analyzer != s_mapAnalyzerActionsAndReporters.end()) + { + vector_pair_string_string_t::iterator it_a = analyzer->second.begin(); + for (; it_a != analyzer->second.end(); it_a++) + { + const char *plugin_name = it_a->first.c_str(); + CAction* action = g_pPluginManager->GetAction(plugin_name, /*silent:*/ true); + if (!action) + { + /* GetAction() already complained if no such plugin. + * If plugin exists but isn't an Action, it's not an error. + */ + continue; + } + try + { + action->Run(pDebugDumpDir, it_a->second.c_str(), force); + } + catch (CABRTException& e) + { + update_client("Action performed by '%s' was not successful: %s", plugin_name, e.what()); + } + } + } +} + +/** + * Save a debugdump into database. If saving is + * successful, then crash info is filled. Otherwise the crash info is + * not changed. + * @param pUUID A local UUID of a crash. + * @param pUID An UID of an user. + * @param pTime Time when a crash occurs. + * @param pDebugDumpPath A debugdump path. + * @param pCrashData A filled crash info. + * @return It return results of operation. See mw_result_t. + */ +static mw_result_t SaveDebugDumpToDatabase(const char *crash_id, + bool inform_all_users, + const char *pTime, + const char *pDebugDumpDir, + map_crash_data_t& pCrashData) +{ + CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); + database->Connect(); + /* note: if [UUID,UID] record exists, pDebugDumpDir is not updated in the record */ + database->Insert_or_Update(crash_id, inform_all_users, pDebugDumpDir, pTime); + database_row_t row = database->GetRow(crash_id); + database->DisConnect(); + + mw_result_t res = FillCrashInfo(crash_id, pCrashData); + if (res == MW_OK) + { + const char *first = get_crash_data_item_content(pCrashData, CD_DUMPDIR).c_str(); + if (row.m_sReported == "1") + { + log("Crash is in database already (dup of %s) and is reported", first); + return MW_REPORTED; + } + if (row.m_sCount != "1") + { + log("Crash is in database already (dup of %s)", first); + return MW_OCCURRED; + } + } + return res; +} + +mw_result_t SaveDebugDump(const char *pDebugDumpDir, + map_crash_data_t& pCrashData) +{ + std::string UID; + std::string time; + std::string analyzer; + std::string executable; + std::string cmdline; + bool remote = false; + try + { + CDebugDump dd; + dd.Open(pDebugDumpDir); + dd.LoadText(FILENAME_TIME, time); + dd.LoadText(CD_UID, UID); + dd.LoadText(FILENAME_ANALYZER, analyzer); + dd.LoadText(FILENAME_EXECUTABLE, executable); + dd.LoadText(FILENAME_CMDLINE, cmdline); + if (dd.Exist(FILENAME_REMOTE)) + { + std::string remote_str; + dd.LoadText(FILENAME_REMOTE, remote_str); + remote = (remote_str.find('1') != std::string::npos); + } + } + catch (CABRTException& e) + { + error_msg("%s", e.what()); + return MW_ERROR; + } + + /* Convert UID string to number uid_num. The UID string can be modified by user or + wrongly saved (empty or non-numeric), so xatou() cannot be used here, + because it would kill the daemon. */ + char *endptr; + errno = 0; + unsigned long uid_num = strtoul(UID.c_str(), &endptr, 10); + if (errno || UID.c_str() == endptr || *endptr != '\0' || uid_num > UINT_MAX) + { + error_msg("Invalid UID '%s' loaded from %s", UID.c_str(), pDebugDumpDir); + return MW_ERROR; + } + + if (is_debug_dump_saved(uid_num, pDebugDumpDir)) + return MW_IN_DB; + + mw_result_t res = SavePackageDescriptionToDebugDump(executable.c_str(), cmdline.c_str(), remote, pDebugDumpDir); + if (res != MW_OK) + return res; + + std::string UUID = GetLocalUUID(analyzer.c_str(), pDebugDumpDir); + std::string crash_id = ssprintf("%s:%s", UID.c_str(), UUID.c_str()); + /* Loads pCrashData (from the *first debugdump dir* if this one is a dup) + * Returns: + * MW_REPORTED: "the crash is flagged as reported in DB" (which also means it's a dup) + * MW_OCCURRED: "crash count is != 1" (iow: it is > 1 - dup) + * MW_OK: "crash count is 1" (iow: this is a new crash, not a dup) + * else: an error code + */ + return SaveDebugDumpToDatabase(crash_id.c_str(), + analyzer_has_InformAllUsers(analyzer.c_str()), + time.c_str(), + pDebugDumpDir, + pCrashData); +} + +mw_result_t FillCrashInfo(const char *crash_id, + map_crash_data_t& pCrashData) +{ + CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); + database->Connect(); + database_row_t row = database->GetRow(crash_id); + database->DisConnect(); + + std::string package; + std::string executable; + std::string description; + std::string analyzer; + try + { + CDebugDump dd; + dd.Open(row.m_sDebugDumpDir.c_str()); + load_crash_data_from_debug_dump(dd, pCrashData); + } + catch (CABRTException& e) + { + error_msg("%s", e.what()); + return MW_ERROR; + } + + add_to_crash_data(pCrashData, CD_UID , row.m_sUID.c_str() ); + add_to_crash_data(pCrashData, CD_UUID , row.m_sUUID.c_str() ); + add_to_crash_data(pCrashData, CD_INFORMALL , row.m_sInformAll.c_str() ); + add_to_crash_data(pCrashData, CD_COUNT , row.m_sCount.c_str() ); + add_to_crash_data(pCrashData, CD_REPORTED , row.m_sReported.c_str() ); + add_to_crash_data(pCrashData, CD_MESSAGE , row.m_sMessage.c_str() ); + add_to_crash_data(pCrashData, CD_DUMPDIR , row.m_sDebugDumpDir.c_str()); + add_to_crash_data(pCrashData, FILENAME_TIME , row.m_sTime.c_str() ); + + return MW_OK; +} + +void GetUUIDsOfCrash(long caller_uid, vector_string_t &result) +{ + CDatabase* database = g_pPluginManager->GetDatabase(g_settings_sDatabase.c_str()); + vector_database_rows_t rows; + database->Connect(); + rows = database->GetUIDData(caller_uid); + database->DisConnect(); + + unsigned ii; + for (ii = 0; ii < rows.size(); ii++) + { + string crash_id = ssprintf("%s:%s", rows[ii].m_sUID.c_str(), rows[ii].m_sUUID.c_str()); + result.push_back(crash_id); + } +} + +void AddAnalyzerActionOrReporter(const char *pAnalyzer, + const char *pAnalyzerOrReporter, + const char *pArgs) +{ + s_mapAnalyzerActionsAndReporters[pAnalyzer].push_back(make_pair(std::string(pAnalyzerOrReporter), std::string(pArgs))); +} + +void AddActionOrReporter(const char *pActionOrReporter, + const char *pArgs) +{ + VERB3 log("AddActionOrReporter('%s','%s')", pActionOrReporter, pArgs); + s_vectorActionsAndReporters.push_back(make_pair(std::string(pActionOrReporter), std::string(pArgs))); +} diff --git a/src/daemon/MiddleWare.h b/src/daemon/MiddleWare.h new file mode 100644 index 00000000..ac998872 --- /dev/null +++ b/src/daemon/MiddleWare.h @@ -0,0 +1,158 @@ +/* + MiddleWare.h - header file for MiddleWare library. It wraps plugins and + take case of them. + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef MIDDLEWARE_H_ +#define MIDDLEWARE_H_ + +#include "abrt_types.h" +#include "PluginManager.h" + +/** + * An enum contains all return codes. + */ +typedef enum { + MW_OK, /**< No error.*/ + MW_ERROR, /**< Common error.*/ + MW_BLACKLISTED, /**< Package is blacklisted.*/ + MW_CORRUPTED, /**< Debugdump directory is corrupted.*/ + MW_PACKAGE_ERROR, /**< Cannot determine package name.*/ + MW_GPG_ERROR, /**< Package is not signed properly.*/ + MW_REPORTED, /**< Crash is already reported.*/ + MW_OCCURRED, /**< Crash occurred in the past, but it is not reported yet.*/ + MW_IN_DB, /**< Debugdump directory is already saved in a database.*/ + MW_IN_DB_ERROR, /**< Error while working with a database.*/ + MW_PLUGIN_ERROR, /**< plugin wasn't found or error within plugin*/ + MW_FILE_ERROR /**< Error when trying open debugdump directory or + when trying open file in debug dump directory..*/ +} mw_result_t; + +typedef enum { + RS_CODE, + RS_MESSAGE +} report_status_items_t; + + +void LoadOpenGPGPublicKey(const char* key); + +/** + * Takes care of getting all additional data needed + * for computing UUIDs and creating a report for particular analyzer + * plugin. This report could be send somewhere afterwards. If a creation + * is successful, then a crash report is filled. + * @param pAnalyzer A name of an analyzer plugin. + * @param pDebugDumpPath A debugdump dir containing all necessary data. + * @param pCrashData A filled crash report. + * @return It return results of operation. See mw_result_t. + */ +mw_result_t CreateCrashReport(const char *crash_id, + long caller_uid, + int force, + map_crash_data_t& pCrashData); +/** + * Activates particular action plugin. + * @param pActionDir A directory, which is passed as working to a action plugin. + * @param pPluginName An action plugin name. + * @param pPluginArgs Action plugin's arguments. + */ +void RunAction(const char *pActionDir, + const char *pPluginName, + const char *pPluginArgs); +/** + * Activates all action and reporter plugins when any + * crash occurs. + * @param pDebugDumpDir A debugdump dir containing all necessary data. + */ +void RunActionsAndReporters(const char *pDebugDumpDir); +/** + * Reports a crash report to particular receiver. It + * takes an user uid, tries to find user config file and load it. If it + * fails, then default config is used. If pUID is emply string, default + * config is used. + * ...). + * @param crash_data + * A crash report. + * @param reporters + * List of allowed reporters. Which reporters will be used depends + * on the analyzer of the crash_data. Reporters missing from this list + * will not be used. + * @param caller_uid + * An user uid. + * @return + * A report status, which reporters ends successfuly with messages. + */ +report_status_t Report(const map_crash_data_t& crash_data, + const vector_string_t& reporters, + map_map_string_t& settings, + long caller_uid); +/** + * Adds package name and description to debugdump dir. + * Saves debugdump into database. + * Detects whether it's a duplicate crash. + * Fills crash info. + * Note that if it's a dup, loads _first crash_ info, not this one's. + * @param pDebugDumpDir A debugdump directory. + * @param pCrashData A crash info. + * @return It return results of operation. See mw_result_t. + */ +mw_result_t SaveDebugDump(const char *pDebugDumpDir, + map_crash_data_t& pCrashData); +/** + * Get one crash info. If getting is successful, + * then crash info is filled. + * @param pUUID A local UUID of a crash. + * @param pUID An UID of an user. + * @param pCrashData A crash info. + * @return It return results of operation. See mw_result_t. + */ +mw_result_t FillCrashInfo(const char *crash_id, + map_crash_data_t& pCrashData); +/** + * Gets all local UUIDs and UIDs of crashes. These crashes + * occurred when a particular user was logged in. + * @param pUID an UID of an user. + * @return A vector of pairs (local UUID, UID). + */ +void GetUUIDsOfCrash(long caller_uid, vector_string_t &result); +/** + * Adds one association among alanyzer plugin and its + * action and reporter plugins. + * @param pAnalyzer A name of an analyzer plugin. + * @param pActionOrReporter A name of an action or reporter plugin. + * @param pArgs An arguments for action or reporter plugin. + */ +void AddAnalyzerActionOrReporter(const char *pAnalyzer, + const char *pActionOrReporter, + const char *pArgs); +/** + * Add action and reporter plugins, which are activated + * when any crash occurs. + * @param pActionOrReporter A name of an action or reporter plugin. + * @param pArgs An arguments for action or reporter plugin. + */ +void AddActionOrReporter(const char *pActionOrReporter, + const char *pArgs); + +bool analyzer_has_InformAllUsers(const char *analyzer_name); + +bool analyzer_has_AutoReportUIDs(const char *analyzer_name, const char *uid_str); + +void autoreport(const pair_string_string_t& reporter_options, const map_crash_data_t& crash_report); +#endif /*MIDDLEWARE_H_*/ diff --git a/src/daemon/PluginManager.cpp b/src/daemon/PluginManager.cpp new file mode 100644 index 00000000..4f64ed00 --- /dev/null +++ b/src/daemon/PluginManager.cpp @@ -0,0 +1,463 @@ +/* + PluginManager.cpp + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include +#include "abrtlib.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" +#include "Polkit.h" +#include "PluginManager.h" + +using namespace std; + + +/** + * CLoadedModule class. A class which contains a loaded plugin. + */ +class CLoadedModule +{ + private: + /* dlopen'ed library */ + void *m_pHandle; + const plugin_info_t *m_pPluginInfo; + CPlugin* (*m_pFnPluginNew)(); + + public: + CLoadedModule(void *handle, const char *mod_name); + ~CLoadedModule() { dlclose(m_pHandle); } + int GetMagicNumber() { return m_pPluginInfo->m_nMagicNumber; } + const char *GetVersion() { return m_pPluginInfo->m_sVersion; } + const char *GetName() { return m_pPluginInfo->m_sName; } + const char *GetDescription() { return m_pPluginInfo->m_sDescription; } + const char *GetEmail() { return m_pPluginInfo->m_sEmail; } + const char *GetWWW() { return m_pPluginInfo->m_sWWW; } + const char *GetGTKBuilder() { return m_pPluginInfo->m_sGTKBuilder; } + plugin_type_t GetType() { return m_pPluginInfo->m_Type; } + CPlugin *PluginNew() { return m_pFnPluginNew(); } +}; +CLoadedModule::CLoadedModule(void *handle, const char *mod_name) +{ + m_pHandle = handle; + /* All errors are fatal */ +#define LOADSYM(fp, handle, name) \ + do { \ + fp = (typeof(fp)) (dlsym(handle, name)); \ + if (!fp) \ + error_msg_and_die("'%s' has no %s entry", mod_name, name); \ + } while (0) + + LOADSYM(m_pPluginInfo, handle, "plugin_info"); + LOADSYM(m_pFnPluginNew, handle, "plugin_new"); +#undef LOADSYM +} + + +/** + * Text representation of plugin types. + */ +static const char *const plugin_type_str[] = { + "Analyzer", + "Action", + "Reporter", + "Database" +}; + + +/** + * A function. It saves settings. On success it returns true, otherwise returns false. + * @param path A path of config file. + * @param settings Plugin's settings. + * @return if it success it returns true, otherwise it returns false. + */ +static bool SavePluginSettings(const char *pPath, const map_plugin_settings_t& pSettings) +{ + FILE* fOut = fopen(pPath, "w"); + if (fOut) + { + fprintf(fOut, "# Settings were written by abrt\n"); + map_plugin_settings_t::const_iterator it = pSettings.begin(); + for (; it != pSettings.end(); it++) + { + fprintf(fOut, "%s = %s\n", it->first.c_str(), it->second.c_str()); + } + fclose(fOut); + return true; + } + return false; +} + + +CPluginManager::CPluginManager() +{} + +CPluginManager::~CPluginManager() +{} + +void CPluginManager::LoadPlugins() +{ + DIR *dir = opendir(PLUGINS_LIB_DIR); + if (dir != NULL) + { + struct dirent *dent; + while ((dent = readdir(dir)) != NULL) + { + if (!is_regular_file(dent, PLUGINS_LIB_DIR)) + continue; + char *ext = strrchr(dent->d_name, '.'); + if (!ext || strcmp(ext + 1, PLUGINS_LIB_EXTENSION) != 0) + continue; + *ext = '\0'; + if (strncmp(dent->d_name, PLUGINS_LIB_PREFIX, sizeof(PLUGINS_LIB_PREFIX)-1) != 0) + continue; + LoadPlugin(dent->d_name + sizeof(PLUGINS_LIB_PREFIX)-1, /*enabled_only:*/ true); + } + closedir(dir); + } +} + +void CPluginManager::UnLoadPlugins() +{ + map_loaded_module_t::iterator it_module; + while ((it_module = m_mapLoadedModules.begin()) != m_mapLoadedModules.end()) + { + UnLoadPlugin(it_module->first.c_str()); + } +} + +CPlugin* CPluginManager::LoadPlugin(const char *pName, bool enabled_only) +{ + map_plugin_t::iterator it_plugin = m_mapPlugins.find(pName); + if (it_plugin != m_mapPlugins.end()) + { + return it_plugin->second; /* ok */ + } + + map_string_t plugin_info; + plugin_info["Name"] = pName; + + const char *conf_name = pName; + if (strncmp(pName, "Kerneloops", sizeof("Kerneloops")-1) == 0) + { + /* Kerneloops{,Scanner,Reporter} share the same .conf file */ + conf_name = "Kerneloops"; + } + map_plugin_settings_t pluginSettings; + string conf_fullname = ssprintf(PLUGINS_CONF_DIR"/%s."PLUGINS_CONF_EXTENSION, conf_name); + LoadPluginSettings(conf_fullname.c_str(), pluginSettings); + m_map_plugin_settings[pName] = pluginSettings; + /* If settings are empty, most likely .conf file does not exist. + * Don't mislead the user: */ + VERB3 if (!pluginSettings.empty()) log("Loaded %s.conf", conf_name); + + if (enabled_only) + { + map_plugin_settings_t::iterator it = pluginSettings.find("Enabled"); + if (it == pluginSettings.end() || !string_to_bool(it->second.c_str())) + { + plugin_info["Enabled"] = "no"; + string empty; + plugin_info["Type"] = empty; + plugin_info["Version"] = empty; + plugin_info["Description"] = empty; + plugin_info["Email"] = empty; + plugin_info["WWW"] = empty; + plugin_info["GTKBuilder"] = empty; + m_map_plugin_info[pName] = plugin_info; + VERB3 log("Plugin %s: 'Enabled' is not set, not loading it (yet)", pName); + return NULL; /* error */ + } + } + + string libPath = ssprintf(PLUGINS_LIB_DIR"/"PLUGINS_LIB_PREFIX"%s."PLUGINS_LIB_EXTENSION, pName); + void *handle = dlopen(libPath.c_str(), RTLD_NOW); + if (!handle) + { + error_msg("Can't load '%s': %s", libPath.c_str(), dlerror()); + return NULL; /* error */ + } + CLoadedModule *module = new CLoadedModule(handle, pName); + if (module->GetMagicNumber() != PLUGINS_MAGIC_NUMBER + || module->GetType() < 0 + || module->GetType() > MAX_PLUGIN_TYPE + ) { + error_msg("Can't load non-compatible plugin %s: magic %d != %d or type %d is not in [0,%d]", + pName, + module->GetMagicNumber(), PLUGINS_MAGIC_NUMBER, + module->GetType(), MAX_PLUGIN_TYPE); + delete module; + return NULL; /* error */ + } + VERB3 log("Loaded plugin %s v.%s", pName, module->GetVersion()); + + CPlugin *plugin = NULL; + try + { + plugin = module->PluginNew(); + plugin->Init(); + plugin->SetSettings(pluginSettings); + } + catch (CABRTException& e) + { + error_msg("Can't initialize plugin %s: %s", + pName, + e.what() + ); + delete plugin; + delete module; + return NULL; /* error */ + } + + plugin_info["Enabled"] = "yes"; + plugin_info["Type"] = plugin_type_str[module->GetType()]; + //plugin_info["Name"] = module->GetName(); + plugin_info["Version"] = module->GetVersion(); + plugin_info["Description"] = module->GetDescription(); + plugin_info["Email"] = module->GetEmail(); + plugin_info["WWW"] = module->GetWWW(); + plugin_info["GTKBuilder"] = module->GetGTKBuilder(); + + m_map_plugin_info[pName] = plugin_info; + m_mapLoadedModules[pName] = module; + m_mapPlugins[pName] = plugin; + log("Registered %s plugin '%s'", plugin_type_str[module->GetType()], pName); + return plugin; /* ok */ +} + +void CPluginManager::UnLoadPlugin(const char *pName) +{ + map_loaded_module_t::iterator it_module = m_mapLoadedModules.find(pName); + if (it_module != m_mapLoadedModules.end()) + { + map_plugin_t::iterator it_plugin = m_mapPlugins.find(pName); + if (it_plugin != m_mapPlugins.end()) /* always true */ + { + it_plugin->second->DeInit(); + delete it_plugin->second; + m_mapPlugins.erase(it_plugin); + } + log("UnRegistered %s plugin %s", plugin_type_str[it_module->second->GetType()], pName); + delete it_module->second; + m_mapLoadedModules.erase(it_module); + } +} + +#ifdef PLUGIN_DYNAMIC_LOAD_UNLOAD +void CPluginManager::RegisterPluginDBUS(const char *pName, const char *pDBUSSender) +{ + int polkit_result = polkit_check_authorization(pDBUSSender, + "org.fedoraproject.abrt.change-daemon-settings"); + if (polkit_result == PolkitYes) + { +//TODO: report success/failure + LoadPlugin(pName); + } else + { + log("User %s not authorized, returned %d", pDBUSSender, polkit_result); + } +} + +void CPluginManager::UnRegisterPluginDBUS(const char *pName, const char *pDBUSSender) +{ + int polkit_result = polkit_check_authorization(pDBUSSender, + "org.fedoraproject.abrt.change-daemon-settings"); + if (polkit_result == PolkitYes) + { + UnLoadPlugin(pName); + } else + { + log("user %s not authorized, returned %d", pDBUSSender, polkit_result); + } +} +#endif + +CAnalyzer* CPluginManager::GetAnalyzer(const char *pName) +{ + CPlugin *plugin = LoadPlugin(pName); + if (!plugin) + { + error_msg("Plugin '%s' is not registered", pName); + return NULL; + } + if (m_mapLoadedModules[pName]->GetType() != ANALYZER) + { + error_msg("Plugin '%s' is not an analyzer plugin", pName); + return NULL; + } + return (CAnalyzer*)plugin; +} + +CReporter* CPluginManager::GetReporter(const char *pName) +{ + CPlugin *plugin = LoadPlugin(pName); + if (!plugin) + { + error_msg("Plugin '%s' is not registered", pName); + return NULL; + } + if (m_mapLoadedModules[pName]->GetType() != REPORTER) + { + error_msg("Plugin '%s' is not a reporter plugin", pName); + return NULL; + } + return (CReporter*)plugin; +} + +CAction* CPluginManager::GetAction(const char *pName, bool silent) +{ + CPlugin *plugin = LoadPlugin(pName); + if (!plugin) + { + error_msg("Plugin '%s' is not registered", pName); + return NULL; + } + if (m_mapLoadedModules[pName]->GetType() != ACTION) + { + if (!silent) + error_msg("Plugin '%s' is not an action plugin", pName); + return NULL; + } + return (CAction*)plugin; +} + +CDatabase* CPluginManager::GetDatabase(const char *pName) +{ + CPlugin *plugin = LoadPlugin(pName); + if (!plugin) + { + throw CABRTException(EXCEP_PLUGIN, "Plugin '%s' is not registered", pName); + } + if (m_mapLoadedModules[pName]->GetType() != DATABASE) + { + throw CABRTException(EXCEP_PLUGIN, "Plugin '%s' is not a database plugin", pName); + } + return (CDatabase*)plugin; +} + +plugin_type_t CPluginManager::GetPluginType(const char *pName) +{ + CPlugin *plugin = LoadPlugin(pName); + if (!plugin) + { + throw CABRTException(EXCEP_PLUGIN, "Plugin '%s' is not registered", pName); + } + map_loaded_module_t::iterator it_module = m_mapLoadedModules.find(pName); + return it_module->second->GetType(); +} + +void CPluginManager::SetPluginSettings(const char *pName, + const char *pUID, + const map_plugin_settings_t& pSettings) +{ + map_loaded_module_t::iterator it_module = m_mapLoadedModules.find(pName); + if (it_module == m_mapLoadedModules.end()) + { + return; + } + map_plugin_t::iterator it_plugin = m_mapPlugins.find(pName); + if (it_plugin == m_mapPlugins.end()) + { + return; + } + it_plugin->second->SetSettings(pSettings); + +#if 0 /* Writing to ~user/.abrt/ is bad wrt security */ + if (it_module->second->GetType() != REPORTER) + { + return; + } + + const char *home = get_home_dir(xatoi_u(pUID.c_str())); + if (home == NULL || strlen(home) == 0) + return; + + string confDir = home + "/.abrt"; + string confPath = confDir + "/" + pName + "."PLUGINS_CONF_EXTENSION; + uid_t uid = xatoi_u(pUID.c_str()); + struct passwd* pw = getpwuid(uid); + gid_t gid = pw ? pw->pw_gid : uid; + + struct stat buf; + if (stat(confDir.c_str(), &buf) != 0) + { + if (mkdir(confDir.c_str(), 0700) == -1) + { + perror_msg("Can't create dir '%s'", confDir.c_str()); + return; + } + if (chmod(confDir.c_str(), 0700) == -1) + { + perror_msg("Can't change mod of dir '%s'", confDir.c_str()); + return; + } + if (chown(confDir.c_str(), uid, gid) == -1) + { + perror_msg("Can't change '%s' ownership to %lu:%lu", confPath.c_str(), (long)uid, (long)gid); + return; + } + } + else if (!S_ISDIR(buf.st_mode)) + { + perror_msg("'%s' is not a directory", confDir.c_str()); + return; + } + + /** we don't want to save it from daemon if it's running under root + but wi might get back to this once we make the daemon to not run + with root privileges + */ + /* + SavePluginSettings(confPath, pSettings); + if (chown(confPath.c_str(), uid, gid) == -1) + { + perror_msg("Can't change '%s' ownership to %lu:%lu", confPath.c_str(), (long)uid, (long)gid); + return; + } + */ +#endif +} + +map_plugin_settings_t CPluginManager::GetPluginSettings(const char *pName) +{ + map_plugin_settings_t ret; + + map_loaded_module_t::iterator it_module = m_mapLoadedModules.find(pName); + if (it_module != m_mapLoadedModules.end()) + { + map_plugin_t::iterator it_plugin = m_mapPlugins.find(pName); + if (it_plugin != m_mapPlugins.end()) + { + VERB3 log("Returning settings for loaded plugin %s", pName); + ret = it_plugin->second->GetSettings(); + return ret; + } + } + /* else: module is not loaded */ + map_map_string_t::iterator it_settings = m_map_plugin_settings.find(pName); + if (it_settings != m_map_plugin_settings.end()) + { + /* but it exists, its settings are available nevertheless */ + VERB3 log("Returning settings for non-loaded plugin %s", pName); + ret = it_settings->second; + return ret; + } + + VERB3 log("Request for settings of unknown plugin %s, returning null result", pName); + return ret; +} diff --git a/src/daemon/PluginManager.h b/src/daemon/PluginManager.h new file mode 100644 index 00000000..a00fd3e4 --- /dev/null +++ b/src/daemon/PluginManager.h @@ -0,0 +1,157 @@ +/* + PluginManager.h - header file for plugin manager. it takes care about + (un)loading plugins + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef PLUGINMANAGER_H_ +#define PLUGINMANAGER_H_ + +#include "abrt_types.h" +#include "plugin.h" +#include "analyzer.h" +#include "reporter.h" +#include "database.h" +#include "action.h" + +class CLoadedModule; /* opaque */ + +/** + * A class. It takes care of loading, registering and manipulating with + * plugins. When a plugin is loaded, its library is opened, but no plugin + * instance is created. It is possible after plugin registration. + */ +class CPluginManager +{ + private: + typedef std::map map_loaded_module_t; + typedef std::map map_plugin_t; + + /** + * Loaded plugins. A key is a plugin name. + */ + map_loaded_module_t m_mapLoadedModules; + /** + * Registered plugins. A key is a plugin name. + */ + map_plugin_t m_mapPlugins; + /** + * List of all possible plugins (loaded or not), with some attributes. + */ + map_map_string_t m_map_plugin_info; + map_map_string_t m_map_plugin_settings; + + public: + /** + * A constructor. + * @param pPluginsConfDir A plugins configuration directory. + * @param pPluginsLibDir A plugins library directory. + */ + CPluginManager(); + /** + * A destructor. + */ + ~CPluginManager(); + /** + * A method, which loads all plugins in plugins library direcotry. + */ + void LoadPlugins(); + /** + * A method, which unregister and unload all loaded plugins. + */ + void UnLoadPlugins(); + /** + * A method, which loads particular plugin. + * @param pName A plugin name. + */ + CPlugin* LoadPlugin(const char *pName, bool enabled_only = false); + /** + * A method, which unloads particular plugin. + * @param pName A plugin name. + */ + void UnLoadPlugin(const char *pName); +#ifdef PLUGIN_DYNAMIC_LOAD_UNLOAD + /** + * A method, which registers particular plugin. + * @param pName A plugin name. + */ + void RegisterPluginDBUS(const char *pName, const char *pDBUSSender); + /** + * A method, which unregister particular plugin, + * called via DBUS + * @param pName A plugin name. + * @param pDBUSSender DBUS user identification + */ + void UnRegisterPluginDBUS(const char *pName, const char *pDBUSSender); +#endif + /** + * A method, which returns instance of particular analyzer plugin. + * @param pName A plugin name. + * @return An analyzer plugin. + */ + CAnalyzer* GetAnalyzer(const char *pName); + /** + * A method, which returns instance of particular reporter plugin. + * @param pName A plugin name. + * @return A reporter plugin. + */ + CReporter* GetReporter(const char *pName); + /** + * A method, which returns instance of particular action plugin. + * @param pName A plugin name. + * @return An action plugin. + */ + CAction* GetAction(const char *pName, bool silent = false); + /** + * A method, which returns instance of particular database plugin. + * @param pName A plugin name. + * @return A database plugin. + */ + CDatabase* GetDatabase(const char *pName); + /** + * A method, which returns type of particular plugin. + * @param pName A plugin name. + * @return A plugin type. + */ + plugin_type_t GetPluginType(const char *pName); + /** + * A method, which gets all plugins info (even those plugins which are + * disabled). It can be sent via DBus to GUI and displayed to an user. + * Then user can fill all needed informations like URLs etc. + * @return A vector of maps + */ + const map_map_string_t& GetPluginsInfo() { return m_map_plugin_info; } + /** + * A method, which sets up a plugin. The settings are also saved in home + * directory of an user. + * @param pName A plugin name. + * @param pUID An uid of user. + * @param pSettings A plugin's settings. + */ + void SetPluginSettings(const char *pName, + const char *pUID, + const map_plugin_settings_t& pSettings); + /** + * A method, which returns plugin's settings according to user. + * @param pName A plugin name. + * @return Plugin's settings. + */ + map_plugin_settings_t GetPluginSettings(const char *pName); +}; + +#endif /*PLUGINMANAGER_H_*/ diff --git a/src/daemon/Settings.cpp b/src/daemon/Settings.cpp new file mode 100644 index 00000000..5dda12a0 --- /dev/null +++ b/src/daemon/Settings.cpp @@ -0,0 +1,562 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 RedHat Inc + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "Settings.h" +#include "abrtlib.h" +#include "abrt_types.h" +#include "Polkit.h" + +#define SECTION_COMMON "Common" +#define SECTION_ANALYZER_ACTIONS_AND_REPORTERS "AnalyzerActionsAndReporters" +#define SECTION_CRON "Cron" + +/* Conf file has this format: + * [ section_name1 ] + * name1 = value1 + * name2 = value2 + * [ section_name2 ] + * name = value + */ + +/* Static data */ +/* Filled by LoadSettings() */ + +/* map["name"] = "value" strings from [ Common ] section. + * If the same name found on more than one line, + * the values are appended, separated by comma: map["name"] = "value1,value2" */ +static map_string_t s_mapSectionCommon; +/* ... from [ AnalyzerActionsAndReporters ] */ +static map_string_t s_mapSectionAnalyzerActionsAndReporters; +/* ... from [ Cron ] */ +static map_string_t s_mapSectionCron; + +/* Public data */ +/* Written out exactly in this order by SaveSettings() */ + +/* [ Common ] */ +/* one line: "OpenGPGCheck = value" */ +bool g_settings_bOpenGPGCheck = false; +/* one line: "OpenGPGPublicKeys = value1,value2" */ +set_string_t g_settings_setOpenGPGPublicKeys; +set_string_t g_settings_setBlackListedPkgs; +set_string_t g_settings_setBlackListedPaths; +std::string g_settings_sDatabase; +std::string g_settings_sWatchCrashdumpArchiveDir; +unsigned int g_settings_nMaxCrashReportsSize = 1000; +bool g_settings_bProcessUnpackaged = false; + +/* one line: "ActionsAndReporters = aa_first,bb_first(bb_second),cc_first" */ +vector_pair_string_string_t g_settings_vectorActionsAndReporters; +/* [ AnalyzerActionsAndReporters ] */ +/* many lines, one per key: "map_key = aa_first,bb_first(bb_second),cc_first" */ +map_analyzer_actions_and_reporters_t g_settings_mapAnalyzerActionsAndReporters; +/* [ Cron ] */ +/* many lines, one per key: "map_key = aa_first,bb_first(bb_second),cc_first" */ +map_cron_t g_settings_mapCron; + + +/* + * Loading + */ + +static set_string_t ParseList(const char* pList) +{ + unsigned ii; + std::string item; + set_string_t set; + for (ii = 0; pList[ii]; ii++) + { + if (pList[ii] == ',') + { + set.insert(item); + item = ""; + } + else + { + item += pList[ii]; + } + } + if (item != "") + { + set.insert(item); + } + return set; +} + +/* Format: name, name(param),name("param with spaces \"and quotes\"") */ +static vector_pair_string_string_t ParseListWithArgs(const char *pValue, int *err) +{ + VERB3 log(" ParseListWithArgs(%s)", pValue); + + vector_pair_string_string_t pluginsWithArgs; + std::string item; + std::string action; + bool is_quote = false; + bool is_arg = false; + for (int ii = 0; pValue[ii]; ii++) + { + if (is_quote && pValue[ii] == '\\' && pValue[ii + 1]) + { + ii++; + item += pValue[ii]; + continue; + } + if (pValue[ii] == '"') + { + is_quote = !is_quote; + /*item += pValue[ii]; - wrong! name("param") must be == name(param) */ + continue; + } + if (is_quote) + { + item += pValue[ii]; + continue; + } + if (pValue[ii] == '(') + { + if (!is_arg) + { + action = item; + item = ""; + is_arg = true; + } + else + { + *err = 1; + error_msg("Parser error: Invalid syntax on column %d in \"%s\"", ii, pValue); + } + + continue; + } + if (pValue[ii] == ')') + { + if (is_arg) + { + VERB3 log(" adding (%s,%s)", action.c_str(), item.c_str()); + pluginsWithArgs.push_back(make_pair(action, item)); + item = ""; + is_arg = false; + action = ""; + } + else + { + *err = 1; + error_msg("Parser error: Invalid syntax on column %d in \"%s\"", ii, pValue); + } + + continue; + } + if (pValue[ii] == ',' && !is_arg) + { + if (item != "") + { + VERB3 log(" adding (%s,%s)", item.c_str(), ""); + pluginsWithArgs.push_back(make_pair(item, "")); + item = ""; + } + continue; + } + item += pValue[ii]; + } + + if (is_quote) + { + *err = 1; + error_msg("Parser error: Unclosed quote in \"%s\"", pValue); + } + + if (is_arg) + { + *err = 1; + error_msg("Parser error: Unclosed argument in \"%s\"", pValue); + } + else if (item != "") + { + VERB3 log(" adding (%s,%s)", item.c_str(), ""); + pluginsWithArgs.push_back(make_pair(item, "")); + } + return pluginsWithArgs; +} + +static int ParseCommon() +{ + map_string_t::const_iterator end = s_mapSectionCommon.end(); + map_string_t::const_iterator it = s_mapSectionCommon.find("OpenGPGCheck"); + if (it != end) + { + g_settings_bOpenGPGCheck = string_to_bool(it->second.c_str()); + } + it = s_mapSectionCommon.find("BlackList"); + if (it != end) + { + g_settings_setBlackListedPkgs = ParseList(it->second.c_str()); + } + it = s_mapSectionCommon.find("BlackListedPaths"); + if (it != end) + { + g_settings_setBlackListedPaths = ParseList(it->second.c_str()); + } + it = s_mapSectionCommon.find("Database"); + if (it != end) + { + g_settings_sDatabase = it->second; + } + it = s_mapSectionCommon.find("WatchCrashdumpArchiveDir"); + if (it != end) + { + g_settings_sWatchCrashdumpArchiveDir = it->second; + } + it = s_mapSectionCommon.find("MaxCrashReportsSize"); + if (it != end) + { + g_settings_nMaxCrashReportsSize = xatoi_u(it->second.c_str()); + } + it = s_mapSectionCommon.find("ActionsAndReporters"); + if (it != end) + { + int err = 0; + g_settings_vectorActionsAndReporters = ParseListWithArgs(it->second.c_str(), &err); + if (err) + return err; + } + it = s_mapSectionCommon.find("ProcessUnpackaged"); + if (it != end) + { + g_settings_bProcessUnpackaged = string_to_bool(it->second.c_str()); + } + return 0; /* no error */ +} + +static int ParseCron() +{ + map_string_t::iterator it = s_mapSectionCron.begin(); + for (; it != s_mapSectionCron.end(); it++) + { + int err = 0; + vector_pair_string_string_t actionsAndReporters = ParseListWithArgs(it->second.c_str(), &err); + if (err) + return err; + g_settings_mapCron[it->first] = actionsAndReporters; + } + return 0; /* no error */ +} + +static set_string_t ParseKey(const char *Key, int *err) +{ + unsigned int ii; + std::string item; + std::string key; + set_string_t set; + bool is_quote = false; + for (ii = 0; Key[ii]; ii++) + { + if (Key[ii] == '\"') + { + is_quote = !is_quote; + } + else if (Key[ii] == ':' && !is_quote) + { + key = item; + item = ""; + } + else if (isspace(Key[ii]) && !is_quote) + { + continue; + } + else if ((Key[ii] == ',') && !is_quote) + { + if (!key.empty()) + { + set.insert(key + ":" + item); + item = ""; + } + else + { + *err = 1; + error_msg("Parser error: Invalid syntax on column %d in \"%s\"", ii, Key); + } + } + else + { + item += Key[ii]; + } + } + if (is_quote) + { + *err = 1; + error_msg("Parser error: Unclosed quote in \"%s\"", Key); + } + else if (item != "") + { + if (key == "") + { + set.insert(item); + } + else + { + set.insert(key + ":" + item); + } + } + return set; +} + +static int ParseAnalyzerActionsAndReporters() +{ + map_string_t::iterator it = s_mapSectionAnalyzerActionsAndReporters.begin(); + for (; it != s_mapSectionAnalyzerActionsAndReporters.end(); it++) + { + int err = 0; + set_string_t keys = ParseKey(it->first.c_str(), &err); + vector_pair_string_string_t actionsAndReporters = ParseListWithArgs(it->second.c_str(), &err); + if (err) + return err; + set_string_t::iterator it_keys = keys.begin(); + for (; it_keys != keys.end(); it_keys++) + { + VERB2 log("AnalyzerActionsAndReporters['%s']=...", it_keys->c_str()); + g_settings_mapAnalyzerActionsAndReporters[*it_keys] = actionsAndReporters; + } + } + return 0; /* no error */ +} + +static void LoadGPGKeys() +{ + FILE *fp = fopen(CONF_DIR"/gpg_keys", "r"); + if (fp) + { + /* every line is one key + * FIXME: make it more robust, it doesn't handle comments + */ + char line[512]; + while (fgets(line, sizeof(line), fp)) + { + if (line[0] == '/') // probably the begining of path, so let's handle it as a key + { + strchrnul(line, '\n')[0] = '\0'; + g_settings_setOpenGPGPublicKeys.insert(line); + } + } + fclose(fp); + } +} + +/** + * Reads configuration from file to s_mapSection* static variables. + * The file must be opened for reading. + */ +static int ReadConfigurationFromFile(FILE *fp) +{ + char line[512]; + std::string section; + int lineno = 0; + while (fgets(line, sizeof(line), fp)) + { + strchrnul(line, '\n')[0] = '\0'; + ++lineno; + bool is_key = true; + bool is_section = false; + bool is_quote = false; + unsigned ii; + std::string key, value; + for (ii = 0; line[ii] != '\0'; ii++) + { + if (is_quote && line[ii] == '\\' && line[ii + 1]) + { + value += line[ii]; + ii++; + value += line[ii]; + continue; + } + if (isspace(line[ii]) && !is_quote) + { + continue; + } + if (line[ii] == '#' && !is_quote && key == "") + { + break; + } + if (line[ii] == '[' && !is_quote) + { + is_section = true; + section = ""; + continue; + } + if (line[ii] == '"') + { + is_quote = !is_quote; + value += line[ii]; + continue; + } + if (is_section) + { + if (line[ii] == ']') + break; + section += line[ii]; + continue; + } + if (line[ii] == '=' && !is_quote) + { + is_key = false; + key = value; + value = ""; + continue; + } + value += line[ii]; + } + + if (is_quote) + { + error_msg("abrt.conf: Invalid syntax on line %d", lineno); + return 1; /* error */ + } + + if (is_section) + { + if (line[ii] != ']') /* section not closed */ + { + error_msg("abrt.conf: Section not closed on line %d", lineno); + return 1; /* error */ + } + continue; + } + + if (is_key) + { + if (!value.empty()) /* the key is stored in value */ + { + error_msg("abrt.conf: Invalid syntax on line %d", lineno); + return 1; /* error */ + } + continue; + } + else if (key.empty()) /* A line without key: " = something" */ + { + error_msg("abrt.conf: Invalid syntax on line %d", lineno); + return 1; /* error */ + } + + if (section == SECTION_COMMON) + { + if (s_mapSectionCommon[key] != "") + s_mapSectionCommon[key] += ","; + s_mapSectionCommon[key] += value; + } + else if (section == SECTION_ANALYZER_ACTIONS_AND_REPORTERS) + { + if (s_mapSectionAnalyzerActionsAndReporters[key] != "") + s_mapSectionAnalyzerActionsAndReporters[key] += ","; + s_mapSectionAnalyzerActionsAndReporters[key] += value; + } + else if (section == SECTION_CRON) + { + if (s_mapSectionCron[key] != "") + s_mapSectionCron[key] += ","; + s_mapSectionCron[key] += value; + } + else + { + error_msg("abrt.conf: Ignoring entry in invalid section [%s]", section.c_str()); + return 1; /* error */ + } + } + return 0; /* success */ +} + +/* abrt daemon loads .conf file */ +int LoadSettings() +{ + int err = 0; + + FILE *fp = fopen(CONF_DIR"/abrt.conf", "r"); + if (fp) + { + err = ReadConfigurationFromFile(fp); + fclose(fp); + } + else + error_msg("Unable to read configuration file %s", CONF_DIR"/abrt.conf"); + + if (err == 0) + err = ParseCommon(); + if (err == 0) + err = ParseAnalyzerActionsAndReporters(); + if (err == 0) + err = ParseCron(); + + if (err == 0) + { + /* + * loading gpg keys will invoke LoadOpenGPGPublicKey() from rpm.cpp + * pgpReadPkts which makes nss to re-init and thus makes + * bugzilla plugin work :-/ + */ + //FIXME FIXME FIXME FIXME FIXME FIXME!!! + //if (g_settings_bOpenGPGCheck) + LoadGPGKeys(); + } + + return err; +} + +/* dbus call to retrieve .conf file data from daemon */ +map_abrt_settings_t GetSettings() +{ + map_abrt_settings_t ABRTSettings; + + ABRTSettings[SECTION_COMMON] = s_mapSectionCommon; + ABRTSettings[SECTION_ANALYZER_ACTIONS_AND_REPORTERS] = s_mapSectionAnalyzerActionsAndReporters; + ABRTSettings[SECTION_CRON] = s_mapSectionCron; + + return ABRTSettings; +} + +/* dbus call to change some .conf file data */ +void SetSettings(const map_abrt_settings_t& pSettings, const char *dbus_sender) +{ + int polkit_result; + + polkit_result = polkit_check_authorization(dbus_sender, + "org.fedoraproject.abrt.change-daemon-settings"); + if (polkit_result != PolkitYes) + { + error_msg("user %s not authorized, returned %d", dbus_sender, polkit_result); + return; + } + log("user %s succesfully authorized", dbus_sender); + + map_abrt_settings_t::const_iterator it = pSettings.find(SECTION_COMMON); + map_abrt_settings_t::const_iterator end = pSettings.end(); + if (it != end) + { + s_mapSectionCommon = it->second; + ParseCommon(); + } + it = pSettings.find(SECTION_ANALYZER_ACTIONS_AND_REPORTERS); + if (it != end) + { + s_mapSectionAnalyzerActionsAndReporters = it->second; + ParseAnalyzerActionsAndReporters(); + } + it = pSettings.find(SECTION_CRON); + if (it != end) + { + s_mapSectionCron = it->second; + ParseCron(); + } +} diff --git a/src/daemon/Settings.h b/src/daemon/Settings.h new file mode 100644 index 00000000..3103dbd5 --- /dev/null +++ b/src/daemon/Settings.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 RedHat Inc + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef SETTINGS_H_ +#define SETTINGS_H_ + +#include "abrt_types.h" + +typedef map_vector_pair_string_string_t map_analyzer_actions_and_reporters_t; +typedef map_vector_pair_string_string_t map_cron_t; +typedef map_map_string_t map_abrt_settings_t; + +extern set_string_t g_settings_setOpenGPGPublicKeys; +extern set_string_t g_settings_setBlackListedPkgs; +extern set_string_t g_settings_setBlackListedPaths; +extern unsigned int g_settings_nMaxCrashReportsSize; +extern bool g_settings_bOpenGPGCheck; +extern bool g_settings_bProcessUnpackaged; +extern std::string g_settings_sDatabase; +extern std::string g_settings_sWatchCrashdumpArchiveDir; +extern map_cron_t g_settings_mapCron; +extern vector_pair_string_string_t g_settings_vectorActionsAndReporters; +extern map_analyzer_actions_and_reporters_t g_settings_mapAnalyzerActionsAndReporters; + +int LoadSettings(); +void SaveSettings(); +void SetSettings(const map_abrt_settings_t& pSettings, const char * dbus_sender); +map_abrt_settings_t GetSettings(); + +#endif diff --git a/src/daemon/abrt-debuginfo-install b/src/daemon/abrt-debuginfo-install new file mode 100755 index 00000000..3a236b59 --- /dev/null +++ b/src/daemon/abrt-debuginfo-install @@ -0,0 +1,418 @@ +#!/bin/sh +# Called by abrtd before producing a backtrace. +# The task of this script is to install debuginfos. +# +# Just using [pk-]debuginfo-install does not work well. +# - they can't install more than one version of debuginfo +# for a package +# - their output is unsuitable for scripting +# - debuginfo-install aborts if yum lock is busy +# - pk-debuginfo-install was observed to hang +# +# Usage: abrt-debuginfo-install CORE TEMPDIR [CACHEDIR[:DEBUGINFODIR1:DEBUGINFODIR2...]] +# If CACHEDIR is specified, debuginfos should be installed there. +# If not, debuginfos should be installed into TEMPDIR. +# +# Currently, we are called with CACHEDIR set to "/var/cache/abrt-di", +# but in the future it may be omitted or set to something else. +# Script must be ready for those cases too. Consider, for example, +# corner cases of "" and "/". +# +# Output goes to GUI as debuginfo install log. The script should be careful +# to give useful, but not overly cluttered info to stdout. +# Additionally, abrt daemon handles "MISSING:xxxx" messages specially: +# it is used to inform about missing debuginfos. +# +# Exitcodes: +# 0 - all debuginfos are installed +# 1 - not all debuginfos are installed +# 2+ - serious problem +# +# Algorithm: +# - Create TEMPDIR +# - Extract build-ids from coredump +# - For every build-id, check /usr/lib/debug/.build-id/XX/XXXX.debug +# and CACHEDIR/usr/lib/debug/.build-id/XX/XXXX.debug +# - If they all exist, exit 0 +# - Using "yum provides /usr/lib/debug/.build-id/XX/XXXX.debug", +# figure out which debuginfo packages are needed +# - Download them using "yumdownloader PACKAGE..." +# - Unpack them with rpm2cpio | cpio to TEMPDIR +# - If CACHEDIR is specified, copy usr/lib/debug/.build-id/XX/XXXX.debug +# to CACHEDIR/usr/lib/debug/.build-id/XX/XXXX.debug and delete TEMPDIR +# - Report which XX/XXXX.debug are still missing. +# +# For better debuggability, eu_unstrip.OUT, yum_provides.OUT etc files +# are saved in TEMPDIR, and TEMPDIR is not deleted if we exit with exitcode 2 +# ("serious problem"). + + +debug=false +# Useful if you need to see saved rpms, command outputs etc +keep_tmp=false + + +# Handle options +if test x"$1" = x"--"; then + shift +else + if test x"$1" = x"-v"; then + debug=true + shift + fi + if test $# -lt 2 || test x"$1" = x"--help"; then + echo "Usage:" + echo + echo "abrt-debuginfo-install [-v] CORE TEMPDIR [CACHEDIR[:DEBUGINFODIR...]]" + echo + echo "TEMPDIR must be a name of a new temporary directory. It must not exist." + echo "If CACHEDIR is specified, debuginfos are installed in CACHEDIR," + echo "and TEMPDIR is deleted on exit." + echo "Otherwise, debuginfos are installed into TEMPDIR, which is not deleted." + echo + echo "Options:" + echo " -v Verbose (for debugging)" + echo + exit + fi +fi + + +# Parse params +core="$1" +tempdir="$2" +debuginfodirs="${3//:/ }" +cachedir="${3%%:*}" + + +# stderr may be used for status messages too +exec 2>&1 + + +error_msg_and_die() { + echo "$*" + exit 2 +} + +count_words() { + echo $# +} + +print_missing_build_ids() { + local build_id + local build_id1 + local build_id2 + local file + local d + for build_id in $build_ids; do + build_id1=${build_id:0:2} + build_id2=${build_id:2} + file="usr/lib/debug/.build-id/$build_id1/$build_id2.debug" + test -f "/$file" && continue + # On 2nd pass, we may already have some debuginfos in tempdir + test -f "$tempdir/$file" && continue + # Check cachedir if we have one + for d in $debuginfodirs; do + test -f "$d/$file" && continue 2 + done + echo -n "$build_id " + done +} + +# Note: it is run in `backticks`, use >&2 for error messages +print_missing_debuginfos() { + local build_id + local build_id1 + local build_id2 + local file + local d + for build_id in $build_ids; do + build_id1=${build_id:0:2} + build_id2=${build_id:2} + file="usr/lib/debug/.build-id/$build_id1/$build_id2.debug" + test -f "/$file" && continue + # On 2nd pass, we may already have some debuginfos in tempdir + test -f "$tempdir/$file" && continue + # Check cachedir if we have one + if test x"$cachedir" != x""; then + for d in $debuginfodirs; do + test -f "$d/$file" && continue 2 + done + fi + echo -n "/$file " + done +} + +cleanup_and_report_missing() { +# Which debuginfo files are still missing, including those we just unpacked? + missing_build_ids=`print_missing_build_ids` + $debug && echo "missing_build_ids:$missing_build_ids" >&2 + + # If cachedir is specified, tempdir is just a staging area. Delete it + if test x"$cachedir" != x""; then + $keep_tmp && echo "NOT removing $tempdir (keep_tmp debugging is on)" >&2 + $keep_tmp || { $debug && echo "Removing $tempdir" >&2; rm -rf "$tempdir"; } + fi + + for missing in $missing_build_ids; do + echo "MISSING:$missing" >&2 + done + + test x"$missing_build_ids" != x"" && echo "`count_words $missing_build_ids` debuginfos can't be found" >&2 +} + +# $1: iteration (1,2...) +# Note: it is run in `backticks`, use >&2 for error messages +print_package_names() { + # We'll run something like: + # yum --enablerepo=*debuginfo* --quiet provides \ + # /usr/lib/debug/.build-id/bb/11528d59940983f495e9cb099cafb0cb206051.debug \ + # /usr/lib/debug/.build-id/c5/b84c0ad3676509dc30bfa7d42191574dac5b06.debug ... + local yumopts="" + if test x"$1" = x"1"; then + yumopts="-C" + echo "`count_words $missing_debuginfo_files` missing debuginfos, getting package list from cache" >&2 + else + echo "`count_words $missing_debuginfo_files` missing debuginfos, getting package list from repositories" >&2 + fi + # --showduplicates: do not just show the latest package + # (tried to use -R2 to abort on stuck yum lock but -R is not about that) + local cmd="yum $yumopts $yum_repo_opts --showduplicates --quiet provides $missing_debuginfo_files" + echo "$cmd" >"yum_provides.$1.OUT" + $debug && echo "Running: $cmd" >&2 + # eval is needed to strip away ''s in $yum_repo_opts; cant remove them and just use + # unquoted $cmd, that would perform globbing on '*' + local yum_provides_OUT="`eval $cmd 2>&1`" + local err=$? + printf "%s\nyum exitcode:%s\n" "$yum_provides_OUT" $err >>"yum_provides.$1.OUT" + test $err = 0 || error_msg_and_die "yum provides... exited with $err: +`head yum_provides.$1.OUT`" >&2 + + # The output is pretty machine-unfriendly: + # glibc-debuginfo-2.10.90-24.x86_64 : Debug information for package glibc + # Repo : rawhide-debuginfo + # Matched from: + # Filename : /usr/lib/debug/.build-id/5b/c784c8d63f87dbdeb747a773940956a18ecd2f.debug + # + # 1:dbus-debuginfo-1.2.12-2.fc11.x86_64 : Debug information for package dbus + # Repo : updates-debuginfo + # Matched from: + # Filename : /usr/lib/debug/.build-id/bc/da7d09eb6c9ee380dae0ed3d591d4311decc31.debug + # Need to massage it a lot. + # There can be duplicates (one package may provide many debuginfos). + printf "%s\n" "$yum_provides_OUT" \ + | grep -- -debuginfo- \ + | sed 's/^[0-9]*://' \ + | sed -e 's/ .*//' -e 's/:.*//' \ + | sort | uniq | xargs +} + +abort_if_low_on_disk_space() { + local mb + # free_blocks * block_size / (1024*1024), careful to not overflow: + mb=$((`stat -f -c "%a / 8192 * %S / 128" "$tempdir"`)) + if test $mb -lt $1; then + $debug && echo "Removing $tempdir" >&2 + rm -rf "$tempdir" + error_msg_and_die "Less than $1 Mb of free space in $tempdir: $mb Mb" + fi + if test x"$cachedir" != x"" && test -d "$cachedir"; then + mb=$((`stat -f -c "%a / 8192 * %S / 128" "$cachedir"`)) + if test $mb -lt $1; then + $debug && echo "Removing $tempdir" >&2 + rm -rf "$tempdir" + error_msg_and_die "Less than $1 Mb of free space in $cachedir: $mb Mb" + fi + fi +} + +download_packages() { + local pkg + local err + local file + local build_id + local build_id1 + local build_id2 + local d + + ## Download with one command (too silent): + ## Redirecting, since progress bar stuff only messes up our output + ##yumdownloader --enablerepo=*debuginfo* --quiet $packages >yumdownloader.OUT 2>&1 + ##err=$? + ##echo "exitcode:$err" >>yumdownloader.OUT + ##test $err = 0 || error_msg_and_die ... + >yumdownloader.OUT + i=1 + for pkg in $packages; do + echo "Download $i/$num_packages: $pkg" + echo "Download $i/$num_packages: $pkg" >>yumdownloader.OUT + cmd="yumdownloader $yum_repo_opts --quiet $pkg" + $debug && echo "Running: $cmd" >&2 + # eval is needed to strip away ''s in $yum_repo_opts + eval $cmd >>yumdownloader.OUT 2>&1 & + # using EXIT handler and this, make sure we kill yumdownloader if we exit: + CHILD_PID=$! + wait + err=$? + CHILD_PID="" + echo "exitcode:$err" >>yumdownloader.OUT + echo >>yumdownloader.OUT + test $err = 0 || echo "Download of $pkg failed!" + abort_if_low_on_disk_space 256 + + # Process and delete the *.rpm file just downloaded + # We do it right after download: some users have smallish disks... + for file in *.rpm; do + # Happens if no .rpm's were downloaded (yumdownloader problem) + # In this case, $f is the literal "*.rpm" string + test -f "$file" || { echo "No rpm file downloaded"; continue; } + echo "Unpacking: $file" + echo "Processing: $file" >>unpack.OUT + rpm2cpio <"$file" >"unpacked.cpio" 2>>unpack.OUT || error_msg_and_die "Can't convert '$file' to cpio" + $keep_tmp || rm "$file" + abort_if_low_on_disk_space 256 + cpio -id <"unpacked.cpio" >>unpack.OUT 2>&1 || error_msg_and_die "Can't unpack '$file' cpio archive" + rm "unpacked.cpio" + abort_if_low_on_disk_space 256 + # Copy debuginfo files to cachedir + if test x"$cachedir" != x"" && test -d "$cachedir"; then + # For every needed debuginfo, check whether we have it + for build_id in $build_ids; do + build_id1=${build_id:0:2} + build_id2=${build_id:2} + file="usr/lib/debug/.build-id/$build_id1/$build_id2.debug" + # Do not copy it if it can be found in any of $debuginfodirs + test -f "/$file" && continue + if test x"$cachedir" != x""; then + for d in $debuginfodirs; do + test -f "$d/$file" && continue 2 + done + fi + if test -f "$file"; then + # File is one of those we just installed, cache it + mkdir -p "$cachedir/usr/lib/debug/.build-id/$build_id1" + # Note: this does not preserve symlinks. This is intentional + $debug && echo Copying "$file" to "$cachedir/$file" >&2 + echo "Caching debuginfo: $file" + cp --remove-destination "$file" "$cachedir/$file" || error_msg_and_die "Can't copy $file (disk full?)" + continue + fi + done + fi + # Delete remaining files unpacked from .cpio + # which we didn't need after all + rm -r etc bin sbin usr var opt 2>/dev/null + done + : $((i++)) + done +} + + +# Sanity checking +test -f "$core" || error_msg_and_die "not a file: '$core'" +# cachedir is optional +test x"$cachedir" = x"" || test -d "$cachedir" || error_msg_and_die "bad cachedir '$cachedir'" +# tempdir must not exist +test -e "$tempdir" && error_msg_and_die "tempdir exists: '$tempdir'" + +# Intentionally not using -p: we want to abort if tempdir exists +mkdir -- "$tempdir" || exit 2 +cd "$tempdir" || exit 2 + + +abort_if_low_on_disk_space 1024 + + +# A hook to stop yumdownloader, in case we are terminated by kill -TERM etc. +CHILD_PID="" +trap 'test x"$CHILD_PID" != x"" && kill -- "$CHILD_PID"' EXIT + + +$debug && echo "Downloading rpms to $tempdir" + + +echo "Getting list of build IDs" +# Observed errors: +# eu-unstrip: /var/spool/abrt/ccpp-1256301004-2754/coredump: Callback returned failure +eu_unstrip_OUT=`eu-unstrip "--core=$core" -n 2>eu_unstrip.ERR` +err=$? +printf "%s\neu-unstrip exitcode:%s\n" "$eu_unstrip_OUT" $err >eu_unstrip.OUT +test $err = 0 || error_msg_and_die "eu-unstrip exited with $err: +`cat eu_unstrip.ERR` +`head eu_unstrip.OUT`" + +# eu-unstrip output example: +# 0x400000+0x209000 23c77451cf6adff77fc1f5ee2a01d75de6511dda@0x40024c - - [exe] +# or +# 0x400000+0x20d000 233aa1a57e9ffda65f53efdaf5e5058657a39993@0x40024c /usr/libexec/im-settings-daemon /usr/lib/debug/usr/libexec/im-settings-daemon.debug [exe] +# 0x7fff5cdff000+0x1000 0d3eb4326fd7489fcf9b598269f1edc420e2c560@0x7fff5cdff2f8 . - linux-vdso.so.1 +# 0x3d15600000+0x208000 20196628d1bc062279622615cc9955554e5bb227@0x3d156001a0 /usr/lib64/libnotify.so.1.1.3 /usr/lib/debug/usr/lib64/libnotify.so.1.1.3.debug libnotify.so.1 +# 0x7fd8ae931000+0x62d000 dd49f44f958b5a11a1635523b2f09cb2e45c1734@0x7fd8ae9311a0 /usr/lib64/libgtk-x11-2.0.so.0.1600.6 /usr/lib/debug/usr/lib64/libgtk-x11-2.0.so.0.1600.6.debug +# +# Get space-separated list of all build-ids +# There can be duplicates (observed in real world) +build_ids=`printf "%s\n" "$eu_unstrip_OUT" \ +| while read junk1 build_id binary_file di_file lib_name junk2; do + build_id=${build_id%%@*} + + # This filters out linux-vdso.so, among others + test x"$lib_name" != x"[exe]" && test x"${binary_file:0:1}" != x"/" && continue + # Sanitize build_id: must be longer than 2 chars + test ${#build_id} -le 2 && continue + # Sanitize build_id: must have only hex digits + test x"${build_id//[0-9a-f]/}" != x"" && continue + + echo "$build_id" +done | sort | uniq | xargs` +$debug && echo "build_ids:$build_ids" + + +# Prepare list of repos to use. +# When we look for debuginfo we need only -debuginfo* repos, we can disable the rest +# and thus make it faster. +yum_repo_opts="'--disablerepo=*'" +#// Disabled. Too often, debuginfo repos have names which do not conform to "foo-debuginfo" scheme, +#// and users get bad backtraces. +#// # (Without -C, yum for some reason wants to talk to repos! If one is down, it becomes S..L..O..W) +#// for enabled_repo in `LANG=C yum -C repolist all | grep 'enabled:' | cut -f1 -d' ' | grep -v -- '-debuginfo'`; do +#// yum_repo_opts="$yum_repo_opts '--enablerepo=${enabled_repo}-debuginfo*'" +#// done +yum_repo_opts="$yum_repo_opts '--enablerepo=*-debug*'" + + +# We try to not run yum without -C unless absolutely necessary. +# Therefore we loop. yum is run by print_package_names function, +# on first iteration it is run with -C, on second - without, +# which usually causes yum to download updated filelists, +# which in turn takes several minutes and annoys users. +iter=0 +while test $((++iter)) -le 2; do + # Analyze $build_ids and check which debuginfos are present + missing_debuginfo_files=`print_missing_debuginfos` + # Did print_missing_debuginfos fail? + test $? = 0 || exit 2 + $debug && echo "missing_debuginfo_files:$missing_debuginfo_files" + + test x"$missing_debuginfo_files" = x"" && break + + # Map $missing_debuginfo_files to package names. + # yum is run here. + packages=`print_package_names $iter` + # Did print_package_names fail? + test $? = 0 || exit 2 + $debug && echo "packages ($iter):$packages" + + # yum may return "" here if it found no packages (say, if coredump + # is from a new, unreleased package fresh from koji). + test x"$packages" = x"" && continue + + num_packages=`count_words $packages` + echo "Downloading $num_packages packages" + download_packages +done + +cleanup_and_report_missing + +test x"$missing_build_ids" != x"" && exit 1 +echo "All needed debuginfos are present" +exit 0 diff --git a/src/daemon/abrt-handle-upload b/src/daemon/abrt-handle-upload new file mode 100755 index 00000000..55eeb7a6 --- /dev/null +++ b/src/daemon/abrt-handle-upload @@ -0,0 +1,74 @@ +#!/bin/sh +# Called by abrtd when a new file is noticed in upload directory. +# The task of this script is to unpack the file and move +# crashdump(s) found in it to abrt's crashdump directory. +# +# Usage: abrt-handle-upload ABRT_DIR UPLOAD_DIR FILENAME + +#echo "Started: $0 $*" + +print_clean_and_die() +{ + printf "%s\n" "$*" + #echo delete_on_exit="$delete_on_exit" + test "$delete_on_exit" && rm -rf -- $delete_on_exit + exit $die_exitcode +} + +die_exitcode=1 +delete_on_exit="" + +abrt_dir="$1" +upload_dir="$2" +archive="$3" + +test -d "$abrt_dir" || print_clean_and_die "Not a directory: '$abrt_dir'" +test -d "$upload_dir" || print_clean_and_die "Not a directory: '$upload_dir'" +test x"${archive%.working}" != x"$archive" && print_clean_and_die "Skipping: '$archive'" +test x"${archive#/}" != x"$archive" && print_clean_and_die "Skipping: '$archive' (starts with slash)" +test x"${archive#.}" != x"$archive" && print_clean_and_die "Skipping: '$archive' (starts with dot)" +test x"${archive#*..}" != x"$archive" && print_clean_and_die "Skipping: '$archive' (contains ..)" +test x"${archive#* }" != x"$archive" && print_clean_and_die "Skipping: '$archive' (contains space)" +# Note: next line has a tab! +test x"${archive#* }" != x"$archive" && print_clean_and_die "Skipping: '$archive' (contains tab)" + +cd -- "$upload_dir" || print_clean_and_die "Can't chdir to '$upload_dir'" + +unpacker="" +test x"${archive%.tar.gz}" != x"$archive" && unpacker="gunzip" +test x"${archive%.tar.bz2}" != x"$archive" && unpacker="bunzip2" +test x"${archive%.tar.xz}" != x"$archive" && unpacker="unxz" + +test "$unpacker" || print_clean_and_die "Unknown file type: '$archive'" + +tempdir="remote.`date +%Y-%m-%d-%H:%M:%S.%N`.$$" + +mv -- "$archive" "$archive.working" || print_clean_and_die "Can't lock '$archive'" + +delete_on_exit="$archive.working" +$unpacker -t -- "$archive.working" || print_clean_and_die "Verification error on '$archive'" + +echo "Unpacking '$archive'" +mkdir "$tempdir" || print_clean_and_die "Can't create '$tempdir' directory" +delete_on_exit="$archive.working $tempdir" +$unpacker <"$archive.working" | tar xf - -C "$tempdir" || print_clean_and_die "Can't unpack '$archive'" + +# The archive can contain either plain dump files +# or one or more complete crashdump directories. +# Checking second possibility first. +if test -f "$tempdir/analyzer" && test -f "$tempdir/time" && test -f "$tempdir/uid"; then + printf "1" >"$tempdir/remote" + mv -- "$tempdir" "$abrt_dir" +else + for d in "$tempdir"/*; do + test -d "$d" || continue + printf "1" >"$d/remote" + dst="$abrt_dir/$d" + test -e "$dst" && dst="$abrt_dir/$d.$$" + test -e "$dst" && continue + mv -- "$d" "$dst" + done +fi + +die_exitcode=0 +print_clean_and_die "'$archive' processed successfully" diff --git a/src/daemon/abrt.conf b/src/daemon/abrt.conf new file mode 100644 index 00000000..534aef8f --- /dev/null +++ b/src/daemon/abrt.conf @@ -0,0 +1,61 @@ +[ Common ] +# With this option set to "yes", +# only crashes in signed packages will be analyzed. +# the list of public keys used to check the signature is +# in the file gpg_keys +# +OpenGPGCheck = yes + +# Blacklisted packages +# +BlackList = nspluginwrapper, valgrind, strace + +# Process crashes in executables which do not belong to any package? +# +ProcessUnpackaged = no + +# Blacklisted executable paths (shell patterns) +# +BlackListedPaths = /usr/share/doc/*, */example* + +# Which database plugin to use +# +Database = SQLite3 + +# Enable this if you want abrtd to auto-unpack crashdump tarballs which appear +# in this directory (for example, uploaded via ftp, scp etc). +# Note: you must ensure that whatever directory you specify here exists +# and is writable for abrtd. abrtd will not create it automatically. +# +#WatchCrashdumpArchiveDir = /var/spool/abrt-upload + +# Max size for crash storage [MiB] or 0 for unlimited +# +MaxCrashReportsSize = 1000 + +# Vector of actions and reporters which are activated immediately +# after a crash occurs, comma separated. +# +#ActionsAndReporters = Mailx("[abrt] new crash was detected") +#ActionsAndReporters = FileTransfer("store") +ActionsAndReporters = RunApp("test x\"`cat component`\" = x\"xorg-x11-server-Xorg\" && cp /var/log/Xorg.0.log .") + + +# What actions or reporters to run on each crash type +# +[ AnalyzerActionsAndReporters ] +Kerneloops = KerneloopsReporter +CCpp = Bugzilla, Logger +Python = Bugzilla, Logger +#CCpp:xorg-x11-apps = RunApp("date", "date.txt") + + +# Which Action plugins to run repeatedly +# +[ Cron ] +# h:m - at h:m +# s - every s seconds + +120 = KerneloopsScanner + +#02:00 = FileTransfer diff --git a/src/daemon/abrt.conf.5 b/src/daemon/abrt.conf.5 new file mode 100644 index 00000000..f8afe393 --- /dev/null +++ b/src/daemon/abrt.conf.5 @@ -0,0 +1,95 @@ +.TH "abrt.conf" "5" "28 May 2009" "" +.SH NAME +abrt.conf \- configuration file for abrt +.SH DESCRIPTION +.P +.I abrt +is a daemon that watches for application crashes. When a crash occurs, +it collects the crash data and takes action according to +its configuration. This manual page describes \fIabrt\fP's configuration +file. +.P +The configuration file consists of sections, each section contains +several items in the format "Option = Value". A description of each +section follows: +.SS [Common] +.TP +.B OpenGPGCheck = \fIyes\fP | \fIno\fP +When set to "yes", +.I abrt +will report crashes only in GPG signed packages. When set to "no", +it will report crashes also in unsigned packages. The default is "no". +.TP +.B OpenGPGPublicKeys = \fIfilename\fP , \fIfilename\fP ... +These are the trusted GPG keys with which packages have to be +signed for +.I abrt +to report them if "OpenGPGCheck = yes". +.TP +.B BlackList = \fIpackageName\fP, \fIpackageName\fP ... +.I abrt +will ignore packages in this list and will not handle their crashes. +.TP +.B BlackListedPaths = \fI/path/to/ignore/*\fP, \fI*/another/ignored/path*\fP ... +.I abrt +will ignore crashes in executables whose absolute path matches +one of specified patterns. +.TP +.B Database = \fIdatabasePlugin\fP +This specifies which database plugin +.I abrt +uses to store metadata about the crash. +.TP +.B MaxCrashReportsSize = \fInumber\fP +The maximum disk space (specified in megabytes) that +.I abrt +will use for all the crash dumps. Specify a value here to ensure +that the crash dumps will not fill all available storage space. +The default is 1000. +.TP +.B ActionsAndReporters = \fIplugin\fP, \fIplugin(parameters)\fP +List of reporter and action plugins which will be +run at crash time. +.TP +.B ProcessUnpackaged = \fIyes\fP | \fIno\fP +When set to "yes", +.I abrt +will catch all crashes in the system. When set to "no", +it will catch crashes only in Fedora packages. +The default is "no". + +.SS [ AnalyzerActionsAndReporters ] +This section contains associations between analyzers and action +or reporter plugins. +.TP +.B CCpp = \fIplugin\fP +.I abrt +will use this plugin when a C/C++ program crashes. +You can specify a plugin for specific program, for example +.br +CCpp:bind = Mailx("[abrt] Bind crashed") +.br +The Mailx plugin will run when bind crashes, instead of the plugin specified for +all the C/C++ programs. +.TP +.B Kerneloops = \fIplugin\fP +This plugin will be used in the case of kernel crashes. +.SS [ Cron ] +This section specifies tasks that will be run at some specified time. You can specify +either a time of day in the format +.br +.B hh:mm = \fIplugin\fP +.br +or an interval +.br +.B ss = \fIplugin2\fP +.br +in this case, \fIplugin2\fP will be run every \fIss\fP seconds (this number +can be greater than 60). +.SH "SEE ALSO" +.IR abrtd (8), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Zdeněk Přikryl and +Jiří Moskovčák . Manual page written by Daniel +Novotný . diff --git a/src/daemon/abrtd.8 b/src/daemon/abrtd.8 new file mode 100644 index 00000000..150f0c3d --- /dev/null +++ b/src/daemon/abrtd.8 @@ -0,0 +1,43 @@ +.TH abrtd "8" "28 May 2009" "" +.SH NAME +abrtd \- automated bug reporting tool's daemon +.SH SYNOPSIS +.B abrtd [-dsv[v]...] +.SH DESCRIPTION +.I abrtd +is a daemon that watches for application crashes. When a crash occurs, +it collects the crash data (core file, application's command line etc.) +and takes action according to the type of application that +crashed and according to the configuration in the +.I abrt.conf +config file. There are plugins for various actions: for example to report +the crash to Bugzilla, to mail the report, or to transfer the +report via FTP or SCP. See the manual pages for the +respective plugins. +.SH OPTIONS + +.TP +.B "\-d" + +Stay in the foreground and log to standard error. +.TP +.B "\-s" + +Log to system log even with option -d. +.TP +.B "\-v" + +Log more detailed debugging information. +.SH CAVEATS +When you use some other crash-catching tool specific for an application +or an application type (for example BugBuddy for GNOME applications), +crashes of this type will be handled by that tool and +not by \fIabrtd\fP. If you want \fIabrtd\fP to handle these crashes, +turn off the higher-level crash-catching tool. +.SH "SEE ALSO" +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Zdeněk Přikryl and +Jiří Moskovčák . Manual page written by Daniel +Novotný . diff --git a/src/daemon/abrtd.service b/src/daemon/abrtd.service new file mode 100644 index 00000000..e170e324 --- /dev/null +++ b/src/daemon/abrtd.service @@ -0,0 +1,11 @@ +[Unit] +Description=ABRT Automated Bug Reporting Tool +After=syslog.target + +[Service] +Type=dbus +BusName=com.redhat.abrt +ExecStart=/usr/sbin/abrtd -d -s + +[Install] +WantedBy=multi-user.target diff --git a/src/daemon/com.redhat.abrt.service b/src/daemon/com.redhat.abrt.service new file mode 100644 index 00000000..b251ef7f --- /dev/null +++ b/src/daemon/com.redhat.abrt.service @@ -0,0 +1,7 @@ +[D-BUS Service] +Name=com.redhat.abrt +# For testing, you may add -t33 to use small timeout of 33 seconds. +# This will make "abrtd exited while clients existed but were idle" +# situations easy to trigger +Exec=/usr/sbin/abrtd -ds +User=root diff --git a/src/daemon/dbus-abrt.conf b/src/daemon/dbus-abrt.conf new file mode 100644 index 00000000..11b2a111 --- /dev/null +++ b/src/daemon/dbus-abrt.conf @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/daemon/dumpsocket.cpp b/src/daemon/dumpsocket.cpp new file mode 100644 index 00000000..699a0609 --- /dev/null +++ b/src/daemon/dumpsocket.cpp @@ -0,0 +1,573 @@ +/* + Copyright (C) 2010 ABRT team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "dumpsocket.h" +#include "abrtlib.h" +#include +#include +#include +#include +#include "debug_dump.h" +#include "crash_types.h" +#include "abrt_exception.h" +#include "hooklib.h" +#include "strbuf.h" + +#define SOCKET_FILE VAR_RUN"/abrt/abrt.socket" +#define SOCKET_PERMISSION 0666 + +/* Maximal length of backtrace. */ +#define MAX_BACKTRACE_SIZE (1024*1024) + +/* Amount of data received from one client for a message before reporting error. */ +#define MAX_MESSAGE_SIZE (4*MAX_BACKTRACE_SIZE) + +/* Maximum number of simultaneously opened client connections. */ +#define MAX_CLIENT_COUNT 10 + +/* Interval between checks of client halt, in seconds. */ +#define CLIENT_CHECK_INTERVAL 10 + +/* Interval with no data received from client, after which the client is + considered halted, in seconds. */ +#define CLIENT_HALT_INTERVAL 10 + +/* Maximal number of characters read from socket at once. */ +#define INPUT_BUFFER_SIZE 1024 + +static GIOChannel *channel = NULL; +static guint channel_cb_id = 0; +static int client_count = 0; + +/* Information about single socket session. */ +struct client +{ + /* Client user id */ + uid_t uid; + /* Buffer for incomplete incoming messages. */ + GByteArray *messagebuf; + /* Executable. */ + char *executable; + /* Process ID. */ + int pid; + char *backtrace; + /* Python, Ruby etc. */ + char *analyzer; + /* Directory base name: python (or pyhook), ruby etc. */ + char *basename; + /* Crash reason. + * Python example: "CCMainWindow.py:1::ZeroDivisionError: + * integer division or modulo by zero" + */ + char *reason; + /* Last time some data were received over the socket + * from the client. + */ + time_t lastwrite; + /* Timer checking client halt. */ + guint timer_id; + /* Client socket callback id. */ + guint socket_id; + /* Client socket channel */ + GIOChannel *channel; +}; + +static gboolean server_socket_cb(GIOChannel *source, + GIOCondition condition, + gpointer data); + +/* Releases all memory that belongs to a client session. */ +static void client_free(struct client *client) +{ + /* Delete the uncompleted message if there is some. */ + g_byte_array_free(client->messagebuf, TRUE); + free(client->executable); + free(client->backtrace); + free(client->analyzer); + free(client->basename); + free(client->reason); + g_source_remove(client->timer_id); + g_source_remove(client->socket_id); + g_io_channel_unref(client->channel); + free(client); + --client_count; + if (!channel_cb_id) + { + channel_cb_id = g_io_add_watch(channel, + (GIOCondition)(G_IO_IN | G_IO_PRI), + (GIOFunc)server_socket_cb, + NULL); + if (!channel_cb_id) + perror_msg_and_die("dumpsocket: Can't add socket watch"); + } +} + +/* Callback called by glib main loop at regular intervals when + some client is connected. */ +static gboolean client_check_cb(gpointer data) +{ + struct client *client = (struct client*)data; + if (time(NULL) - client->lastwrite > CLIENT_HALT_INTERVAL) + { + log("dumpsocket: client socket timeout reached, closing connection"); + client_free(client); + return FALSE; + } + return TRUE; +} + +/* Caller is responsible to free() the returned value. */ +static char *giocondition_to_string(GIOCondition condition) +{ + struct strbuf *strbuf = strbuf_new(); + if (condition & G_IO_HUP) + strbuf_append_str(strbuf, "G_IO_HUP | "); + if (condition & G_IO_ERR) + strbuf_append_str(strbuf, "G_IO_ERR | "); + if (condition & G_IO_NVAL) + strbuf_append_str(strbuf, "G_IO_NVAL | "); + if (condition & G_IO_IN) + strbuf_append_str(strbuf, "G_IO_IN | "); + if (condition & G_IO_OUT) + strbuf_append_str(strbuf, "G_IO_OUT | "); + if (condition & G_IO_PRI) + strbuf_append_str(strbuf, "G_IO_PRI | "); + if (strbuf->len == 0) + strbuf_append_str(strbuf, "none"); + else + { + /* remove the last " | " */ + strbuf->len -= 3; + strbuf->buf[strbuf->len] = '\0'; + } + char *result = strbuf->buf; + strbuf_free_nobuf(strbuf); + return result; +} + +/* Create a new debug dump from client session. + * Caller must ensure that all fields in struct client + * are properly filled. + */ +static void create_debug_dump(struct client *client) +{ + /* Create temp directory with the debug dump. + This directory is renamed to final directory name after + all files have been stored into it. + */ + char *path = xasprintf(DEBUG_DUMPS_DIR"/%s-%ld-%u.new", + client->basename, + (long)time(NULL), + client->pid); + /* No need to check the path length, as all variables used are limited, and dd.Create() + fails if the path is too long. */ + + CDebugDump dd; + try { + dd.Create(path, client->uid); + } catch (CABRTException &e) { + dd.Delete(); + dd.Close(); + error_msg_and_die("dumpsocket: Error while creating crash dump %s: %s", path, e.what()); + } + + dd.SaveText(FILENAME_ANALYZER, client->analyzer); + dd.SaveText(FILENAME_EXECUTABLE, client->executable); + dd.SaveText(FILENAME_BACKTRACE, client->backtrace); + dd.SaveText(FILENAME_REASON, client->reason); + + /* Obtain and save the command line. */ + char *cmdline = get_cmdline(client->pid); // never NULL + dd.SaveText(FILENAME_CMDLINE, cmdline); + free(cmdline); + + /* Store id of the user whose application crashed. */ + char uid_str[sizeof(long) * 3 + 2]; + sprintf(uid_str, "%lu", (long)client->uid); + dd.SaveText(CD_UID, uid_str); + + dd.Close(); + + /* Move the completely created debug dump to + final directory. */ + char *newpath = xstrndup(path, strlen(path) - strlen(".new")); + if (rename(path, newpath) == 0) + strcpy(path, newpath); + free(newpath); + + log("dumpsocket: Saved %s crash dump of pid %u to %s", + client->analyzer, client->pid, path); + + /* Handle free space checking. */ + unsigned maxCrashReportsSize = 0; + parse_conf(NULL, &maxCrashReportsSize, NULL, NULL); + if (maxCrashReportsSize > 0) + { + check_free_space(maxCrashReportsSize); + trim_debug_dumps(maxCrashReportsSize, path); + } + + free(path); +} + +/* Checks if a string contains only printable characters. */ +static bool printable_str(const char *str) +{ + do { + if ((unsigned char)(*str) < ' ' || *str == 0x7f) + return false; + str++; + } while (*str); + return true; +} + +/* Checks if a string has certain prefix. */ +static bool starts_with(const char *str, const char *start) +{ + return strncmp(str, start, strlen(start)) == 0; +} + +/* @returns + * Caller is responsible to call free() on the returned + * pointer. + * If NULL is returned, string extraction failed. + */ +static char *try_to_get_string(const char *message, + const char *tag, + size_t max_len, + bool printable, + bool allow_slashes) +{ + if (!starts_with(message, tag)) + return NULL; + + const char *contents = message + strlen(tag); + if ((printable && !printable_str(contents)) || + (!allow_slashes && strchr(contents, '/'))) + { + error_msg("dumpsocket: Received %s contains invalid characters -> skipping", tag); + return NULL; + } + + if (strlen(contents) > max_len) + { + char *max_len_str = g_format_size_for_display(max_len); + error_msg("dumpsocket: Received %s too long -> trimming to %s", tag, max_len_str); + g_free(max_len_str); + } + + return xstrndup(contents, max_len); +} + +/* Handles a message received from client over socket. */ +static void process_message(struct client *client, const char *message) +{ +/* @param tag + * The message identifier. Message starting with it + * is handled by this macro. + * @param field + * Member in struct client, which should be filled by + * the field contents. + * @param max_len + * Maximum length of the field in bytes. + * Exceeding bytes are trimmed. + * @param printable + * Whether to limit the field contents to ASCII only. + * @param allow_slashes + * Whether to allow slashes to be a part of input. + */ +#define HANDLE_INCOMING_STRING(tag, field, max_len, printable, allow_slashes) \ + char *field = try_to_get_string(message, tag, max_len, printable, allow_slashes); \ + if (field) \ + { \ + free(client->field); \ + client->field = field; \ + return; \ + } + + HANDLE_INCOMING_STRING("EXECUTABLE=", executable, PATH_MAX, true, true); + HANDLE_INCOMING_STRING("BACKTRACE=", backtrace, MAX_BACKTRACE_SIZE, false, true); + HANDLE_INCOMING_STRING("BASENAME=", basename, 100, true, false); + HANDLE_INCOMING_STRING("ANALYZER=", analyzer, 100, true, true); + HANDLE_INCOMING_STRING("REASON=", reason, 512, false, true); + +#undef HANDLE_INCOMING_STRING + + /* PID is not handled as a string, we convert it to pid_t. */ + if (starts_with(message, "PID=")) + { + /* xatou() cannot be used here, because it would + * kill whole daemon by non-numeric string. + */ + char *endptr; + errno = 0; + const char *nptr = message + strlen("PID="); + unsigned long number = strtoul(nptr, &endptr, 10); + /* pid == 0 is error, the lowest PID is 1. */ + if (errno || nptr == endptr || *endptr != '\0' || number > UINT_MAX || number == 0) + { + error_msg("dumpsocket: invalid PID received -> ignoring"); + return; + } + client->pid = number; + return; + } + + /* Creates debug dump if all fields were already provided. */ + if (starts_with(message, "DONE")) + { + if (client->pid == 0 || + client->backtrace == NULL || + client->executable == NULL || + client->analyzer == NULL || + client->basename == NULL || + client->reason == NULL) + { + error_msg("dumpsocket: DONE received, but some data are missing -> ignoring"); + return; + } + + create_debug_dump(client); + return; + } +} + +/* Callback called by glib main loop when ABRT receives data that have + * been written to the socket by some client. + */ +static gboolean client_socket_cb(GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + struct client *client = (struct client*)data; + + /* Detailed logging, useful for debugging. */ + if (g_verbose >= 3) + { + char *cond = giocondition_to_string(condition); + log("dumpsocket: client condition %s", cond); + free(cond); + } + + /* Handle incoming data. */ + if (condition & (G_IO_IN | G_IO_PRI)) + { + guint loop = client->messagebuf->len; + gsize len; + gchar buf[INPUT_BUFFER_SIZE]; + GError *err = NULL; + /* Read data in chunks of size INPUT_BUFFER_SIZE. This allows to limit the number of + bytes received (to prevent memory exhaustion). */ + do { + GIOStatus result = g_io_channel_read_chars(source, buf, INPUT_BUFFER_SIZE, &len, &err); + if (result == G_IO_STATUS_ERROR) + { + g_assert(err); + error_msg("dumpsocket: Error while reading data from client socket: %s", err->message); + g_error_free(err); + client_free(client); + return FALSE; + } + + if (g_verbose >= 3) + log("dumpsocket: received %zu bytes of data", len); + + /* Append the incoming data to the message buffer. */ + g_byte_array_append(client->messagebuf, (guint8*)buf, len); + + if (client->messagebuf->len > MAX_MESSAGE_SIZE) + { + error_msg("dumpsocket: Message too long."); + client_free(client); + return FALSE; + } + } while (len > 0); + + /* Check, if we received a complete message now. */ + for (; loop < client->messagebuf->len; ++loop) + { + if (client->messagebuf->data[loop] != '\0') + continue; + + VERB3 log("dumpsocket: Processing message: %s", + client->messagebuf->data); + + /* Process the message. */ + process_message(client, (char*)client->messagebuf->data); + /* Remove the message including the ending \0 */ + g_byte_array_remove_range(client->messagebuf, 0, loop + 1); + loop = 0; + } + + /* Update the last write access time */ + client->lastwrite = time(NULL); + } + + /* Handle socket disconnection. + It is important to do it after handling G_IO_IN, because sometimes + G_IO_HUP comes together with G_IO_IN. It means that some data arrived + and then the socket has been closed. + */ + if (condition & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) + { + log("dumpsocket: Socket client disconnected"); + client_free(client); + return FALSE; + } + + return TRUE; +} + +/* If the status indicates failure, report it. */ +static void check_status(GIOStatus status, GError *err, const char *operation) +{ + if (status == G_IO_STATUS_NORMAL) + return; + + if (err) + { + error_msg("dumpsocket: Error while %s: %s", operation, err->message); + g_error_free(err); + } + else + error_msg("dumpsocket: Error while %s", operation); +} + +/* Initializes a new client session data structure. */ +static struct client *client_new(int socket) +{ + struct client *client = (struct client*)xzalloc(sizeof(struct client)); + + /* Get credentials for the socket client. */ + struct ucred cr; + socklen_t crlen = sizeof(struct ucred); + if (0 != getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &cr, &crlen)) + perror_msg_and_die("dumpsocket: Failed to get client uid"); + if (crlen != sizeof(struct ucred)) + perror_msg_and_die("dumpsocket: Failed to get client uid (crlen)"); + client->uid = cr.uid; + + client->messagebuf = g_byte_array_new(); + client->lastwrite = time(NULL); + + close_on_exec_on(socket); + + /* Create client IO channel. */ + client->channel = g_io_channel_unix_new(socket); + g_io_channel_set_close_on_unref(client->channel, TRUE); + + /* Set nonblocking access. */ + GError *err = NULL; + GIOStatus status = g_io_channel_set_flags(client->channel, G_IO_FLAG_NONBLOCK, &err); + check_status(status, err, "setting NONBLOCK flag"); + + /* Disable channel encoding to protect binary data. */ + err = NULL; + status = g_io_channel_set_encoding(client->channel, NULL, &err); + check_status(status, err, "setting encoding"); + + /* Start timer to check the client problems. */ + client->timer_id = g_timeout_add_seconds(CLIENT_CHECK_INTERVAL, client_check_cb, client); + if (!client->timer_id) + error_msg_and_die("dumpsocket: Can't add client timer"); + + /* Register client callback. */ + client->socket_id = g_io_add_watch(client->channel, + (GIOCondition)(G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL), + (GIOFunc)client_socket_cb, + client); + if (!client->socket_id) + error_msg_and_die("dumpsocket: Can't add client socket watch"); + + ++client_count; + return client; +} + +/* Callback called by glib main loop when a client newly opens ABRT's socket. */ +static gboolean server_socket_cb(GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + /* Check the limit for number of simultaneously attached clients. */ + if (client_count >= MAX_CLIENT_COUNT) + { + error_msg("dumpsocket: Too many clients, refusing connection."); + /* To avoid infinite loop caused by the descriptor in "ready" state, + the callback must be disabled. + It is added back in client_free(). */ + g_source_remove(channel_cb_id); + channel_cb_id = 0; + return TRUE; + } + + struct sockaddr_un remote; + socklen_t len = sizeof(remote); + int socket = accept(g_io_channel_unix_get_fd(source), + (struct sockaddr*)&remote, &len); + if (socket == -1) + { + perror_msg("dumpsocket: Server can not accept client"); + return TRUE; + } + + log("dumpsocket: New client connected"); + client_new(socket); + return TRUE; +} + +/* Initializes the dump socket, usually in /var/run directory + * (the path depends on compile-time configuration). + */ +void dumpsocket_init() +{ + struct sockaddr_un local; + unlink(SOCKET_FILE); /* not caring about the result */ + int socketfd = xsocket(AF_UNIX, SOCK_STREAM, 0); + close_on_exec_on(socketfd); + memset(&local, 0, sizeof(local)); + local.sun_family = AF_UNIX; + strcpy(local.sun_path, SOCKET_FILE); + xbind(socketfd, (struct sockaddr*)&local, sizeof(local)); + xlisten(socketfd, MAX_CLIENT_COUNT); + + if (chmod(SOCKET_FILE, SOCKET_PERMISSION) != 0) + perror_msg_and_die("dumpsocket: failed to chmod socket file"); + + channel = g_io_channel_unix_new(socketfd); + g_io_channel_set_close_on_unref(channel, TRUE); + channel_cb_id = g_io_add_watch(channel, + (GIOCondition)(G_IO_IN | G_IO_PRI), + (GIOFunc)server_socket_cb, + NULL); + if (!channel_cb_id) + perror_msg_and_die("dumpsocket: Can't add socket watch"); +} + +/* Releases all resources used by dumpsocket. */ +void dumpsocket_shutdown() +{ + /* Set everything to pre-initialization state. */ + if (channel) + { + /* This one is for g_io_add_watch. */ + if (channel_cb_id) + g_source_remove(channel_cb_id); + /* This one is for g_io_channel_unix_new. */ + g_io_channel_unref(channel); + channel = NULL; + } +} diff --git a/src/daemon/dumpsocket.h b/src/daemon/dumpsocket.h new file mode 100644 index 00000000..e5b79d60 --- /dev/null +++ b/src/daemon/dumpsocket.h @@ -0,0 +1,81 @@ +/* + Copyright (C) 2010 ABRT team + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef ABRT_DUMPSOCKET_H +#define ABRT_DUMPSOCKET_H + +/* +Unix socket in ABRT daemon for creating new dump directories. + +Why to use socket for creating dump dirs? Security. When a Python +script throwns unexpected exception, ABRT handler catches it, running +as a part of that broken Python application. The application is running +with certain SELinux privileges, for example it can not execute other +programs, or to create files in /var/cache or anything else required +to properly fill a dump directory. Adding these privileges to every +application would weaken the security. +The most suitable solution is for the Python application +to open a socket where ABRT daemon is listening, write all relevant +data to that socket, and close it. ABRT daemon handles the rest. + +** Protocol + +Initializing new dump: +open /var/run/abrt.socket + +Providing dump data (hook writes to the socket): +-> "PID=" + number 0 - PID_MAX (/proc/sys/kernel/pid_max) + \0 +-> "EXECUTABLE=" + string (maximum length ~MAX_PATH) + \0 +-> "BACKTRACE=" + string (maximum length 1 MB) + \0 +-> "ANALYZER=" + string (maximum length 100 bytes) + \0 +-> "BASENAME=" + string (maximum length 100 bytes, no slashes) + \0 +-> "REASON=" + string (maximum length 512 bytes) + \0 + +Finalizing dump creation: +-> "DONE" + \0 +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializes the dump socket, usually in /var/run directory + * (the path depends on compile-time configuration). + */ +extern void dumpsocket_init(); + +/* Releases all resources used by dumpsocket. */ +extern void dumpsocket_shutdown(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/daemon/gpg_keys b/src/daemon/gpg_keys new file mode 100644 index 00000000..cde50f13 --- /dev/null +++ b/src/daemon/gpg_keys @@ -0,0 +1 @@ +/etc/pki/rpm-gpg/RPM-GPG-KEY-fedora diff --git a/src/daemon/org.fedoraproject.abrt.policy b/src/daemon/org.fedoraproject.abrt.policy new file mode 100644 index 00000000..9261acdc --- /dev/null +++ b/src/daemon/org.fedoraproject.abrt.policy @@ -0,0 +1,38 @@ + + + + + + + The ABRT Team + https://fedorahosted.org/abrt/ + + + Manage settings + Changing the global settings requires authentication + + no + auth_admin_keep + no + + + + + + Install debuginfos + Installing debuginfos requires authentication + + yes + yes + yes + + + diff --git a/src/daemon/rpm.c b/src/daemon/rpm.c new file mode 100644 index 00000000..d367dccd --- /dev/null +++ b/src/daemon/rpm.c @@ -0,0 +1,244 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 RedHat Inc + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +//#include "abrtlib.h" +#include "rpm.h" + +/** +* A set, which contains finger prints. +*/ + +static GList *list_fingerprints = NULL; + +//TODO: npajkovs: where to place it? Should it be sotred in value or query from rpm? +/* cuts the name from the NVR format: foo-1.2.3-1.el6 + returns a newly allocated string +*/ +char* get_package_name_from_NVR_or_NULL(const char* package_nvr) +{ + char* package_name = NULL; + if (package_nvr != NULL) + { + VERB1 log("package_nvr %s", package_nvr); + package_name = xstrdup(package_nvr); + char *pos = strrchr(package_name, '-'); + if (pos != NULL) + { + *pos = 0; + pos = strrchr(package_name, '-'); + if (pos != NULL) + { + *pos = 0; + } + } + } + return package_name; +} + +void rpm_init() +{ + int status = rpmReadConfigFiles((const char*)NULL, (const char*)NULL); + if (status != 0) + error_msg("error reading rc files"); + + list_fingerprints = g_list_alloc(); +} + +static void list_free(gpointer data, gpointer user_data) +{ + free(data); +} + +void rpm_destroy() +{ + rpmFreeRpmrc(); + rpmFreeCrypto(); + + g_list_foreach(list_fingerprints, list_free, NULL); + g_list_free(list_fingerprints); +} + +void rpm_load_gpgkey(const char* filename) +{ + uint8_t *pkt = NULL; + size_t pklen; + if (pgpReadPkts(filename, &pkt, &pklen) != PGPARMOR_PUBKEY) + { + free(pkt); + error_msg("Can't load public GPG key %s", filename); + return; + } + + uint8_t keyID[8]; + if (pgpPubkeyFingerprint(pkt, pklen, keyID) == 0) + { + char *fingerprint = pgpHexStr(keyID, sizeof(keyID)); + if (fingerprint != NULL) + list_fingerprints = g_list_append(list_fingerprints, fingerprint); + } + free(pkt); +} + +bool rpm_chk_fingerprint(const char* pkg) +{ + bool ret = false; + char *pgpsig = NULL; + const char *errmsg = NULL; + + rpmts ts = rpmtsCreate(); + rpmdbMatchIterator iter = rpmtsInitIterator(ts, RPMTAG_NAME, pkg, 0); + Header header = rpmdbNextIterator(iter); + + if (!header) + goto error; + + pgpsig = headerFormat(header, "%{SIGGPG:pgpsig}", &errmsg); + if (!pgpsig && errmsg) + { + VERB1 log("cannot get siggpg:pgpsig. reason: %s", errmsg); + goto error; + } + + { + char *pgpsig_tmp = strstr(pgpsig, " Key ID "); + if (pgpsig_tmp) + { + pgpsig_tmp += sizeof(" Key ID ") - 1; + ret = g_list_find(list_fingerprints, pgpsig_tmp) != NULL; + } + } + +error: + free(pgpsig); + rpmdbFreeIterator(iter); + rpmtsFree(ts); + return ret; +} + +/* + Checking the MD5 sum requires to run prelink to "un-prelink" the + binaries - this is considered potential security risk so we don't + use it, until we find some non-intrusive way +*/ + +/* + * Not woking, need to be rewriten + * +bool CheckHash(const char* pPackage, const char* pPath) +{ + bool ret = true; + rpmts ts = rpmtsCreate(); + rpmdbMatchIterator iter = rpmtsInitIterator(ts, RPMTAG_NAME, pPackage, 0); + Header header = rpmdbNextIterator(iter); + if (header == NULL) + goto error; + + rpmfi fi = rpmfiNew(ts, header, RPMTAG_BASENAMES, RPMFI_NOHEADER); + pgpHashAlgo hashAlgo; + std::string headerHash; + char computedHash[1024] = ""; + + while (rpmfiNext(fi) != -1) + { + if (strcmp(pPath, rpmfiFN(fi)) == 0) + { + headerHash = rpmfiFDigestHex(fi, &hashAlgo); + rpmDoDigest(hashAlgo, pPath, 1, (unsigned char*) computedHash, NULL); + ret = (headerHash != "" && headerHash == computedHash); + break; + } + } + rpmfiFree(fi); +error: + rpmdbFreeIterator(iter); + rpmtsFree(ts); + return ret; +} +*/ + +char* rpm_get_description(const char* pkg) +{ + char *dsc = NULL; + const char *errmsg = NULL; + rpmts ts = rpmtsCreate(); + + rpmdbMatchIterator iter = rpmtsInitIterator(ts, RPMTAG_NAME, pkg, 0); + Header header = rpmdbNextIterator(iter); + if (!header) + goto error; + + dsc = headerFormat(header, "%{SUMMARY}\n\n%{DESCRIPTION}", &errmsg); + if (!dsc && errmsg) + error_msg("cannot get summary and description. reason: %s", errmsg); + +error: + rpmdbFreeIterator(iter); + rpmtsFree(ts); + return dsc; +} + +char* rpm_get_component(const char* filename) +{ + char *ret = NULL; + char *srpm = NULL; + const char *errmsg = NULL; + + rpmts ts = rpmtsCreate(); + rpmdbMatchIterator iter = rpmtsInitIterator(ts, RPMTAG_BASENAMES, filename, 0); + Header header = rpmdbNextIterator(iter); + if (!header) + goto error; + + srpm = headerFormat(header, "%{SOURCERPM}", &errmsg); + if (!srpm && errmsg) + { + error_msg("cannot get srpm. reason: %s", errmsg); + goto error; + } + + ret = get_package_name_from_NVR_or_NULL(srpm); + free(srpm); + +error: + rpmdbFreeIterator(iter); + rpmtsFree(ts); + return ret; +} + +// caller is responsible to free returned value +char* rpm_get_package_nvr(const char* filename) +{ + char* nvr = NULL; + const char *errmsg = NULL; + + rpmts ts = rpmtsCreate(); + rpmdbMatchIterator iter = rpmtsInitIterator(ts, RPMTAG_BASENAMES, filename, 0); + Header header = rpmdbNextIterator(iter); + + if (!header) + goto error; + + nvr = headerFormat(header, "%{NAME}-%{VERSION}-%{RELEASE}", &errmsg); + if (!nvr && errmsg) + error_msg("cannot get nvr. reason: %s", errmsg); + +error: + rpmdbFreeIterator(iter); + rpmtsFree(ts); + return nvr; +} diff --git a/src/daemon/rpm.h b/src/daemon/rpm.h new file mode 100644 index 00000000..9b6b339c --- /dev/null +++ b/src/daemon/rpm.h @@ -0,0 +1,90 @@ +/* + RPM.h - header file for rpm database + - it implements query for local rpm database + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef RPM_H_ +#define RPM_H_ + +#include +#include +#include +#include +#include +#include + +#include "xfuncs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Checks if an application is modified by third party. + * @param pPackage A package name. The package contains the application. + * @param pPath A path to the application. + * + * Not used. Delete? + */ +//bool CheckHash(const char* pPackage, const char* pPath); + +void rpm_init(); + +void rpm_destroy(); + +/** + * A function, which loads one GPG public key. + * @param filename A path to the public key. + */ +void rpm_load_gpgkey(const char* filename); + +/** + * A function, which checks if package's finger print is valid. + * @param pkg A package name. + */ +bool rpm_chk_fingerprint(const char* pkg); + +/** + * Gets a package description. + * @param pkg A package name. + * @return A package description. + */ +char* rpm_get_description(const char* pkg); +/** + * Gets a package name. This package contains particular + * file. If the file doesn't belong to any package, empty string is + * returned. + * @param filename A file name. + * @return A package name (malloc'ed string) + */ +char* rpm_get_package_nvr(const char* filename); +/** + * Finds a main package for given file. This package contains particular + * file. If the file doesn't belong to any package, empty string is + * returned. + * @param filename A file name. + * @return a malloc'ed package name. Need to be freed. + */ +char* rpm_get_component(const char* filename); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/gui/ABRTExceptions.py b/src/gui/ABRTExceptions.py new file mode 100644 index 00000000..0016632c --- /dev/null +++ b/src/gui/ABRTExceptions.py @@ -0,0 +1,16 @@ +from abrt_utils import _, log, log1, log2 + +class IsRunning(Exception): + def __init__(self): + Exception.__init__(self) + self.what = _("Another client is already running, trying to wake it...") + def __str__(self): + return self.what + +class WrongData(Exception): + def __init__(self): + Exception.__init__(self) + self.what = _("Got unexpected data from the daemon (is the database properly updated?).") + + def __str__(self): + return self.what diff --git a/src/gui/ABRTPlugin.py b/src/gui/ABRTPlugin.py new file mode 100644 index 00000000..c54f670c --- /dev/null +++ b/src/gui/ABRTPlugin.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +""" PluginInfo keys: +WWW +Name +Enabled +GTKBuilder +Version +Type +Email +Description +""" +from abrt_utils import _, log, log1, log2 +from ConfBackend import getCurrentConfBackend, ConfBackendInitError + +class PluginSettings(dict): + def __init__(self): + dict.__init__(self) + self.client_side_conf = None + try: + self.client_side_conf = getCurrentConfBackend() + except ConfBackendInitError, e: + print e + + def check(self): + # if present, these should be non-empty + for key in ["Password", "Login"]: + if key in self.keys(): + if not self[key]: + # some of the required keys are missing + return False + # settings are OK + return True + + def load_daemon_settings(self, name, daemon_settings): + # load settings from daemon + for key in daemon_settings.keys(): + self[str(key)] = str(daemon_settings[key]) + + if self.client_side_conf: + # FIXME: this fails when gk-authoriaztion fails + # we need to show a dialog to user and let him know + # for now just silently ignore it to avoid rhbz#559342 + settings = {} + try: + settings = self.client_side_conf.load(name) + except Exception, e: + print e + # overwrite daemon data with user setting + for key in settings.keys(): + # only rewrite keys which exist in plugin's keys. + # e.g. we don't want a password field for logger plugin + if key in daemon_settings.keys(): + self[str(key)] = str(settings[key]) + + def save_on_client_side(self, name): + if self.client_side_conf: + self.client_side_conf.save(name, self) + +class PluginInfo(): + """Class to represent common plugin info""" + types = {"":_("Not loaded plugins"), + "Analyzer":_("Analyzer plugins"), + "Action":_("Action plugins"), + "Reporter":_("Reporter plugins"), + "Database":_("Database plugins")} + keys = ["WWW", "Name", "Enabled", + "GTKBuilder", "Version", + "Type", "Email", "Description"] + + def __init__(self): + self.WWW = None + self.Name = None + self.Enabled = None + self.GTKBuilder = None + self.Version = None + self.Type = None + self.Email = None + self.Description = None + self.Settings = PluginSettings() + + def getName(self): + return self.Name + + def getDescription(self): + return self.Description + + def getType(self): + return self.Type + + def getGUI(self): + return self.GTKBuilder + + def __str__(self): + return self.Name + + def __getitem__(self, item): + return self.__dict__[item] + + def load_daemon_settings(self, daemon_settings): + if self.Name: + self.Settings.load_daemon_settings(self.Name, daemon_settings) + else: + log("Plugin name is not set, can't load its settings") + + def save_settings_on_client_side(self): + self.Settings.save_on_client_side(str(self.Name)) diff --git a/src/gui/CCDBusBackend.py b/src/gui/CCDBusBackend.py new file mode 100644 index 00000000..5ddb9cdf --- /dev/null +++ b/src/gui/CCDBusBackend.py @@ -0,0 +1,231 @@ +# -*- coding: utf-8 -*- +import time # for sleep() +import gobject +import dbus +import dbus.service +from dbus.mainloop.glib import DBusGMainLoop +from dbus.exceptions import * +import ABRTExceptions +from abrt_utils import _, log, log1, log2 + +ABRTD_DBUS_NAME = 'com.redhat.abrt' +ABRTD_DBUS_PATH = '/com/redhat/abrt' +ABRTD_DBUS_IFACE = 'com.redhat.abrt' + +APP_NAME = 'com.redhat.abrt.gui' +APP_PATH = '/com/redhat/abrt/gui' +APP_IFACE = 'com.redhat.abrt.gui' + +class DBusManager(gobject.GObject): + """ Class to provide communication with daemon over dbus """ + # and later with policyKit + bus = None + def __init__(self): + session = None + # binds the dbus to glib mainloop + DBusGMainLoop(set_as_default=True) + class DBusInterface(dbus.service.Object): + def __init__(self, dbusmanager): + self.dbusmanager = dbusmanager + dbus.service.Object.__init__(self, dbus.SessionBus(), APP_PATH) + + @dbus.service.method(dbus_interface=APP_IFACE) + def show(self): + self.dbusmanager.emit("show") + try: + session = dbus.SessionBus() + except Exception: + # probably run after "$ su" + pass + + if session: + try: + app_proxy = session.get_object(APP_NAME, APP_PATH) + app_iface = dbus.Interface(app_proxy, dbus_interface=APP_IFACE) + # app is running, so make it show it self + app_iface.show() + raise ABRTExceptions.IsRunning() + except DBusException, e: + # cannot create proxy or call the method => gui is not running + pass + + gobject.GObject.__init__(self) + # signal emited when new crash is detected + gobject.signal_new("crash", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()) + # signal emited when new analyze is complete + gobject.signal_new("analyze-complete", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) + # signal emited when smth fails + gobject.signal_new("abrt-error", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) + gobject.signal_new("warning", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) + # signal emited to update gui with current status + gobject.signal_new("update", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) + # signal emited to show gui if user try to run it again + gobject.signal_new("show", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()) + gobject.signal_new("daemon-state-changed", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) + gobject.signal_new("report-done", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) + + # export the app dbus interface + if session: + session.request_name(APP_NAME) + iface = DBusInterface(self) + + self.bus = dbus.SystemBus() + if not self.bus: + raise Exception(_("Cannot connect to system dbus.")) + self.bus.add_signal_receiver(self.owner_changed_cb, "NameOwnerChanged", dbus_interface="org.freedesktop.DBus") + # new crash notification + self.bus.add_signal_receiver(self.crash_cb, "Crash", dbus_interface=ABRTD_DBUS_IFACE) + # watch for updates + self.bus.add_signal_receiver(self.update_cb, "Update", dbus_interface=ABRTD_DBUS_IFACE) + # watch for warnings + self.bus.add_signal_receiver(self.warning_cb, "Warning", dbus_interface=ABRTD_DBUS_IFACE) + # watch for job-done signals + self.bus.add_signal_receiver(self.jobdone_cb, "JobDone", dbus_interface=ABRTD_DBUS_IFACE) + + # We use this function instead of caching and reusing of + # dbus.Interface(proxy, dbus_interface=ABRTD_DBUS_IFACE) because we want + # to restart abrtd in this scenario: + # (1) abrt-gui was run + # (2) user generated the report, then left for coffee break + # (3) abrtd exited on inactivity timeout + # (4) user returned and wants to submit the report + # for (4) to restart abrtd, we must recreate proxy and daemon + def daemon(self): + if not self.bus: + self.bus = dbus.SystemBus() + if not self.bus: + raise Exception(_("Cannot connect to system dbus.")) + # Autostart hack + # Theoretically, this is not needed, the first dbus call + # should autostart daemon if needed. In practice, + # without this code autostart is not reliable. + # Update: fixed the problem on daemon side, + # but retaining this code for now. More stability won't hurt us... + try: + (always_true, rc) = self.bus.start_service_by_name(ABRTD_DBUS_NAME, flags=0) + # rc is either self.bus.START_REPLY_SUCCESS or self.bus.START_REPLY_ALREADY_RUNNING + if rc == self.bus.START_REPLY_SUCCESS: + # Better solution may be to have daemon emit a signal and wait for it + log1("dbus auto-started abrt daemon, giving it 1 sec to initialize") + time.sleep(1) + except DBusException: + raise Exception("abrt daemon is not running, and DBus can't start it") + # End of autostart hack + try: + # follow_name_owner_changes=True: switch to new daemon if daemon is restarted + proxy = self.bus.get_object(ABRTD_DBUS_NAME, ABRTD_DBUS_PATH, introspect=False, follow_name_owner_changes=True) + except DBusException: + raise Exception("Can't connect to abrt daemon") + if not proxy: + raise Exception(_("Please check if the abrt daemon is running.")) + daemon = dbus.Interface(proxy, dbus_interface=ABRTD_DBUS_IFACE) + if not daemon: + raise Exception(_("Please check if the abrt daemon is running.")) + return daemon + +# # disconnect callback +# def disconnected(self, *args): +# print "disconnect" + + def error_handler_cb(self, error): + self.emit("abrt-error", error) + + def warning_handler_cb(self, arg): + self.emit("warning", arg) + + def error_handler(self, arg): + # used to silently ingore dbus timeouts + pass + + def dummy(self, *args): + # dummy function for async method call to workaround the timeout + pass + + def crash_cb(self,*args): + #FIXME "got another crash, gui should reload!" + #for arg in args: + # print arg + #emit a signal + #print "crash" + self.emit("crash") + + def update_cb(self, message): + log1("Update:%s", message) + self.emit("update", message) + + def warning_cb(self, message): + log1("Warning:%s", message) + self.emit("warning", message) + + def owner_changed_cb(self, name, old_owner, new_owner): + if name == ABRTD_DBUS_NAME: + if new_owner: + self.emit("daemon-state-changed", "up") + else: + self.emit("daemon-state-changed", "down") + + def jobdone_cb(self): + log1("Our job for crash_id %s is done", self.job_crash_id) + report = self.daemon().CreateReport(self.job_crash_id) + if report: + self.emit("analyze-complete", report) + else: + # FIXME: BUG: BarWindow remains. (how2reproduce: delete "component" in a dump dir and try to report it) + self.emit("abrt-error", _("Daemon did not return a valid report info.\nIs debuginfo missing?")) + + def report_done(self, result): + self.emit("report-done", result) + + def start_job(self, crash_id, force=0): + # 2nd param is "force recreating of backtrace etc" + self.daemon().StartJob(crash_id, force, timeout=60) + self.job_crash_id = crash_id + + def Report(self, report, reporters, reporters_settings = None): + # map < Plugin_name vec > + # daemon expects plugin names, not the objects + reporters_names = [str(reporter) for reporter in reporters] + if reporters_settings: + self.daemon().Report(report, reporters_names, reporters_settings, reply_handler=self.report_done, error_handler=self.error_handler_cb, timeout=60) + else: + self.daemon().Report(report, reporters_names, reply_handler=self.report_done, error_handler=self.error_handler_cb, timeout=60) + + def DeleteDebugDump(self, crash_id): + return self.daemon().DeleteDebugDump(crash_id) + + def getDumps(self): + row_dict = None + rows = [] + # FIXME check the arguments + for row in self.daemon().GetCrashInfos(): + row_dict = {} + for column in row: + row_dict[column] = row[column] + rows.append(row_dict) + return rows + + def getPluginsInfo(self): + return self.daemon().GetPluginsInfo() + + def getPluginSettings(self, plugin_name): + settings = self.daemon().GetPluginSettings(plugin_name) + return settings + +# "Enable" toggling in GUI is disabled for now. Grep for PLUGIN_DYNAMIC_LOAD_UNLOAD +# def registerPlugin(self, plugin_name): +# return self.daemon().RegisterPlugin(plugin_name) +# +# def unRegisterPlugin(self, plugin_name): +# return self.daemon().UnRegisterPlugin(plugin_name) + + def setPluginSettings(self, plugin_name, plugin_settings): + return self.daemon().SetPluginSettings(plugin_name, plugin_settings) + + def getSettings(self): + return self.daemon().GetSettings() + + def setSettings(self, settings): + # FIXME: STUB!!!! + log1("setSettings stub") + retval = self.daemon().SetSettings(self.daemon().GetSettings()) + print ">>>", retval diff --git a/src/gui/CCDump.py b/src/gui/CCDump.py new file mode 100644 index 00000000..d8a15f9a --- /dev/null +++ b/src/gui/CCDump.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +from datetime import datetime + +from abrt_utils import _, init_logging, log, log1, log2 + +# Should match CrashTypes.h! +CD_TYPE = 0 +CD_EDITABLE = 1 +CD_CONTENT = 2 + +CD_SYS = "s" +CD_BIN = "b" +CD_TXT = "t" + +FILENAME_ARCHITECTURE = "architecture" +FILENAME_KERNEL = "kernel" +FILENAME_TIME = "time" +FILENAME_PACKAGE = "package" +FILENAME_COMPONENT = "component" +FILENAME_DESCRIPTION = "description" +FILENAME_ANALYZER = "analyzer" +FILENAME_RELEASE = "release" +FILENAME_EXECUTABLE = "executable" +FILENAME_REASON = "reason" +FILENAME_COMMENT = "comment" +FILENAME_REPRODUCE = "reproduce" +FILENAME_RATING = "rating" +FILENAME_CMDLINE = "cmdline" +FILENAME_COREDUMP = "coredump" +FILENAME_BACKTRACE = "backtrace" +FILENAME_MEMORYMAP = "memorymap" + +CD_UID = "uid" +CD_UUID = "UUID" +CD_INFORMALL = "InformAll" +CD_DUPHASH = "DUPHASH" +CD_DUMPDIR = "DumpDir" +CD_COUNT = "Count" +CD_REPORTED = "Reported" +CD_MESSAGE = "Message" + +# FIXME - create method or smth that returns type|editable|content + + +class Dump(): + """Class for mapping the debug dump to python object""" + not_required_fields = ["comment", "Message"] + def __init__(self): + # we set all attrs dynamically, so no need to have it in init + for field in self.not_required_fields: + self.__dict__[field] = None + + def __setattr__(self, name, value): + if value != None: + if name == "time": + try: + self.__dict__["date"] = datetime.fromtimestamp(int(value[CD_CONTENT])).strftime("%c") + except Exception, ex: + self.__dict__["date"] = value[CD_CONTENT] + log2("can't convert timestamp to date: %s" % ex) + self.__dict__[name] = value[CD_CONTENT] + else: + self.__dict__[name] = value + + def __str__(self): + return "Dump instance" + + def getUUID(self): + return self.UUID + + def getUID(self): + return self.uid + + def getCount(self): + return int(self.Count) + + def getExecutable(self): + return self.executable + + def getPackage(self): + return self.package + + def isReported(self): + return self.Reported == "1" + + def getMessage(self): + if not self.Message: + return "" #[] + #return self.Message[CD_CONTENT].split('\n') + return self.Message + + def getTime(self, fmt=None): + if self.time: + if fmt: + try: + return datetime.fromtimestamp(int(self.time)).strftime(fmt) + except Exception, ex: + log1(ex) + return int(self.time) + return self.time + + def getPackageName(self): + name_delimiter_pos = self.package[:self.package.rfind("-")].rfind("-") + # fix for kerneloops + if name_delimiter_pos > 0: + return self.package[:name_delimiter_pos] + return self.package + + def getDescription(self): + return self.description + + def getAnalyzerName(self): + return self.analyzer + + def get_release(self): + return self.release + + def get_reason(self): + return self.reason + + def get_comment(self): + return self.comment + + def get_component(self): + return self.component + + def get_cmdline(self): + return self.cmdline + + def get_arch(self): + return self.architecture + + def get_kernel(self): + return self.kernel + + def get_backtrace(self): + try: + return self.backtrace + except AttributeError: + return None + + def get_rating(self): + try: + return self.rating + except AttributeError: + return None + + def get_hostname(self): + try: + return self.hostname + except AttributeError: + return None diff --git a/src/gui/CCDumpList.py b/src/gui/CCDumpList.py new file mode 100644 index 00000000..3c555d84 --- /dev/null +++ b/src/gui/CCDumpList.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +from CCDump import Dump + +from abrt_utils import _, init_logging, log, log1, log2 + +class DumpList(list): + """Class to store list of debug dumps""" + def __init__(self,dbus_manager=None): + list.__init__(self) + self.dm = dbus_manager + + def load(self): + if self.dm: + #print "loading DumpList" + try: + rows = self.dm.getDumps() + #print rows + for row in rows: + entry = Dump() + for column in row: + log2(" Dump.%s='%s'", column, row[column]) + entry.__setattr__(column, row[column]) + self.append(entry) + except Exception: + # FIXME handle exception better + # this is just temporary workaround for rhbz#543725 + raise + else: + print "db == None!" + + def getDumpByCrashID(self, crashid): + for dump in self: + # crashid can be either hash or uid:hash + if crashid in (dump.getUUID(),dump.getUID()+":"+dump.getUUID()): + return dump + +__PFList = None +__PFList_dbmanager = None + +def getDumpList(dbmanager,refresh=None): + global __PFList + global __PFList_dbmanager + + if __PFList == None or refresh or __PFList_dbmanager != dbmanager: + __PFList = DumpList(dbus_manager=dbmanager) + __PFList.load() + __PFList_dbmanager = dbmanager + return __PFList + +__PFList = None diff --git a/src/gui/CCMainWindow.py b/src/gui/CCMainWindow.py new file mode 100644 index 00000000..4ee14768 --- /dev/null +++ b/src/gui/CCMainWindow.py @@ -0,0 +1,457 @@ +# -*- coding: utf-8 -*- +import sys +import pwd +import getopt + +from abrt_utils import _, init_logging, log, log1, log2 +import gobject +gobject.set_prgname(_("Automatic Bug Reporting Tool")) +import pygtk +pygtk.require("2.0") +try: + import gtk +except RuntimeError,e: + # rhbz#552039 + print e + sys.exit() +import gtk.glade + +from ConfBackend import getCurrentConfBackend, ConfBackendInitError +import CCDBusBackend +from CC_gui_functions import * +from CCDumpList import getDumpList +from CCDump import * # FILENAME_xxx, CD_xxx +from CCReporterDialog import ReporterDialog, ReporterSelector +from CReporterAssistant import ReporterAssistant +from PluginsSettingsDialog import PluginsSettingsDialog +from SettingsDialog import SettingsDialog +from PluginList import getPluginInfoList +import ABRTExceptions + + +class MainWindow(): + ccdaemon = None + def __init__(self, daemon): + self.theme = gtk.icon_theme_get_default() + self.updates = "" + self.ccdaemon = daemon + #Set the Glade file + self.gladefile = "%s/ccgui.glade" % sys.path[0] + self.wTree = gtk.glade.XML(self.gladefile) + + #Get the Main Window, and connect the "destroy" event + self.window = self.wTree.get_widget("main_window") + if self.window: + self.window.set_default_size(600, 700) + self.window.connect("delete_event", self.delete_event_cb) + self.window.connect("destroy", self.destroy) + self.window.connect("focus-in-event", self.focus_in_cb) + self.wTree.get_widget("vp_details").modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse("#FFFFFF")) + #init the dumps treeview + self.dlist = self.wTree.get_widget("tvDumps") + #rows of items with: + STATUS_COL = 0 + APP_NAME_COL = 1 + TIME_STR_COL = 2 + HOSTNAME_COL = 3 + UNIX_TIME_COL = 4 + DUMP_OBJECT_COL = 5 + #is_reported, application_name, hostname, date, time_in_sec ?object? + self.dumpsListStore = gtk.ListStore(str, str, str, str, int, object) + self.dlist.set_model(self.dumpsListStore) + # add pixbuff separatelly + icon_column = gtk.TreeViewColumn(_("Reported")) + icon_column.cell = gtk.CellRendererPixbuf() + #icon_column.cell.set_property('cell-background', "#C9C9C9") + n = self.dlist.append_column(icon_column) + icon_column.pack_start(icon_column.cell, True) + icon_column.set_attributes(icon_column.cell, stock_id=(n-1))# cell_background_set=6) + # =============================================== + columns = [] + columns.append(gtk.TreeViewColumn(_("Application"))) + columns[-1].set_sort_column_id(APP_NAME_COL) + columns.append(gtk.TreeViewColumn(_("Hostname"))) + columns[-1].set_sort_column_id(HOSTNAME_COL) + columns.append(gtk.TreeViewColumn(_("Latest Crash"))) + columns[-1].set_sort_column_id(UNIX_TIME_COL) + # add cells to colums and bind cells to the liststore values + for column in columns: + n = self.dlist.append_column(column) + column.cell = gtk.CellRendererText() + column.pack_start(column.cell, False) + column.set_attributes(column.cell, text=(n-1)) + column.set_resizable(True) + #connect signals + self.dlist.connect("cursor-changed", self.on_tvDumps_cursor_changed) + self.dlist.connect("row-activated", self.on_dumpRowActivated) + self.dlist.connect("button-press-event", self.on_popupActivate) + self.wTree.get_widget("bDelete").connect("clicked", self.on_bDelete_clicked, self.dlist) + self.wTree.get_widget("bReport").connect("clicked", self.on_bReport_clicked) + self.wTree.get_widget("b_close").connect("clicked", self.on_bQuit_clicked) + self.wTree.get_widget("b_copy").connect("clicked", self.on_b_copy_clicked) + self.wTree.get_widget("b_help").connect("clicked", self.on_miAbout_clicked) + self.wTree.get_widget("miQuit").connect("activate", self.on_bQuit_clicked) + self.wTree.get_widget("miAbout").connect("activate", self.on_miAbout_clicked) + self.wTree.get_widget("miPlugins").connect("activate", self.on_miPreferences_clicked) + self.wTree.get_widget("miPreferences").connect("activate", self.on_miSettings_clicked) + self.wTree.get_widget("miReport").connect("activate", self.on_bReport_clicked) + self.wTree.get_widget("miDelete").connect("activate", self.on_bDelete_clicked, self.dlist) + # connect handlers for daemon signals + self.ccdaemon.connect("crash", self.on_data_changed_cb, None) + self.ccdaemon.connect("abrt-error", self.error_cb) + #self.ccdaemon.connect("update", self.update_cb) + # for now, just treat them the same (w/o this, we don't even see daemon warnings in logs!): + #self.ccdaemon.connect("warning", self.update_cb) + self.ccdaemon.connect("show", self.show_cb) + self.ccdaemon.connect("daemon-state-changed", self.on_daemon_state_changed_cb) + self.ccdaemon.connect("report-done", self.on_report_done_cb) + + self.pluginlist = None + + def on_report_done_cb(self, daemon, result): + self.hydrate() + + def on_daemon_state_changed_cb(self, widget, state): + if state == "up": + self.hydrate() # refresh crash list + #self.window.set_sensitive(True) + # abrtd might just die on timeout, it's not fatal + #elif state == "down": + # self.window.set_sensitive(False) + + def on_popupActivate(self, widget, event): + menu = self.wTree.get_widget("popup_menu") + # 3 == right mouse button + if event.button == 3: + menu.popup(None, None, None, event.button, event.time) + + def on_miAbout_clicked(self, widget): + dialog = self.wTree.get_widget("about") + result = dialog.run() + dialog.hide() + + def on_miPreferences_clicked(self, widget): + dialog = PluginsSettingsDialog(self.window,self.ccdaemon) + dialog.hydrate() + dialog.show() + + def on_miSettings_clicked(self, widget): + dialog = SettingsDialog(self.window, self.ccdaemon) + try: + dialog.hydrate() + except Exception, ex: + gui_error_message(_("Cannot show the settings dialog.\n%s" % ex)) + return + dialog.show() + + def error_cb(self, daemon, message=None): + gui_error_message(_("Unable to finish the current task!\n%s" % message), parent_dialog=self.window) + + def update_cb(self, daemon, message): + self.updates += message + if self.updates[-1] != '\n': + self.updates += '\n' + message = message.replace('\n',' ') + self.wTree.get_widget("lStatus").set_text(message) + buff = gtk.TextBuffer() + buff.set_text(self.updates) + end = buff.get_insert() + tvUpdates = self.wTree.get_widget("tvUpdates") + tvUpdates.set_buffer(buff) + tvUpdates.scroll_mark_onscreen(end) + + def get_username_from_uid(self, uid): + # if uid == None or "" return it back + if not uid: + return uid + user = "N/A" + if uid != "-1": # compat: only abrt <= 1.0.9 used UID = -1 + try: + user = pwd.getpwuid(int(uid))[0] + except Exception, ex: + user = "UID: %s" % uid + return user + + + def hydrate(self): + n = None + self.dumpsListStore.clear() + try: + dumplist = getDumpList(self.ccdaemon, refresh=True) + except Exception, ex: + # there is something wrong with the daemon if we cant get the dumplist + gui_error_message(_("Error while loading the dumplist.\n%s" % ex)) + # so we shouldn't continue.. + sys.exit() + for entry in dumplist[::-1]: + n = self.dumpsListStore.append([["gtk-no","gtk-yes"][entry.isReported()], + entry.getExecutable(), + entry.get_hostname(), + entry.getTime("%c"), + entry.getTime(), + entry]) + # activate the first row if any.. + if n: + # we can use (0,) as path for the first row, but what if API changes? + self.dlist.set_cursor(self.dumpsListStore.get_path(self.dumpsListStore.get_iter_first())) + + def filter_dumps(self, model, miter, data): + # for later.. + return True + + def dumplist_get_selected(self): + selection = self.dlist.get_selection() + if selection: + # returns (dumpsListStore, path) tuple + dumpsListStore, path = selection.get_selected_rows() + return dumpsListStore, path + else: + return None, None + + def on_tvDumps_cursor_changed(self, treeview): + dumpsListStore, path = self.dumplist_get_selected() + if not path: + self.wTree.get_widget("bDelete").set_sensitive(False) + self.wTree.get_widget("bReport").set_sensitive(False) + self.wTree.get_widget("b_copy").set_sensitive(False) + # create an empty dump to fill the labels with empty strings + self.wTree.get_widget("sw_details").hide() + return + else: + self.wTree.get_widget("sw_details").show() + self.wTree.get_widget("bDelete").set_sensitive(True) + self.wTree.get_widget("bReport").set_sensitive(True) + self.wTree.get_widget("b_copy").set_sensitive(True) + # this should work until we keep the row object in the last position + dump = dumpsListStore.get_value(dumpsListStore.get_iter(path[0]), + dumpsListStore.get_n_columns()-1) + + try: + icon = get_icon_for_package(self.theme, dump.getPackageName()) + except: + icon = None + + i_package_icon = self.wTree.get_widget("i_package_icon") + if icon: + i_package_icon.set_from_pixbuf(icon) + else: + i_package_icon.set_from_icon_name("application-x-executable", gtk.ICON_SIZE_DIALOG) + + l_heading = self.wTree.get_widget("l_detail_heading") + l_heading.set_markup(_("%s Crash\n%s") % (dump.getPackageName().title(),dump.getPackage())) + + # process the labels in sw_details + # hide the fields that are not filled by daemon - e.g. comments + # and how to reproduce + for field in dump.not_required_fields: + self.wTree.get_widget("l_%s" % field.lower()).hide() + self.wTree.get_widget("l_%s_heading" % field.lower()).hide() + + # fill the details + # read attributes from CCDump object and if a corresponding label is + # found, then the label text is set to the attribute's value + # field names in glade file: + # heading label: l__heading + # text label: l_ + for att in dump.__dict__: + label = self.wTree.get_widget("l_%s" % str(att).lower()) + if label: + label.show() + if att in dump.not_required_fields: + try: + lbl_heading = self.wTree.get_widget("l_%s_heading" % str(att).lower()) + lbl_heading.show() + except: + # we don't care if we fail to show the heading, it will + # break the gui a little, but it's better then exit + log2("failed to show the heading for >%s< : %s" % (att,e)) + pass + if dump.__dict__[att] != None: + label.set_text(dump.__dict__[att]) + else: + label.set_text("") + self.wTree.get_widget("l_date").set_text(dump.getTime("%c")) + self.wTree.get_widget("l_user").set_text(self.get_username_from_uid(dump.getUID())) + + #move this to Dump class + hb_reports = self.wTree.get_widget("hb_reports") + lReported = self.wTree.get_widget("l_message") + if dump.isReported(): + hb_reports.show() + report_label_raw = "" + report_label = "" + # plugin message follows, but at least in case of kerneloops, + # it is not informative (no URL to the report) + for message in dump.getMessage().split(';'): + if message: + report_message = tag_urls_in_text(message) + report_label += "%s\n" % report_message + report_label_raw += "%s\n" % message + log2("setting markup '%s'", report_label) + # Sometimes (!) set_markup() fails with + # "GtkWarning: Failed to set text from markup due to error parsing + # markup: Unknown tag 'a'" If it does, then set_text() + # in "fill the details" above acts as a fallback + lReported.set_markup(report_label) + else: + hb_reports.hide() + + def mark_last_selected_row(self, dump_list_store, path, iter, last_selected_uuid): + # Get dump object from list (in our list it's in last col) + dump = dump_list_store.get_value(iter, dump_list_store.get_n_columns()-1) + if dump.getUUID() == last_selected_uuid: + self.dlist.set_cursor(dump_list_store.get_path(iter)[0]) + return True # done, stop iteration + return False + + def on_bDelete_clicked(self, button, treeview): + dumpsListStore, path = self.dumplist_get_selected() + if not path: + return + # this should work until we keep the dump object in the last position + dump = dumpsListStore.get_value(dumpsListStore.get_iter(path[0]), dumpsListStore.get_n_columns()-1) + next_iter = dumpsListStore.iter_next(dumpsListStore.get_iter(path[0])) + last_dump = None + if next_iter: + last_dump = dumpsListStore.get_value(next_iter, dumpsListStore.get_n_columns()-1) + try: + self.ccdaemon.DeleteDebugDump("%s:%s" % (dump.getUID(), dump.getUUID())) + self.hydrate() + if last_dump: + # we deleted the selected line, so we want to select the next one + dumpsListStore.foreach(self.mark_last_selected_row, last_dump.getUUID()) + treeview.emit("cursor-changed") + except Exception, ex: + print ex + + def dumplist_get_selected_values(self): + dumpsListStore, path = self.dumplist_get_selected() + if path and dumpsListStore: + return dumpsListStore.get_value(dumpsListStore.get_iter(path[0]), dumpsListStore.get_n_columns()-1) + return None + + def on_b_copy_clicked(self, button): + clipboard = gtk.clipboard_get() + dump = self.dumplist_get_selected_values() + if not dump: + gui_info_dialog(_("You have to select a crash to copy."), parent=self.window) + return + # dictionaries are not sorted, so we need this as a workaround + dumpinfo = [("Package:", dump.package), + ("Latest Crash:", dump.date), + ("Command:", dump.cmdline), + ("Reason:", dump.reason), + ("Comment:", dump.comment), + ("Bug Reports:", dump.Message), + ] + dumpinfo_text = "" + for line in dumpinfo: + dumpinfo_text += ("%-12s\t%s" % (line[0], line[1])).replace('\n','\n\t\t') + dumpinfo_text += '\n' + clipboard.set_text(dumpinfo_text) + + def destroy(self, widget, data=None): + gtk.main_quit() + + def on_data_changed_cb(self, *_args): + # FIXME mark the new entry somehow.... + # remember the selected row + last_dump = None + dumpsListStore, path = self.dumplist_get_selected() + if path and dumpsListStore: + last_dump = dumpsListStore.get_value(dumpsListStore.get_iter(path[0]), dumpsListStore.get_n_columns()-1) + self.hydrate() + if last_dump: + # re-select the line that was selected before a new crash happened + dumpsListStore.foreach(self.mark_last_selected_row, last_dump.getUUID()) + + + def on_bReport_clicked(self, button): + dumpsListStore, path = self.dumplist_get_selected() + self.on_dumpRowActivated(self.dlist, None, path, None) + + def on_dumpRowActivated(self, treeview, it, path, user_data=None): + dumpsListStore, path = self.dumplist_get_selected() + if not path: + return + dump = dumpsListStore.get_value(dumpsListStore.get_iter(path[0]), dumpsListStore.get_n_columns()-1) + + # Do we want to let user decide which UI they want to use? + #rs = ReporterSelector(dump, self.ccdaemon, parent=self.window) + #rs.show() + assistant = ReporterAssistant(dump, self.ccdaemon, parent=self.window) + assistant.hydrate() + + def delete_event_cb(self, widget, event, data=None): + gtk.main_quit() + + def focus_in_cb(self, widget, event, data=None): + self.window.set_urgency_hint(False) + + def on_bQuit_clicked(self, widget): + try: + gtk.main_quit() + except: # prevent "RuntimeError: called outside of a mainloop" + sys.exit() + + def show(self): + self.window.show() + + def show_cb(self, daemon): + if self.window: + if self.window.is_active(): + return + self.window.set_urgency_hint(True) + self.window.present() + +if __name__ == "__main__": + verbose = 0 + crashid = None + try: + opts, args = getopt.getopt(sys.argv[1:], "vh", ["help", "report="]) + except getopt.GetoptError, err: + print str(err) # prints something like "option -a not recognized" + sys.exit(2) + + for opt, arg in opts: + if opt == "-v": + verbose += 1 + elif opt == "--report": + crashid=arg + elif opt in ("-h", "--help"): + print _("Usage: abrt-gui [OPTIONS]" + "\n\t-v[vv]\t\t\tVerbose" + "\n\t--report=CRASH_ID\tDirectly report crash with CRASH_ID" + ) + sys.exit() + + init_logging("abrt-gui", verbose) + log1("log level:%d", verbose) + + try: + daemon = CCDBusBackend.DBusManager() + except ABRTExceptions.IsRunning: + # another instance is running, so exit quietly + sys.exit() + except Exception, ex: + # show error message if connection fails + gui_error_message("%s" % ex) + sys.exit() + + if crashid: + dumplist = getDumpList(daemon) + crashdump = dumplist.getDumpByCrashID(crashid) + if not crashdump: + gui_error_message(_("No such crash in the database, probably wrong crashid." + "\ncrashid=%s" % crashid)) + sys.exit() + assistant = ReporterAssistant(crashdump, daemon, parent=None) + assistant.hydrate() + # Do we want to let the users to decide which UI to use? +# rs = ReporterSelector(crashdump, daemon, parent=None) +# rs.show() + else: + cc = MainWindow(daemon) + cc.hydrate() + cc.show() + gtk.main() diff --git a/src/gui/CCReporterDialog.py b/src/gui/CCReporterDialog.py new file mode 100644 index 00000000..947a2582 --- /dev/null +++ b/src/gui/CCReporterDialog.py @@ -0,0 +1,578 @@ +# -*- coding: utf-8 -*- +import pygtk +pygtk.require("2.0") +import gtk +import gobject +import sys +from CC_gui_functions import * +import CellRenderers +from ABRTPlugin import PluginInfo +from PluginSettingsUI import PluginSettingsUI +from PluginList import getPluginInfoList +from CCDump import * # FILENAME_xxx, CD_xxx +from abrt_utils import _, log, log1, log2, get_verbose_level, g_verbose + +# FIXME - create method or smth that returns type|editable|content + +# response +REFRESH = -50 +SHOW_LOG = -60 + +# default texts +COMMENT_HINT_TEXT = _("Brief description of how to reproduce this or what you did...") +HOW_TO_HINT_TEXT = "1.\n2.\n3.\n" + +class ReporterDialog(): + """Reporter window""" + def __init__(self, report, daemon, log=None, parent=None): + self.editable = [] + self.row_dict = {} + self.report = report + #Set the Glade file + # FIXME add to path + builderfile = "%s/report.glade" % sys.path[0] + self.builder = gtk.Builder() + self.builder.add_from_file(builderfile) + #Get the Main Window, and connect the "destroy" event + self.window = self.builder.get_object("reporter_dialog") + self.window.set_default_size(-1, 800) + self.window.connect("response", self.on_response, daemon) + if parent: + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.window.set_transient_for(parent) + self.window.set_modal(True) + else: + self.window.set_position(gtk.WIN_POS_CENTER) + + # comment textview + self.tvComment = self.builder.get_object("tvComment") + self.tvComment.connect("focus-in-event", self.on_comment_focus_cb) + self.show_hint_comment = 1 + + # "how to reproduce" textview + self.tevHowToReproduce = self.builder.get_object("tevHowToReproduce") + + self.builder.get_object("fErrors").hide() + bLog = self.builder.get_object("bLog") + #if g_verbose > 0: - doesn't work! why?! + if get_verbose_level() > 0: + bLog.connect("clicked", self.show_log_cb, log) + else: + bLog.unset_flags(gtk.VISIBLE) + tb_send_bt = self.builder.get_object("cbSendBacktrace") + tb_send_bt.connect("toggled", self.on_send_backtrace_toggled) + try: + tb_send_bt.get_child().modify_fg(gtk.STATE_NORMAL,gtk.gdk.color_parse("red")) + except Exception, ex: + # we don't want gui to die if it fails to set the button color + log(ex) + self.allow_send() + self.hydrate() + + def check_backtrace(self): + print "checking backtrace" + + def warn_user(self, warnings): + # FIXME: show in lError + fErrors = self.builder.get_object("fErrors") + lErrors = self.builder.get_object("lErrors") + warning_lbl = None + for warning in warnings: + if warning_lbl: + warning_lbl += "\n* %s" % warning + else: + warning_lbl = "* %s" % warning + lErrors.set_label(warning_lbl) + fErrors.show_all() + + def hide_warning(self): + fErrors = self.builder.get_object("fErrors") + lErrors = self.builder.get_object("lErrors") + fErrors.hide() + + def allow_send(self): + self.hide_warning() + bSend = self.builder.get_object("bSend") + SendBacktrace = self.builder.get_object("cbSendBacktrace").get_active() + send = True + error_msgs = [] + try: + rating = int(self.report[FILENAME_RATING][CD_CONTENT]) + except: + rating = None + # active buttons acording to required fields + # if an backtrace has rating use it + if not SendBacktrace: + send = False + error_msgs.append(_("You must check the backtrace for sensitive data.")) + # we have both SendBacktrace and rating + if rating != None: + try: + package = self.report[FILENAME_PACKAGE][CD_CONTENT] + # if we don't have package for some reason + except: + package = None + # not usable report + if int(self.report[FILENAME_RATING][CD_CONTENT]) < 3: + if package: + error_msgs.append(_("Reporting disabled because the backtrace is unusable.\nPlease try to install debuginfo manually using the command: debuginfo-install %s \nthen use the Refresh button to regenerate the backtrace." % package[0:package.rfind('-',0,package.rfind('-'))])) + else: + error_msgs.append(_("The backtrace is unusable, you cannot report this!")) + send = False + # probably usable 3 + elif int(self.report[FILENAME_RATING][CD_CONTENT]) < 4: + error_msgs.append(_("The backtrace is incomplete, please make sure you provide the steps to reproduce.")) + + if error_msgs: + self.warn_user(error_msgs) + bSend.set_sensitive(send) + if not send: + bSend.set_tooltip_text(_("Reporting disabled, please fix the problems shown above.")) + else: + bSend.set_tooltip_text(_("Sends the report using the selected plugin.")) + + def on_send_backtrace_toggled(self, toggle_button): + self.allow_send() + + def show_log_cb(self, widget, log): + show_log(log, parent=self.window) + + # this callback is called when user press Cancel or Report button in Report dialog + def on_response(self, dialog, response_id, daemon): + # the button has been pressed (probably) + if response_id == gtk.RESPONSE_APPLY: + if not (self.check_report()): + dialog.stop_emission("response") + self.builder.get_object("bSend").stop_emission("clicked") + if response_id == SHOW_LOG: + # prevent the report dialog from quitting the run() and closing itself + dialog.stop_emission("response") + + def on_send_toggled(self, cell, path, model): + model[path][3] = not model[path][3] + + def on_comment_focus_cb(self, widget, event): + if self.show_hint_comment: + # clear "hint" text by supplying a fresh, empty TextBuffer + widget.set_buffer(gtk.TextBuffer()) + self.show_hint_comment = 0 + + def set_label(self, label_widget, text): + if len(text) > label_widget.get_max_width_chars(): + label_widget.set_tooltip_text(text) + label_widget.set_text(text) + + def hydrate(self): + self.editable = [] + self.old_comment = COMMENT_HINT_TEXT + self.old_how_to_reproduce = HOW_TO_HINT_TEXT + for item in self.report: + try: + log2("report[%s]:%s/%s/%s", item, self.report[item][0], self.report[item][1], self.report[item][2][0:20]) + except: + pass + + if item == FILENAME_BACKTRACE: + buff = gtk.TextBuffer() + tvBacktrace = self.builder.get_object("tvBacktrace") + buff.set_text(self.report[item][CD_CONTENT]) + tvBacktrace.set_buffer(buff) + continue + + if item == FILENAME_COMMENT: + try: + if self.report[item][CD_CONTENT]: + self.old_comment = self.report[item][CD_CONTENT] + except Exception, e: + pass + continue + + if item == FILENAME_REPRODUCE: + try: + if self.report[item][CD_CONTENT]: + self.old_how_to_reproduce = self.report[item][CD_CONTENT] + except Exception, e: + pass + continue + + if self.report[item][CD_TYPE] == CD_SYS: + continue + + # item name 0| value 1| editable? 2| toggled? 3| visible?(attachment)4 + # FIXME: handle editable fields + if self.report[item][CD_TYPE] == CD_BIN: + self.builder.get_object("fAttachment").show() + vbAttachments = self.builder.get_object("vbAttachments") + toggle = gtk.CheckButton(self.report[item][CD_CONTENT]) + vbAttachments.pack_start(toggle) + # bind item to checkbox + toggle.item = item + #FIXME: temporary workaround, in 1.0.4 reporters don't care + # about this, they just send what they want to + # TicketUploader even sends coredump!! + #toggle.show() + continue + + # It must be CD_TXT field + item_label = self.builder.get_object("l%s" % item) + if item_label: + self.set_label(item_label, self.report[item][CD_CONTENT]) + else: + # no widget to show this item + # probably some new item need to adjust the GUI! + # FIXME: add some window+button to show all the info + # in raw form (smth like the old report dialog) + pass + #end for + + buff = gtk.TextBuffer() + self.show_hint_comment = (self.old_comment == COMMENT_HINT_TEXT) + if self.show_hint_comment: + buff.set_text(COMMENT_HINT_TEXT) + else: + buff.set_text(self.old_comment) + self.tvComment.set_buffer(buff) + + buff = gtk.TextBuffer() + if self.old_how_to_reproduce == "": + buff.set_text(HOW_TO_HINT_TEXT) + else: + buff.set_text(self.old_how_to_reproduce) + self.tevHowToReproduce.set_buffer(buff) + + def dehydrate(self): + ## # handle attachments + ## vbAttachments = self.builder.get_object("vbAttachments") + ## for attachment in vbAttachments.get_children(): + ## #print "%s file %s" % (["not sending","sending"][attachment.get_active()], attachment.get_label()) + ## del self.report[attachment.item] + + # handle comment + buff = self.tvComment.get_buffer() + text = buff.get_text(buff.get_start_iter(), buff.get_end_iter()) + if self.old_comment != text: + self.report[FILENAME_COMMENT] = [CD_TXT, 'y', text] + # handle how to reproduce + buff = self.tevHowToReproduce.get_buffer() + text = buff.get_text(buff.get_start_iter(), buff.get_end_iter()) + if self.old_how_to_reproduce != text: + self.report[FILENAME_REPRODUCE] = [CD_TXT, 'y', text] + # handle backtrace + tev_backtrace = self.builder.get_object("tvBacktrace") + buff = tev_backtrace.get_buffer() + text = buff.get_text(buff.get_start_iter(), buff.get_end_iter()) + self.report[FILENAME_BACKTRACE] = [CD_TXT, 'y', text] + + def check_report(self): + # FIXME: check the report for passwords and some other potentially + # sensitive info + self.dehydrate() + return True + + def run(self): + result = self.window.run() + self.window.destroy() + return (result, self.report) + +class ReporterSelector(): + def __init__(self, crashdump, daemon, log=None, parent=None): + self.connected_signals = [] + self.updates = "" + self.daemon = daemon + self.dump = crashdump + self.selected_reporters = [] + #FIXME: cache settings! Create some class to represent it like PluginList + self.settings = daemon.getSettings() + pluginlist = getPluginInfoList(daemon) + self.reporters = [] + AnalyzerActionsAndReporters = self.settings["AnalyzerActionsAndReporters"] + try: + reporters = None + try: + reporters = AnalyzerActionsAndReporters[self.dump.getAnalyzerName()+":"+self.dump.getPackageName()] + except KeyError: + pass + if not reporters: + reporters = AnalyzerActionsAndReporters[crashdump.getAnalyzerName()] + for reporter_name in reporters.split(','): + reporter = pluginlist.getReporterByName(reporter_name) + if reporter: + self.reporters.append(reporter) + except KeyError: + # Analyzer has no associated reporters. + pass + + builderfile = "%s/report.glade" % sys.path[0] + self.builder = gtk.Builder() + self.builder.add_from_file(builderfile) + self.window = self.builder.get_object("w_reporters") + b_cancel = self.builder.get_object("b_close") + + if parent: + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.window.set_transient_for(parent) + self.window.set_modal(True) + self.connect_signal(self.window, "delete-event", self.on_window_delete) + self.connect_signal(self.window, "destroy-event", self.on_window_delete) + self.connect_signal(b_cancel, "clicked", self.on_close_clicked) + else: + # if we don't have parent we want to quit the mainloop on close + self.window.set_position(gtk.WIN_POS_CENTER) + self.connect_signal(self.window, "delete-event", gtk.main_quit) + self.connect_signal(self.window, "destroy-event", gtk.main_quit) + self.connect_signal(b_cancel, "clicked", gtk.main_quit) + + + self.pBarWindow = self.builder.get_object("pBarWindow") + self.pBarWindow.set_transient_for(self.window) + + reporters_vbox = self.builder.get_object("vb_reporters") + for reporter in self.reporters: + button = gtk.Button(str(reporter)) + self.connect_signal(button, "clicked", self.on_reporter_clicked, data=reporter) + reporters_vbox.pack_start(button) + + # progress bar window to show while bt is being extracted + self.pBarWindow = self.builder.get_object("pBarWindow") + if self.pBarWindow: + self.connect_signal(self.pBarWindow, "delete_event", self.sw_delete_event_cb) + if parent: + self.pBarWindow.set_transient_for(parent) + else: + self.pBarWindow.set_transient_for(self.window) + self.pBar = self.builder.get_object("pBar") + + # connect handlers for daemon signals + #self.ccdaemon.connect("abrt-error", self.error_cb) + self.connect_signal(daemon, "update", self.update_cb) + # for now, just treat them the same (w/o this, we don't even see daemon warnings in logs!): + #self.ccdaemon.connect("warning", self.update_cb) + #self.ccdaemon.connect("show", self.show_cb) + #self.ccdaemon.connect("daemon-state-changed", self.on_daemon_state_changed_cb) + self.connect_signal(daemon, "report-done", self.on_report_done_cb) + self.connect_signal(daemon, "analyze-complete", self.on_analyze_complete_cb, self.pBarWindow) + + def connect_signal(self, obj, signal, callback, data=None): + if data: + signal_id = obj.connect(signal, callback, data) + else: + signal_id = obj.connect(signal, callback) + self.connected_signals.append((obj, signal_id)) + + def disconnect_signals(self): + # we need to disconnect all signals in order to break all references + # to this object, otherwise python won't destroy this object and the + # signals emmited by daemon will get caught by multiple instances of + # this class + for obj, signal_id in self.connected_signals: + obj.disconnect(signal_id) + + def cleanup_and_exit(self): + if not self.window.get_property("visible"): + self.disconnect_signals() + # if the reporter selector doesn't have a parent + if not self.window.get_transient_for(): + gtk.main_quit() + + def update_cb(self, daemon, message): + self.updates += message + if self.updates[-1] != '\n': + self.updates += '\n' + message = message.replace('\n',' ') + self.builder.get_object("lStatus").set_text(message) + buff = gtk.TextBuffer() + buff.set_text(self.updates) + end = buff.get_insert() + tvUpdates = self.builder.get_object("tvUpdates") + tvUpdates.set_buffer(buff) + tvUpdates.scroll_mark_onscreen(end) + + def sw_delete_event_cb(self, widget, event, data=None): + if self.timer: + gobject.source_remove(self.timer) + widget.hide() + return True + + def show(self): + if not self.reporters: + gui_error_message(_("No reporter plugin available for this type of crash.\n" + "Please check abrt.conf.")) + elif len(self.reporters) > 1: + self.builder.get_object("vb_reporters").show_all() + self.window.show() + else: + # we have only one reporter in the list + self.selected_reporters = [str(self.reporters[0])] + self.show_report() + + def on_config_plugin_clicked(self, button, plugin, image): + ui = PluginSettingsUI(plugin, parent=self.window) + ui.hydrate() + response = ui.run() + if response == gtk.RESPONSE_APPLY: + ui.dehydrate() + if plugin.Settings.check(): + try: + plugin.save_settings_on_client_side() + except Exception, e: + gui_error_message(_("Cannot save plugin settings:\n %s" % e)) + box = image.get_parent() + im = gtk.Image() + im.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_MENU) + box.remove(image) + box.pack_start(im, expand = False, fill = False) + im.show() + image.destroy() + button.set_sensitive(False) + elif response == gtk.RESPONSE_CANCEL: + log1("cancel") + ui.destroy() + + def check_settings(self, reporters): + wrong_conf_plugs = [] + for reporter in reporters: + if reporter.Settings.check() == False: + wrong_conf_plugs.append(reporter) + + if wrong_conf_plugs: + gladefile = "%s%ssettings_wizard.glade" % (sys.path[0],"/") + builder = gtk.Builder() + builder.add_from_file(gladefile) + dialog = builder.get_object("WrongSettings") + vbWrongSettings = builder.get_object("vbWrongSettings") + for plugin in wrong_conf_plugs: + hbox = gtk.HBox() + hbox.set_spacing(6) + image = gtk.Image() + image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_MENU) + button = gtk.Button(_("Configure %s options" % plugin.getName())) + button.connect("clicked", self.on_config_plugin_clicked, plugin, image) + hbox.pack_start(button) + hbox.pack_start(image, expand = False, fill = False) + vbWrongSettings.pack_start(hbox) + vbWrongSettings.show_all() + dialog.set_transient_for(self.window) + dialog.set_modal(True) + response = dialog.run() + dialog.destroy() + if response != gtk.RESPONSE_YES: + # user cancelled reporting + return False + return True + + def on_reporter_clicked(self, widget, reporter): + self.selected_reporters = [reporter] + if self.check_settings(self.selected_reporters): + self.show_report() + + def on_close_clicked(self, widget): + self.disconnect_signals() + self.window.destroy() + + def on_window_delete(self, window, event): + self.disconnect_signals() + return False + + def on_report_done_cb(self, daemon, result): + try: + gobject.source_remove(self.timer) + except: + pass + self.pBarWindow.hide() + gui_report_dialog(result, self.window) + + self.cleanup_and_exit() + + def on_analyze_complete_cb(self, daemon, report, pBarWindow): + try: + gobject.source_remove(self.timer) + except: + pass + self.pBarWindow.hide() +#FIXME - why we need this?? -> timeout warnings +# try: +# dumplist = getDumpList(self.daemon) +# except Exception, e: +# print e + if not report: + gui_error_message(_("Unable to get report!\nIs debuginfo missing?")) + return + + # if we have only one reporter enabled, the window with + # the selection is not shown, so we can't use it as a parent + # and we use the mainwindow instead + if self.window.get_property("visible"): + parent_window = self.window + else: + parent_window = self.window.get_transient_for() + + report_dialog = ReporterDialog(report, self.daemon, log=self.updates, parent=parent_window) + # (response, report) + response, result = report_dialog.run() + + if response == gtk.RESPONSE_APPLY: + try: + self.pBarWindow.show_all() + self.timer = gobject.timeout_add(100, self.progress_update_cb) + pluginlist = getPluginInfoList(self.daemon) + reporters_settings = pluginlist.getReporterPluginsSettings() + log2("Report(result,reporters,settings):") + log2(" result:%s", str(result)) + # Careful, this will print reporters_settings["Password"] too + log2(" settings:%s", str(reporters_settings)) + self.daemon.Report(result, self.selected_reporters, reporters_settings) + log2("Report() returned") + #self.hydrate() + except Exception, ex: + gui_error_message(_("Reporting failed!\n%s" % ex)) + # -50 == REFRESH + elif response == -50: + self.refresh_report(report) + else: + self.cleanup_and_exit() + + # call to update the progressbar + def progress_update_cb(self, *args): + self.pBar.pulse() + return True + + def refresh_report(self, report): + self.updates = "" + self.pBarWindow.show_all() + self.timer = gobject.timeout_add(100, self.progress_update_cb) + + # show the report window with selected report + try: + self.daemon.start_job("%s:%s" % (report[CD_UID][CD_CONTENT], report[CD_UUID][CD_CONTENT]), force=1) + except Exception, ex: + # FIXME #3 dbus.exceptions.DBusException: org.freedesktop.DBus.Error.NoReply: Did not receive a reply + # do this async and wait for yum to end with debuginfoinstal + if self.timer: + gobject.source_remove(self.timer) + self.pBarWindow.hide() + gui_error_message(_("Error acquiring the report: %s" % ex)) + return + + def show_report(self): + self.updates = "" + # FIXME don't duplicate the code, move to function + #self.pBar.show() + self.pBarWindow.show_all() + self.timer = gobject.timeout_add(100, self.progress_update_cb) + + # show the report window with selected dump + # when getReport is done it emits "analyze-complete" and on_analyze_complete_cb is called + # FIXME: does it make sense to change it to use callback rather then signal emitting? + try: + self.daemon.start_job("%s:%s" % (self.dump.getUID(), self.dump.getUUID())) + except Exception, ex: + # FIXME #3 dbus.exceptions.DBusException: org.freedesktop.DBus.Error.NoReply: Did not receive a reply + # do this async and wait for yum to end with debuginfoinstal + if self.timer: + gobject.source_remove(self.timer) + self.pBarWindow.hide() + gui_error_message(_("Error acquiring the report: %s" % ex)) + return + + def __del__(self): + log1("ReporterSelector: instance is about to be garbage-collected") diff --git a/src/gui/CC_gui_functions.py b/src/gui/CC_gui_functions.py new file mode 100644 index 00000000..56abae1b --- /dev/null +++ b/src/gui/CC_gui_functions.py @@ -0,0 +1,253 @@ +# -*- coding: utf-8 -*- +from glib import markup_escape_text +import gtk +import pango +import subprocess +import sys + +try: + # we don't want to add dependency to rpm, but if we have it, we can use it + import rpm +except ImportError: + rpm = None +from abrt_utils import _, log, log1, log2 + + +def tag_urls_in_text(text): + url_marks = ["http://", "https://", "ftp://", "ftps://", "file://"] + text = markup_escape_text(text) + lines = text.split('\n') + lines_dict = {} + for index in xrange(len(lines)): + lines_dict[index] = lines[index] + + for mark in url_marks: + for ix,line in lines_dict.items(): + last_mark = line.find(mark) + if last_mark != -1: + url_end = line.find(' ',last_mark) + if url_end == -1: + url_end = len(line) + url = line[last_mark:url_end] + tagged_url = "%s" % (url, url) + lines_dict[ix] = line.replace(url, tagged_url) + retval = "" + for line in lines_dict.itervalues(): + retval += line + retval +='\n' + # strip the trailing \n + return retval[:-1] + +def on_label_resize(label, allocation): + label.set_size_request(allocation.width,-1) + +def gui_report_dialog ( report_status_dict, parent_dialog, + message_type=gtk.MESSAGE_INFO, + widget=None, page=0, broken_widget=None ): + MAX_WIDTH = 50 + builder = gtk.Builder() + builderfile = "%s%sdialogs.glade" % (sys.path[0],"/") + builder.add_from_file(builderfile) + dialog = builder.get_object("ReportDialog") + dialog.set_geometry_hints(dialog, min_width=450, min_height=150) + dialog.set_resizable(True) + main_hbox = builder.get_object("main_hbox") + + STATUS = 0 + MESSAGE = 1 + status_vbox = gtk.VBox() + for plugin, res in report_status_dict.iteritems(): + plugin_status_vbox = gtk.VBox() + plugin_label = gtk.Label() + plugin_label.set_markup("%s: " % plugin) + plugin_label.set_justify(gtk.JUSTIFY_RIGHT) + plugin_label.set_alignment(0, 0) + status_label = gtk.Label() + status_label.connect("size-allocate",on_label_resize) + status_label.set_max_width_chars(MAX_WIDTH) + status_label.set_size_request(400,-1) + status_label.set_selectable(True) + status_label.set_line_wrap(True) + status_label.set_line_wrap_mode(pango.WRAP_CHAR) + status_label.set_alignment(0, 0) + plugin_status_vbox.pack_start(plugin_label, expand=False) + plugin_status_vbox.pack_start(status_label, fill=True, expand=True) + # 0 means not succesfull + #if report_status_dict[plugin][STATUS] == '0': + # this first one is actually a fallback to set at least + # a raw text in case when set_markup() fails + status_label.set_text(report_status_dict[plugin][MESSAGE]) + status_label.set_markup("%s" % markup_escape_text(report_status_dict[plugin][MESSAGE])) + # if the report was not succesful then this won't pass so this runs only + # if report succeds and gets overwriten by the status message + if report_status_dict[plugin][STATUS] == '1': + status_label.set_markup(tag_urls_in_text(report_status_dict[plugin][MESSAGE])) + + if len(report_status_dict[plugin][1]) > MAX_WIDTH: + status_label.set_tooltip_text(report_status_dict[plugin][1]) + status_vbox.pack_start(plugin_status_vbox, fill=True, expand=False) + main_hbox.pack_start(status_vbox) + + if widget != None: + if isinstance (widget, gtk.CList): + widget.select_row (page, 0) + elif isinstance (widget, gtk.Notebook): + widget.set_current_page (page) + if broken_widget != None: + broken_widget.grab_focus () + if isinstance (broken_widget, gtk.Entry): + broken_widget.select_region (0, -1) + + if parent_dialog: + dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT) + dialog.set_transient_for(parent_dialog) + else: + dialog.set_position (gtk.WIN_POS_CENTER) + + main_hbox.show_all() + ret = dialog.run() + dialog.destroy() + return ret + +def gui_info_dialog ( message, parent=None, + message_type=gtk.MESSAGE_INFO, + widget=None, page=0, broken_widget=None ): + + dialog = gtk.MessageDialog( parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + message_type, gtk.BUTTONS_OK, + message ) + dialog.set_markup(message) + if widget != None: + if isinstance (widget, gtk.CList): + widget.select_row (page, 0) + elif isinstance (widget, gtk.Notebook): + widget.set_current_page (page) + if broken_widget != None: + broken_widget.grab_focus () + if isinstance (broken_widget, gtk.Entry): + broken_widget.select_region (0, -1) + + if parent: + dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT) + dialog.set_transient_for(parent) + else: + dialog.set_position (gtk.WIN_POS_CENTER) + + ret = dialog.run () + dialog.destroy() + return ret + +def gui_error_message ( message, parent_dialog=None, + message_type=gtk.MESSAGE_ERROR, + widget=None, page=0, broken_widget=None ): + + dialog = gtk.MessageDialog( parent_dialog, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + message_type, gtk.BUTTONS_OK, message ) + dialog.set_markup(message) + if parent_dialog: + dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT) + dialog.set_transient_for(parent_dialog) + else: + dialog.set_position (gtk.WIN_POS_CENTER) + + ret = dialog.run () + dialog.destroy() + return ret + +def gui_question_dialog ( message, parent_dialog=None, + message_type=gtk.MESSAGE_QUESTION, + widget=None, page=0, broken_widget=None ): + + dialog = gtk.MessageDialog( parent_dialog, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + message_type, gtk.BUTTONS_NONE, + message ) + dialog.add_button("gtk-cancel", gtk.RESPONSE_CANCEL) + dialog.add_button("gtk-no", gtk.RESPONSE_NO) + dialog.add_button("gtk-yes", gtk.RESPONSE_YES) + dialog.set_markup(message) + if parent_dialog: + dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT) + dialog.set_transient_for(parent_dialog) + else: + dialog.set_position (gtk.WIN_POS_CENTER) + + ret = dialog.run () + dialog.destroy() + return ret + +def get_icon_for_package(theme, package): + log2("get_icon_for_package('%s')", package) + package_icon = None + try: + package_icon = theme.load_icon(package, 48, gtk.ICON_LOOKUP_USE_BUILTIN) + except: + # try to find icon filename by manually + if not rpm: + return None + ts = rpm.TransactionSet() + mi = ts.dbMatch('name', package) + possible_icons = [] + icon_filename = "" + icon_name = "" + filenames = "" + for h in mi: + filenames = h['filenames'] + for filename in filenames: + # add check only for last 4 chars + if filename.rfind(".png") != -1: + possible_icons.append(filename) + if filename.rfind(".desktop") != -1: + log2("desktop file:'%s'", filename) + desktop_file = open(filename, 'r') + lines = desktop_file.readlines() + for line in lines: + if line.find("Icon=") != -1: + log2("Icon='%s'", line[5:-1]) + icon_name = line[5:-1] + break + desktop_file.close() + # .desktop file found + if icon_name: + try: + package_icon = theme.load_icon(icon_name, 48, gtk.ICON_LOOKUP_USE_BUILTIN) + except: + # we should get here only if the .desktop file is wrong.. + for filename in h['filenames']: + if filename.rfind("%s.png" % icon_name) != -1: + icon_filename = filename + # if we found size 48x48 we don't need to continue + if "48x48" in icon_filename: + log2("png file:'%s'", filename) + break + if icon_filename: + log1("icon created from %s", icon_filename) + package_icon = gtk.gdk.pixbuf_new_from_file_at_size(icon_filename, 48, 48) + return package_icon + +def show_log(message_log, parent=None): + builder = gtk.Builder() + builderfile = "%s%sdialogs.glade" % (sys.path[0],"/") + builder.add_from_file(builderfile) + dialog = builder.get_object("LogViewer") + tevLog = builder.get_object("tevLog") + + if parent: + dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT) + dialog.set_transient_for(parent) + else: + dialog.set_position (gtk.WIN_POS_CENTER) + + buff = gtk.TextBuffer() + buff.set_text(message_log) + tevLog.set_buffer(buff) + + dialog.run() + dialog.destroy() + +if __name__ == "__main__": + window = gtk.Window() + gui_report_dialog("Bugzilla: CReporterBugzilla::Report(): CReporterBugzilla::Login(): RPC response indicates failure. The username or password you entered is not valid.\nLogger: Report was stored into: /var/log/abrt.log", window) + gtk.main() diff --git a/src/gui/CReporterAssistant.py b/src/gui/CReporterAssistant.py new file mode 100644 index 00000000..0ddd18fa --- /dev/null +++ b/src/gui/CReporterAssistant.py @@ -0,0 +1,894 @@ +import gtk +from PluginList import getPluginInfoList +from abrt_utils import _, log, log1, log2, get_verbose_level, g_verbose, warn +from CCDump import * # FILENAME_xxx, CD_xxx +from PluginSettingsUI import PluginSettingsUI +import sys +import gobject +from CC_gui_functions import * +import pango + +# assistant pages +PAGE_REPORTER_SELECTOR = 0 +PAGE_BACKTRACE_APPROVAL = 1 +PAGE_EXTRA_INFO = 2 +PAGE_CONFIRM = 3 +PAGE_REPORT_DONE = 4 +NO_PROBLEMS_DETECTED = -50 +HOW_TO_HINT_TEXT = "1.\n2.\n3.\n" +COMMENT_HINT_TEXT = _("Brief description of how to reproduce this or what you did...") +MISSING_BACKTRACE_TEXT = _("Crash info doesn't contain a backtrace") + +DEFAULT_WIDTH = 800 +DEFAULT_HEIGHT = 500 + +class ReporterAssistant(): + def __init__(self, report, daemon, log=None, parent=None): + self.connected_signals = [] + self.plugins_cb = [] + self.daemon = daemon + self.updates = "" + self.pdict = {} + self.report = report + self.parent = parent + self.comment_changed = False + self.howto_changed = False + self.report_has_bt = False + self.selected_reporters = [] + """ create the assistant """ + self.assistant = gtk.Assistant() + self.assistant.set_icon_name("abrt") + self.assistant.set_default_size(DEFAULT_WIDTH,DEFAULT_HEIGHT) + self.assistant.set_size_request(DEFAULT_WIDTH,DEFAULT_HEIGHT) + self.connect_signal(self.assistant, "prepare",self.on_page_prepare) + self.connect_signal(self.assistant, "cancel",self.on_cancel_clicked) + self.connect_signal(self.assistant, "close",self.on_close_clicked) + self.connect_signal(self.assistant, "apply",self.on_apply_clicked) + if parent: + self.assistant.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.assistant.set_transient_for(parent) + else: + # if we don't have parent we want to quit the mainloop on close + self.assistant.set_position(gtk.WIN_POS_CENTER) + + ### progress bar window + self.builder = gtk.Builder() + builderfile = "%s/progress_window.glade" % sys.path[0] + self.builder.add_from_file(builderfile) + self.pBarWindow = self.builder.get_object("pBarWindow") + if self.pBarWindow: + self.connect_signal(self.pBarWindow, "delete_event", self.sw_delete_event_cb) + if parent: + self.pBarWindow.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.pBarWindow.set_transient_for(parent) + else: + self.pBarWindow.set_position(gtk.WIN_POS_CENTER) + self.pBar = self.builder.get_object("pBar") + else: + log1("Couldn't create the progressbar window") + + self.connect_signal(daemon, "analyze-complete", self.on_analyze_complete_cb, self.pBarWindow) + self.connect_signal(daemon, "report-done", self.on_report_done_cb) + self.connect_signal(daemon, "update", self.update_cb) + + # call to update the progressbar + def progress_update_cb(self, *args): + self.pBar.pulse() + return True + + def on_show_bt_clicked(self, button): + viewer = gtk.Window() + viewer.set_icon_name("abrt") + viewer.set_default_size(600,500) + viewer.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + viewer.set_transient_for(self.assistant) + vbox = gtk.VBox() + viewer.add(vbox) + bt_tev = gtk.TextView() + backtrace_scroll_w = gtk.ScrolledWindow() + backtrace_scroll_w.add(bt_tev) + backtrace_scroll_w.set_policy(gtk.POLICY_AUTOMATIC, + gtk.POLICY_AUTOMATIC) + bt_tev.set_buffer(self.backtrace_buff) + vbox.pack_start(backtrace_scroll_w) + b_close = gtk.Button(stock=gtk.STOCK_CLOSE) + b_close.connect("clicked",lambda *w: viewer.destroy()) + vbox.pack_start(b_close, False) + viewer.show_all() + + def on_report_done_cb(self, daemon, result): + self.hide_progress() + STATUS = 0 + MESSAGE = 1 + # 0 means not succesfull + #if report_status_dict[plugin][STATUS] == '0': + # this first one is actually a fallback to set at least + # a raw text in case when set_markup() fails + for plugin, res in result.iteritems(): + bug_report = gtk.Label() + bug_report.set_selectable(True) + bug_report.set_line_wrap(True) + bug_report.set_alignment(0.0, 0.0) + bug_report.set_justify(gtk.JUSTIFY_LEFT) + bug_report.set_size_request(DEFAULT_WIDTH-50, -1) + bug_report.set_text(result[plugin][MESSAGE]) + bug_report.set_markup("%s" % markup_escape_text(result[plugin][MESSAGE])) + # if the report was not succesful then this won't pass so this runs only + # if report succeds and gets overwriten by the status message + if result[plugin][STATUS] == '1': + bug_report.set_markup(tag_urls_in_text(result[plugin][MESSAGE])) + self.bug_reports_vbox.pack_start(bug_report, expand=False) + bug_report.show() + + + #if len(result[plugin][1]) > MAX_WIDTH: + # self.bug_reports.set_tooltip_text(result[plugin][1]) + #gui_report_dialog(result, self.parent) + + def cleanup_and_exit(self): + self.disconnect_signals() + self.assistant.destroy() + if not self.parent: + gtk.main_quit() + + def update_cb(self, daemon, message): + self.updates += message + if self.updates[-1] != '\n': + self.updates += '\n' + message = message.replace('\n',' ') + self.builder.get_object("lStatus").set_text(message) + buff = gtk.TextBuffer() + buff.set_text(self.updates) + end = buff.get_insert() + tvUpdates = self.builder.get_object("tvUpdates") + tvUpdates.set_buffer(buff) + tvUpdates.scroll_mark_onscreen(end) + + def sw_delete_event_cb(self, widget, event, data=None): + if self.timer: + gobject.source_remove(self.timer) + widget.hide() + return True + + def connect_signal(self, obj, signal, callback, data=None): + if data: + signal_id = obj.connect(signal, callback, data) + else: + signal_id = obj.connect(signal, callback) + log1("connected signal %s:%s" % (signal, signal_id)) + self.connected_signals.append((obj, signal_id)) + + def disconnect_signals(self): + # we need to disconnect all signals in order to break all references + # to this object, otherwise python won't destroy this object and the + # signals emmited by daemon will get caught by multiple instances of + # this class + for obj, signal_id in self.connected_signals: + log1("disconnect %s:%s" % (obj, signal_id)) + obj.disconnect(signal_id) + + def on_cancel_clicked(self, assistant, user_data=None): + self.cleanup_and_exit() + + def on_close_clicked(self, assistant, user_data=None): + self.cleanup_and_exit() + + def on_apply_clicked(self, assistant, user_data=None): + self.send_report(self.result) + + def hide_progress(self): + try: + gobject.source_remove(self.timer) + except: + pass + self.pBarWindow.hide() + + def on_config_plugin_clicked(self, button, parent, plugin, image): + try: + ui = PluginSettingsUI(plugin, parent=parent) + except Exception, ex: + gui_error_message(str(ex)) + return + + ui.hydrate() + response = ui.run() + if response == gtk.RESPONSE_APPLY: + ui.dehydrate() + if plugin.Settings.check(): + try: + plugin.save_settings_on_client_side() + except Exception, e: + gui_error_message(_("Cannot save plugin settings:\n %s" % e)) + box = image.get_parent() + im = gtk.Image() + im.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_MENU) + box.remove(image) + box.pack_start(im, expand = False, fill = False) + im.show() + image.destroy() + button.set_sensitive(False) + elif response == gtk.RESPONSE_CANCEL: + log1("cancel") + ui.destroy() + + def check_settings(self, reporters): + wrong_conf_plugs = [] + for reporter in reporters: + if reporter.Settings.check() == False: + wrong_conf_plugs.append(reporter) + + if wrong_conf_plugs: + gladefile = "%s%ssettings_wizard.glade" % (sys.path[0],"/") + builder = gtk.Builder() + builder.add_from_file(gladefile) + dialog = builder.get_object("WrongSettings") + vbWrongSettings = builder.get_object("vbWrongSettings") + for plugin in wrong_conf_plugs: + hbox = gtk.HBox() + hbox.set_spacing(6) + image = gtk.Image() + image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_MENU) + button = gtk.Button(_("Configure %s options" % plugin.getName())) + button.connect("clicked", self.on_config_plugin_clicked, dialog, plugin, image) + hbox.pack_start(button) + hbox.pack_start(image, expand = False, fill = False) + vbWrongSettings.pack_start(hbox) + vbWrongSettings.show_all() + dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + dialog.set_transient_for(self.assistant) + dialog.set_modal(True) + response = dialog.run() + dialog.destroy() + if response == gtk.RESPONSE_YES: + return True + else: + # user cancelled reporting + return False + else: + return NO_PROBLEMS_DETECTED + + def warn_user(self, warnings): + # FIXME: show in lError + #self.lErrors = self.builder.get_object("lErrors") + warning_lbl = None + for warning in warnings: + if warning_lbl: + warning_lbl += "\n* %s" % warning + else: + warning_lbl = "* %s" % warning + # fallback + self.lbl_errors.set_label(warning_lbl) + self.lbl_errors.set_markup(warning_lbl) + self.errors_hbox.show_all() + #fErrors.show_all() + + def hide_warning(self): + self.errors_hbox.hide() + + def allow_send(self, send_toggle): + self.hide_warning() + #bSend = self.builder.get_object("bSend") + SendBacktrace = send_toggle.get_active() + send = True + error_msgs = [] + rating_required = False + + for reporter in self.selected_reporters: + if "RatingRequired" in reporter.Settings.keys(): + if reporter.Settings["RatingRequired"] == "yes": + rating_required = True + log1(_("Rating is required by the %s plugin") % reporter) + if self.selected_reporters and not rating_required: + log1(_("Rating is not required by any plugin, skipping the check...")) + + try: + rating = int(self.result[FILENAME_RATING][CD_CONTENT]) + log1(_("Rating is %s" % rating)) + except Exception, ex: + rating = None + log1(_("Crashdump doesn't have rating => we suppose it's not required")) + # active buttons acording to required fields + # if an backtrace has rating use it + if not SendBacktrace: + send = False + error_msgs.append(_("You should check the backtrace for sensitive data.")) + error_msgs.append(_("You must agree with sending the backtrace.")) + # we have both SendBacktrace and rating + # if analyzer doesn't provide the rating, then we suppose that it's + # not required e.g.: kerneloops, python + if rating_required and rating != None: + try: + package = self.result[FILENAME_PACKAGE][CD_CONTENT] + # if we don't have package for some reason + except: + package = None + # not usable report + if rating < 3: + if package: + error_msgs.append(_("Reporting disabled because the backtrace is unusable.\nPlease try to install debuginfo manually using the command: debuginfo-install %s \nthen use the Refresh button to regenerate the backtrace." % self.report.getPackageName())) + else: + error_msgs.append(_("Reporting disabled because the backtrace is unusable.")) + send = False + # probably usable 3 + elif rating < 4: + error_msgs.append(_("The backtrace is incomplete, please make sure you provide the steps to reproduce.")) + + if error_msgs: + self.warn_user(error_msgs) + #bSend.set_sensitive(send) + self.assistant.set_page_complete(self.pdict_get_page(PAGE_BACKTRACE_APPROVAL), send) + + def on_page_prepare(self, assistant, page): + if page == self.pdict_get_page(PAGE_REPORTER_SELECTOR): + pass + + # this is where dehydrate happens + elif page == self.pdict_get_page(PAGE_EXTRA_INFO): + if not self.howto_changed: + # howto + buff = gtk.TextBuffer() + try: + buff.set_text(self.result[FILENAME_REPRODUCE][CD_CONTENT]) + self.howto_changed = True + except KeyError: + buff.set_text(HOW_TO_HINT_TEXT) + self.howto_tev.set_buffer(buff) + # don't refresh the comment if user changed it + if not self.comment_changed: + # comment + buff = gtk.TextBuffer() + try: + buff.set_text(self.result[FILENAME_COMMENT][CD_CONTENT]) + self.comment_changed = True + except KeyError: + buff.set_text(COMMENT_HINT_TEXT) + self.comment_tev.set_buffer(buff) + elif page == self.pdict_get_page(PAGE_CONFIRM): + # howto + if self.howto_changed: + howto_buff = self.howto_tev.get_buffer() + howto_text = howto_buff.get_text(howto_buff.get_start_iter(), howto_buff.get_end_iter()) + # user has changed the steps to reproduce + self.steps.set_text(howto_text) + self.result[FILENAME_REPRODUCE] = [CD_TXT, 'y', howto_text] + else: + self.steps.set_text(_("You did not provide any steps to reproduce.")) + try: + del self.result[FILENAME_REPRODUCE] + except KeyError: + # if this is a first time, then we don't have key FILENAME_REPRODUCE + pass + #comment + if self.comment_changed: + comment_buff = self.comment_tev.get_buffer() + comment_text = comment_buff.get_text(comment_buff.get_start_iter(), comment_buff.get_end_iter()) + # user has changed the comment + self.comments.set_text(comment_text) + self.result[FILENAME_COMMENT] = [CD_TXT, 'y', comment_text] + else: + self.comments.set_text(_("You did not provide any comments.")) + try: + del self.result[FILENAME_COMMENT] + except KeyError: + # if this is a first time, then we don't have key FILENAME_COMMENT + pass + + # backtrace + backtrace_text = self.backtrace_buff.get_text(self.backtrace_buff.get_start_iter(), self.backtrace_buff.get_end_iter()) + if self.report_has_bt: + self.result[FILENAME_BACKTRACE] = [CD_TXT, 'y', backtrace_text] + if page == self.pdict_get_page(PAGE_BACKTRACE_APPROVAL): + self.allow_send(self.backtrace_cb) + + def send_report(self, report): + try: + self.pBarWindow.show_all() + self.timer = gobject.timeout_add(100, self.progress_update_cb) + pluginlist = getPluginInfoList(self.daemon) + reporters_settings = pluginlist.getReporterPluginsSettings() + log2("Report(report, reporters, settings):") + log2(" result:%s", str(report)) + # Careful, this will print reporters_settings["Password"] too + log2(" settings:%s", str(reporters_settings)) + self.daemon.Report(report, self.selected_reporters, reporters_settings) + log2("Report() returned") + #self.hydrate() + except Exception, ex: + self.hide_progress() + gui_error_message(_("Reporting failed!\n%s" % ex)) + + def on_plugin_toggled(self, plugin, plugins, reporter, page): + complete = False + if plugin.get_active(): + log1("Plugin >>%s<< activated" % reporter) + self.selected_reporters.append(reporter) + check_result = self.check_settings([reporter]) + if check_result == NO_PROBLEMS_DETECTED: + pass + elif check_result: + page_n = self.assistant.get_current_page() + self.assistant.set_page_complete(page, True) + self.assistant.set_current_page(page_n+1) + else: + plugin.set_active(False) + else: + self.selected_reporters.remove(reporter) + log1("Plugin >>%s<< de-activated" % reporter) + if self.selected_reporters: + complete = True + log1("Selected reporters: %s" % [str(x) for x in self.selected_reporters]) + self.assistant.set_page_complete(page, complete) + + def on_bt_toggled(self, togglebutton, page): + self.allow_send(togglebutton) + + def pdict_add_page(self, page, name): + # FIXME try, except?? + if name not in self.pdict: + self.pdict[name] = page + else: + warn("The page %s is already in the dictionary" % name) + #raise Exception("The page %s is already in the dictionary" % name) + + def pdict_get_page(self, name): + try: + return self.pdict[name] + except Exception, e: + log2(e) + return None + + def prepare_page_1(self): + plugins_cb = [] + page = gtk.VBox(spacing=10) + page.set_border_width(10) + self.assistant.insert_page(page, PAGE_REPORTER_SELECTOR) + lbl_default_info = gtk.Label() + lbl_default_info.set_line_wrap(True) + lbl_default_info.set_alignment(0.0, 0.0) + lbl_default_info.set_justify(gtk.JUSTIFY_LEFT) + lbl_default_info.set_size_request(DEFAULT_WIDTH-50, -1) + lbl_default_info.set_markup(_("It looks like an application from the " + "package %s has crashed " + "on your system. It is a good idea to send " + "a bug report about this issue. The report " + "will provide software maintainers with " + "information essential in figuring out how " + "to provide a bug fix for you.\n\n" + "Please review the information that follows " + "and modify it as needed to ensure your bug " + "report does not contain any sensitive data " + "you would rather not share.\n\n" + "Select where you would like to report the " + "bug, and press 'Forward' to continue.") + % self.report.getPackageName()) + page.pack_start(lbl_default_info, expand=True, fill=True) + vbox_plugins = gtk.VBox() + page.pack_start(vbox_plugins) + + # add checkboxes for enabled reporters + self.selected_reporters = [] + #FIXME: cache settings! Create some class to represent it like PluginList + self.settings = self.daemon.getSettings() + pluginlist = getPluginInfoList(self.daemon) + self.reporters = [] + AnalyzerActionsAndReporters = self.settings["AnalyzerActionsAndReporters"] + try: + reporters = None + try: + reporters = AnalyzerActionsAndReporters[self.report.getAnalyzerName()+":"+self.report.getPackageName()] + log1("Found per-package reporters, " + "using it instead of the common reporter") + except KeyError: + pass + # the package specific reporter has higher priority, + # so don't overwrite it if it's set + if not reporters: + reporters = AnalyzerActionsAndReporters[self.report.getAnalyzerName()] + # FIXME: split(',') doesn't work for RunApp("date", "date.txt") + # but since we don't have reporters with parameters, it will find + # the reporter plugins anyway, but it should be more clever... + for reporter_name in reporters.split(','): + reporter = pluginlist.getReporterByName(reporter_name) + if reporter: + log1("Adding >>%s<< to reporters", reporter) + self.reporters.append(reporter) + except KeyError: + # Analyzer has no associated reporters. + # but we don't care, maybe user just want to read the backtrace?? + pass + for reporter in self.reporters: + cb = gtk.CheckButton(str(reporter)) + cb.connect("toggled", self.on_plugin_toggled, plugins_cb, reporter, page) + plugins_cb.append(cb) + vbox_plugins.pack_start(cb, fill=True, expand=False) + # automatically select the reporter if we have only one reporter plugin + if len(self.reporters) == 1: + # we want to skip it only if the plugin is properly configured + if self.reporters[0].Settings.check(): + #self.selected_reporters.append(self.reporters[0]) + self.assistant.set_page_complete(page, True) + log1(_("Only one reporter plugin is configured.")) + # this is safe, because in python the variable is visible even + # outside the for loop + cb.set_active(True) + self.pdict_add_page(page, PAGE_REPORTER_SELECTOR) + self.assistant.set_page_type(page, gtk.ASSISTANT_PAGE_INTRO) + self.assistant.set_page_title(page, _("Send a bug report")) + page.show_all() + + def on_bt_copy(self, button, bt_text_view): + buff = bt_text_view.get_buffer() + bt_text = buff.get_text(buff.get_start_iter(), buff.get_end_iter()) + clipboard = gtk.clipboard_get() + clipboard.set_text(bt_text) + + def tv_text_changed(self, textview, default_text): + buff = textview.get_buffer() + text = buff.get_text(buff.get_start_iter(), buff.get_end_iter()) + if text: + if text and text != default_text: + return True + return False + else: + buff.set_text(default_text) + + def on_howto_focusout_cb(self, textview, event): + self.howto_changed = self.tv_text_changed(textview, HOW_TO_HINT_TEXT) + + def on_comment_focusin_cb(self, textview, event): + if not self.comment_changed: + # clear "hint" text by supplying a fresh, empty TextBuffer + textview.set_buffer(gtk.TextBuffer()) + + def on_comment_focusout_cb(self, textview, event): + self.comment_changed = self.tv_text_changed(textview, COMMENT_HINT_TEXT) + + def prepare_page_2(self): + page = gtk.VBox(spacing=10) + page.set_border_width(10) + lbl_default_info = gtk.Label() + lbl_default_info.set_line_wrap(True) + lbl_default_info.set_alignment(0.0, 0.0) + lbl_default_info.set_justify(gtk.JUSTIFY_FILL) + lbl_default_info.set_size_request(DEFAULT_WIDTH-50, -1) + lbl_default_info.set_text(_("Below is the backtrace associated with your " + "crash. A crash backtrace provides developers with details about " + "how the crash happened, helping them track down the source of the " + "problem.\n\n" + "Please review the backtrace below and modify it as needed to " + "ensure your bug report does not contain any sensitive data you would " + "rather not share:") + ) + page.pack_start(lbl_default_info, expand=False, fill=True) + self.backtrace_tev = gtk.TextView() + # global? + self.backtrace_buff = gtk.TextBuffer() + #self.backtrace_buff.set_text(self.report[FILENAME_BACKTRACE][CD_CONTENT]) + self.backtrace_tev.set_buffer(self.backtrace_buff) + backtrace_scroll_w = gtk.ScrolledWindow() + backtrace_scroll_w.add(self.backtrace_tev) + backtrace_scroll_w.set_policy(gtk.POLICY_AUTOMATIC, + gtk.POLICY_AUTOMATIC) + # backtrace + hbox_bt = gtk.HBox() + vbox_bt = gtk.VBox(homogeneous=False, spacing=5) + hbox_bt.pack_start(vbox_bt) + backtrace_alignment = gtk.Alignment() + hbox_bt.pack_start(backtrace_alignment, expand=False, padding=10) + # bad backtrace, reporting disabled + vbox_bt.pack_start(backtrace_scroll_w) + # warnings about wrong bt + self.errors_hbox = gtk.HBox() + self.warning_image = gtk.Image() + self.warning_image.set_from_stock(gtk.STOCK_DIALOG_WARNING,gtk.ICON_SIZE_DIALOG) + self.lbl_errors = gtk.Label() + self.lbl_errors.set_line_wrap(True) + #self.lbl_errors.set_alignment(0.0, 0.0) + self.lbl_errors.set_justify(gtk.JUSTIFY_FILL) + self.lbl_errors.set_size_request(DEFAULT_WIDTH-50, -1) + self.errors_hbox.pack_start(self.warning_image, False, False) + self.errors_hbox.pack_start(self.lbl_errors) + ### + vbox_bt.pack_start(self.errors_hbox, False, False) + hbox_buttons = gtk.HBox(homogeneous=True) + button_alignment = gtk.Alignment() + b_refresh = gtk.Button(_("Refresh")) + b_refresh.connect("clicked", self.hydrate, 1) + b_copy = gtk.Button(_("Copy")) + b_copy.connect("clicked", self.on_bt_copy, self.backtrace_tev) + hbox_buttons.pack_start(button_alignment) + hbox_buttons.pack_start(b_refresh, expand=False, fill=True) + hbox_buttons.pack_start(b_copy, expand=False, fill=True) + vbox_bt.pack_start(hbox_buttons, expand=False, fill=False) + self.backtrace_cb = gtk.CheckButton(_("I agree with submitting the backtrace")) + self.backtrace_cb.connect("toggled", self.on_bt_toggled, page) + self.assistant.insert_page(page, PAGE_BACKTRACE_APPROVAL) + self.pdict_add_page(page, PAGE_BACKTRACE_APPROVAL) + self.assistant.set_page_type(page, gtk.ASSISTANT_PAGE_CONTENT) + self.assistant.set_page_title(page, _("Approve the backtrace")) + page.pack_start(hbox_bt) + page.pack_start(self.backtrace_cb, expand=False, fill=False) + page.show_all() + + def prepare_page_3(self): + page = gtk.VBox(spacing=10) + page.set_border_width(10) + #lbl_default_info = gtk.Label() + #lbl_default_info.set_line_wrap(True) + #lbl_default_info.set_alignment(0.0, 0.0) + #lbl_default_info.set_justify(gtk.JUSTIFY_FILL) + #lbl_default_info.set_size_request(600, -1) + #page.pack_start(lbl_default_info, expand=False, fill=True) + details_hbox = gtk.HBox() + details_hbox.set_border_width(10) + details_alignment = gtk.Alignment() + details_vbox = gtk.VBox(spacing=10) + details_hbox.pack_start(details_vbox) + details_hbox.pack_start(details_alignment, expand=False, padding=30) + + # how to reproduce + howto_vbox = gtk.VBox(spacing=5) + howto_lbl = gtk.Label(_("How did this crash happen (step-by-step)? " + "How would you reproduce it?")) + howto_lbl.set_alignment(0.0, 0.0) + howto_lbl.set_justify(gtk.JUSTIFY_FILL) + self.howto_tev = gtk.TextView() + self.howto_tev.set_accepts_tab(False) + self.howto_tev.connect("focus-out-event", self.on_howto_focusout_cb) + howto_buff = gtk.TextBuffer() + howto_buff.set_text(HOW_TO_HINT_TEXT) + self.howto_tev.set_buffer(howto_buff) + howto_scroll_w = gtk.ScrolledWindow() + howto_scroll_w.add(self.howto_tev) + howto_scroll_w.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + howto_vbox.pack_start(howto_lbl, expand=False, fill=True) + howto_vbox.pack_start(howto_scroll_w) + + # comment + comment_vbox = gtk.VBox(spacing=5) + comment_lbl = gtk.Label(_("Are there any comments you would like to share " + "with the software maintainers?")) + comment_lbl.set_alignment(0.0, 0.0) + comment_lbl.set_justify(gtk.JUSTIFY_FILL) + self.comment_tev = gtk.TextView() + self.comment_tev.set_accepts_tab(False) + self.comment_tev.connect("focus-in-event", self.on_comment_focusin_cb) + self.comment_tev.connect("focus-out-event", self.on_comment_focusout_cb) + comment_scroll_w = gtk.ScrolledWindow() + comment_scroll_w.add(self.comment_tev) + comment_scroll_w.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + comment_vbox.pack_start(comment_lbl, expand=False, fill=True) + comment_vbox.pack_start(comment_scroll_w) + + details_vbox.pack_start(howto_vbox) + details_vbox.pack_start(comment_vbox) + self.assistant.insert_page(page, PAGE_EXTRA_INFO) + self.pdict_add_page(page, PAGE_EXTRA_INFO) + self.assistant.set_page_type(page, gtk.ASSISTANT_PAGE_CONTENT) + self.assistant.set_page_title(page, _("Provide additional details")) + self.assistant.set_page_complete(page, True) + tip_hbox = gtk.HBox() + tip_image = gtk.Image() + tip_lbl = gtk.Label("") + tip_lbl.set_alignment(0.0, 0.0) + tip_lbl.set_justify(gtk.JUSTIFY_FILL) + tip_lbl.set_markup(_("Tip: Your comments are not private. " + "Please watch what you say accordingly.")) + #tip_hbox.pack_start(tip_image) + tip_hbox.pack_start(tip_lbl, expand=False) + page.pack_start(details_hbox) + tip_alignment = gtk.Alignment() + #page.pack_start(tip_alignment, padding=10) + page.pack_start(tip_hbox, expand=False) + page.show_all() + + def prepare_page_4(self): + lines_in_table = {} + width, height = self.assistant.get_size() + def add_info_to_table(table, heading, text): + line = 0 + if table in lines_in_table: + line = lines_in_table[table] + + heading_lbl = gtk.Label() + heading_lbl.set_alignment(0.0, 0.0) + heading_lbl.set_justify(gtk.JUSTIFY_LEFT) + heading_lbl.set_markup("%s:" % heading) + table.attach(heading_lbl, 0, 1, line, line+1, + xoptions=gtk.FILL, yoptions=gtk.EXPAND|gtk.FILL, + xpadding=5, ypadding=5) + lbl = gtk.Label(text) + lbl.set_line_wrap(True) + lbl.set_size_request(width/4, -1) + lbl.set_alignment(0.0, 0.0) + lbl.set_justify(gtk.JUSTIFY_LEFT) + table.attach(lbl, 1, 2, line, line+1, + xoptions=gtk.FILL, yoptions=gtk.EXPAND|gtk.FILL, + xpadding=5, ypadding=5) + + lines_in_table[table] = line+1 + + page = gtk.VBox(spacing=20) + page.set_border_width(10) + self.assistant.insert_page(page, PAGE_CONFIRM) + self.pdict_add_page(page, PAGE_CONFIRM) + self.assistant.set_page_type(page, gtk.ASSISTANT_PAGE_CONFIRM) + self.assistant.set_page_title(page, _("Confirm and send the report")) + self.assistant.set_page_complete(page, True) + summary_lbl = gtk.Label(_("Below is a summary of your bug report. " + "Please click 'Apply' to submit it.")) + summary_lbl.set_alignment(0.0, 0.0) + summary_lbl.set_justify(gtk.JUSTIFY_FILL) + basic_details_lbl = gtk.Label() + basic_details_lbl.set_markup(_("Basic details")) + basic_details_lbl.set_alignment(0.0, 0.0) + basic_details_lbl.set_justify(gtk.JUSTIFY_FILL) + + summary_table_left = gtk.Table(rows=4, columns=2) + summary_table_right = gtk.Table(rows=4, columns=2) + # left table + add_info_to_table(summary_table_left, _("Component"), "%s" % self.report.get_component()) + add_info_to_table(summary_table_left, _("Package"), "%s" % self.report.getPackageName()) + add_info_to_table(summary_table_left, _("Executable"), "%s" % self.report.getExecutable()) + add_info_to_table(summary_table_left, _("Cmdline"), "%s" % self.report.get_cmdline()) + #right table + add_info_to_table(summary_table_right, _("Architecture"), "%s" % self.report.get_arch()) + add_info_to_table(summary_table_right, _("Kernel"), "%s" % self.report.get_kernel()) + add_info_to_table(summary_table_right, _("Release"),"%s" % self.report.get_release()) + add_info_to_table(summary_table_right, _("Reason"), "%s" % self.report.get_reason()) + + summary_hbox = gtk.HBox(spacing=5, homogeneous=True) + left_table_vbox = gtk.VBox() + left_table_vbox.pack_start(summary_table_left, expand=False, fill=False) + left_table_vbox.pack_start(gtk.Alignment()) + summary_hbox.pack_start(left_table_vbox, expand=False, fill=True) + summary_hbox.pack_start(summary_table_right, expand=False, fill=True) + + # backtrace + backtrace_lbl = gtk.Label() + backtrace_lbl.set_markup(_("Backtrace")) + backtrace_lbl.set_alignment(0.0, 0.5) + backtrace_lbl.set_justify(gtk.JUSTIFY_LEFT) + backtrace_show_btn = gtk.Button(_("Click to view...")) + backtrace_show_btn.connect("clicked", self.on_show_bt_clicked) + backtrace_hbox = gtk.HBox(homogeneous=True) + hb = gtk.HBox() + hb.pack_start(backtrace_lbl) + hb.pack_start(backtrace_show_btn, expand=False) + backtrace_hbox.pack_start(hb) + alignment = gtk.Alignment() + backtrace_hbox.pack_start(alignment) + + # steps to reporoduce + reproduce_lbl = gtk.Label() + reproduce_lbl.set_markup(_("Steps to reproduce:")) + reproduce_lbl.set_alignment(0.0, 0.0) + reproduce_lbl.set_justify(gtk.JUSTIFY_LEFT) + self.steps = gtk.Label() + self.steps.set_alignment(0.0, 0.0) + self.steps.set_justify(gtk.JUSTIFY_LEFT) + self.steps.set_line_wrap(True) + self.steps.set_line_wrap_mode(pango.WRAP_CHAR) + self.steps.set_size_request(int(DEFAULT_WIDTH*0.8), -1) + #self.steps_lbl.set_text("1. Fill in information about step 1.\n" + # "2. Fill in information about step 2.\n" + # "3. Fill in information about step 3.\n") + steps_aligned_hbox = gtk.HBox() + self.steps_hbox = gtk.HBox(spacing=10) + self.steps_hbox.pack_start(reproduce_lbl) + self.steps_hbox.pack_start(self.steps) + steps_aligned_hbox.pack_start(self.steps_hbox, expand=False) + steps_aligned_hbox.pack_start(gtk.Alignment()) + + # comments + comments_lbl = gtk.Label() + comments_lbl.set_markup(_("Comments:")) + comments_lbl.set_alignment(0.0, 0.0) + comments_lbl.set_justify(gtk.JUSTIFY_LEFT) + self.comments = gtk.Label(_("No comment provided!")) + self.comments.set_line_wrap(True) + self.comments.set_line_wrap_mode(pango.WRAP_CHAR) + self.comments.set_size_request(int(DEFAULT_WIDTH*0.8), -1) + comments_hbox = gtk.HBox(spacing=10) + comments_hbox.pack_start(comments_lbl) + comments_hbox.pack_start(self.comments) + comments_aligned_hbox = gtk.HBox() + comments_aligned_hbox.pack_start(comments_hbox, expand=False) + comments_aligned_hbox.pack_start(gtk.Alignment()) + + # pack all into the page + + summary_vbox = gtk.VBox(spacing=20) + summary_vbox.pack_start(summary_hbox, expand=False) + summary_vbox.pack_start(backtrace_hbox, expand=False) + summary_vbox.pack_start(steps_aligned_hbox, expand=False) + summary_vbox.pack_start(comments_aligned_hbox, expand=False) + summary_scroll = gtk.ScrolledWindow() + summary_scroll.set_shadow_type(gtk.SHADOW_NONE) + summary_scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS) + scroll_viewport = gtk.Viewport() + scroll_viewport.set_shadow_type(gtk.SHADOW_NONE) + scroll_viewport.add(summary_vbox) + summary_scroll.add(scroll_viewport) + page.pack_start(summary_lbl, expand=False) + page.pack_start(basic_details_lbl, expand=False) + page.pack_start(summary_scroll) + page.show_all() + + def prepare_page_5(self): + page = gtk.VBox(spacing=20) + page.set_border_width(10) + self.assistant.insert_page(page, PAGE_REPORT_DONE) + self.pdict_add_page(page, PAGE_REPORT_DONE) + self.assistant.set_page_type(page, gtk.ASSISTANT_PAGE_SUMMARY) + self.assistant.set_page_title(page, _("Finished sending the bug report")) + bug_reports_lbl = gtk.Label() + bug_reports_lbl.set_alignment(0.0, 0.0) + bug_reports_lbl.set_justify(gtk.JUSTIFY_LEFT) + bug_reports_lbl.set_markup(_("Bug reports:")) + width, height = self.assistant.get_size() + self.bug_reports_vbox = gtk.VBox(spacing=5) + self.bug_reports_vbox.pack_start(bug_reports_lbl, expand=False) + page.pack_start(self.bug_reports_vbox) + page.show_all() + + def __del__(self): + log1("wizard: about to be deleted") + + def on_analyze_complete_cb(self, daemon, result, pBarWindow): + try: + gobject.source_remove(self.timer) + except: + pass + self.pBarWindow.hide() + if not result: + gui_error_message(_("Unable to get report!\nIs debuginfo missing?")) + return + self.result = result + # set the backtrace text + try: + self.backtrace_buff.set_text(self.result[FILENAME_BACKTRACE][CD_CONTENT]) + self.report_has_bt = True + except: + self.backtrace_buff.set_text(MISSING_BACKTRACE_TEXT) + self.backtrace_cb.set_active(True) + log1("Crash info doesn't contain a backtrace, is it disabled?") + + self.allow_send(self.backtrace_cb) + self.show() + + def hydrate(self, button=None, force=0): + if not force: + self.prepare_page_1() + self.prepare_page_2() + self.prepare_page_3() + self.prepare_page_4() + self.prepare_page_5() + self.updates = "" + # FIXME don't duplicate the code, move to function + #self.pBar.show() + self.pBarWindow.show_all() + self.timer = gobject.timeout_add(100, self.progress_update_cb) + + # show the report window with selected report + # when getReport is done it emits "analyze-complete" and on_analyze_complete_cb is called + # FIXME: does it make sense to change it to use callback rather then signal emitting? + try: + self.daemon.start_job("%s:%s" % (self.report.getUID(), self.report.getUUID()), force) + except Exception, ex: + # FIXME #3 dbus.exceptions.DBusException: org.freedesktop.DBus.Error.NoReply: Did not receive a reply + # do this async and wait for yum to end with debuginfoinstal + if self.timer: + gobject.source_remove(self.timer) + self.pBarWindow.hide() + gui_error_message(_("Error acquiring the report: %s" % ex)) + return + + def show(self): + self.assistant.show() + + +if __name__ == "__main__": + wiz = ReporterAssistant() + wiz.show() + gtk.main() diff --git a/src/gui/CellRenderers.py b/src/gui/CellRenderers.py new file mode 100644 index 00000000..fe17b3ed --- /dev/null +++ b/src/gui/CellRenderers.py @@ -0,0 +1,65 @@ +import gtk + +class CellTextView(gtk.TextView, gtk.CellEditable): + + __gtype_name__ = "CellTextView" + + def do_editing_done(self, *args): + self.remove_widget() + + def do_remove_widget(self, *args): + pass + + def do_start_editing(self, *args): + pass + + def get_text(self): + text_buffer = self.get_buffer() + bounds = text_buffer.get_bounds() + return text_buffer.get_text(*bounds) + + def set_text(self, text): + self.get_buffer().set_text(text) + + +class MultilineCellRenderer(gtk.CellRendererText): + + __gtype_name__ = "MultilineCellRenderer" + + def __init__(self): + gtk.CellRendererText.__init__(self) + self._in_editor_menu = False + self.old_text = "" + + def _on_editor_focus_out_event(self, editor, event): + if self._in_editor_menu: return + editor.remove_widget() + self.emit("edited", editor.get_data("path"), editor.get_text()) + + def _on_editor_key_press_event(self, editor, event): + if event.state & (gtk.gdk.SHIFT_MASK | gtk.gdk.CONTROL_MASK): return + if event.keyval == gtk.keysyms.Escape: + editor.set_text(self.old_text) + editor.remove_widget() + self.emit("editing-canceled") + + def _on_editor_populate_popup(self, editor, menu): + self._in_editor_menu = True + def on_menu_unmap(menu, self): + self._in_editor_menu = False + menu.connect("unmap", on_menu_unmap, self) + + def do_start_editing(self, event, widget, path, bg_area, cell_area, flags): + editor = CellTextView() + editor.modify_font(self.props.font_desc) + self.old_text = self.props.text + editor.set_text(self.props.text) + editor.set_size_request(cell_area.width, cell_area.height) + editor.set_border_width(min(self.props.xpad, self.props.ypad)) + editor.set_data("path", path) + editor.connect("focus-out-event", self._on_editor_focus_out_event) + editor.connect("key-press-event", self._on_editor_key_press_event) + editor.connect("populate-popup", self._on_editor_populate_popup) + editor.show() + return editor + diff --git a/src/gui/ConfBackend.py b/src/gui/ConfBackend.py new file mode 100644 index 00000000..d3def183 --- /dev/null +++ b/src/gui/ConfBackend.py @@ -0,0 +1,246 @@ +from abrt_utils import _, log, log1, log2 + +# Doc on Gnome keyring API: +# http://library.gnome.org/devel/gnome-keyring/stable/ +# Python bindings are in gnome-python2-desktop package + +try: + import gnomekeyring as gkey +except ImportError, e: + gkey = None + +# Exceptions +class ConfBackendInitError(Exception): + def __init__(self, msg): + Exception.__init__(self) + self.what = msg + + def __str__(self): + return self.what + +class ConfBackendSaveError(Exception): + def __init__(self, msg): + Exception.__init__(self) + self.what = msg + + def __str__(self): + return self.what + +class ConfBackendLoadError(Exception): + def __init__(self, msg): + Exception.__init__(self) + self.what = msg + + def __str__(self): + return self.what + + +class ConfBackend(object): + def __init__(self): + pass + + def save(self, name, settings): + """ Default save method has to be implemented in derived class """ + raise NotImplementedError + + def load(self, name): + """ Default load method has to be implemented in derived class """ + raise NotImplementedError + + +# We use Gnome keyring in the following way: +# we store passwords for each plugin in a key named "abrt:". +# The value of the key becomes the value of "Password" setting. +# Other settings (if plugin has them) are stored as attributes of this key. +# +# Example: Key "abrt:Bugzilla" with bugzilla password as value, and with attributes: +# +# Application: abrt +# AbrtPluginInfo: Bugzilla +# NoSSLVerify: yes +# Login: user@host.com +# BugzillaURL: https://host.with.bz.com/ +# +# Attributes "Application" and "AbrtPluginInfo" are special, they are used +# for efficient key retrieval via keyring API find_items_sync() function. + +g_default_key_ring = None + +class ConfBackendGnomeKeyring(ConfBackend): + def __init__(self): + global g_default_key_ring + + ConfBackend.__init__(self) + if g_default_key_ring: + return + if not gkey or not gkey.is_available(): + raise ConfBackendInitError(_("Cannot connect to the Gnome Keyring daemon.")) + try: + g_default_key_ring = gkey.get_default_keyring_sync() + except: + # could happen if keyring daemon is running, but we run gui under + # user who is not the owner of the running session - using su + raise ConfBackendInitError(_("Cannot get the default keyring.")) + + def save(self, name, settings): + settings_tmp = settings.copy() + settings_tmp["Application"] = "abrt" + settings_tmp["AbrtPluginInfo"] = name + + # delete all keyring items containg "AbrtPluginInfo":"", + # so we always have only 1 item per plugin + try: + item_list = gkey.find_items_sync(gkey.ITEM_GENERIC_SECRET, { "AbrtPluginInfo": str(name) }) + for item in item_list: + log2("found old keyring item: ring:'%s' item_id:%s attrs:%s", item.keyring, item.item_id, str(item.attributes)) + log2("deleting it from keyring '%s'", g_default_key_ring) + gkey.item_delete_sync(g_default_key_ring, item.item_id) + except gkey.NoMatchError: + # nothing found + pass + except gkey.DeniedError: + raise ConfBackendSaveError(_("Access to gnome-keyring has been denied, plugins settings will not be saved.")) + # if plugin has a "Password" setting, we handle it specially: in keyring, + # it is stored as item.secret, not as one of attributes + password = "" + if "Password" in settings_tmp: + password = settings_tmp["Password"] + del settings_tmp["Password"] + # store new settings for this plugin as one keyring item + try: + gkey.item_create_sync(g_default_key_ring, + gkey.ITEM_GENERIC_SECRET, + "abrt:%s" % name, # display_name + settings_tmp, # attrs + password, # secret + True) + except gkey.DeniedError: + raise ConfBackendSaveError(_("Access to gnome-keyring has been denied, plugins settings will not be saved.")) + + def load(self, name): + item_list = None + #FIXME: make this configurable + # this actually makes GUI to ask twice per every plugin + # which have it's settings stored in keyring + attempts = 2 + while attempts: + try: + log2("looking for keyring items with 'AbrtPluginInfo:%s' attr", str(name)) + item_list = gkey.find_items_sync(gkey.ITEM_GENERIC_SECRET, {"AbrtPluginInfo":str(name)}) + for item in item_list: + # gnome keyring is weeeeird. why display_name, type, mtime, ctime + # aren't available in find_items_sync() results? why we need to + # get them via additional call, item_get_info_sync()? + # internally, item has GNOME_KEYRING_TYPE_FOUND type, + # and info has GNOME_KEYRING_TYPE_ITEM_INFO type. + # why not use the same type for both? + # + # and worst of all, this information took four hours of googling... + # + #info = gkey.item_get_info_sync(item.keyring, item.item_id) + log2("found keyring item: ring:'%s' item_id:%s attrs:%s", # "secret:'%s' display_name:'%s'" + item.keyring, item.item_id, str(item.attributes) #, item.secret, info.get_display_name() + ) + except gkey.NoMatchError: + # nothing found + pass + except gkey.DeniedError: + attempts -= 1 + log2("gk-authorization has failed %i time(s)", 2-attempts) + if attempts == 0: + # we tried 2 times, so giving up the authorization + raise ConfBackendLoadError(_("Access to gnome-keyring has been denied, cannot load the settings for %s!" % name)) + continue + break + + if item_list: + retval = item_list[0].attributes.copy() + retval["Password"] = item_list[0].secret + return retval + return {} + + # This routine loads setting for all plugins. It doesn't need plugin name. + # Thus we can avoid talking to abrtd just in order to get plugin names. + def load_all(self): + retval = {} + item_list = {} + + # UGLY compat cludge for users who has saved items without "Application" attr + # (abrt <= 1.0.3 was saving those) + item_ids = gkey.list_item_ids_sync(g_default_key_ring) + log2("all keyring item ids:%s", item_ids) + for item_id in item_ids: + info = gkey.item_get_info_sync(g_default_key_ring, item_id) + attrs = gkey.item_get_attributes_sync(g_default_key_ring, item_id) + log2("keyring item %s: attrs:%s", item_id, str(attrs)) + if "AbrtPluginInfo" in attrs: + if not "Application" in attrs: + log2("updating old-style keyring item") + attrs["Application"] = "abrt" + try: + gkey.item_set_attributes_sync(g_default_key_ring, item_id, attrs) + except: + log2("error updating old-style keyring item") + plugin_name = attrs["AbrtPluginInfo"] + # If plugin has a "Password" setting, we handle it specially: in keyring, + # it is stored as item.secret, not as one of attributes + if info.get_secret(): + attrs["Password"] = info.get_secret() + # avoiding sending useless duplicate info over dbus... + del attrs["AbrtPluginInfo"] + try: + del attrs["Application"] + except: + pass + retval[plugin_name] = attrs; + # end of UGLY compat cludge + + try: + log2("looking for keyring items with 'Application:abrt' attr") + item_list = gkey.find_items_sync(gkey.ITEM_GENERIC_SECRET, { "Application": "abrt" }) + except gkey.NoMatchError: + # nothing found + pass + except gkey.DeniedError: + raise ConfBackendLoadError(_("Access to gnome-keyring has been denied, cannot load settings.")) + + for item in item_list: + # gnome keyring is weeeeird. why display_name, type, mtime, ctime + # aren't available in find_items_sync() results? why we need to + # get them via additional call, item_get_info_sync()? + # internally, item has GNOME_KEYRING_TYPE_FOUND type, + # and info has GNOME_KEYRING_TYPE_ITEM_INFO type. + # why not use the same type for both? + # + # and worst of all, this information took four hours of googling... + # + #info = gkey.item_get_info_sync(item.keyring, item.item_id) + log2("found keyring item: ring:%s item_id:%s attrs:%s", # "secret:%s display_name:'%s'" + item.keyring, item.item_id, str(item.attributes) #, item.secret, info.get_display_name() + ) + attrs = item.attributes.copy() + if "AbrtPluginInfo" in attrs: + plugin_name = attrs["AbrtPluginInfo"] + # If plugin has a "Password" setting, we handle it specially: in keyring, + # it is stored as item.secret, not as one of attributes + if item.secret: + attrs["Password"] = item.secret + # avoiding sending useless duplicate info over dbus... + del attrs["AbrtPluginInfo"] + try: + del attrs["Application"] + except: + pass + retval[plugin_name] = attrs + return retval + + +# Rudimentary backend factory + +currentConfBackend = None + +def getCurrentConfBackend(): + global currentConfBackend + if not currentConfBackend: + currentConfBackend = ConfBackendGnomeKeyring() + return currentConfBackend diff --git a/src/gui/Makefile.am b/src/gui/Makefile.am new file mode 100644 index 00000000..002b3bc7 --- /dev/null +++ b/src/gui/Makefile.am @@ -0,0 +1,33 @@ +bin_SCRIPTS = abrt-gui + +PYTHON_FILES = CCDBusBackend.py CCDumpList.py CCDump.py CC_gui_functions.py \ + CCReporterDialog.py abrt_utils.py \ + CCMainWindow.py CellRenderers.py ABRTExceptions.py \ + SettingsDialog.py ABRTPlugin.py PluginList.py PluginSettingsUI.py \ + PluginsSettingsDialog.py ConfBackend.py CReporterAssistant.py + +GLADE_FILES = ccgui.glade report.glade settings.glade dialogs.glade \ + settings_wizard.glade progress_window.glade + +pkgdata_PYTHON = $(PYTHON_FILES) +pkgdata_DATA = $(GLADE_FILES) + +@INTLTOOL_DESKTOP_RULE@ + +desktopdir = $(datadir)/applications +desktop_in_files = abrt.desktop.in + +desktop_DATA = $(desktop_in_files:.desktop.in=.desktop) + +EXTRA_DIST = $(PYTHON_FILES) $(GLADE_FILES) abrt-gui $(desktop_in_files) + +CLEANFILES := $(notdir $(wildcard *~)) $(notdir $(wildcard *\#)) $(notdir $(wildcard \.\#*)) $(notdir $(wildcard *.pyc)) + +install-exec-hook: + for b in $(bin_SCRIPTS); do \ + sed 's:/usr/share:$(datadir):g' -i $(DESTDIR)$(bindir)/$$b || exit $$?; \ + sed 's:VERSION:@VERSION@:g' -i $(DESTDIR)$(bindir)/$$b || exit $$?; \ + done + +install-data-hook: + sed 's:@VER@:$(VERSION):g' -i $(DESTDIR)$(pkgdatadir)/ccgui.glade || exit $$? diff --git a/src/gui/PluginList.py b/src/gui/PluginList.py new file mode 100644 index 00000000..f93e64e4 --- /dev/null +++ b/src/gui/PluginList.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +import CCDBusBackend +from ABRTPlugin import PluginInfo, PluginSettings +from abrt_utils import _, log, log1, log2 + +class PluginInfoList(list): + """Class to store list of PluginInfos""" + def __init__(self,dbus_manager=None): + list.__init__(self) + self.dm = dbus_manager + + def load(self): + if self.dm: + rows = self.dm.getPluginsInfo() + for plugin_name in rows: + row = rows[plugin_name] + entry = PluginInfo() + for attr_name in row: + log2("PluginInfoList: adding %s[%s]:%s", plugin_name, attr_name, row[attr_name]) + entry.__dict__[attr_name] = row[attr_name] + daemon_settings = self.dm.getPluginSettings(plugin_name) + entry.load_daemon_settings(daemon_settings) + self.append(entry) + else: + log("PluginInfoList: db == None") + + def getReporterByName(self, name): + try: + return [x for x in self if x["Name"] == name and x.Type == "Reporter" ][0] + except: + # if such reporter doesnt't exist return None + return None + + def getEnabledPlugins(self): + return [x for x in self if x["Enabled"] == 'yes'] + + def getActionPlugins(self): + return [x for x in self if x["Enabled"] == 'yes' and x["Type"] == 'Action'] + + def getDatabasePlugins(self): + return [x for x in self if x["Enabled"] == 'yes' and x["Type"] == 'Database'] + + def getAnalyzerPlugins(self): + return [x for x in self if x["Enabled"] == 'yes' and x["Type"] == 'Analyzer'] + + def getReporterPlugins(self): + return [x for x in self if x["Enabled"] == 'yes' and x["Type"] == 'Reporter'] + + def getReporterPluginsSettings(self): + reporters_settings = {} + for plugin in self.getReporterPlugins(): + reporters_settings[str(plugin)] = plugin.Settings + return reporters_settings + + +__PFList = None +__PFList_dbmanager = None + +def getPluginInfoList(dbmanager,refresh=None): + global __PFList + global __PFList_dbmanager + + if __PFList == None or refresh or __PFList_dbmanager != dbmanager: + __PFList = PluginInfoList(dbus_manager=dbmanager) + __PFList.load() + __PFList_dbmanager = dbmanager + return __PFList + +__PFList = None diff --git a/src/gui/PluginSettingsUI.py b/src/gui/PluginSettingsUI.py new file mode 100644 index 00000000..db4c92de --- /dev/null +++ b/src/gui/PluginSettingsUI.py @@ -0,0 +1,91 @@ +import gtk +from abrt_utils import _, log, log1, log2 + +class PluginSettingsUI(): + def __init__(self, pluginfo, parent=None): + #print "Init PluginSettingsUI" + self.plugin_name = pluginfo.Name + self.Settings = pluginfo.Settings + self.pluginfo = pluginfo + self.plugin_gui = None + + if pluginfo.getGUI(): + self.plugin_gui = gtk.Builder() + self.plugin_gui.add_from_file(pluginfo.getGUI()) + self.dialog = self.plugin_gui.get_object("PluginDialog") + if not self.dialog: + raise Exception(_("Cannot find PluginDialog widget in the UI description!")) + self.dialog.set_title("%s" % pluginfo.getName()) + if parent: + self.dialog.set_transient_for(parent) + else: + # we shouldn't get here, but just to be safe + log1("No UI for plugin %s" % pluginfo) + raise Exception(_("No UI for the plugin %s, this is probably a bug.\n" + "Please report it at " + "" + "https://fedorahosted.org/abrt/newticket") % pluginfo) + return + + if parent: + self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.dialog.set_transient_for(parent) + self.dialog.set_modal(True) + + def on_show_pass_toggled(self, button, entry=None): + if entry: + entry.set_visibility(button.get_active()) + + def hydrate(self): + if self.plugin_gui: + if self.pluginfo.Enabled == "yes": + if self.Settings: + #print "Hydrating %s" % self.plugin_name + for key, value in self.Settings.iteritems(): + #print "%s:%s" % (key,value) + widget = self.plugin_gui.get_object("conf_%s" % key) + if type(widget) == gtk.Entry: + widget.set_text(value) + if widget.get_visibility() == False: + # if we find toggle button called the same name as entry and entry has + # visibility set to False, connect set_visible to it + # coz I guess it's toggle for revealing the password + button = self.plugin_gui.get_object("cb_%s" % key) + if type(button) == gtk.CheckButton: + button.connect("toggled", self.on_show_pass_toggled, widget) + elif type(widget) == gtk.CheckButton: + widget.set_active(value == "yes") + elif type(widget) == gtk.ComboBox: + print _("Combo box is not implemented") + else: + #print "Plugin %s has no configuration." % self.plugin_name + pass + else: + #print "Plugin %s is disabled." % self.plugin_name + pass + + else: + print _("Nothing to hydrate!") + + def dehydrate(self): + #print "dehydrating %s" % self.pluginfo.getName() + if self.Settings: + for key in self.Settings.keys(): + #print key + #print "%s:%s" % (key,value) + widget = self.plugin_gui.get_object("conf_%s" % key) + if type(widget) == gtk.Entry: + self.Settings[key] = widget.get_text() + elif type(widget) == gtk.CheckButton: + if widget.get_active(): + self.Settings[key] = "yes" + else: + self.Settings[key] = "no" + elif type(widget) == gtk.ComboBox: + print _("Combo box is not implemented") + + def destroy(self): + self.dialog.destroy() + + def run(self): + return self.dialog.run() diff --git a/src/gui/PluginsSettingsDialog.py b/src/gui/PluginsSettingsDialog.py new file mode 100644 index 00000000..2951320e --- /dev/null +++ b/src/gui/PluginsSettingsDialog.py @@ -0,0 +1,195 @@ +import sys +import gtk +from PluginList import getPluginInfoList, PluginInfoList +from CC_gui_functions import * +from PluginSettingsUI import PluginSettingsUI +from ABRTPlugin import PluginSettings, PluginInfo +from abrt_utils import _, log, log1, log2 + + +class PluginsSettingsDialog: + def __init__(self, parent, daemon): + #print "Settings dialog init" + self.ccdaemon = daemon + + self.builder = gtk.Builder() + builderfile = "%s%ssettings.glade" % (sys.path[0], "/") + #print builderfile + try: + self.builder.add_from_file(builderfile) + except Exception, e: + print e + self.window = self.builder.get_object("wPluginsSettings") + if not self.window: + raise Exception(_("Cannot load the GUI description for SettingsDialog!")) + + if parent: + self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.window.set_transient_for(parent) + self.window.set_modal(True) + + self.pluginlist = self.builder.get_object("tvSettings") # a TreeView + # cell_text, toggle_active, toggle_visible, group_name_visible, color, plugin + self.pluginsListStore = gtk.TreeStore(str, bool, bool, bool, str, object) + # set filter + modelfilter = self.pluginsListStore.filter_new() + modelfilter.set_visible_func(self.filter_plugins, None) + self.pluginlist.set_model(modelfilter) + + # Create/configure columns and add them to pluginlist + # column "name" has two kind of cells: + column = gtk.TreeViewColumn(_("Name")) + # cells for individual plugins (white) + cell_name = gtk.CellRendererText() + column.pack_start(cell_name, True) + column.set_attributes(cell_name, markup=0, visible=2) # show 0th field (plugin name) from data items if 2th field is true + # cells for plugin types (gray) + cell_plugin_type = gtk.CellRendererText() + column.pack_start(cell_plugin_type, True) + column.add_attribute(cell_plugin_type, "visible", 3) + column.add_attribute(cell_plugin_type, "markup", 0) + column.add_attribute(cell_plugin_type, "cell_background", 4) + # column "name" is ready, insert + column.set_resizable(True) + self.pluginlist.append_column(column) + +# "Enable" toggle column is disabled for now. Grep for PLUGIN_DYNAMIC_LOAD_UNLOAD +# column = gtk.TreeViewColumn(_("Enabled")) +# # column "enabled" has one kind of cells: +# cell_toggle_enable = gtk.CellRendererToggle() +# cell_toggle_enable.set_property("activatable", True) +# cell_toggle_enable.connect("toggled", self.on_enabled_toggled, self.pluginsListStore) +# column.pack_start(cell_toggle_enable, True) +# column.add_attribute(cell_toggle_enable, "active", 1) +# column.add_attribute(cell_toggle_enable, "visible", 2) +# self.pluginlist.append_column(column) + + #connect signals + self.pluginlist.connect("cursor-changed", self.on_tvDumps_cursor_changed) + self.builder.get_object("bConfigurePlugin").connect("clicked", self.on_bConfigurePlugin_clicked, self.pluginlist) + self.builder.get_object("bClose").connect("clicked", self.on_bClose_clicked) + self.builder.get_object("bConfigurePlugin").set_sensitive(False) + +# "Enable" toggle column is disabled for now. Grep for PLUGIN_DYNAMIC_LOAD_UNLOAD +# def on_enabled_toggled(self,cell, path, model): +# plugin = model[path][model.get_n_columns()-1] +# if plugin: +# if model[path][1]: +# #print "self.ccdaemon.UnRegisterPlugin(%s)" % (plugin.getName()) +# self.ccdaemon.unRegisterPlugin(plugin.getName()) +# # FIXME: create class plugin and move this into method Plugin.Enable() +# plugin.Enabled = "no" +# plugin.Settings = None +# else: +# #print "self.ccdaemon.RegisterPlugin(%s)" % (model[path][model.get_n_columns()-1]) +# self.ccdaemon.registerPlugin(plugin.getName()) +# # FIXME: create class plugin and move this into method Plugin.Enable() +# plugin.Enabled = "yes" +# default_settings = self.ccdaemon.getPluginSettings(plugin.getName()) +# plugin.Settings = PluginSettings() +# plugin.Settings.load(plugin.getName(), default_settings) +# model[path][1] = not model[path][1] + + def filter_plugins(self, model, miter, data): + return True + + def hydrate(self): + #print "settings hydrate" + self.pluginsListStore.clear() + try: + #pluginlist = getPluginInfoList(self.ccdaemon, refresh=True) + # don't force refresh as it will overwrite settings if g-k is not available + pluginlist = getPluginInfoList(self.ccdaemon) + except Exception, e: + log("Error while loading plugins info: %s", e) + #gui_error_message("Error while loading plugins info, please check if abrt daemon is running\n %s" % e) + return + plugin_rows = {} + group_empty = {} + for plugin_type in PluginInfo.types.keys(): + it = self.pluginsListStore.append(None, + # cell_text, toggle_active, toggle_visible, group_name_visible, color, plugin + ["%s" % PluginInfo.types[plugin_type], 0, 0, 1, "gray", None]) + plugin_rows[plugin_type] = it + group_empty[plugin_type] = it + for entry in pluginlist: + if entry.Description: + text = "%s\n%s" % (entry.getName(), entry.Description) + else: + # non-loaded plugins have empty description + text = "%s" % entry.getName() + plugin_type = entry.getType() + self.pluginsListStore.append(plugin_rows[plugin_type], + # cell_text, toggle_active, toggle_visible, group_name_visible, color, plugin + [text, entry.Enabled == "yes", 1, 0, "white", entry]) + if group_empty.has_key(plugin_type): + del group_empty[plugin_type] + # rhbz#560971 "Don't show empty 'Not loaded plugins' section" + # don't show any empty groups + for it in group_empty.values(): + self.pluginsListStore.remove(it) + + self.pluginlist.expand_all() + + def dehydrate(self): + # we have nothing to save, plugin's does the work + pass + + def show(self): + self.window.show() + #if result == gtk.RESPONSE_APPLY: + # self.dehydrate() + #self.window.destroy() + #return result + + def on_bConfigurePlugin_clicked(self, button, pluginview): + pluginsListStore, path = pluginview.get_selection().get_selected_rows() + if not path: + gui_info_dialog(_("Please select a plugin from the list to edit it's options."), parent=self.window) + return + # this should work until we keep the row object in the last position + pluginfo = pluginsListStore.get_value(pluginsListStore.get_iter(path[0]), pluginsListStore.get_n_columns()-1) + if pluginfo: + try: + ui = PluginSettingsUI(pluginfo, parent=self.window) + except Exception, e: + gui_error_message(_("Error while opening the plugin settings UI: \n\n%s" % e)) + return + ui.hydrate() + response = ui.run() + if response == gtk.RESPONSE_APPLY: + ui.dehydrate() + if pluginfo.Settings: + try: + pluginfo.save_settings_on_client_side() + # FIXME: do we need to call this? all reporters set their settings + # when Report() is called + self.ccdaemon.setPluginSettings(pluginfo.getName(), pluginfo.Settings) + except Exception, e: + gui_error_message(_("Cannot save plugin settings:\n %s" % e)) + #for key, val in pluginfo.Settings.iteritems(): + # print "%s:%s" % (key, val) + elif response == gtk.RESPONSE_CANCEL: + pass + else: + log("unknown response from settings dialog:%d", response) + ui.destroy() + + def on_bClose_clicked(self, button): + self.window.destroy() + + def on_tvDumps_cursor_changed(self, treeview): + pluginsListStore, path = treeview.get_selection().get_selected_rows() + if not path: + self.builder.get_object("lDescription").set_label("No description") + return + # this should work until we keep the row object in the last position + pluginfo = pluginsListStore.get_value(pluginsListStore.get_iter(path[0]), pluginsListStore.get_n_columns()-1) + if pluginfo: + self.builder.get_object("lPluginAuthor").set_text(pluginfo.Email) + self.builder.get_object("lPluginVersion").set_text(pluginfo.Version) + self.builder.get_object("lPluginWebSite").set_text(pluginfo.WWW) + self.builder.get_object("lPluginName").set_text(pluginfo.Name) + self.builder.get_object("lPluginDescription").set_text(pluginfo.Description) + # print (pluginfo.Enabled == "yes" and pluginfo.GTKBuilder != "") + self.builder.get_object("bConfigurePlugin").set_sensitive(pluginfo != None and pluginfo.Enabled == "yes" and pluginfo.GTKBuilder != "") diff --git a/src/gui/SettingsDialog.py b/src/gui/SettingsDialog.py new file mode 100644 index 00000000..a69a68ec --- /dev/null +++ b/src/gui/SettingsDialog.py @@ -0,0 +1,242 @@ +import sys +import gtk +from PluginList import getPluginInfoList +from CC_gui_functions import * +#from PluginSettingsUI import PluginSettingsUI +from abrt_utils import _, log, log1, log2 + + +#FIXME: create a better struct, to automatize hydrate/dehydrate process +settings_dict = { "Common": + {"OpenGPGCheck":bool, + "Database":object, + "EnabledPlugins": list, + "BlackList": list, + "MaxCrashReportsSize": int, + "OpenGPGPublicKeys": list, + }, + } + +class SettingsDialog: + def __init__(self, parent, daemon): + builderfile = "%s%ssettings.glade" % (sys.path[0],"/") + self.ccdaemon = daemon + self.builder = gtk.Builder() + self.builder.add_from_file(builderfile) + self.window = self.builder.get_object("wGlobalSettings") + self.builder.get_object("bSaveSettings").connect("clicked", self.on_ok_clicked) + self.builder.get_object("bCancelSettings").connect("clicked", self.on_cancel_clicked) + self.builder.get_object("bAddCronJob").connect("clicked", self.on_bAddCronJob_clicked) + + # action plugin list for Cron tab + self.actionPluginsListStore = gtk.ListStore(str, object) + self.actionPluginsListStore.append([_("Select plugin"), None]) + # database plugin list + self.databasePluginsListStore = gtk.ListStore(str, object) + self.databasePluginsListStore.append([_("Select database backend"), None]) + + self.dbcombo = self.builder.get_object("cbDatabase") + self.dbcombo.set_model(self.databasePluginsListStore) + cell = gtk.CellRendererText() + self.dbcombo.pack_start(cell) + self.dbcombo.add_attribute(cell, "markup", 0) + # blacklist edit + self.builder.get_object("bEditBlackList").connect("clicked", self.on_blacklistEdit_clicked) + + self.builder.get_object("bOpenGPGPublicKeys").connect("clicked", self.on_GPGKeysEdit_clicked) + self.builder.get_object("bAddAction").connect("clicked", self.on_bAddAction_clicked) + # AnalyzerActionsAndReporters + self.analyzerPluginsListStore = gtk.ListStore(str, object) + self.analyzerPluginsListStore.append([_("Select plugin"), None]) + # GPG keys + self.wGPGKeys = self.builder.get_object("wGPGKeys") + self.GPGKeysListStore = gtk.ListStore(str) + self.tvGPGKeys = self.builder.get_object("tvGPGKeys") + self.tvGPGKeys.set_model(self.GPGKeysListStore) + self.builder.get_object("bCancelGPGKeys").connect("clicked", self.on_bCancelGPGKeys_clicked) + self.builder.get_object("bSaveGPGKeys").connect("clicked", self.on_bSaveGPGKeys_clicked) + + gpg_column = gtk.TreeViewColumn() + cell = gtk.CellRendererText() + gpg_column.pack_start(cell) + gpg_column.add_attribute(cell, "text", 0) + self.tvGPGKeys.append_column(gpg_column) + + def filter_settings(self, model, miter, data): + return True + + def hydrate(self): + try: + self.settings = self.ccdaemon.getSettings() + except Exception, e: + # FIXME: this should be error gui message! + print e + try: + self.pluginlist = getPluginInfoList(self.ccdaemon, refresh=True) + except Exception, e: + raise Exception("Comunication with daemon has failed, have you restarted the daemon after update?") + + ## hydrate cron jobs: + for key,val in self.settings["Cron"].iteritems(): + # actions are separated by ',' + actions = val.split(',') + self.settings["Cron"][key] = actions + for plugin in self.pluginlist.getActionPlugins(): + it = self.actionPluginsListStore.append([plugin.getName(), plugin]) + for key,val in self.settings["Cron"].iteritems(): + if plugin.getName() in val: + cron_job = (key,it) + self.add_CronJob(cron_job) + self.settings["Cron"][key].remove(plugin.getName()) + # hydrate common + common = self.settings["Common"] + # ensure that all expected keys exist: + if "OpenGPGCheck" not in common: + common["OpenGPGCheck"] = "no" # check unsigned pkgs too + ## gpgcheck + self.builder.get_object("cbOpenGPGCheck").set_active(common["OpenGPGCheck"] == 'yes') + ## database + for dbplugin in self.pluginlist.getDatabasePlugins(): + it = self.databasePluginsListStore.append([dbplugin.getName(), dbplugin]) + if common["Database"] == dbplugin.getName(): + self.dbcombo.set_active_iter(it) + ## MaxCrashSize + self.builder.get_object("sbMaxCrashReportsSize").set_value(float(common["MaxCrashReportsSize"])) + ## GPG keys + try: + self.builder.get_object("eOpenGPGPublicKeys").set_text(common["OpenGPGPublicKeys"]) + self.gpgkeys = common["OpenGPGPublicKeys"].split(',') + for gpgkey in self.gpgkeys: + self.GPGKeysListStore.append([gpgkey]) + except: + pass + + ## blacklist + self.builder.get_object("eBlacklist").set_text(common["BlackList"]) + # hydrate AnalyzerActionsAndReporters + AnalyzerActionsAndReporters = self.settings["AnalyzerActionsAndReporters"] + for analplugin in self.pluginlist.getAnalyzerPlugins(): + it = self.analyzerPluginsListStore.append([analplugin.getName(), analplugin]) + if analplugin.getName() in AnalyzerActionsAndReporters: + action = (AnalyzerActionsAndReporters[analplugin.getName()], it) + self.add_AnalyzerAction(action) + + def on_bCancelGPGKeys_clicked(self, button): + self.wGPGKeys.hide() + + def on_bSaveGPGKeys_clicked(self, button): + self.wGPGKeys.hide() + + def on_bAddGPGKey_clicked(self, button): + print "add GPG key" + + def on_bRemoveGPGKey_clicked(self, button): + print "add GPG key" + + def on_blacklistEdit_clicked(self, button): + print "edit blacklist" + + def on_GPGKeysEdit_clicked(self, button): + self.wGPGKeys.show() + + def on_ok_clicked(self, button): + self.dehydrate() + self.window.hide() + + def on_cancel_clicked(self, button): + self.window.hide() + + def on_remove_CronJob_clicked(self, button, job_hbox): + self.removeHBoxWihtChildren(job_hbox) + + def on_remove_Action_clicked(self, button, binding_hbox): + self.removeHBoxWihtChildren(binding_hbox) + + def removeHBoxWihtChildren(self, job_hbox): + job_hbox.get_parent().remove(job_hbox) + for child in job_hbox.get_children(): + child.destroy() + job_hbox.destroy() + + def add_CronJob(self, job=None): + hbox = gtk.HBox() + hbox.set_spacing(6) + time = gtk.Entry() + remove_image = gtk.Image() + remove_image.set_from_stock("gtk-remove",gtk.ICON_SIZE_MENU) + remove_button = gtk.Button() + remove_button.set_image(remove_image) + remove_button.set_tooltip_text(_("Remove this job")) + remove_button.connect("clicked", self.on_remove_CronJob_clicked, hbox) + plugins = gtk.ComboBox() + cell = gtk.CellRendererText() + + plugins.pack_start(cell) + plugins.add_attribute(cell, 'markup', 0) + plugins.set_model(self.actionPluginsListStore) + + if job: + time.set_text(job[0]) + plugins.set_active_iter(job[1]) + else: + plugins.set_active(0) + hbox.pack_start(plugins,True) + hbox.pack_start(time,True) + hbox.pack_start(remove_button,False) + self.builder.get_object("vbCronJobs").pack_start(hbox,False) + + hbox.show_all() + + def on_bAddCronJob_clicked(self, button): + self.add_CronJob() + print "add" + + def on_bEditAction_clicked(self, button, data=None): + print "edit action" + + def add_AnalyzerAction(self, action=None): + #print "add_AnalyzerAction" + hbox = gtk.HBox() + hbox.set_spacing(6) + action_list = gtk.Entry() + edit_actions = gtk.Button() + edit_actions.set_tooltip_text("Edit actions") + edit_image = gtk.Image() + edit_image.set_from_stock("gtk-edit", gtk.ICON_SIZE_MENU) + edit_actions.set_image(edit_image) + edit_actions.connect("clicked", self.on_bEditAction_clicked) + + remove_image = gtk.Image() + remove_image.set_from_stock("gtk-remove",gtk.ICON_SIZE_MENU) + remove_button = gtk.Button() + remove_button.set_image(remove_image) + remove_button.set_tooltip_text(_("Remove this action")) + remove_button.connect("clicked", self.on_remove_Action_clicked, hbox) + + reporters = gtk.ComboBox() + cell = gtk.CellRendererText() + reporters.pack_start(cell) + reporters.add_attribute(cell, 'markup', 0) + reporters.set_model(self.analyzerPluginsListStore) + + if action: + action_list.set_text(action[0]) + reporters.set_active_iter(action[1]) + else: + reporters.set_active(0) + + hbox.pack_start(reporters,True) + hbox.pack_start(action_list,True) + hbox.pack_start(edit_actions,False) + hbox.pack_start(remove_button,False) + self.builder.get_object("vbActions").pack_start(hbox,False) + hbox.show_all() + + def on_bAddAction_clicked(self, button): + self.add_AnalyzerAction() + + def dehydrate(self): + self.ccdaemon.setSettings(self.settings) + + def show(self): + self.window.show() diff --git a/src/gui/abrt-gui b/src/gui/abrt-gui new file mode 100755 index 00000000..059d8676 --- /dev/null +++ b/src/gui/abrt-gui @@ -0,0 +1,8 @@ +#!/bin/sh +if test x"$1" = x"--version"; then + echo "abrt-gui VERSION" + exit 0 +fi +export PYTHONPATH=/usr/share/abrt +export XLOGNAME=$LOGNAME +exec /usr/bin/python /usr/share/abrt/CCMainWindow.py "$@" diff --git a/src/gui/abrt.desktop.in b/src/gui/abrt.desktop.in new file mode 100644 index 00000000..99d7f8f8 --- /dev/null +++ b/src/gui/abrt.desktop.in @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +_Name=Automatic Bug Reporting Tool +_Comment=View and report application crashes +Exec=abrt-gui +Icon=abrt +Terminal=false +Type=Application +Categories=System;X-Red-Hat-Base; +StartupNotify=true +X-Desktop-File-Install-Version=0.15 diff --git a/src/gui/abrt.png b/src/gui/abrt.png new file mode 100644 index 00000000..dc24865e Binary files /dev/null and b/src/gui/abrt.png differ diff --git a/src/gui/abrt_utils.py b/src/gui/abrt_utils.py new file mode 100644 index 00000000..f9c60b52 --- /dev/null +++ b/src/gui/abrt_utils.py @@ -0,0 +1,46 @@ +import sys + +GETTEXT_PROGNAME = "abrt" +PROGNAME = "abrt-gui" +g_verbose = 0 + +import locale +import gettext + +_ = lambda x: gettext.lgettext(x) + +def init_logging(progname, v): + import gtk.glade + global PROGNAME, g_verbose + PROGNAME = progname + g_verbose = v + try: + locale.setlocale(locale.LC_ALL, "") + except locale.Error: + import os + os.environ['LC_ALL'] = 'C' + locale.setlocale(locale.LC_ALL, "") + gettext.bind_textdomain_codeset(GETTEXT_PROGNAME, locale.nl_langinfo(locale.CODESET)) + gettext.bindtextdomain(GETTEXT_PROGNAME, '/usr/share/locale') + gtk.glade.bindtextdomain(GETTEXT_PROGNAME, '/usr/share/locale') + gtk.glade.textdomain(GETTEXT_PROGNAME) + gettext.textdomain(GETTEXT_PROGNAME) + +def get_verbose_level(): + # Just importing g_verbose from another module doesn't work (why!?), + # need to use a function + return g_verbose + +def log(fmt, *args): + sys.stderr.write("%s: %s\n" % (PROGNAME, fmt % args)) + +def log1(fmt, *args): + if g_verbose >= 1: + sys.stderr.write("%s: %s\n" % (PROGNAME, fmt % args)) + +def log2(fmt, *args): + if g_verbose >= 2: + sys.stderr.write("%s: %s\n" % (PROGNAME, fmt % args)) + +def warn(fmt, *args): + sys.stderr.write("WARNING: %s: %s\n" % (PROGNAME, fmt % args)) diff --git a/src/gui/ccgui.glade b/src/gui/ccgui.glade new file mode 100644 index 00000000..7d46c298 --- /dev/null +++ b/src/gui/ccgui.glade @@ -0,0 +1,697 @@ + + + + + + 5 + About ABRT + False + center-on-parent + abrt + dialog + False + ABRT + @VER@ + (C) 2009, 2010 Red Hat, Inc. + http://fedorahosted.org/abrt/ + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. + Anton Arapov <aarapov@redhat.com> +Karel Klic <kklic@redhat.com> +Jiri Moskovcak <jmoskovc@redhat.com> +Nikola Pajkovsky <npajkovs@redhat.com> +Zdenek Prikryl <zprikryl@redhat.com> +Denys Vlasenko <dvlasenk@redhat.com> + translator-credits + Patrick Connelly <pcon@fedoraproject.org> +Lapo Calamandrei + +UI Design: +Máirín Duffy <duffy@redhat.com> + abrt + True + + + True + 2 + + + + + + True + end + + + False + end + 0 + + + + + + + Automatic Bug Reporting Tool + center + abrt + + + True + vertical + + + True + + + True + _File + True + + + True + + + gtk-quit + True + True + True + True + + + + + + + + + True + _Edit + True + + + True + + + True + Plugins + + + + + gtk-preferences + True + True + True + True + + + + + + + + + True + _Help + True + + + True + + + View log + True + + + + + gtk-about + True + True + True + True + + + + + + + + + False + 0 + + + + + True + True + 12 + vertical + + + True + True + automatic + automatic + in + + + True + True + True + True + 1 + + + + + False + False + + + + + False + never + automatic + in + + + True + queue + none + + + True + vertical + + + True + 10 + + + True + 5 + gtk-missing-image + 6 + + + False + False + 0 + + + + + True + 0 + True + + + 1 + + + + + False + False + 10 + 0 + + + + + True + 5 + + + True + 0 + 0 + 5 + <b>Bug Reports:</b> + True + + + False + 0 + + + + + True + 0 + 0 + True + + + 1 + + + + + False + False + 1 + + + + + True + + + True + 5 + 2 + 5 + 5 + + + True + 0 + 0 + <b>Latest Crash:</b> + True + + + GTK_FILL + + + + + + True + 0 + 0 + <b>Command:</b> + True + + + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + 0 + 0 + <b>User:</b> + True + + + 2 + 3 + GTK_FILL + + + + + + True + 0 + 0 + <b>Crash Count:</b> + True + + + 3 + 4 + GTK_FILL + + + + + + True + 0 + 0 + True + 30 + + + 1 + 2 + GTK_FILL + + + + + + True + 0 + 0 + True + 30 + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + + True + 1.862645149230957e-09 + 0 + + + 1 + 2 + 2 + 3 + GTK_FILL + GTK_FILL + + + + + True + 0 + 0 + + + 1 + 2 + 3 + 4 + GTK_FILL + GTK_FILL + + + + + True + + + + + + 4 + 5 + + + + + True + + + + + + 1 + 2 + 4 + 5 + + + + + False + False + 5 + 0 + + + + + True + vertical + 5 + + + True + 0 + 0 + <b>Reason:</b> + True + + + False + False + 0 + + + + + True + 0 + 0 + True + 40 + + + 1 + + + + + True + 0 + 0 + <b>Comment:</b> + True + + + False + False + 2 + + + + + True + 0 + 0 + True + 40 + + + 3 + + + + + 1 + + + + + False + False + 2 + + + + + + + + + False + False + + + + + 1 + + + + + True + 10 + 5 + True + + + True + + + + + + 0 + + + + + gtk-delete + True + False + True + True + True + + + 1 + + + + + Copy to Clipboard + True + False + True + True + + + 2 + + + + + Report + True + False + True + True + + + 3 + + + + + False + False + 2 + + + + + True + + + + + + False + 10 + 3 + + + + + True + True + + + gtk-help + True + True + True + True + + + 10 + 0 + + + + + True + + + + + + 1 + + + + + True + + + + + + 2 + + + + + True + + + + + + 3 + + + + + gtk-close + True + True + True + True + + + 10 + 4 + + + + + False + 4 + + + + + True + + + + + + False + 10 + 5 + + + + + + + True + + + gtk-delete + True + True + True + True + + + + + Report + True + False + True + + + True + gtk-go-up + 1 + + + + + + diff --git a/src/gui/dialogs.glade b/src/gui/dialogs.glade new file mode 100644 index 00000000..1f251de5 --- /dev/null +++ b/src/gui/dialogs.glade @@ -0,0 +1,139 @@ + + + + + + 6 + Report done + True + center-on-parent + True + abrt + normal + False + + + True + vertical + 12 + + + True + 6 + 12 + + + True + 0 + 0 + gtk-dialog-info + 6 + + + False + False + 0 + + + + + + + + 1 + + + + + True + end + + + gtk-ok + True + True + True + True + + + False + False + 0 + + + + + False + end + 0 + + + + + + button1 + + + + 5 + Log + True + 450 + 260 + normal + False + + + True + vertical + 2 + + + True + True + automatic + automatic + + + True + True + False + + + + + 1 + + + + + True + end + + + gtk-close + True + True + True + True + + + False + False + 0 + + + + + False + end + 0 + + + + + + bClose + + + diff --git a/src/gui/progress_window.glade b/src/gui/progress_window.glade new file mode 100644 index 00000000..af48ee55 --- /dev/null +++ b/src/gui/progress_window.glade @@ -0,0 +1,70 @@ + + + + + + 500 + 12 + Please wait... + True + center-on-parent + abrt + + + True + vertical + 12 + + + True + 0 + + + False + 0 + + + + + True + 0 + + + False + 1 + + + + + True + True + + + True + True + automatic + automatic + etched-in + + + True + True + + + + + + + True + Details + + + + + 2 + + + + + + diff --git a/src/gui/report.glade b/src/gui/report.glade new file mode 100644 index 00000000..2f79b39f --- /dev/null +++ b/src/gui/report.glade @@ -0,0 +1,827 @@ + + + + + + 5 + Automatic Bug Reporting Tool + True + abrt + normal + False + + + True + vertical + 2 + + + True + True + never + automatic + + + True + queue + none + + + True + vertical + 5 + + + True + 0 + in + + + True + 12 + + + True + + + True + vertical + True + + + True + 0 + <span fgcolor="blue">Package:</span> + True + + + 0 + + + + + True + 0 + <span fgcolor="blue">Component:</span> + True + + + 1 + + + + + True + 0 + <span fgcolor="blue">Executable:</span> + True + + + 2 + + + + + True + 0 + <span fgcolor="blue">Cmdline:</span> + True + + + 3 + + + + + False + False + 0 + + + + + True + vertical + True + + + True + 0 + 5 + N/A + True + 40 + + + 0 + + + + + True + 0 + 5 + N/A + True + 40 + + + 1 + + + + + True + 0 + 5 + N/A + True + 40 + + + 2 + + + + + True + 0 + 5 + N/A + True + 40 + + + 3 + + + + + 1 + + + + + True + vertical + True + + + True + 0 + <span fgcolor="blue">Architecture:</span> + True + + + 0 + + + + + True + 0 + <span fgcolor="blue">Kernel:</span> + True + + + 1 + + + + + True + 0 + <span fgcolor="blue">Release:</span> + True + + + 2 + + + + + True + 0 + <span fgcolor="blue">Reason:</span> + True + + + 3 + + + + + False + False + 2 + + + + + True + vertical + + + True + 0 + 5 + N/A + True + 40 + + + 0 + + + + + True + 0 + 5 + N/A + True + 40 + + + 1 + + + + + True + 0 + 5 + N/A + True + 40 + + + 2 + + + + + True + 0 + 5 + N/A + True + 40 + + + 3 + + + + + 3 + + + + + + + + + + + + False + False + 0 + + + + + True + 0 + none + + + True + 12 + + + True + vertical + + + True + True + automatic + automatic + + + 200 + True + True + False + + + + + 0 + + + + + True + + + I checked the backtrace and removed sensitive data (passwords, etc) + True + True + False + True + + + False + False + 0 + + + + + True + + + + + + 1 + + + + + False + False + 1 + + + + + + + + + True + 5 + <b>Backtrace</b> + True + + + + + 1 + + + + + True + 0 + none + + + True + 12 + + + True + vertical + 12 + + + True + vertical + 12 + + + True + 0 + none + + + True + + + True + True + automatic + automatic + + + True + True + word-char + False + + + + + + + + + True + <b>How to reproduce (in a few simple steps)</b> + True + + + + + 0 + + + + + True + 0 + none + + + True + + + True + True + automatic + automatic + + + True + True + word-char + False + + + + + + + + + True + <b>Comment</b> + True + + + + + 1 + + + + + 0 + + + + + + + + + + + + 2 + + + + + 0 + none + + + True + 12 + + + True + vertical + + + + + + + + + + True + <b>Attachments</b> + True + + + + + False + False + 3 + + + + + True + 0 + in + + + True + + + True + + + + + + 0 + + + + + True + gtk-dialog-warning + 6 + + + False + False + 1 + + + + + True + vertical + + + True + <b>Please fix the following problems:</b> + True + + + 0 + + + + + True + + True + True + + + 1 + + + + + 2 + + + + + True + + + + + + 3 + + + + + + + + + + False + False + 4 + + + + + + + + + 1 + + + + + True + end + + + Show log + True + True + True + + + False + False + 0 + True + + + + + gtk-cancel + True + True + True + True + + + False + False + 1 + + + + + gtk-refresh + True + True + True + Forces ABRT to regenerate the backtrace. + True + + + False + False + 2 + + + + + Send report + True + True + True + + + False + False + 3 + + + + + False + end + 0 + + + + + + bLog + bCancel + bRefresh + bSend + + + + Reporter Selector + True + center-on-parent + abrt + + + True + vertical + + + True + 5 + 10 + <b>Where do you want to report this incident?</b> + True + + + 0 + + + + + True + vertical + + + + + + 1 + + + + + True + + + + + + 9 + 2 + + + + + True + + + gtk-close + True + True + True + True + + + 0 + + + + + False + 3 + + + + + + + 500 + 12 + Please wait... + True + center-on-parent + abrt + w_reporters + + + True + vertical + 12 + + + True + 0 + + + False + 0 + + + + + True + 0 + + + False + 1 + + + + + True + True + + + True + True + automatic + automatic + etched-in + + + True + True + + + + + + + True + Details + + + + + 2 + + + + + + diff --git a/src/gui/settings.glade b/src/gui/settings.glade new file mode 100644 index 00000000..b83e6617 --- /dev/null +++ b/src/gui/settings.glade @@ -0,0 +1,855 @@ + + + + + + Plugins + True + center-on-parent + 450 + 400 + abrt + + + True + 12 + vertical + 12 + + + True + vertical + 6 + + + True + True + immediate + never + automatic + in + + + True + True + + + + + 0 + + + + + True + True + + + True + 12 + + + True + + + True + 5 + 2 + 12 + 6 + + + True + 0 + Web Site: + + + 4 + 5 + GTK_FILL + + + + + True + 2.2351741291171123e-10 + Author: + + + 2 + 3 + GTK_FILL + + + + + True + 0 + Version: + + + 3 + 4 + GTK_FILL + + + + + True + 2.2351741291171123e-10 + True + + + 1 + 2 + 3 + 4 + GTK_FILL + + + + + True + 2.2351741291171123e-10 + True + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + True + 0 + True + + + 1 + 2 + 4 + 5 + GTK_FILL + + + + + True + 0 + Description: + + + 1 + 2 + GTK_FILL + + + + + True + 0 + Name: + + + + + True + 0 + True + + + 1 + 2 + 1 + 2 + + + + + True + 0 + True + + + 1 + 2 + + + + + False + False + 0 + + + + + + + + + + + + True + <b>Plugin details</b> + True + + + + + False + False + 1 + + + + + 0 + + + + + True + 12 + end + + + C_onfigure Plugin + True + True + True + True + + + False + False + 0 + + + + + gtk-close + True + True + True + True + + + False + False + 1 + + + + + False + False + 1 + + + + + + + Preferences + True + center-on-parent + 450 + 400 + abrt + + + True + 12 + vertical + 6 + + + True + True + False + + + True + 6 + 5 + 2 + 12 + 6 + + + Check package GPG signature + True + True + False + True + + + 2 + GTK_FILL + + + + + True + 0 + 5 + Database backend: + + + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + 0 + 5 + Blacklisted packages: + + + 2 + 3 + GTK_FILL + GTK_FILL + + + + + True + 2.2351741291171123e-10 + 5 + Max coredump storage size (MB): + + + 3 + 4 + GTK_FILL + GTK_FILL + + + + + True + 0 + 5 + GPG keys: + + + 4 + 5 + GTK_FILL + GTK_FILL + + + + + True + True + + adjMaxRepSize + True + + + 1 + 2 + 3 + 4 + GTK_FILL + + + + + True + 6 + + + True + True + + + + 0 + + + + + True + True + True + imEdit + + + False + False + 1 + + + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + True + 6 + + + True + True + False + + + + 0 + + + + + True + True + True + imEdit1 + + + False + False + 1 + + + + + 1 + 2 + 4 + 5 + GTK_FILL + + + + + + + True + Common + + + False + + + + + True + 6 + vertical + 6 + + + True + True + automatic + automatic + + + True + queue + none + + + True + vertical + + + True + + + True + <b>Plugin</b> + True + + + 0 + + + + + True + <b>Time (or period)</b> + True + + + 1 + + + + + False + 0 + + + + + True + vertical + + + + + + 1 + + + + + + + + + 0 + + + + + True + 12 + end + + + gtk-add + True + True + True + True + + + False + False + 0 + + + + + False + False + 1 + + + + + 1 + + + + + True + Cron + + + 1 + False + + + + + True + 6 + vertical + 6 + + + True + True + automatic + automatic + + + True + queue + none + + + True + vertical + + + True + + + True + <b>Analyzer plugin</b> + True + + + 0 + + + + + True + <b>Associated action</b> + True + + + 1 + + + + + True + + + + + + 2 + + + + + False + 0 + + + + + True + vertical + + + + + + 1 + + + + + + + + + 0 + + + + + True + 12 + end + + + gtk-add + True + True + True + True + + + False + False + 0 + + + + + False + False + 1 + + + + + 2 + + + + + True + Analyzers, Actions, Reporters + + + 2 + False + + + + + 0 + + + + + True + 12 + end + + + gtk-cancel + True + True + True + True + + + False + False + 0 + + + + + gtk-ok + True + False + True + True + True + + + False + False + 1 + + + + + False + False + 1 + + + + + + + 1000000 + 1 + + + GPG Keys + True + center-on-parent + 400 + 400 + abrt + wGlobalSettings + + + True + vertical + + + True + True + + + 0 + + + + + True + 12 + 12 + end + + + gtk-add + True + True + True + True + + + False + False + 0 + + + + + gtk-remove + True + True + True + True + + + False + False + 1 + + + + + gtk-ok + True + True + True + True + + + False + False + 2 + + + + + gtk-cancel + True + True + True + True + + + False + False + 3 + + + + + False + False + 1 + + + + + + + True + gtk-edit + 1 + + + True + gtk-edit + 1 + + diff --git a/src/gui/settings_wizard.glade b/src/gui/settings_wizard.glade new file mode 100644 index 00000000..24cb200a --- /dev/null +++ b/src/gui/settings_wizard.glade @@ -0,0 +1,129 @@ + + + + + + 5 + Wrong Settings Detected + False + center-on-parent + abrt + normal + False + + + True + vertical + 6 + + + True + vertical + 6 + + + True + 12 + + + True + gtk-dialog-warning + 6 + + + 0 + + + + + True + Wrong settings were detected for some of the enabled reporter plugins. Please use the buttons below to open the respective configuration and fix it before you proceed, otherwise, the reporting process may fail. + + True + fill + True + + + 1 + + + + + 0 + + + + + True + vertical + 6 + + + + + + 1 + + + + + True + <b>Do you want to continue?</b> + True + + + 2 + + + + + 1 + + + + + True + end + + + gtk-no + True + True + True + True + + + False + False + 0 + + + + + gtk-yes + True + True + True + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + bCancel + bContinue + + + diff --git a/src/hooks/Makefile.am b/src/hooks/Makefile.am new file mode 100644 index 00000000..55ffc446 --- /dev/null +++ b/src/hooks/Makefile.am @@ -0,0 +1,44 @@ +libexec_PROGRAMS = abrt-hook-ccpp +bin_PROGRAMS = dumpoops + +# abrt-hook-ccpp +abrt_hook_ccpp_SOURCES = abrt-hook-ccpp.cpp +abrt_hook_ccpp_CPPFLAGS = \ + -I$(srcdir)/../../inc \ + -I$(srcdir)/../../lib/utils \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -D_GNU_SOURCE +abrt_hook_ccpp_LDADD = \ + ../../lib/utils/libABRTUtils.la + +# dumpoops +dumpoops_SOURCES = dumpoops.cpp +dumpoops_CPPFLAGS = \ + -I$(srcdir)/../../inc \ + -I$(srcdir)/../../lib/utils \ + -I$(srcdir)/../../lib/plugins \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -D_GNU_SOURCE +# build will succeed, but at runtime plugins do need ABRT*d*Utils +dumpoops_LDADD = \ + ../../lib/utils/libABRTUtils.la \ + ../../lib/utils/libABRTdUtils.la + +python_PYTHON = abrt.pth abrt_exception_handler.py +EXTRA_DIST = abrt_exception_handler.py.in $(man_MANS) + +CLEANFILES := $(notdir $(wildcard *~)) $(notdir $(wildcard *\#)) $(notdir $(wildcard \.\#*)) $(notdir $(wildcard *.pyc)) + +# Must be synchronized with another sed call below. +abrt_exception_handler.py: + sed s,\@VAR_RUN\@,\"$(VAR_RUN)\",g abrt_exception_handler.py.in > abrt_exception_handler.py + +# RPM fix: we need to regenerate abrt_exception_handler.py, because it has the default ddir +install-data-local: + sed s,\@VAR_RUN\@,\"$(VAR_RUN)\",g abrt_exception_handler.py.in > abrt_exception_handler.py diff --git a/src/hooks/abrt-hook-ccpp.cpp b/src/hooks/abrt-hook-ccpp.cpp new file mode 100644 index 00000000..e53007a4 --- /dev/null +++ b/src/hooks/abrt-hook-ccpp.cpp @@ -0,0 +1,553 @@ +/* + abrt-hook-ccpp.cpp - the hook for C/C++ crashing program + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "abrtlib.h" +#include "hooklib.h" +#include "debug_dump.h" +#include "crash_types.h" +#include "abrt_exception.h" +#include + +using namespace std; + +static char* malloc_readlink(const char *linkname) +{ + char buf[PATH_MAX + 1]; + int len; + + len = readlink(linkname, buf, sizeof(buf)-1); + if (len >= 0) + { + buf[len] = '\0'; + return xstrdup(buf); + } + return NULL; +} + +/* Custom version of copyfd_xyz, + * one which is able to write into two descriptors at once. + */ +#define CONFIG_FEATURE_COPYBUF_KB 4 +static off_t copyfd_sparse(int src_fd, int dst_fd1, int dst_fd2, off_t size2) +{ + off_t total = 0; + int last_was_seek = 0; +#if CONFIG_FEATURE_COPYBUF_KB <= 4 + char buffer[CONFIG_FEATURE_COPYBUF_KB * 1024]; + enum { buffer_size = sizeof(buffer) }; +#else + char *buffer; + int buffer_size; + + /* We want page-aligned buffer, just in case kernel is clever + * and can do page-aligned io more efficiently */ + buffer = mmap(NULL, CONFIG_FEATURE_COPYBUF_KB * 1024, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + /* ignored: */ -1, 0); + buffer_size = CONFIG_FEATURE_COPYBUF_KB * 1024; + if (buffer == MAP_FAILED) { + buffer = alloca(4 * 1024); + buffer_size = 4 * 1024; + } +#endif + + while (1) { + ssize_t rd = safe_read(src_fd, buffer, buffer_size); + if (!rd) { /* eof */ + if (last_was_seek) { + if (lseek(dst_fd1, -1, SEEK_CUR) < 0 + || safe_write(dst_fd1, "", 1) != 1 + || (dst_fd2 >= 0 + && (lseek(dst_fd2, -1, SEEK_CUR) < 0 + || safe_write(dst_fd2, "", 1) != 1 + ) + ) + ) { + perror_msg("write error"); + total = -1; + goto out; + } + } + /* all done */ + goto out; + } + if (rd < 0) { + perror_msg("read error"); + total = -1; + goto out; + } + + /* checking sparseness */ + ssize_t cnt = rd; + while (--cnt >= 0) { + if (buffer[cnt] != 0) { + /* not sparse */ + errno = 0; + ssize_t wr1 = full_write(dst_fd1, buffer, rd); + ssize_t wr2 = (dst_fd2 >= 0 ? full_write(dst_fd2, buffer, rd) : rd); + if (wr1 < rd || wr2 < rd) { + perror_msg("write error"); + total = -1; + goto out; + } + last_was_seek = 0; + goto adv; + } + } + /* sparse */ + xlseek(dst_fd1, rd, SEEK_CUR); + if (dst_fd2 >= 0) + xlseek(dst_fd2, rd, SEEK_CUR); + last_was_seek = 1; + adv: + total += rd; + size2 -= rd; + if (size2 < 0) + dst_fd2 = -1; + } + out: + +#if CONFIG_FEATURE_COPYBUF_KB > 4 + if (buffer_size != 4 * 1024) + munmap(buffer, buffer_size); +#endif + return total; +} + +static char* get_executable(pid_t pid, int *fd_p) +{ + char buf[sizeof("/proc/%lu/exe") + sizeof(long)*3]; + + sprintf(buf, "/proc/%lu/exe", (long)pid); + *fd_p = open(buf, O_RDONLY); /* might fail and return -1, it's ok */ + char *executable = malloc_readlink(buf); + /* find and cut off " (deleted)" from the path */ + char *deleted = executable + strlen(executable) - strlen(" (deleted)"); + if (deleted > executable && strcmp(deleted, " (deleted)") == 0) + { + *deleted = '\0'; + log("file %s seems to be deleted", executable); + } + /* find and cut off prelink suffixes from the path */ + char *prelink = executable + strlen(executable) - strlen(".#prelink#.XXXXXX"); + if (prelink > executable && strncmp(prelink, ".#prelink#.", strlen(".#prelink#.")) == 0) + { + log("file %s seems to be a prelink temporary file", executable); + *prelink = '\0'; + } + return executable; +} + +static char* get_cwd(pid_t pid) +{ + char buf[sizeof("/proc/%lu/cwd") + sizeof(long)*3]; + + sprintf(buf, "/proc/%lu/cwd", (long)pid); + return malloc_readlink(buf); +} + +static char core_basename[sizeof("core.%lu") + sizeof(long)*3] = "core"; + +static int open_user_core(const char *user_pwd, uid_t uid, pid_t pid) +{ + struct passwd* pw = getpwuid(uid); + gid_t gid = pw ? pw->pw_gid : uid; + xsetegid(gid); + xseteuid(uid); + + errno = 0; + if (user_pwd == NULL + || chdir(user_pwd) != 0 + ) { + perror_msg("can't cd to %s", user_pwd); + return 0; + } + + /* Mimic "core.PID" if requested */ + char buf[] = "0\n"; + int fd = open("/proc/sys/kernel/core_uses_pid", O_RDONLY); + if (fd >= 0) + { + read(fd, buf, sizeof(buf)); + close(fd); + } + if (strcmp(buf, "1\n") == 0) + { + sprintf(core_basename, "core.%lu", (long)pid); + } + + /* man core: + * There are various circumstances in which a core dump file + * is not produced: + * + * [skipped obvious ones] + * The process does not have permission to write the core file. + * ...if a file with the same name exists and is not writable + * or is not a regular file (e.g., it is a directory or a symbolic link). + * + * A file with the same name already exists, but there is more + * than one hard link to that file. + * + * The file system where the core dump file would be created is full; + * or has run out of inodes; or is mounted read-only; + * or the user has reached their quota for the file system. + * + * The RLIMIT_CORE or RLIMIT_FSIZE resource limits for the process + * are set to zero. + * [shouldn't it be checked by kernel? 2.6.30.9-96 doesn't, still + * calls us even if "ulimit -c 0"] + * + * The binary being executed by the process does not have + * read permission enabled. [how we can check it here?] + * + * The process is executing a set-user-ID (set-group-ID) program + * that is owned by a user (group) other than the real + * user (group) ID of the process. [TODO?] + * (However, see the description of the prctl(2) PR_SET_DUMPABLE operation, + * and the description of the /proc/sys/fs/suid_dumpable file in proc(5).) + */ + + /* Do not O_TRUNC: if later checks fail, we do not want to have file already modified here */ + struct stat sb; + errno = 0; + int user_core_fd = open(core_basename, O_WRONLY | O_CREAT | O_NOFOLLOW, 0600); /* kernel makes 0600 too */ + xsetegid(0); + xseteuid(0); + if (user_core_fd < 0 + || fstat(user_core_fd, &sb) != 0 + || !S_ISREG(sb.st_mode) + || sb.st_nlink != 1 + /* kernel internal dumper checks this too: if (inode->i_uid != current->fsuid) , need to mimic? */ + ) { + perror_msg("%s/%s is not a regular file with link count 1", user_pwd, core_basename); + return -1; + } + if (ftruncate(user_core_fd, 0) != 0) { + /* perror first, otherwise unlink may trash errno */ + perror_msg("truncate %s/%s", user_pwd, core_basename); + return -1; + } + + return user_core_fd; +} + +int main(int argc, char** argv) +{ + int i; + struct stat sb; + + if (argc < 5) + { + error_msg_and_die("Usage: %s: DUMPDIR PID SIGNO UID CORE_SIZE_LIMIT", argv[0]); + } + + /* Not needed on 2.6.30. + * At least 2.6.18 has a bug where + * argv[1] = "DUMPDIR PID SIGNO UID CORE_SIZE_LIMIT" + * argv[2] = "PID SIGNO UID CORE_SIZE_LIMIT" + * and so on. Fixing it: + */ + for (i = 1; argv[i]; i++) + { + strchrnul(argv[i], ' ')[0] = '\0'; + } + + openlog("abrt", LOG_PID, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + + errno = 0; + const char* dddir = argv[1]; + pid_t pid = xatoi_u(argv[2]); + const char* signal_str = argv[3]; + int signal_no = xatoi_u(argv[3]); + uid_t uid = xatoi_u(argv[4]); + off_t ulimit_c = strtoull(argv[5], NULL, 10); + if (ulimit_c < 0) /* unlimited? */ + { + /* set to max possible >0 value */ + ulimit_c = ~((off_t)1 << (sizeof(off_t)*8-1)); + } + if (errno || pid <= 0) + { + error_msg_and_die("pid '%s' or limit '%s' is bogus", argv[2], argv[5]); + } + + int src_fd_binary; + char* executable = get_executable(pid, &src_fd_binary); + if (executable == NULL) + { + perror_msg_and_die("can't read /proc/%lu/exe link", (long)pid); + } + if (strstr(executable, "/abrt-hook-ccpp")) + { + error_msg_and_die("pid %lu is '%s', not dumping it to avoid recursion", + (long)pid, executable); + } + + char *user_pwd = get_cwd(pid); /* may be NULL on error */ + + /* Parse abrt.conf and plugins/CCpp.conf */ + unsigned setting_MaxCrashReportsSize = 0; + bool setting_MakeCompatCore = false; + bool setting_SaveBinaryImage = false; + parse_conf(CONF_DIR"/plugins/CCpp.conf", &setting_MaxCrashReportsSize, &setting_MakeCompatCore, &setting_SaveBinaryImage); + if (!setting_SaveBinaryImage && src_fd_binary >= 0) + { + close(src_fd_binary); + src_fd_binary = -1; + } + + /* Open a fd to compat coredump, if requested and is possible */ + int user_core_fd = -1; + if (setting_MakeCompatCore && ulimit_c != 0) + user_core_fd = open_user_core(user_pwd, uid, pid); + + const char *signame = NULL; + /* Tried to use array for this but C++ does not support v[] = { [IDX] = "str" } */ + switch (signal_no) + { + case SIGILL : signame = "ILL" ; break; + case SIGFPE : signame = "FPE" ; break; + case SIGSEGV: signame = "SEGV"; break; + case SIGBUS : signame = "BUS" ; break; //Bus error (bad memory access) + case SIGABRT: signame = "ABRT"; break; //usually when abort() was called + //case SIGQUIT: signame = "QUIT"; break; //Quit from keyboard + //case SIGSYS : signame = "SYS" ; break; //Bad argument to routine (SVr4) + //case SIGTRAP: signame = "TRAP"; break; //Trace/breakpoint trap + //case SIGXCPU: signame = "XCPU"; break; //CPU time limit exceeded (4.2BSD) + //case SIGXFSZ: signame = "XFSZ"; break; //File size limit exceeded (4.2BSD) + default: goto create_user_core; // not a signal we care about + } + + if (!daemon_is_ok()) + { + /* not an error, exit with exitcode 0 */ + log("abrt daemon is not running. If it crashed, " + "/proc/sys/kernel/core_pattern contains a stale value, " + "consider resetting it to 'core'" + ); + goto create_user_core; + } + + try + { + if (setting_MaxCrashReportsSize > 0) + { + check_free_space(setting_MaxCrashReportsSize); + } + + char path[PATH_MAX]; + + /* Check /var/spool/abrt/last-ccpp marker, do not dump repeated crashes + * if they happen too often. Else, write new marker value. + */ + snprintf(path, sizeof(path), "%s/last-ccpp", dddir); + int fd = open(path, O_RDWR | O_CREAT, 0600); + if (fd >= 0) + { + int sz; + fstat(fd, &sb); /* !paranoia. this can't fail. */ + + if (sb.st_size != 0 /* if it wasn't created by us just now... */ + && (unsigned)(time(NULL) - sb.st_mtime) < 20 /* and is relatively new [is 20 sec ok?] */ + ) { + sz = read(fd, path, sizeof(path)-1); /* (ab)using path as scratch buf */ + if (sz > 0) + { + path[sz] = '\0'; + if (strcmp(executable, path) == 0) + { + error_msg("not dumping repeating crash in '%s'", executable); + if (setting_MakeCompatCore) + goto create_user_core; + return 1; + } + } + lseek(fd, 0, SEEK_SET); + } + sz = write(fd, executable, strlen(executable)); + if (sz >= 0) + ftruncate(fd, sz); + close(fd); + } + + if (strstr(executable, "/abrtd")) + { + /* If abrtd crashes, we don't want to create a _directory_, + * since that can make new copy of abrtd to process it, + * and maybe crash again... + * Unlike dirs, mere files are ignored by abrtd. + */ + snprintf(path, sizeof(path), "%s/abrtd-coredump", dddir); + int abrt_core_fd = xopen3(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + off_t core_size = copyfd_eof(STDIN_FILENO, abrt_core_fd, COPYFD_SPARSE); + if (core_size < 0 || fsync(abrt_core_fd) != 0) + { + unlink(path); + /* copyfd_eof logs the error including errno string, + * but it does not log file name */ + error_msg_and_die("error saving coredump to %s", path); + } + log("saved core dump of pid %lu (%s) to %s (%llu bytes)", (long)pid, executable, path, (long long)core_size); + return 0; + } + + unsigned path_len = snprintf(path, sizeof(path), "%s/ccpp-%ld-%lu.new", + dddir, (long)time(NULL), (long)pid); + if (path_len >= (sizeof(path) - sizeof("/"FILENAME_COREDUMP))) + return 1; + + CDebugDump dd; + char *cmdline = get_cmdline(pid); /* never NULL */ + char *reason = xasprintf("Process %s was killed by signal %s (SIG%s)", executable, signal_str, signame ? signame : signal_str); + dd.Create(path, uid); + dd.SaveText(FILENAME_ANALYZER, "CCpp"); + dd.SaveText(FILENAME_EXECUTABLE, executable); + dd.SaveText(FILENAME_CMDLINE, cmdline); + dd.SaveText(FILENAME_REASON, reason); + free(cmdline); + free(reason); + + if (src_fd_binary > 0) + { + strcpy(path + path_len, "/"FILENAME_BINARY); + int dst_fd_binary = xopen3(path, O_WRONLY | O_CREAT | O_TRUNC, 0600); + off_t sz = copyfd_eof(src_fd_binary, dst_fd_binary, COPYFD_SPARSE); + if (sz < 0 || fsync(dst_fd_binary) != 0) + { + unlink(path); + error_msg_and_die("error saving binary image to %s", path); + } + close(dst_fd_binary); + close(src_fd_binary); + } + + /* We need coredumps to be readable by all, because + * when abrt daemon processes coredump, + * process producing backtrace is run under the same UID + * as the crashed process. + * Thus 644, not 600 */ + strcpy(path + path_len, "/"FILENAME_COREDUMP); + int abrt_core_fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); + if (abrt_core_fd < 0) + { + int sv_errno = errno; + dd.Delete(); + dd.Close(); + if (user_core_fd >= 0) + { + xchdir(user_pwd); + unlink(core_basename); + } + errno = sv_errno; + perror_msg_and_die("can't open '%s'", path); + } + + /* We write both coredumps at once. + * We can't write user coredump first, since it might be truncated + * and thus can't be copied and used as abrt coredump; + * and if we write abrt coredump first and then copy it as user one, + * then we have a race when process exits but coredump does not exist yet: + * $ echo -e '#include\nmain(){raise(SIGSEGV);}' | gcc -o test -x c - + * $ rm -f core*; ulimit -c unlimited; ./test; ls -l core* + * 21631 Segmentation fault (core dumped) ./test + * ls: cannot access core*: No such file or directory <=== BAD + */ +//TODO: fchown abrt_core_fd to uid:abrt? +//Currently it is owned by 0:0 but is readable by anyone, so the owner +//of the crashed binary still can access it, as he has +//r-x access to the dump dir. + off_t core_size = copyfd_sparse(STDIN_FILENO, abrt_core_fd, user_core_fd, ulimit_c); + if (core_size < 0 || fsync(abrt_core_fd) != 0) + { + unlink(path); + dd.Delete(); + dd.Close(); + if (user_core_fd >= 0) + { + xchdir(user_pwd); + unlink(core_basename); + } + /* copyfd_sparse logs the error including errno string, + * but it does not log file name */ + error_msg_and_die("error writing %s", path); + } + log("saved core dump of pid %lu (%s) to %s (%llu bytes)", (long)pid, executable, path, (long long)core_size); + if (user_core_fd >= 0 && core_size >= ulimit_c) + { + /* user coredump is too big, nuke it */ + xchdir(user_pwd); + unlink(core_basename); + } + + /* We close dumpdir before we start catering for crash storm case. + * Otherwise, delete_debug_dump_dir's from other concurrent + * CCpp's won't be able to delete our dump (their delete_debug_dump_dir + * will wait for us), and we won't be able to delete their dumps. + * Classic deadlock. + */ + dd.Close(); + path[path_len] = '\0'; /* path now contains only directory name */ + char *newpath = xstrndup(path, path_len - (sizeof(".new")-1)); + if (rename(path, newpath) == 0) + strcpy(path, newpath); + free(newpath); + + /* rhbz#539551: "abrt going crazy when crashing process is respawned" */ + if (setting_MaxCrashReportsSize > 0) + { + trim_debug_dumps(setting_MaxCrashReportsSize, path); + } + + return 0; + } + catch (CABRTException& e) + { + error_msg_and_die("%s", e.what()); + } + catch (std::exception& e) + { + error_msg_and_die("%s", e.what()); + } + + /* We didn't create abrt dump, but may need to create compat coredump */ + create_user_core: + if (user_core_fd < 0) + return 0; + + off_t core_size = copyfd_size(STDIN_FILENO, user_core_fd, ulimit_c, COPYFD_SPARSE); + if (core_size < 0 || fsync(user_core_fd) != 0) { + /* perror first, otherwise unlink may trash errno */ + perror_msg("error writing %s/%s", user_pwd, core_basename); + xchdir(user_pwd); + unlink(core_basename); + return 1; + } + if (core_size >= ulimit_c) + { + xchdir(user_pwd); + unlink(core_basename); + return 1; + } + log("saved core dump of pid %lu to %s/%s (%llu bytes)", (long)pid, user_pwd, core_basename, (long long)core_size); + + return 0; +} diff --git a/src/hooks/abrt.pth b/src/hooks/abrt.pth new file mode 100644 index 00000000..39fc292e --- /dev/null +++ b/src/hooks/abrt.pth @@ -0,0 +1 @@ +import abrt_exception_handler diff --git a/src/hooks/abrt_exception_handler.py.in b/src/hooks/abrt_exception_handler.py.in new file mode 100644 index 00000000..dd6fbaed --- /dev/null +++ b/src/hooks/abrt_exception_handler.py.in @@ -0,0 +1,167 @@ +#:mode=python: +# -*- coding: utf-8 -*- +## Copyright (C) 2001-2005 Red Hat, Inc. +## Copyright (C) 2001-2005 Harald Hoyer +## Copyright (C) 2009 Jiri Moskovcak + +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. + +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. + +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +""" +Module for the ABRT exception handling hook +""" + +import sys +import os +import syslog +import subprocess +import socket + +def write_dump(pid, tb): + executable = "Exception raised from python shell" + if sys.argv[0]: + executable = os.path.abspath(sys.argv[0]) + + # Open ABRT daemon's socket and write data to it. + try: + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(@VAR_RUN@ + "/abrt/abrt.socket") + s.sendall("PID=%s\0" % pid) + s.sendall("EXECUTABLE=%s\0" % executable) + s.sendall("ANALYZER=Python\0") + s.sendall("BASENAME=pyhook\0") + # This handler puts a short(er) crash descr in 1st line of the backtrace. + # Example: + # CCMainWindow.py:1::ZeroDivisionError: integer division or modulo by zero + s.sendall("REASON=%s\0" % tb.splitlines()[0]) + s.sendall("BACKTRACE=%s\0" % tb) + s.sendall("DONE\0") + s.close() + except Exception, ex: + syslog.syslog("can't communicate with ABRT daemon, is it running? %s", str(ex)) + +def handleMyException((etype, value, tb)): + """ + The exception handling function. + + progname - the name of the application + version - the version of the application + """ + + # restore original exception handler + sys.excepthook = sys.__excepthook__ # pylint: disable-msg=E1101 + # ignore uncaught ctrl-c + if etype == KeyboardInterrupt: + return sys.__excepthook__(etype, value, tb) + + try: + import os + import os.path + import traceback + import errno + + # EPIPE is not a crash, it happens all the time + # Testcase: script.py | true, where script.py is: + ## #!/usr/bin/python + ## import os + ## import time + ## time.sleep(1) + ## os.write(1, "Hello\n") # print "Hello" wouldn't be the same + # + if etype == IOError or etype == OSError: + if value.errno == errno.EPIPE: + return sys.__excepthook__(etype, value, tb) + + # "-c" appears in this case: + # $ python -c 'import sys; print "argv0 is:%s" % sys.argv[0]' + # argv0 is:-c + if not sys.argv[0] or sys.argv[0] == "-c": + # Looks like interactive Python - abort dumping + syslog.syslog("abrt: detected unhandled Python exception") + raise Exception + syslog.syslog("abrt: detected unhandled Python exception in %s" % sys.argv[0]) + if sys.argv[0][0] != "/": + # Relative path - can't reliably determine package + # this script belongs to - abort dumping + # TODO: check abrt.conf and abort only if + # ProcessUnpackaged = no? + raise Exception + + elist = traceback.format_exception(etype, value, tb) + tblast = traceback.extract_tb(tb, limit=None) + if len(tblast): + tblast = tblast[len(tblast)-1] + extxt = traceback.format_exception_only(etype, value) + if tblast and len(tblast) > 3: + ll = [] + ll.extend(tblast[:3]) + ll[0] = os.path.basename(tblast[0]) + tblast = ll + + ntext = "" + for t in tblast: + ntext += str(t) + ":" + + text = ntext + text += extxt[0] + text += "\n" + text += "".join(elist) + + trace = tb + while trace.tb_next: + trace = trace.tb_next + frame = trace.tb_frame + text += ("\nLocal variables in innermost frame:\n") + try: + for (key, val) in frame.f_locals.items(): + text += "%s: %s\n" % (key, repr(val)) + except: + pass + + # add coredump saving + write_dump(os.getpid(), text) + + except: + # silently ignore any error in this hook, + # to not interfere with the python scripts + pass + + return sys.__excepthook__(etype, value, tb) + + +def installExceptionHandler(): + """ + Install the exception handling function. + """ + sys.excepthook = lambda etype, value, tb: handleMyException((etype, value, tb)) + +# install the exception handler when the abrt_exception_handler +# module is imported +try: + installExceptionHandler() +except Exception, e: + # TODO: log errors? + # OTOH, if abrt is deinstalled uncleanly + # and this file (sitecustomize.py) exists but + # abrt_exception_handler module does not exist, we probably + # don't want to irritate admins... + pass + +if __name__ == '__main__': + # test exception raised to show the effect + div0 = 1 / 0 # pylint: disable-msg=W0612 + sys.exit(0) + + +__author__ = "Harald Hoyer " diff --git a/src/hooks/dumpoops.cpp b/src/hooks/dumpoops.cpp new file mode 100644 index 00000000..a2d2353a --- /dev/null +++ b/src/hooks/dumpoops.cpp @@ -0,0 +1,125 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 RedHat Inc + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + Authors: + Denys Vlasenko + Zdenek Prikryl +*/ + +#include "abrtlib.h" +#include "abrt_types.h" +#include "abrt_exception.h" +#include "KerneloopsScanner.h" +#include + +#define LOADSYM(fp, name) \ +do { \ + fp = (typeof(fp)) (dlsym(handle, name)); \ + if (!fp) \ + perror_msg_and_die(PLUGINS_LIB_DIR"/libKerneloopsScanner.so has no %s", name); \ +} while (0) + + +int main(int argc, char **argv) +{ + char *program_name = strrchr(argv[0], '/'); + program_name = program_name ? program_name + 1 : argv[0]; + + /* Parse options */ + bool opt_d = 0, opt_s = 0; + int opt; + while ((opt = getopt(argc, argv, "dsv")) != -1) { + switch (opt) { + case 'd': + opt_d = 1; + break; + case 's': + opt_s = 1; + break; + case 'v': + /* Kerneloops code uses VERB3, thus: */ + g_verbose = 3; + break; + default: + usage: + error_msg_and_die( + "Usage: %s [-dsv] FILE\n\n" + "Options:\n" + "\t-d\tCreate ABRT dump for every oops found\n" + "\t-s\tPrint found oopses on standard output\n" + "\t-v\tVerbose\n" + , program_name + ); + } + } + argv += optind; + if (!argv[0]) + goto usage; + + msg_prefix = xasprintf("%s: ", program_name); + + /* Load KerneloopsScanner plugin */ +// const plugin_info_t *plugin_info; + CPlugin* (*plugin_newf)(void); + int (*scan_syslog_file)(vector_string_t& oopsList, const char *filename, time_t *last_changed_p); + void (*save_oops_to_debug_dump)(const vector_string_t& oopsList); + void *handle; + + errno = 0; +//TODO: use it directly, not via dlopen? + handle = dlopen(PLUGINS_LIB_DIR"/libKerneloopsScanner.so", RTLD_NOW); + if (!handle) + perror_msg_and_die("can't load %s", PLUGINS_LIB_DIR"/libKerneloopsScanner.so"); + +// LOADSYM(plugin_info, "plugin_info"); + LOADSYM(plugin_newf, "plugin_new"); + LOADSYM(scan_syslog_file, "scan_syslog_file"); + LOADSYM(save_oops_to_debug_dump, "save_oops_to_debug_dump"); + +// CKerneloopsScanner* scanner = (CKerneloopsScanner*) plugin_newf(); +// scanner->Init(); +// scanner->LoadSettings(path); + + /* Use it: parse and dump the oops */ + vector_string_t oopsList; + int cnt = scan_syslog_file(oopsList, argv[0], NULL); + log("found oopses: %d", cnt); + + if (cnt > 0) { + if (opt_s) { + int i = 0; + while (i < oopsList.size()) { + printf("\nVersion: %s", oopsList[i].c_str()); + i++; + } + } + if (opt_d) { + log("dumping oopses"); + try { + save_oops_to_debug_dump(oopsList); + } + catch (CABRTException& e) { + fprintf(stderr, "Error: %s\n", e.what()); + return 1; + } + } + } + + /*dlclose(handle); - why bother? */ + return 0; +} diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 0cecf26a..29e06645 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -3,9 +3,9 @@ abrt_backtrace_CFLAGS = -Wall abrt_backtrace_SOURCES = abrt-backtrace.c abrt_backtrace_CPPFLAGS = \ -I$(srcdir)/../../inc \ - -I$(srcdir)/../../lib/Utils + -I$(srcdir)/../../lib/utils abrt_backtrace_LDADD = \ - ../../lib/Utils/libABRTUtils.la + ../../lib/utils/libABRTUtils.la man_MANS = abrt-backtrace.1 EXTRA_DIST = $(man_MANS) -- cgit