diff options
Diffstat (limited to 'src/plugins')
64 files changed, 9410 insertions, 0 deletions
diff --git a/src/plugins/Bugzilla.conf b/src/plugins/Bugzilla.conf new file mode 100644 index 00000000..76e0d1d8 --- /dev/null +++ b/src/plugins/Bugzilla.conf @@ -0,0 +1,12 @@ +# Description: Reports bugs to bugzilla + +Enabled = yes + +# Bugzilla URL +BugzillaURL = https://bugzilla.redhat.com/ +# yes means that ssl certificates will be checked +SSLVerify = yes +# your login has to exist, if you don have any, please create one +Login = +# your password +Password = diff --git a/src/plugins/Bugzilla.cpp b/src/plugins/Bugzilla.cpp new file mode 100644 index 00000000..452d7a58 --- /dev/null +++ b/src/plugins/Bugzilla.cpp @@ -0,0 +1,155 @@ +/* + 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 "crash_types.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" +#include "Bugzilla.h" + +using namespace std; + +CReporterBugzilla::CReporterBugzilla() +{ + m_pSettings["BugzillaURL"] = "https://bugzilla.redhat.com"; + m_pSettings["Login"] = ""; + m_pSettings["Password"] = ""; + m_pSettings["SSLVerify"] = "yes"; + m_pSettings["RatingRequired"] = "yes"; +} + +CReporterBugzilla::~CReporterBugzilla() +{ +} + +void CReporterBugzilla::SetSettings(const map_plugin_settings_t& pSettings) +{ +//BUG! This gets called when user's keyring contains login data, +//then it takes precedence over /etc/abrt/plugins/Bugzilla.conf. +//I got a case when keyring had a STALE password, and there was no way +//for me to know that it is being used. Moreover, when I discovered it +//(by hacking abrt source!), I don't know how to purge it from the keyring. +//At the very least, log("SOMETHING") here. + + /* Can't simply do this: + + m_pSettings = pSettings; + + * - it will erase keys which aren't present in pSettings. + * Example: if Bugzilla.conf doesn't have "Login = foo", + * then there's no pSettings["Login"] and m_pSettings = pSettings + * will nuke default m_pSettings["Login"] = "", + * making GUI think that we have no "Login" key at all + * and thus never overriding it - even if it *has* an override! + */ + + map_plugin_settings_t::iterator it = m_pSettings.begin(); + while (it != m_pSettings.end()) + { + map_plugin_settings_t::const_iterator override = pSettings.find(it->first); + if (override != pSettings.end()) + { + VERB3 log(" 3 settings[%s]='%s'", it->first.c_str(), it->second.c_str()); + it->second = override->second; + } + it++; + } +} + +string CReporterBugzilla::Report(const map_crash_data_t& crash_data, + const map_plugin_settings_t& settings, + const char *args) +{ + /* abrt-action-bugzilla [-s] -c /etc/arbt/Bugzilla.conf -c - -d pCrashData.dir NULL */ + char *argv[9]; + char **pp = argv; + *pp++ = (char*)"abrt-action-bugzilla"; + +//We want to consume output, so don't redirect to syslog. +// if (logmode & LOGMODE_SYSLOG) +// *pp++ = (char*)"-s"; +//TODO: the actions<->daemon interaction will be changed anyway... + + *pp++ = (char*)"-c"; + *pp++ = (char*)(PLUGINS_CONF_DIR"/Bugzilla."PLUGINS_CONF_EXTENSION); + *pp++ = (char*)"-c"; + *pp++ = (char*)"-"; + *pp++ = (char*)"-d"; + *pp++ = (char*)get_crash_data_item_content_or_NULL(crash_data, CD_DUMPDIR); + *pp = NULL; + int pipefds[2]; + pid_t pid = fork_execv_on_steroids(EXECFLG_INPUT + EXECFLG_OUTPUT + EXECFLG_ERR2OUT, + argv, + pipefds, + /* unsetenv_vec: */ NULL, + /* dir: */ NULL, + /* uid(unused): */ 0 + ); + + /* Write the configuration to stdin */ + map_plugin_settings_t::const_iterator it = settings.begin(); + while (it != settings.end()) + { + full_write_str(pipefds[1], it->first.c_str()); + full_write_str(pipefds[1], "="); + full_write_str(pipefds[1], it->second.c_str()); + full_write_str(pipefds[1], "\n"); + it++; + } + close(pipefds[1]); + + FILE *fp = fdopen(pipefds[0], "r"); + if (!fp) + die_out_of_memory(); + + /* Consume log from stdout */ + string bug_status; + char *buf; + while ((buf = xmalloc_fgetline(fp)) != NULL) + { + if (strncmp(buf, "STATUS:", 7) == 0) + bug_status = buf + 7; + else + if (strncmp(buf, "EXCEPT:", 7) == 0) + { + CABRTException e(EXCEP_PLUGIN, "%s", buf + 7); + free(buf); + fclose(fp); + waitpid(pid, NULL, 0); + throw e; + } + update_client("%s", buf); + free(buf); + } + + fclose(fp); /* this also closes pipefds[0] */ + /* wait for child to actually exit, and prevent leaving a zombie behind */ + waitpid(pid, NULL, 0); + + return bug_status; +} + +PLUGIN_INFO(REPORTER, + CReporterBugzilla, + "Bugzilla", + "0.0.4", + _("Reports bugs to bugzilla"), + "npajkovs@redhat.com", + "https://fedorahosted.org/abrt/wiki", + PLUGINS_LIB_DIR"/Bugzilla.glade"); diff --git a/src/plugins/Bugzilla.glade b/src/plugins/Bugzilla.glade new file mode 100644 index 00000000..cabdd06a --- /dev/null +++ b/src/plugins/Bugzilla.glade @@ -0,0 +1,246 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="2.14"/> + <!-- interface-naming-policy project-wide --> + <object class="GtkDialog" id="PluginDialog"> + <property name="border_width">12</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="window_position">center-on-parent</property> + <property name="icon_name">abrt</property> + <property name="type_hint">normal</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child> + <object class="GtkFrame" id="frame1"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">none</property> + <child> + <object class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <property name="top_padding">6</property> + <property name="left_padding">12</property> + <child> + <object class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="n_rows">5</property> + <property name="n_columns">2</property> + <property name="column_spacing">12</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="lBugzillaURL"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Bugzilla URL:</property> + </object> + <packing> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="lLogin"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Login(email):</property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="lPassword"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Password:</property> + </object> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_BugzillaURL"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_Login"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_Password"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="visibility">False</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="cb_Password"> + <property name="label" translatable="yes">Show password</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="xalign">0</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label3"> + <property name="xalign">0</property> + </object> + <packing> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="conf_SSLVerify"> + <property name="label" translatable="yes">SSL verify</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="xalign">0</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="right_attach">2</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + </packing> + </child> + </object> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Bugzilla plugin configuration</b></property> + <property name="use_markup">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="label" translatable="yes">Don't have an account yet?</property> + <property name="use_markup">True</property> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="xpad">5</property> + <property name="label" translatable="yes">You can create it <a href="https://bugzilla.redhat.com/createaccount.cgi">here</a></property> + <property name="use_markup">True</property> + <property name="selectable">True</property> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">2</property> + </packing> + </child> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="button2"> + <property name="label">gtk-cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="bApply"> + <property name="label">gtk-apply</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-6">button2</action-widget> + <action-widget response="-10">bApply</action-widget> + </action-widgets> + </object> + <object class="GtkAction" id="action1"/> +</interface> diff --git a/src/plugins/Bugzilla.h b/src/plugins/Bugzilla.h new file mode 100644 index 00000000..d7f3acf0 --- /dev/null +++ b/src/plugins/Bugzilla.h @@ -0,0 +1,37 @@ +/* + 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 BUGZILLA_H_ +#define BUGZILLA_H_ + +#include "plugin.h" +#include "reporter.h" + +class CReporterBugzilla : public CReporter +{ + public: + CReporterBugzilla(); + virtual ~CReporterBugzilla(); + + virtual std::string Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs); + virtual void SetSettings(const map_plugin_settings_t& pSettings); +}; + +#endif /* BUGZILLA_H_ */ diff --git a/src/plugins/CCpp.conf b/src/plugins/CCpp.conf new file mode 100644 index 00000000..4af91470 --- /dev/null +++ b/src/plugins/CCpp.conf @@ -0,0 +1,44 @@ +# Configuration file for CCpp hook and plugin +Enabled = yes + +# If you also want to dump file named "core" +# in crashed process' current dir, set to "yes" +MakeCompatCore = yes + +# Do you want a copy of crashed binary be saved? +# (useful, for example, when _deleted binary_ segfaults) +SaveBinaryImage = no + +# Generate backtrace +Backtrace = yes +# How long to wait for gdb to finish. Default is 60 seconds. +GdbTimeoutSec = 120 + +# Generate backtrace for crashes uploaded from remote machines. +# Note that for reliable backtrace generation, your local machine +# needs to have the crashed executable and all libraries it uses, +# and they need to be the same versions as on remote machines. +# If you cannot ensure that, it's better to set this option to "no" +BacktraceRemotes = no + +# Generate memory map too (IGNORED FOR NOW) +MemoryMap = no + +# How to get debuginfo: install, mount +## install - download and install debuginfo packages +## mount - mount fedora NFS with debug info +## (IGNORED FOR NOW) +DebugInfo = install + +# If this option is set to "yes", +# debuginfos will be installed to @@LOCALSTATEDIR@@/cache/abrt-di +InstallDebugInfo = yes + +# Additional directories to search for debuginfos. +# For example, you can list a network-mounted shared store +# of all debuginfos here. +# ReadonlyLocalDebugInfoDirs = /path1:/path2:... + +# Keep @@LOCALSTATEDIR@@/cache/abrt-di +# from growing out-of-bounds. +DebugInfoCacheMB = 4000 diff --git a/src/plugins/CCpp.cpp b/src/plugins/CCpp.cpp new file mode 100644 index 00000000..fad9cf62 --- /dev/null +++ b/src/plugins/CCpp.cpp @@ -0,0 +1,279 @@ +/* + CCpp.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 <set> +#include "abrtlib.h" +#include "CCpp.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" +#include "Polkit.h" + +using namespace std; + +#define CORE_PATTERN_IFACE "/proc/sys/kernel/core_pattern" +#define CORE_PATTERN "|"CCPP_HOOK_PATH" "DEBUG_DUMPS_DIR" %p %s %u %c" +#define CORE_PIPE_LIMIT_IFACE "/proc/sys/kernel/core_pipe_limit" +/* core_pipe_limit specifies how many dump_helpers might run at the same time +0 - means unlimited, but the it's not guaranteed that /proc/<pid> of crashing +process might not be available for dump_helper +4 - means that 4 dump_helpers can run at the same time, which should be enough +for ABRT, we can miss some crashes, but what are the odds that more processes +crash at the same time? This value has been recommended by nhorman +*/ +#define CORE_PIPE_LIMIT "4" + +#define DEBUGINFO_CACHE_DIR LOCALSTATEDIR"/cache/abrt-di" + +CAnalyzerCCpp::CAnalyzerCCpp() : + m_bBacktrace(true), + m_bBacktraceRemotes(false), + m_bMemoryMap(false), + m_bInstallDebugInfo(true), + m_nDebugInfoCacheMB(4000), + m_nGdbTimeoutSec(60) +{} + +/* + this is just a workaround until kernel changes it's behavior + when handling pipes in core_pattern +*/ +#ifdef HOSTILE_KERNEL +#define CORE_SIZE_PATTERN "Max core file size=1:unlimited" +static int isdigit_str(char *str) +{ + do { + if (*str < '0' || *str > '9') + return 0; + } while (*++str); + return 1; +} + +static int set_limits() +{ + DIR *dir = opendir("/proc"); + if (!dir) { + /* this shouldn't fail, but to be safe.. */ + return 1; + } + + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + if (!isdigit_str(ent->d_name)) + continue; + + char limits_name[sizeof("/proc/%s/limits") + sizeof(long)*3]; + snprintf(limits_name, sizeof(limits_name), "/proc/%s/limits", ent->d_name); + FILE *limits_fp = fopen(limits_name, "r"); + if (!limits_fp) { + break; + } + + char line[128]; + char *ulimit_c = NULL; + while (1) { + if (fgets(line, sizeof(line)-1, limits_fp) == NULL) + break; + if (strncmp(line, "Max core file size", sizeof("Max core file size")-1) == 0) { + ulimit_c = skip_whitespace(line + sizeof("Max core file size")-1); + skip_non_whitespace(ulimit_c)[0] = '\0'; + break; + } + } + fclose(limits_fp); + if (!ulimit_c || ulimit_c[0] != '0' || ulimit_c[1] != '\0') { + /*process has nonzero ulimit -c, so need to modify it*/ + continue; + } + /* echo -n 'Max core file size=1:unlimited' >/proc/PID/limits */ + int fd = open(limits_name, O_WRONLY); + if (fd >= 0) { + errno = 0; + /*full_*/ + ssize_t n = write(fd, CORE_SIZE_PATTERN, sizeof(CORE_SIZE_PATTERN)-1); + if (n < sizeof(CORE_SIZE_PATTERN)-1) + log("warning: can't write core_size limit to: %s", limits_name); + close(fd); + } + else + { + log("warning: can't open %s for writing", limits_name); + } + } + closedir(dir); + return 0; +} +#endif /* HOSTILE_KERNEL */ + +void CAnalyzerCCpp::Init() +{ + FILE *fp = fopen(CORE_PATTERN_IFACE, "r"); + if (fp) + { + char line[PATH_MAX]; + if (fgets(line, sizeof(line), fp)) + m_sOldCorePattern = line; + fclose(fp); + } + if (m_sOldCorePattern[0] == '|') + { + if (m_sOldCorePattern == CORE_PATTERN) + { + log("warning: %s already contains %s, " + "did abrt daemon crash recently?", + CORE_PATTERN_IFACE, CORE_PATTERN); + /* There is no point in "restoring" CORE_PATTERN_IFACE + * to CORE_PATTERN on exit. Will restore to a default value: + */ + m_sOldCorePattern = "core"; + } else { + log("warning: %s was already set to run a crash analyser (%s), " + "abrt may interfere with it", + CORE_PATTERN_IFACE, CORE_PATTERN); + } + } +#ifdef HOSTILE_KERNEL + if (set_limits() != 0) + log("warning: failed to set core_size limit, ABRT won't detect crashes in" + "compiled apps"); +#endif + + fp = fopen(CORE_PATTERN_IFACE, "w"); + if (fp) + { + fputs(CORE_PATTERN, fp); + fclose(fp); + } + + /* read the core_pipe_limit and change it if it's == 0 + otherwise the abrt-hook-ccpp won't be able to read /proc/<pid> + of the crashing process + */ + fp = fopen(CORE_PIPE_LIMIT_IFACE, "r"); + if (fp) + { + /* we care only about the first char, if it's + * not '0' then we don't have to change it, + * because it means that it's already != 0 + */ + char pipe_limit[2]; + if (!fgets(pipe_limit, sizeof(pipe_limit), fp)) + pipe_limit[0] = '1'; /* not 0 */ + fclose(fp); + if (pipe_limit[0] == '0') + { + fp = fopen(CORE_PIPE_LIMIT_IFACE, "w"); + if (fp) + { + fputs(CORE_PIPE_LIMIT, fp); + fclose(fp); + } + else + { + log("warning: failed to set core_pipe_limit, ABRT won't detect" + "crashes in compiled apps if kernel > 2.6.31"); + } + } + } +} + +void CAnalyzerCCpp::DeInit() +{ + /* no need to restore the core_pipe_limit, because it's only used + when there is s pipe in core_pattern + */ + FILE *fp = fopen(CORE_PATTERN_IFACE, "w"); + if (fp) + { + fputs(m_sOldCorePattern.c_str(), fp); + fclose(fp); + } +} + +void CAnalyzerCCpp::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("Backtrace"); + if (it != end) + { + m_bBacktrace = string_to_bool(it->second.c_str()); + } + it = pSettings.find("BacktraceRemotes"); + if (it != end) + { + m_bBacktraceRemotes = string_to_bool(it->second.c_str()); + } + it = pSettings.find("MemoryMap"); + if (it != end) + { + m_bMemoryMap = string_to_bool(it->second.c_str()); + } + it = pSettings.find("DebugInfo"); + if (it != end) + { + m_sDebugInfo = it->second; + } + it = pSettings.find("DebugInfoCacheMB"); + if (it != end) + { + m_nDebugInfoCacheMB = xatou(it->second.c_str()); + } + it = pSettings.find("GdbTimeoutSec"); + if (it != end) + { + m_nGdbTimeoutSec = xatoi_u(it->second.c_str()); + } + it = pSettings.find("InstallDebugInfo"); + if (it == end) //compat, remove after 0.0.11 + it = pSettings.find("InstallDebuginfo"); + if (it != end) + { + m_bInstallDebugInfo = string_to_bool(it->second.c_str()); + } + m_sDebugInfoDirs = DEBUGINFO_CACHE_DIR; + it = pSettings.find("ReadonlyLocalDebugInfoDirs"); + if (it != end) + { + m_sDebugInfoDirs += ':'; + m_sDebugInfoDirs += it->second; + } +} + +//ok to delete? +//const map_plugin_settings_t& CAnalyzerCCpp::GetSettings() +//{ +// m_pSettings["MemoryMap"] = m_bMemoryMap ? "yes" : "no"; +// m_pSettings["DebugInfo"] = m_sDebugInfo; +// m_pSettings["DebugInfoCacheMB"] = to_string(m_nDebugInfoCacheMB); +// m_pSettings["InstallDebugInfo"] = m_bInstallDebugInfo ? "yes" : "no"; +// +// return m_pSettings; +//} + +PLUGIN_INFO(ANALYZER, + CAnalyzerCCpp, + "CCpp", + "0.0.1", + _("Analyzes crashes in C/C++ programs"), + "zprikryl@redhat.com", + "https://fedorahosted.org/abrt/wiki", + ""); diff --git a/src/plugins/CCpp.h b/src/plugins/CCpp.h new file mode 100644 index 00000000..e95b4d09 --- /dev/null +++ b/src/plugins/CCpp.h @@ -0,0 +1,49 @@ +/* + CCpp.h - header file for C/C++ analyzer plugin + - it can get UUID and memory maps from core files + + 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 CCPP_H_ +#define CCPP_H_ + +#include <string> +#include "plugin.h" +#include "analyzer.h" + +class CAnalyzerCCpp : public CAnalyzer +{ + private: + bool m_bBacktrace; + bool m_bBacktraceRemotes; + bool m_bMemoryMap; + bool m_bInstallDebugInfo; + unsigned m_nDebugInfoCacheMB; + unsigned m_nGdbTimeoutSec; + std::string m_sOldCorePattern; + std::string m_sDebugInfo; + std::string m_sDebugInfoDirs; + + public: + CAnalyzerCCpp(); + virtual void Init(); + virtual void DeInit(); + virtual void SetSettings(const map_plugin_settings_t& pSettings); +}; + +#endif /* CCPP */ diff --git a/src/plugins/FileTransfer.conf b/src/plugins/FileTransfer.conf new file mode 100644 index 00000000..111c1c4b --- /dev/null +++ b/src/plugins/FileTransfer.conf @@ -0,0 +1,35 @@ +# Configuration of the FileTransfer reporter plugin. +Enabled = yes + +# The plugin is invoked in the abrt.conf file, usually in the +# ActionsAndReporters option and/or the [cron] section. +# There are two modes of invocation: +# +# * Specify FileTransfer(one) in ActionsAndReporters directive. +# Immediately after crash is detected, the plugin transfers +# crash data to the server specified via URL directive in this file. +# +# * Specify FileTransfer(store) in ActionsAndReporters directive +# and add "HH:MM = FileTransfer" line in [cron] section. +# At the time of the crash, the plugin stores a record of it +# in its internal list. When specified time is reached, +# the plugin iterates through its internal list and sends +# every recorded crash to the specified URL. +# After that, the internal list is cleared. + + +# URL to upload the files to +# supported: ftp, ftps, http, https, scp, sftp, tftp, file +# for example: ftp://user:password@server.name/directory +# or: scp://user:password@server.name:port/directory etc. +# for testing: file:///tmp/test_directory +URL = + +# Archive type, one of .zip, .tar.gz or .tar.bz2 +ArchiveType = .tar.gz + +# How many times we try to upload the file +RetryCount = 3 + +# How long we wait between we retry the upload (in seconds) +RetryDelay = 20 diff --git a/src/plugins/FileTransfer.cpp b/src/plugins/FileTransfer.cpp new file mode 100644 index 00000000..d964bc9d --- /dev/null +++ b/src/plugins/FileTransfer.cpp @@ -0,0 +1,367 @@ +/* + FileTransfer.cpp + + Copyright (C) 2009 Daniel Novotny (dnovotny@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 _GNU_SOURCE +# define _GNU_SOURCE +#endif +#include <libtar.h> +#include <bzlib.h> +#include <zlib.h> +#include "abrtlib.h" +#include "abrt_curl.h" +#include "FileTransfer.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" + +using namespace std; + +#define HBLEN 255 +#define FILETRANSFER_DIRLIST DEBUG_DUMPS_DIR "/FileTransferDirlist.txt" + +CFileTransfer::CFileTransfer() +: + m_sArchiveType(".tar.gz"), + m_nRetryCount(3), + m_nRetryDelay(20) +{ +} + +void CFileTransfer::SendFile(const char *pURL, const char *pFilename) +{ + int len = strlen(pURL); + if (len == 0) + { + error_msg(_("FileTransfer: URL not specified")); + return; + } + + update_client(_("Sending archive %s to %s"), pFilename, pURL); + + char *whole_url = concat_path_file(pURL, strrchr(pFilename, '/') ? : pFilename); + + int count = m_nRetryCount; + while (1) + { + FILE *f = fopen(pFilename, "r"); + if (!f) + { + free(whole_url); + throw CABRTException(EXCEP_PLUGIN, "Can't open archive file '%s'", pFilename); + } + + struct stat buf; + fstat(fileno(f), &buf); /* never fails */ + + CURL *curl = xcurl_easy_init(); + /* enable uploading */ + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + /* specify target */ + curl_easy_setopt(curl, CURLOPT_URL, whole_url); + /* FILE handle: passed to the default callback, it will fread() it */ + curl_easy_setopt(curl, CURLOPT_READDATA, f); + curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)buf.st_size); + + /* everything is done here; result 0 means success */ + int result = curl_easy_perform(curl); + + curl_easy_cleanup(curl); + fclose(f); + if (result == 0 || --count <= 0) + break; + /* retry the upload if not succesful, wait a bit before next try */ + sleep(m_nRetryDelay); + } + free(whole_url); +} + +static void create_tar(const char *archive_name, const char *directory) +{ + TAR *tar; + + if (tar_open(&tar, (char *)archive_name, NULL, O_WRONLY | O_CREAT, 0644, TAR_GNU) != 0) + { + return; + } + tar_append_tree(tar, (char *)directory, (char*)"."); + tar_close(tar); +} + +static void create_targz(const char *archive_name, const char *directory) +{ + char *name_without_gz = xstrdup(archive_name); + strrchr(name_without_gz, '.')[0] = '\0'; + + create_tar(name_without_gz, directory); + + int fd = open(name_without_gz, O_RDONLY); + if (fd < 0) + { + remove(name_without_gz); + free(name_without_gz); + return; + } + + gzFile gz = gzopen(archive_name, "w"); + if (gz == NULL) + { + close(fd); + remove(name_without_gz); + free(name_without_gz); + return; + } + + char buf[BUFSIZ]; + ssize_t bytesRead; + while ((bytesRead = full_read(fd, buf, BUFSIZ)) > 0) + { + gzwrite(gz, buf, bytesRead); // TODO: check that return value == bytesRead + } + + gzclose(gz); + close(fd); + remove(name_without_gz); + free(name_without_gz); +} + +static void create_tarbz2(const char * archive_name, const char * directory) +{ + char *name_without_bz2 = xstrdup(archive_name); + strrchr(name_without_bz2, '.')[0] = '\0'; + + create_tar(name_without_bz2, directory); + + int tarFD = open(name_without_bz2, O_RDONLY); + if (tarFD == -1) + { + remove(name_without_bz2); + free(name_without_bz2); + return; + } + FILE *f = fopen(archive_name, "w"); + if (f == NULL) + { + close(tarFD); + remove(name_without_bz2); + free(name_without_bz2); + return; + } + int bzError; + BZFILE *bz = BZ2_bzWriteOpen(&bzError, f, /*BLOCK_MULTIPLIER:*/ 7, 0, 0); + if (bz == NULL) + { + fclose(f); + close(tarFD); + remove(name_without_bz2); + free(name_without_bz2); + return; + } + + char buf[BUFSIZ]; + ssize_t bytesRead; + while ((bytesRead = read(tarFD, buf, BUFSIZ)) > 0) + { + BZ2_bzWrite(&bzError, bz, buf, bytesRead); + } + + BZ2_bzWriteClose(&bzError, bz, 0, NULL, NULL); + fclose(f); + close(tarFD); + remove(name_without_bz2); + free(name_without_bz2); +} + +void CFileTransfer::CreateArchive(const char *pArchiveName, const char *pDir) +{ + if (m_sArchiveType == ".tar") + { + create_tar(pArchiveName, pDir); + } + else if (m_sArchiveType == ".tar.gz") + { + create_targz(pArchiveName, pDir); + } + else if (m_sArchiveType == ".tar.bz2") + { + create_tarbz2(pArchiveName, pDir); + } + else + { + throw CABRTException(EXCEP_PLUGIN, "Unknown/unsupported archive type %s", m_sArchiveType.c_str()); + } +} + +/* Returns the last component of the directory path. + * Careful to not return "" on "/path/path2/", but "path2". + */ +static string DirBase(const char *pStr) +{ + int end = strlen(pStr); + if (end > 1 && pStr[end-1] == '/') + { + end--; + } + int beg = end; + while (beg > 0 && pStr[beg-1] != '/') + { + beg--; + } + return string(pStr + beg, end - beg); +} + +void CFileTransfer::Run(const char *pActionDir, const char *pArgs, int force) +{ + if (strcmp(pArgs, "store") == 0) + { + /* Remember pActiveDir for later sending */ + FILE *dirlist = fopen(FILETRANSFER_DIRLIST, "a"); + if (!dirlist) + { + throw CABRTException(EXCEP_PLUGIN, "Can't open "FILETRANSFER_DIRLIST); + } + fprintf(dirlist, "%s\n", pActionDir); + fclose(dirlist); + VERB3 log("Remembered '%s' for future file transfer", pActionDir); + return; + } + + update_client(_("FileTransfer: Creating a report...")); + + char hostname[HBLEN]; + gethostname(hostname, HBLEN-1); + hostname[HBLEN-1] = '\0'; + + char tmpdir_name[] = "/tmp/abrtuploadXXXXXX"; + /* mkdtemp does mkdir(xxx, 0700), should be safe (is it?) */ + if (mkdtemp(tmpdir_name) == NULL) + { + throw CABRTException(EXCEP_PLUGIN, "Can't mkdir a temporary directory in /tmp"); + } + + if (strcmp(pArgs, "one") == 0) + { + /* Just send one archive */ + string archivename = ssprintf("%s/%s-%s%s", tmpdir_name, hostname, DirBase(pActionDir).c_str(), m_sArchiveType.c_str()); + try + { + CreateArchive(archivename.c_str(), pActionDir); + SendFile(m_sURL.c_str(), archivename.c_str()); + } + catch (CABRTException& e) + { + error_msg(_("Cannot create and send an archive: %s"), e.what()); + } + unlink(archivename.c_str()); + } + else + { + /* Tar up and send all remebered directories */ + FILE *dirlist = fopen(FILETRANSFER_DIRLIST, "r"); + if (!dirlist) + { + /* not an error */ + VERB3 log("No saved crashes to transfer"); + goto del_tmp_dir; + } + + char *dirname; + while ((dirname = xmalloc_fgetline(dirlist)) != NULL) + { + string archivename = ssprintf("%s/%s-%s%s", tmpdir_name, hostname, DirBase(dirname).c_str(), m_sArchiveType.c_str()); + try + { + VERB3 log("Creating archive '%s' of dir '%s'", archivename.c_str(), dirname); + CreateArchive(archivename.c_str(), dirname); + VERB3 log("Sending archive to '%s'", m_sURL.c_str()); + SendFile(m_sURL.c_str(), archivename.c_str()); + } + catch (CABRTException& e) + { + error_msg(_("Cannot create and send an archive: %s"), e.what()); + } + VERB3 log("Deleting archive '%s'", archivename.c_str()); + unlink(archivename.c_str()); + free(dirname); + } + + fclose(dirlist); + /* all the files we're able to send should be sent now, + starting over with clean table */ + unlink(FILETRANSFER_DIRLIST); + } + + del_tmp_dir: + rmdir(tmpdir_name); +} + +void CFileTransfer::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("URL"); + if (it != end) + { + m_sURL = it->second; + } + + it = pSettings.find("RetryCount"); + if (it != end) + { + m_nRetryCount = xatoi_u(it->second.c_str()); + } + + it = pSettings.find("RetryDelay"); + if (it != end) + { + m_nRetryDelay = xatoi_u(it->second.c_str()); + } + + it = pSettings.find("ArchiveType"); + if (it != end) + { + /* currently supporting .tar, .tar.gz, .tar.bz2 and .zip */ + m_sArchiveType = it->second; + if (m_sArchiveType[0] != '.') + { + m_sArchiveType = "." + m_sArchiveType; + } + } +} + +//ok to delete? +//const map_plugin_settings_t& CFileTransfer::GetSettings() +//{ +// m_pSettings["URL"] = m_sURL; +// m_pSettings["RetryCount"] = to_string(m_nRetryCount); +// m_pSettings["RetryDelay"] = to_string(m_nRetryDelay); +// m_pSettings["ArchiveType"] = m_sArchiveType; +// +// return m_pSettings; +//} + +PLUGIN_INFO(ACTION, + CFileTransfer, + "FileTransfer", + "0.0.6", + _("Sends a report via FTP or SCTP"), + "dnovotny@redhat.com", + "https://fedorahosted.org/abrt/wiki", + ""); diff --git a/src/plugins/FileTransfer.h b/src/plugins/FileTransfer.h new file mode 100644 index 00000000..17bebf3d --- /dev/null +++ b/src/plugins/FileTransfer.h @@ -0,0 +1,46 @@ +/* + FileTransfer.h - header file for the file transfer plugin + - it uploads the file via ftp or sctp + + Copyright (C) 2009 Daniel Novotny (dnovotny@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 FILETRANSFER_H_ +#define FILETRANSFER_H_ + +#include <string> +#include "plugin.h" +#include "action.h" + +class CFileTransfer : public CAction +{ + private: + std::string m_sURL; + std::string m_sArchiveType; + int m_nRetryCount; + int m_nRetryDelay; + + void CreateArchive(const char *pArchiveName, const char *pDir); + void SendFile(const char *pURL, const char *pFilename); + + public: + CFileTransfer(); + virtual void SetSettings(const map_plugin_settings_t& pSettings); + virtual void Run(const char *pActionDir, const char *pArgs, int force); +}; + +#endif /* FILETRANSFER_H_ */ diff --git a/src/plugins/Kerneloops.conf b/src/plugins/Kerneloops.conf new file mode 100644 index 00000000..67ad07b9 --- /dev/null +++ b/src/plugins/Kerneloops.conf @@ -0,0 +1,13 @@ +Enabled = yes + +# Do we want kernel oopses to be visible to any user? +# Set to "yes" for compatibility with kerneloops.org tool. +InformAllUsers = yes + +# Kerneloops Scanner configuration +################################## +SysLogFile = /var/log/messages + +# KerneloopsReporter configuration +################################## +SubmitURL = http://submit.kerneloops.org/submitoops.php diff --git a/src/plugins/Kerneloops.cpp b/src/plugins/Kerneloops.cpp new file mode 100644 index 00000000..37cab992 --- /dev/null +++ b/src/plugins/Kerneloops.cpp @@ -0,0 +1,31 @@ +/* + 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. + + Authors: + Anton Arapov <anton@redhat.com> + Arjan van de Ven <arjan@linux.intel.com> + */ + +#include "abrtlib.h" +#include "Kerneloops.h" +#include "abrt_exception.h" + +PLUGIN_INFO(ANALYZER, + CAnalyzerKerneloops, + "Kerneloops", + "0.0.2", + _("Analyzes kernel oopses"), + "anton@redhat.com", + "https://people.redhat.com/aarapov", + ""); diff --git a/src/plugins/Kerneloops.h b/src/plugins/Kerneloops.h new file mode 100644 index 00000000..914f1fc8 --- /dev/null +++ b/src/plugins/Kerneloops.h @@ -0,0 +1,38 @@ +/* + * Copyright 2007, Intel Corporation + * Copyright 2009, Red Hat Inc. + * + * This file is part of Abrt. + * + * This program file 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; version 2 of the License. + * + * 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 in a file named COPYING; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * Authors: + * Anton Arapov <anton@redhat.com> + * Arjan van de Ven <arjan@linux.intel.com> + */ + +#ifndef __INCLUDE_GUARD_KERNELOOPS_H_ +#define __INCLUDE_GUARD_KERNELOOPS_H_ + +#include "plugin.h" +#include "analyzer.h" +#include <string> + +class CAnalyzerKerneloops : public CAnalyzer +{ +}; + +#endif diff --git a/src/plugins/KerneloopsReporter.cpp b/src/plugins/KerneloopsReporter.cpp new file mode 100644 index 00000000..ae459737 --- /dev/null +++ b/src/plugins/KerneloopsReporter.cpp @@ -0,0 +1,143 @@ +/* + 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. + + Authors: + Anton Arapov <anton@redhat.com> + Arjan van de Ven <arjan@linux.intel.com> + */ + +#include "abrtlib.h" +#include "comm_layer_inner.h" +#include "abrt_exception.h" +#include "KerneloopsReporter.h" + +using namespace std; + +CKerneloopsReporter::CKerneloopsReporter() +{ + m_pSettings["SubmitURL"] = "http://submit.kerneloops.org/submitoops.php"; +} + +CKerneloopsReporter::~CKerneloopsReporter() +{ +} + +void CKerneloopsReporter::SetSettings(const map_plugin_settings_t& pSettings) +{ + /* Can't simply do this: + + m_pSettings = pSettings; + + * - it will erase keys which aren't present in pSettings. + * Example: if Bugzilla.conf doesn't have "Login = foo", + * then there's no pSettings["Login"] and m_pSettings = pSettings + * will nuke default m_pSettings["Login"] = "", + * making GUI think that we have no "Login" key at all + * and thus never overriding it - even if it *has* an override! + */ + + map_plugin_settings_t::iterator it = m_pSettings.begin(); + while (it != m_pSettings.end()) + { + map_plugin_settings_t::const_iterator override = pSettings.find(it->first); + if (override != pSettings.end()) + { + VERB3 log(" kerneloops settings[%s]='%s'", it->first.c_str(), it->second.c_str()); + it->second = override->second; + } + it++; + } +} + +string CKerneloopsReporter::Report(const map_crash_data_t& crash_data, + const map_plugin_settings_t& settings, + const char *args) +{ + /* abrt-action-kerneloops [-s] -c /etc/arbt/Kerneloops.conf -c - -d pCrashData.dir NULL */ + char *argv[9]; + char **pp = argv; + *pp++ = (char*)"abrt-action-kerneloops"; + +//We want to consume output, so don't redirect to syslog. +// if (logmode & LOGMODE_SYSLOG) +// *pp++ = (char*)"-s"; +//TODO: the actions<->daemon interaction will be changed anyway... + + *pp++ = (char*)"-c"; + *pp++ = (char*)(PLUGINS_CONF_DIR"/Kerneloops."PLUGINS_CONF_EXTENSION); + *pp++ = (char*)"-c"; + *pp++ = (char*)"-"; + *pp++ = (char*)"-d"; + *pp++ = (char*)get_crash_data_item_content_or_NULL(crash_data, CD_DUMPDIR); + *pp = NULL; + int pipefds[2]; + pid_t pid = fork_execv_on_steroids(EXECFLG_INPUT + EXECFLG_OUTPUT + EXECFLG_ERR2OUT, + argv, + pipefds, + /* unsetenv_vec: */ NULL, + /* dir: */ NULL, + /* uid(unused): */ 0 + ); + + /* Write the configuration to stdin */ + map_plugin_settings_t::const_iterator it = settings.begin(); + while (it != settings.end()) + { + full_write_str(pipefds[1], it->first.c_str()); + full_write_str(pipefds[1], "="); + full_write_str(pipefds[1], it->second.c_str()); + full_write_str(pipefds[1], "\n"); + it++; + } + close(pipefds[1]); + + FILE *fp = fdopen(pipefds[0], "r"); + if (!fp) + die_out_of_memory(); + + /* Consume log from stdout */ + string bug_status; + char *buf; + while ((buf = xmalloc_fgetline(fp)) != NULL) + { + if (strncmp(buf, "STATUS:", 7) == 0) + bug_status = buf + 7; + else + if (strncmp(buf, "EXCEPT:", 7) == 0) + { + CABRTException e(EXCEP_PLUGIN, "%s", buf + 7); + free(buf); + fclose(fp); + waitpid(pid, NULL, 0); + throw e; + } + update_client("%s", buf); + free(buf); + } + + fclose(fp); /* this also closes pipefds[0] */ + /* wait for child to actually exit, and prevent leaving a zombie behind */ + waitpid(pid, NULL, 0); + + return bug_status; +} + +PLUGIN_INFO(REPORTER, + CKerneloopsReporter, + "KerneloopsReporter", + "0.0.1", + _("Sends kernel oops information to kerneloops.org"), + "anton@redhat.com", + "http://people.redhat.com/aarapov", + PLUGINS_LIB_DIR"/KerneloopsReporter.glade"); diff --git a/src/plugins/KerneloopsReporter.glade b/src/plugins/KerneloopsReporter.glade new file mode 100644 index 00000000..1ba287b8 --- /dev/null +++ b/src/plugins/KerneloopsReporter.glade @@ -0,0 +1,118 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="2.14"/> + <!-- interface-naming-policy project-wide --> + <object class="GtkDialog" id="PluginDialog"> + <property name="border_width">12</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="icon_name">abrt</property> + <property name="type_hint">normal</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child> + <object class="GtkFrame" id="frame1"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">none</property> + <child> + <object class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <property name="top_padding">6</property> + <property name="left_padding">12</property> + <child> + <object class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="n_columns">2</property> + <property name="column_spacing">12</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="lSubmitURL"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Submit URL:</property> + </object> + <packing> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_SubmitURL"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + </packing> + </child> + </object> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Kerneloops Reporter plugin configuration</b></property> + <property name="use_markup">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="button2"> + <property name="label">gtk-cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="button1"> + <property name="label">gtk-apply</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-6">button2</action-widget> + <action-widget response="-10">button1</action-widget> + </action-widgets> + </object> +</interface> diff --git a/src/plugins/KerneloopsReporter.h b/src/plugins/KerneloopsReporter.h new file mode 100644 index 00000000..e0f4a1bb --- /dev/null +++ b/src/plugins/KerneloopsReporter.h @@ -0,0 +1,47 @@ +/* + * Copyright 2007, Intel Corporation + * Copyright 2009, Red Hat Inc. + * + * This file is part of Abrt. + * + * This program file 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; version 2 of the License. + * + * 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 in a file named COPYING; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * Authors: + * Anton Arapov <anton@redhat.com> + * Arjan van de Ven <arjan@linux.intel.com> + */ + +#ifndef __INCLUDE_GUARD_KERNELOOPSREPORTER_H_ +#define __INCLUDE_GUARD_KERNELOOPSREPORTER_H_ + +#include "plugin.h" +#include "reporter.h" + +#include <string> + +class CKerneloopsReporter : public CReporter +{ + public: + CKerneloopsReporter(); + ~CKerneloopsReporter(); + + virtual void SetSettings(const map_plugin_settings_t& pSettings); + virtual std::string Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs); +}; + +#endif diff --git a/src/plugins/KerneloopsScanner.cpp b/src/plugins/KerneloopsScanner.cpp new file mode 100644 index 00000000..d187daa9 --- /dev/null +++ b/src/plugins/KerneloopsScanner.cpp @@ -0,0 +1,214 @@ +/* + 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: + Anton Arapov <anton@redhat.com> + Arjan van de Ven <arjan@linux.intel.com> +*/ +#include <syslog.h> +#include <asm/unistd.h> /* __NR_syslog */ +#include <glib.h> +#include "abrtlib.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" +#include "KerneloopsSysLog.h" +#include "KerneloopsScanner.h" + +// TODO: https://fedorahosted.org/abrt/ticket/78 + +static int scan_dmesg(GList **oopsList) +{ + VERB1 log("Scanning dmesg"); + + /* syslog(3) - read the last len bytes from the log buffer + * (non-destructively), but dont read more than was written + * into the buffer since the last"clear ring buffer" cmd. + * Returns the number of bytes read. + */ + char *buffer = (char*)xzalloc(16*1024); + syscall(__NR_syslog, 3, buffer, 16*1024 - 1); /* always NUL terminated */ + int cnt_FoundOopses = extract_oopses(oopsList, buffer, strlen(buffer)); + free(buffer); + + return cnt_FoundOopses; +} + + +/* "dumpoops" tool uses these two functions too */ +extern "C" { + +int scan_syslog_file(GList **oopsList, const char *filename, time_t *last_changed_p) +{ + VERB1 log("Scanning syslog file '%s'", filename); + + char *buffer; + struct stat statb; + int fd; + int cnt_FoundOopses; + ssize_t sz; + fd = open(filename, O_RDONLY); + if (fd < 0) + return 0; + statb.st_size = 0; /* paranoia */ + if (fstat(fd, &statb) != 0 || statb.st_size < 1) + { + close(fd); + return 0; + } + + if (last_changed_p != NULL) + { + if (*last_changed_p == statb.st_mtime) + { + VERB1 log("Syslog file '%s' hasn't changed since last scan, skipping", filename); + close(fd); + return 0; + } + *last_changed_p = statb.st_mtime; + } + + /* + * In theory we have a race here, since someone could spew + * to /var/log/messages before we read it in... we try to + * deal with it by reading at most 10kbytes extra. If there's + * more than that.. any oops will be in dmesg anyway. + * Do not try to allocate an absurd amount of memory; ignore + * older log messages because they are unlikely to have + * sufficiently recent data to be useful. 32MB is more + * than enough; it's not worth looping through more log + * if the log is larger than that. + */ + sz = statb.st_size + 10*1024; + if (statb.st_size > (32*1024*1024 - 10*1024)) + { + xlseek(fd, statb.st_size - (32*1024*1024 - 10*1024), SEEK_SET); + sz = 32*1024*1024; + } + buffer = (char*)xzalloc(sz); + sz = full_read(fd, buffer, sz); + close(fd); + + cnt_FoundOopses = 0; + if (sz > 0) + cnt_FoundOopses = extract_oopses(oopsList, buffer, sz); + free(buffer); + + return cnt_FoundOopses; +} + +/* returns number of errors */ +int save_oops_to_debug_dump(GList **oopsList) +{ + unsigned countdown = 16; /* do not report hundreds of oopses */ + unsigned idx = g_list_length(*oopsList); + time_t t = time(NULL); + pid_t my_pid = getpid(); + + VERB1 log("Saving %u oopses as crash dump dirs", idx >= countdown ? countdown-1 : idx); + + int errors = 0; + + while (idx != 0 && --countdown != 0) + { + char path[sizeof(DEBUG_DUMPS_DIR"/kerneloops-%lu-%lu-%lu") + 3 * sizeof(long)*3]; + sprintf(path, DEBUG_DUMPS_DIR"/kerneloops-%lu-%lu-%lu", (long)t, (long)my_pid, (long)idx); + + char *first_line = (char*)g_list_nth_data(*oopsList,--idx); + char *second_line = (char*)strchr(first_line, '\n'); /* never NULL */ + *second_line++ = '\0'; + + struct dump_dir *dd = dd_create(path, /*uid:*/ 0); + if (dd) + { + dd_save_text(dd, FILENAME_ANALYZER, "Kerneloops"); + dd_save_text(dd, FILENAME_EXECUTABLE, "kernel"); + dd_save_text(dd, FILENAME_KERNEL, first_line); + dd_save_text(dd, FILENAME_CMDLINE, "not_applicable"); + dd_save_text(dd, FILENAME_BACKTRACE, second_line); + /* Optional, makes generated bz more informative */ + strchrnul(second_line, '\n')[0] = '\0'; + dd_save_text(dd, FILENAME_REASON, second_line); + dd_close(dd); + } + else + errors++; + } + + return errors; +} + +} /* extern "C" */ + + +CKerneloopsScanner::CKerneloopsScanner() +{ + int cnt_FoundOopses; + m_syslog_last_change = 0; + + /* Scan dmesg, on first call only */ + GList *oopsList = NULL; + cnt_FoundOopses = scan_dmesg(&oopsList); + if (cnt_FoundOopses > 0) + { + int errors = save_oops_to_debug_dump(&oopsList); + if (errors > 0) + log("%d errors while dumping oopses", errors); + } +} + +void CKerneloopsScanner::Run(const char *pActionDir, const char *pArgs, int force) +{ + const char *syslog_file = "/var/log/messages"; + map_plugin_settings_t::const_iterator it = m_pSettings.find("SysLogFile"); + if (it != m_pSettings.end()) + syslog_file = it->second.c_str(); + + GList *oopsList = NULL; + int cnt_FoundOopses = scan_syslog_file(&oopsList, syslog_file, &m_syslog_last_change); + if (cnt_FoundOopses > 0) + { + int errors = save_oops_to_debug_dump(&oopsList); + if (errors > 0) + log("%d errors while dumping oopses", errors); + /* + * This marker in syslog file prevents us from + * re-parsing old oopses (any oops before it is + * ignored by scan_syslog_file()). The only problem + * is that we can't be sure here that syslog_file + * is the file where syslog(xxx) stuff ends up. + */ + openlog("abrt", 0, LOG_KERN); + syslog(LOG_WARNING, + "Kerneloops: Reported %u kernel oopses to Abrt", + cnt_FoundOopses); + closelog(); + } + + for (GList *li = oopsList; li != NULL; li = g_list_next(li)) + free((char*)li->data); + g_list_free(oopsList); +} + +PLUGIN_INFO(ACTION, + CKerneloopsScanner, + "KerneloopsScanner", + "0.0.1", + _("Periodically scans for and saves kernel oopses"), + "anton@redhat.com", + "http://people.redhat.com/aarapov", + ""); diff --git a/src/plugins/KerneloopsScanner.h b/src/plugins/KerneloopsScanner.h new file mode 100644 index 00000000..2bddb0f4 --- /dev/null +++ b/src/plugins/KerneloopsScanner.h @@ -0,0 +1,42 @@ +/* + * Copyright 2007, Intel Corporation + * Copyright 2009, Red Hat Inc. + * + * This file is part of Abrt. + * + * This program file 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; version 2 of the License. + * + * 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 in a file named COPYING; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * Authors: + * Anton Arapov <anton@redhat.com> + * Arjan van de Ven <arjan@linux.intel.com> + */ +#ifndef KERNELOOPSSCANNER_H_ +#define KERNELOOPSSCANNER_H_ + +#include "abrt_types.h" +#include "plugin.h" +#include "action.h" + +class CKerneloopsScanner : public CAction +{ + private: + time_t m_syslog_last_change; + public: + CKerneloopsScanner(); + virtual void Run(const char *pActionDir, const char *pArgs, int force); +}; + +#endif diff --git a/src/plugins/KerneloopsSysLog.cpp b/src/plugins/KerneloopsSysLog.cpp new file mode 100644 index 00000000..68f309bc --- /dev/null +++ b/src/plugins/KerneloopsSysLog.cpp @@ -0,0 +1,383 @@ +/* + 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. + + Authors: + Anton Arapov <anton@redhat.com> + Arjan van de Ven <arjan@linux.intel.com> + */ +#include "abrtlib.h" +#include "KerneloopsSysLog.h" +#include <glib.h> + +static void queue_oops(GList **vec, const char *data, const char *version) +{ + char *ver_data = xasprintf("%s\n%s", version, data); + *vec = g_list_append(*vec, ver_data); +} + +/* + * extract_version tries to find the kernel version in given data + */ +static char *extract_version(const char *linepointer) +{ + if (strstr(linepointer, "Pid") + || strstr(linepointer, "comm") + || strstr(linepointer, "CPU") + || strstr(linepointer, "REGS") + || strstr(linepointer, "EFLAGS") + ) { + char* start; + char* end; + + start = strstr((char*)linepointer, "2.6."); + if (start) + { + end = strchr(start, ')'); + if (!end) + end = strchrnul(start, ' '); + return xstrndup(start, end-start); + } + } + + return NULL; +} + +/* + * extract_oops tries to find oops signatures in a log + */ +struct line_info { + char *ptr; + char level; +}; + +static int record_oops(GList **oopses, struct line_info* lines_info, int oopsstart, int oopsend) +{ + int q; + int len; + char *oops; + char *version; + + len = 2; + for (q = oopsstart; q <= oopsend; q++) + len += strlen(lines_info[q].ptr) + 1; + + oops = (char*)xzalloc(len); + + version = NULL; + for (q = oopsstart; q <= oopsend; q++) + { + if (!version) + version = extract_version(lines_info[q].ptr); + + if (lines_info[q].ptr[0]) + { + strcat(oops, lines_info[q].ptr); + strcat(oops, "\n"); + } + } + int rv = 1; + /* too short oopses are invalid */ + if (strlen(oops) > 100) + queue_oops(oopses, oops, version ? version : "undefined"); + else + { + VERB3 log("Dropped oops: too short"); + rv = 0; + } + free(oops); + free(version); + return rv; +} +#define REALLOC_CHUNK 1000 +int extract_oopses(GList **oopses, char *buffer, size_t buflen) +{ + char *c; + int linecount = 0; + int lines_info_alloc = 0; + struct line_info *lines_info = NULL; + + /* Split buffer into lines */ + + if (buflen != 0) + buffer[buflen - 1] = '\n'; /* the buffer usually ends with \n, but let's make sure */ + c = buffer; + while (c < buffer + buflen) + { + char linelevel; + char *c9; + char *colon; + + c9 = (char*)memchr(c, '\n', buffer + buflen - c); /* a \n will always be found */ + assert(c9); + *c9 = '\0'; /* turn the \n into a string termination */ + if (c9 == c) + goto next_line; + + /* Is it a syslog file (/var/log/messages or similar)? + * Even though _usually_ it looks like "Nov 19 12:34:38 localhost kernel: xxx", + * some users run syslog in non-C locale: + * "2010-02-22T09:24:08.156534-08:00 gnu-4 gnome-session[2048]: blah blah" + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ !!! + * We detect it by checking for N:NN:NN pattern in first 15 chars + * (and this still is not good enough... false positive: "pci 0000:15:00.0: PME# disabled") + */ + colon = strchr(c, ':'); + if (colon && colon > c && colon < c + 15 + && isdigit(colon[-1]) /* N:... */ + && isdigit(colon[1]) /* ...N:NN:... */ + && isdigit(colon[2]) + && colon[3] == ':' + && isdigit(colon[4]) /* ...N:NN:NN... */ + && isdigit(colon[5]) + ) { + /* It's syslog file, not a bare dmesg */ + + /* Skip non-kernel lines */ + char *kernel_str = strstr(c, "kernel: "); + if (kernel_str == NULL) + { + /* if we see our own marker: + * "hostname abrt: Kerneloops: Reported 1 kernel oopses to Abrt" + * we know we submitted everything upto here already */ + if (strstr(c, "abrt:") && strstr(c, "Abrt")) + { + VERB3 log("Found our marker at line %d, restarting line count from 0", linecount); + linecount = 0; + lines_info_alloc = 0; + free(lines_info); + lines_info = NULL; + } + goto next_line; + } + c = kernel_str + sizeof("kernel: ")-1; + } + + linelevel = 0; + /* store and remove kernel log level */ + if (*c == '<' && c[1] && c[2] == '>') + { + linelevel = c[1]; + c += 3; + } + /* remove jiffies time stamp counter if present */ + if (*c == '[') + { + char *c2 = strchr(c, '.'); + char *c3 = strchr(c, ']'); + if (c2 && c3 && (c2 < c3) && (c3-c) < 14 && (c2-c) < 8) + { + c = c3 + 1; + if (*c == ' ') + c++; + } + } + if (linecount >= lines_info_alloc) + { + lines_info_alloc += REALLOC_CHUNK; + lines_info = (line_info*)xrealloc(lines_info, + lines_info_alloc * sizeof(struct line_info)); + } + lines_info[linecount].ptr = c; + lines_info[linecount].level = linelevel; + linecount++; +next_line: + c = c9 + 1; + } + + /* Analyze lines */ + + int i; + char prevlevel = 0; + int oopsstart = -1; + int inbacktrace = 0; + int oopsesfound = 0; + + i = 0; + while (i < linecount) + { + char *curline = lines_info[i].ptr; + + if (curline == NULL) + { + i++; + continue; + } + while (*curline == ' ') + curline++; + + if (oopsstart < 0) + { + /* find start-of-oops markers */ + if (strstr(curline, "general protection fault:")) + oopsstart = i; + else if (strstr(curline, "BUG:")) + oopsstart = i; + else if (strstr(curline, "kernel BUG at")) + oopsstart = i; + else if (strstr(curline, "do_IRQ: stack overflow:")) + oopsstart = i; + else if (strstr(curline, "RTNL: assertion failed")) + oopsstart = i; + else if (strstr(curline, "Eeek! page_mapcount(page) went negative!")) + oopsstart = i; + else if (strstr(curline, "near stack overflow (cur:")) + oopsstart = i; + else if (strstr(curline, "double fault:")) + oopsstart = i; + else if (strstr(curline, "Badness at")) + oopsstart = i; + else if (strstr(curline, "NETDEV WATCHDOG")) + oopsstart = i; + else if (strstr(curline, "WARNING: at ")) /* WARN_ON() generated message */ + oopsstart = i; + else if (strstr(curline, "Unable to handle kernel")) + oopsstart = i; + else if (strstr(curline, "sysctl table check failed")) + oopsstart = i; + else if (strstr(curline, "INFO: possible recursive locking detected")) + oopsstart = i; + // Not needed: "--[ cut here ]--" is always followed + // by "Badness at", "kernel BUG at", or "WARNING: at" string + //else if (strstr(curline, "------------[ cut here ]------------")) + // oopsstart = i; + else if (strstr(curline, "list_del corruption.")) + oopsstart = i; + else if (strstr(curline, "list_add corruption.")) + oopsstart = i; + if (strstr(curline, "Oops:") && i >= 3) + oopsstart = i-3; + + if (oopsstart >= 0) + { + /* debug information */ + VERB3 { + log("Found oops at line %d: '%s'", oopsstart, lines_info[oopsstart].ptr); + if (oopsstart != i) + log("Trigger line is %d: '%s'", i, c); + } + /* try to find the end marker */ + int i2 = i + 1; + while (i2 < linecount && i2 < (i+50)) + { + if (strstr(lines_info[i2].ptr, "---[ end trace")) + { + inbacktrace = 1; + i = i2; + break; + } + i2++; + } + } + } + + /* Are we entering a call trace part? */ + /* a call trace starts with "Call Trace:" or with the " [<.......>] function+0xFF/0xAA" pattern */ + if (oopsstart >= 0 && !inbacktrace) + { + if (strstr(curline, "Call Trace:")) + inbacktrace = 1; + else + if (strnlen(curline, 9) > 8 + && curline[0] == '[' && curline[1] == '<' + && strstr(curline, ">]") + && strstr(curline, "+0x") + && strstr(curline, "/0x") + ) { + inbacktrace = 1; + } + } + + /* Are we at the end of an oops? */ + else if (oopsstart >= 0 && inbacktrace) + { + int oopsend = INT_MAX; + + /* line needs to start with " [" or have "] [" if it is still a call trace */ + /* example: "[<ffffffffa006c156>] radeon_get_ring_head+0x16/0x41 [radeon]" */ + if (curline[0] != '[' + && !strstr(curline, "] [") + && !strstr(curline, "--- Exception") + && !strstr(curline, "LR =") + && !strstr(curline, "<#DF>") + && !strstr(curline, "<IRQ>") + && !strstr(curline, "<EOI>") + && !strstr(curline, "<<EOE>>") + && strncmp(curline, "Code: ", 6) != 0 + && strncmp(curline, "RIP ", 4) != 0 + && strncmp(curline, "RSP ", 4) != 0 + ) { + oopsend = i-1; /* not a call trace line */ + } + /* oops lines are always more than 8 chars long */ + else if (strnlen(curline, 8) < 8) + oopsend = i-1; + /* single oopses are of the same loglevel */ + else if (lines_info[i].level != prevlevel) + oopsend = i-1; + else if (strstr(curline, "Instruction dump:")) + oopsend = i; + /* if a new oops starts, this one has ended */ + else if (strstr(curline, "WARNING: at ") && oopsstart != i) /* WARN_ON() generated message */ + oopsend = i-1; + else if (strstr(curline, "Unable to handle") && oopsstart != i) + oopsend = i-1; + /* kernel end-of-oops marker (not including marker itself) */ + else if (strstr(curline, "---[ end trace")) + oopsend = i-1; + + if (oopsend <= i) + { + VERB3 log("End of oops at line %d (%d): '%s'", oopsend, i, lines_info[oopsend].ptr); + if (record_oops(oopses, lines_info, oopsstart, oopsend)) + oopsesfound++; + oopsstart = -1; + inbacktrace = 0; + } + } + + prevlevel = lines_info[i].level; + i++; + + if (oopsstart >= 0) + { + /* Do we have a suspiciously long oops? Cancel it */ + if (i-oopsstart > 60) + { + inbacktrace = 0; + oopsstart = -1; + VERB3 log("Dropped oops, too long"); + continue; + } + if (!inbacktrace && i-oopsstart > 40) + { + /*inbacktrace = 0; - already is */ + oopsstart = -1; + VERB3 log("Dropped oops, too long"); + continue; + } + } + } /* while (i < linecount) */ + + /* process last oops if we have one */ + if (oopsstart >= 0 && inbacktrace) + { + int oopsend = i-1; + VERB3 log("End of oops at line %d (end of file): '%s'", oopsend, lines_info[oopsend].ptr); + if (record_oops(oopses, lines_info, oopsstart, oopsend)) + oopsesfound++; + } + + free(lines_info); + return oopsesfound; +} diff --git a/src/plugins/KerneloopsSysLog.h b/src/plugins/KerneloopsSysLog.h new file mode 100644 index 00000000..d8b4d32b --- /dev/null +++ b/src/plugins/KerneloopsSysLog.h @@ -0,0 +1,35 @@ +/* + * Copyright 2007, Intel Corporation + * Copyright 2009, Red Hat Inc. + * + * This file is part of Abrt. + * + * This program file 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; version 2 of the License. + * + * 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 in a file named COPYING; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * Authors: + * Anton Arapov <anton@redhat.com> + * Arjan van de Ven <arjan@linux.intel.com> + */ + +#ifndef __INCLUDE_GUARD_KERNELOOPSSYSLOG_H_ +#define __INCLUDE_GUARD_KERNELOOPSSYSLOG_H_ + +#include "abrt_types.h" +#include <glib.h> + +int extract_oopses(GList **oopses, char *buffer, size_t buflen); + +#endif diff --git a/src/plugins/Logger.conf b/src/plugins/Logger.conf new file mode 100644 index 00000000..aadd3515 --- /dev/null +++ b/src/plugins/Logger.conf @@ -0,0 +1,8 @@ +# Description: Writes report to a file + +# Configuration for Logger plugin +Enabled = yes + +LogPath = /var/log/abrt.log + +AppendLogs = yes diff --git a/src/plugins/Logger.glade b/src/plugins/Logger.glade new file mode 100644 index 00000000..a0a909a4 --- /dev/null +++ b/src/plugins/Logger.glade @@ -0,0 +1,135 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="2.14"/> + <!-- interface-naming-policy project-wide --> + <object class="GtkDialog" id="PluginDialog"> + <property name="border_width">12</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="window_position">center-on-parent</property> + <property name="icon_name">abrt</property> + <property name="type_hint">normal</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child> + <object class="GtkFrame" id="frame1"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">none</property> + <child> + <object class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <property name="top_padding">6</property> + <property name="left_padding">12</property> + <child> + <object class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="n_rows">2</property> + <property name="n_columns">2</property> + <property name="column_spacing">12</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="lLogPath"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Logger file:</property> + </object> + <packing> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_LogPath"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="conf_AppendLogs"> + <property name="label" translatable="yes">Append new logs</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="xalign">0</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + </object> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Logger plugin configuration</b></property> + <property name="use_markup">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="button2"> + <property name="label">gtk-cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="button1"> + <property name="label">gtk-apply</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-6">button2</action-widget> + <action-widget response="-10">button1</action-widget> + </action-widgets> + </object> +</interface> diff --git a/src/plugins/Mailx.conf b/src/plugins/Mailx.conf new file mode 100644 index 00000000..1d946427 --- /dev/null +++ b/src/plugins/Mailx.conf @@ -0,0 +1,17 @@ +# Description: Sends an email with a report (using mailx command) + +# Configuration to Email reporter plugin +Enabled = yes + +# In abrt.conf, plugin takes one parameter: subject (in "" if you need to embed spaces). +# If it isn't specified, then a default subject is taken from this file +Subject = "[abrt] crash report" + +# Your Email +EmailFrom = user@localhost + +# Email To +EmailTo = root@localhost + +# Warning! enabling this may cause sending a lot of MB via email +SendBinaryData = no diff --git a/src/plugins/Mailx.cpp b/src/plugins/Mailx.cpp new file mode 100644 index 00000000..255c873d --- /dev/null +++ b/src/plugins/Mailx.cpp @@ -0,0 +1,153 @@ +/* + Mailx.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 "abrtlib.h" +#include "Mailx.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" + +#define MAILX_COMMAND "/bin/mailx" + +CMailx::CMailx() +{ + m_email_from = xstrdup("user@localhost"); + m_email_to = xstrdup("root@localhost"); + m_subject = xstrdup("[abrt] full crash report"); + m_send_binary_data = false; +} + +CMailx::~CMailx() +{ + free(m_email_from); + free(m_email_to); + free(m_subject); +} + +static void exec_and_feed_input(uid_t uid, const char* text, char **args) +{ + int pipein[2]; + + pid_t child = fork_execv_on_steroids( + EXECFLG_INPUT | EXECFLG_QUIET | EXECFLG_SETGUID, + args, + pipein, + /*unsetenv_vec:*/ NULL, + /*dir:*/ NULL, + uid); + + full_write_str(pipein[1], text); + close(pipein[1]); + + waitpid(child, NULL, 0); /* wait for command completion */ +} + +static char** append_str_to_vector(char **vec, unsigned &size, const char *str) +{ + //log("old vec: %p", vec); + vec = (char**) xrealloc(vec, (size+2) * sizeof(vec[0])); + vec[size] = xstrdup(str); + //log("new vec: %p, added [%d] %p", vec, size, vec[size]); + size++; + vec[size] = NULL; + return vec; +} + +std::string CMailx::Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs) +{ + SetSettings(pSettings); + char **args = NULL; + unsigned arg_size = 0; + args = append_str_to_vector(args, arg_size, MAILX_COMMAND); + + char *dsc = make_dsc_mailx(pCrashData); + + map_crash_data_t::const_iterator it; + for (it = pCrashData.begin(); it != pCrashData.end(); it++) + { + if (it->second[CD_TYPE] == CD_BIN && m_send_binary_data) + { + args = append_str_to_vector(args, arg_size, "-a"); + args = append_str_to_vector(args, arg_size, it->second[CD_CONTENT].c_str()); + } + } + + args = append_str_to_vector(args, arg_size, "-s"); + args = append_str_to_vector(args, arg_size, (pArgs[0] != '\0' ? pArgs : m_subject)); + args = append_str_to_vector(args, arg_size, "-r"); + args = append_str_to_vector(args, arg_size, m_email_from); + args = append_str_to_vector(args, arg_size, m_email_to); + + update_client(_("Sending an email...")); + const char *uid_str = get_crash_data_item_content_or_NULL(pCrashData, CD_UID); + exec_and_feed_input(xatoi_u(uid_str), dsc, args); + + free(dsc); + + while (*args) + { + free(*args++); + } + args -= arg_size; + free(args); + + return ssprintf("Email was sent to: %s", m_email_to); +} + +void CMailx::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("Subject"); + if (it != end) + { + free(m_subject); + m_subject = xstrdup(it->second.c_str()); + } + it = pSettings.find("EmailFrom"); + if (it != end) + { + free(m_email_from); + m_email_from = xstrdup(it->second.c_str()); + } + it = pSettings.find("EmailTo"); + if (it != end) + { + free(m_email_to); + m_email_to = xstrdup(it->second.c_str()); + } + it = pSettings.find("SendBinaryData"); + if (it != end) + { + m_send_binary_data = string_to_bool(it->second.c_str()); + } +} + +PLUGIN_INFO(REPORTER, + CMailx, + "Mailx", + "0.0.2", + _("Sends an email with a report (via mailx command)"), + "zprikryl@redhat.com", + "https://fedorahosted.org/abrt/wiki", + PLUGINS_LIB_DIR"/Mailx.glade"); diff --git a/src/plugins/Mailx.glade b/src/plugins/Mailx.glade new file mode 100644 index 00000000..656204b5 --- /dev/null +++ b/src/plugins/Mailx.glade @@ -0,0 +1,184 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="2.14"/> + <!-- interface-naming-policy project-wide --> + <object class="GtkDialog" id="PluginDialog"> + <property name="border_width">12</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="window_position">center-on-parent</property> + <property name="icon_name">abrt</property> + <property name="type_hint">normal</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child> + <object class="GtkFrame" id="frame1"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">none</property> + <child> + <object class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <property name="top_padding">6</property> + <property name="left_padding">12</property> + <child> + <object class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="n_rows">4</property> + <property name="n_columns">2</property> + <property name="column_spacing">12</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="lSubject"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Subject:</property> + </object> + <packing> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_Subject"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_EmailFrom"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="lEmailFrom"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Your Email:</property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="lEmailTo"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Recipient's Email:</property> + </object> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_EmailTo"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="conf_SendBinaryData"> + <property name="label" translatable="yes">Send Binary Data</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + </packing> + </child> + </object> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Mailx plugin configuration</b></property> + <property name="use_markup">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="button2"> + <property name="label">gtk-cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="button1"> + <property name="label">gtk-apply</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-6">button2</action-widget> + <action-widget response="-10">button1</action-widget> + </action-widgets> + </object> +</interface> diff --git a/src/plugins/Mailx.h b/src/plugins/Mailx.h new file mode 100644 index 00000000..326a6371 --- /dev/null +++ b/src/plugins/Mailx.h @@ -0,0 +1,48 @@ +/* + Mailx.h - header file for Mailx reporter plugin + - it simple sends an email to specific address via mailx command + + 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 MAILX_H_ +#define MAILX_H_ + +#include <string> +#include "plugin.h" +#include "reporter.h" + +class CMailx : public CReporter +{ + private: + char *m_email_from; + char *m_email_to; + char *m_subject; + bool m_send_binary_data; + + public: + CMailx(); + ~CMailx(); + + virtual void SetSettings(const map_plugin_settings_t& pSettings); + + virtual std::string Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs); +}; + +#endif diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am new file mode 100644 index 00000000..2cb85709 --- /dev/null +++ b/src/plugins/Makefile.am @@ -0,0 +1,316 @@ +INC_PATH=$(srcdir)/../../inc +UTILS_PATH=$(srcdir)/../../lib/utils +AM_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) +pluginslibdir=$(PLUGINS_LIB_DIR) +libexec_SCRIPTS = \ + abrt-action-install-debuginfo + +pluginslib_LTLIBRARIES = \ + libCCpp.la \ + libMailx.la \ + libSQLite3.la \ + libKerneloopsScanner.la\ + libKerneloops.la \ + libKerneloopsReporter.la \ + libSOSreport.la \ + libBugzilla.la \ + libRHTSupport.la \ + libReportUploader.la \ + libPython.la \ + libFileTransfer.la + +dist_pluginslib_DATA = \ + Logger.glade \ + Mailx.glade \ + Bugzilla.glade \ + RHTSupport.glade \ + ReportUploader.glade \ + KerneloopsReporter.glade + +pluginsconfdir = $(PLUGINS_CONF_DIR) +dist_pluginsconf_DATA = \ + CCpp.conf \ + Mailx.conf \ + SQLite3.conf \ + Logger.conf \ + Kerneloops.conf \ + Bugzilla.conf \ + RHTSupport.conf \ + ReportUploader.conf \ + FileTransfer.conf \ + Python.conf \ + SOSreport.conf + +man_MANS = \ + abrt-FileTransfer.7 \ + abrt-Bugzilla.7 \ + abrt-KerneloopsScanner.7 \ + abrt-KerneloopsReporter.7 \ + abrt-Logger.7 \ + abrt-Mailx.7 \ + abrt-plugins.7 \ + abrt-SQLite3.7 \ + abrt-ReportUploader.7 + +EXTRA_DIST = $(man_MANS) abrt-action-install-debuginfo + +$(DESTDIR)/$(DEBUG_INFO_DIR): + $(mkdir_p) '$@' + +install-data-hook: $(DESTDIR)/$(DEBUG_INFO_DIR) + sed 's: = /var/: = $(localstatedir)/:g' -i \ + $(DESTDIR)$(sysconfdir)/abrt/plugins/SQLite3.conf \ + $(DESTDIR)$(sysconfdir)/abrt/plugins/Logger.conf + +# CCpp +libCCpp_la_SOURCES = CCpp.cpp CCpp.h +libCCpp_la_LDFLAGS = -avoid-version +libCCpp_la_CPPFLAGS = -Wall -Werror \ + -I$(INC_PATH) -I$(UTILS_PATH) \ + -DCCPP_HOOK_PATH=\"${libexecdir}/abrt-hook-ccpp\" \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' +# -DHOSTILE_KERNEL + +# Kerneloops +libKerneloops_la_SOURCES = Kerneloops.cpp Kerneloops.h +libKerneloops_la_LDFLAGS = -avoid-version +libKerneloops_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) + +# KerneloopsReporter +libKerneloopsReporter_la_SOURCES = KerneloopsReporter.cpp KerneloopsReporter.h +libKerneloopsReporter_la_LDFLAGS = -avoid-version +libKerneloopsReporter_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" + +# KerneloopsScanner +libKerneloopsScanner_la_SOURCES = KerneloopsScanner.cpp KerneloopsScanner.h KerneloopsSysLog.cpp KerneloopsSysLog.h +libKerneloopsScanner_la_LDFLAGS = -avoid-version $(GLIB_LIBS) +libKerneloopsScanner_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" $(GLIB_CFLAGS) + +# Mailx +libMailx_la_SOURCES = Mailx.cpp Mailx.h +libMailx_la_LDFLAGS = -avoid-version +libMailx_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" -DLOCALSTATEDIR='"$(localstatedir)"' + +# SQLite3 +libSQLite3_la_SOURCES = SQLite3.cpp SQLite3.h +libSQLite3_la_LDFLAGS = -avoid-version +libSQLite3_la_LIBADD = $(SQLITE3_LIBS) $(GLIB_LIBS) +libSQLite3_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) $(SQLITE3_CFLAGS) -DLOCALSTATEDIR='"$(localstatedir)"' $(GLIB_CFLAGS) + +# SOSreport +libSOSreport_la_SOURCES = SOSreport.cpp SOSreport.h +libSOSreport_la_LDFLAGS = -avoid-version + +# Bugzilla +libBugzilla_la_SOURCES = Bugzilla.h Bugzilla.cpp +libBugzilla_la_LIBADD = +libBugzilla_la_LDFLAGS = -avoid-version +libBugzilla_la_CPPFLAGS = \ + -I$(INC_PATH) -I$(UTILS_PATH) \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" + +# RHTSupport +libRHTSupport_la_SOURCES = RHTSupport.h RHTSupport.cpp +libRHTSupport_la_LIBADD = +libRHTSupport_la_LDFLAGS = -avoid-version +libRHTSupport_la_CPPFLAGS = \ + -I$(INC_PATH) -I$(UTILS_PATH) \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' + +# ReportUploader +libReportUploader_la_SOURCES = ReportUploader.h ReportUploader.cpp +libReportUploader_la_LDFLAGS = -avoid-version +libReportUploader_la_LIBADD = $(CURL_LIBS) +libReportUploader_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) $(CURL_CFLAGS) -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" + +# Python +libPython_la_SOURCES = Python.h Python.cpp +#libPython_la_LIBADD = $(NSS_LIBS) +libPython_la_LDFLAGS = -avoid-version +libPython_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) + +# FileTrasfer +libFileTransfer_la_SOURCES = FileTransfer.cpp FileTransfer.h +libFileTransfer_la_LDFLAGS = -avoid-version -ltar -lbz2 -lz +libFileTransfer_la_LIBADD = $(CURL_LIBS) +libFileTransfer_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) $(CURL_CFLAGS) -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" + +libexec_PROGRAMS = \ + abrt-action-analyze-c \ + abrt-action-analyze-python \ + abrt-action-analyze-oops \ + abrt-action-generate-backtrace \ + abrt-action-bugzilla \ + abrt-action-rhtsupport \ + abrt-action-kerneloops \ + abrt-action-print + +abrt_action_analyze_c_SOURCES = \ + abrt-action-analyze-c.c +abrt_action_analyze_c_CPPFLAGS = \ + -I$(srcdir)/../../inc \ + -I$(srcdir)/../../lib/utils \ + -DBIN_DIR=\"$(bindir)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -D_GNU_SOURCE \ + -Wall -Werror +abrt_action_analyze_c_LDADD = \ + ../../lib/utils/libABRTUtils.la + +abrt_action_analyze_python_SOURCES = \ + abrt-action-analyze-python.c +abrt_action_analyze_python_CPPFLAGS = \ + -I$(srcdir)/../../inc \ + -I$(srcdir)/../../lib/utils \ + -DBIN_DIR=\"$(bindir)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -D_GNU_SOURCE \ + -Wall -Werror +abrt_action_analyze_python_LDADD = \ + ../../lib/utils/libABRTUtils.la + +abrt_action_analyze_oops_SOURCES = \ + abrt-action-analyze-oops.c +abrt_action_analyze_oops_CPPFLAGS = \ + -I$(srcdir)/../../inc \ + -I$(srcdir)/../../lib/utils \ + -DBIN_DIR=\"$(bindir)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -D_GNU_SOURCE \ + -Wall -Werror +abrt_action_analyze_oops_LDADD = \ + ../../lib/utils/libABRTUtils.la + +abrt_action_generate_backtrace_SOURCES = \ + abrt-action-generate-backtrace.c +abrt_action_generate_backtrace_CPPFLAGS = \ + -I$(srcdir)/../../inc \ + -I$(srcdir)/../../lib/utils \ + -DBIN_DIR=\"$(bindir)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -D_GNU_SOURCE \ + -Wall -Werror +abrt_action_generate_backtrace_LDADD = \ + ../../lib/utils/libABRTUtils.la \ + ../btparser/libbtparser.la + +abrt_action_bugzilla_SOURCES = \ + abrt-action-bugzilla.cpp +abrt_action_bugzilla_CPPFLAGS = \ + -I$(srcdir)/../../inc \ + -I$(srcdir)/../../lib/utils \ + -DBIN_DIR=\"$(bindir)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' \ + -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) \ + -D_GNU_SOURCE \ + -Wall -Werror +abrt_action_bugzilla_LDADD = \ + $(GLIB_LIBS) \ + ../../lib/utils/libABRT_web_utils.la \ + ../../lib/utils/libABRTdUtils.la \ + ../../lib/utils/libABRTUtils.la + +abrt_action_rhtsupport_SOURCES = \ + abrt_rh_support.h abrt_rh_support.c \ + abrt-action-rhtsupport.cpp +abrt_action_rhtsupport_CPPFLAGS = \ + -I$(srcdir)/../../inc \ + -I$(srcdir)/../../lib/utils \ + -DBIN_DIR=\"$(bindir)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' \ + -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) \ + $(XMLRPC_CFLAGS) $(XMLRPC_CLIENT_CFLAGS) \ + -D_GNU_SOURCE \ + -Wall -Werror +abrt_action_rhtsupport_LDFLAGS = -ltar +abrt_action_rhtsupport_LDADD = \ + $(GLIB_LIBS) \ + $(XMLRPC_LIBS) $(XMLRPC_CLIENT_LIBS) \ + ../../lib/utils/libABRT_web_utils.la \ + ../../lib/utils/libABRTdUtils.la \ + ../../lib/utils/libABRTUtils.la + +abrt_action_kerneloops_SOURCES = \ + abrt-action-kerneloops.cpp +abrt_action_kerneloops_CPPFLAGS = \ + -I$(srcdir)/../../inc \ + -I$(srcdir)/../../lib/utils \ + -DBIN_DIR=\"$(bindir)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' \ + -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) \ + -D_GNU_SOURCE \ + -Wall -Werror +# libABRTdUtils is used only because of LoadPluginSettings: +abrt_action_kerneloops_LDADD = \ + ../../lib/utils/libABRT_web_utils.la \ + ../../lib/utils/libABRTdUtils.la \ + ../../lib/utils/libABRTUtils.la + +abrt_action_print_SOURCES = \ + abrt-action-print.cpp +abrt_action_print_CPPFLAGS = \ + -I$(srcdir)/../../inc \ + -I$(srcdir)/../../lib/utils \ + -DBIN_DIR=\"$(bindir)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' \ + -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) \ + -D_GNU_SOURCE \ + -Wall -Werror +# libABRTdUtils is used only because of make_description_logger: +abrt_action_print_LDADD = \ + ../../lib/utils/libABRTdUtils.la \ + ../../lib/utils/libABRTUtils.la + +DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ diff --git a/src/plugins/Python.conf b/src/plugins/Python.conf new file mode 100644 index 00000000..3201c6da --- /dev/null +++ b/src/plugins/Python.conf @@ -0,0 +1 @@ +Enabled = yes diff --git a/src/plugins/Python.cpp b/src/plugins/Python.cpp new file mode 100644 index 00000000..e955b5fb --- /dev/null +++ b/src/plugins/Python.cpp @@ -0,0 +1,30 @@ +/* + 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 "Python.h" +#include "abrt_exception.h" + +PLUGIN_INFO(ANALYZER, + CAnalyzerPython, + "Python", + "0.0.1", + _("Analyzes crashes in Python programs"), + "zprikryl@redhat.com, jmoskovc@redhat.com", + "https://fedorahosted.org/abrt/wiki", + ""); diff --git a/src/plugins/Python.h b/src/plugins/Python.h new file mode 100644 index 00000000..3f01d2c6 --- /dev/null +++ b/src/plugins/Python.h @@ -0,0 +1,30 @@ +/* + 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 PYTHON_H_ +#define PYTHON_H_ + +#include <string> +#include "plugin.h" +#include "analyzer.h" + +class CAnalyzerPython : public CAnalyzer +{ +}; + +#endif /* PYTHON_H_ */ diff --git a/src/plugins/RHTSupport.conf b/src/plugins/RHTSupport.conf new file mode 100644 index 00000000..ecd5992a --- /dev/null +++ b/src/plugins/RHTSupport.conf @@ -0,0 +1,11 @@ +# Description: Reports crashes to Red Hat support + +Enabled = yes + +URL = https://api.access.redhat.com/rs +# No means that ssl certificates will not be checked +SSLVerify = yes +# Your login has to exist +Login = +# Your password +Password = diff --git a/src/plugins/RHTSupport.cpp b/src/plugins/RHTSupport.cpp new file mode 100644 index 00000000..3732afe3 --- /dev/null +++ b/src/plugins/RHTSupport.cpp @@ -0,0 +1,147 @@ +/* + 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 "crash_types.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" +#include "RHTSupport.h" + +using namespace std; + +CReporterRHticket::CReporterRHticket() +{ + m_pSettings["URL"] = "https://api.access.redhat.com/rs"; + m_pSettings["Login"] = ""; + m_pSettings["Password"] = ""; + m_pSettings["SSLVerify"] = "yes"; +} + +CReporterRHticket::~CReporterRHticket() +{ +} + +void CReporterRHticket::SetSettings(const map_plugin_settings_t& pSettings) +{ + /* Can't simply do this: + + m_pSettings = pSettings; + + * - it will erase keys which aren't present in pSettings. + * Example: if Bugzilla.conf doesn't have "Login = foo", + * then there's no pSettings["Login"] and m_pSettings = pSettings + * will nuke default m_pSettings["Login"] = "", + * making GUI think that we have no "Login" key at all + * and thus never overriding it - even if it *has* an override! + */ + + map_plugin_settings_t::iterator it = m_pSettings.begin(); + while (it != m_pSettings.end()) + { + map_plugin_settings_t::const_iterator override = pSettings.find(it->first); + if (override != pSettings.end()) + { + VERB3 log(" rhtsupport settings[%s]='%s'", it->first.c_str(), it->second.c_str()); + it->second = override->second; + } + it++; + } +} + +string CReporterRHticket::Report(const map_crash_data_t& crash_data, + const map_plugin_settings_t& settings, + const char *args) +{ + /* abrt-action-rhtsupport [-s] -c /etc/arbt/RHTSupport.conf -c - -d pCrashData.dir NULL */ + char *argv[9]; + char **pp = argv; + *pp++ = (char*)"abrt-action-rhtsupport"; + +//We want to consume output, so don't redirect to syslog. +// if (logmode & LOGMODE_SYSLOG) +// *pp++ = (char*)"-s"; +//TODO: the actions<->daemon interaction will be changed anyway... + + *pp++ = (char*)"-c"; + *pp++ = (char*)(PLUGINS_CONF_DIR"/RHTSupport."PLUGINS_CONF_EXTENSION); + *pp++ = (char*)"-c"; + *pp++ = (char*)"-"; + *pp++ = (char*)"-d"; + *pp++ = (char*)get_crash_data_item_content_or_NULL(crash_data, CD_DUMPDIR); + *pp = NULL; + int pipefds[2]; + pid_t pid = fork_execv_on_steroids(EXECFLG_INPUT + EXECFLG_OUTPUT + EXECFLG_ERR2OUT, + argv, + pipefds, + /* unsetenv_vec: */ NULL, + /* dir: */ NULL, + /* uid(unused): */ 0 + ); + + /* Write the configuration to stdin */ + map_plugin_settings_t::const_iterator it = settings.begin(); + while (it != settings.end()) + { + full_write_str(pipefds[1], it->first.c_str()); + full_write_str(pipefds[1], "="); + full_write_str(pipefds[1], it->second.c_str()); + full_write_str(pipefds[1], "\n"); + it++; + } + close(pipefds[1]); + + FILE *fp = fdopen(pipefds[0], "r"); + if (!fp) + die_out_of_memory(); + + /* Consume log from stdout */ + string bug_status; + char *buf; + while ((buf = xmalloc_fgetline(fp)) != NULL) + { + if (strncmp(buf, "STATUS:", 7) == 0) + bug_status = buf + 7; + else + if (strncmp(buf, "EXCEPT:", 7) == 0) + { + CABRTException e(EXCEP_PLUGIN, "%s", buf + 7); + free(buf); + fclose(fp); + waitpid(pid, NULL, 0); + throw e; + } + update_client("%s", buf); + free(buf); + } + + fclose(fp); /* this also closes pipefds[0] */ + /* wait for child to actually exit, and prevent leaving a zombie behind */ + waitpid(pid, NULL, 0); + + return bug_status; +} + +PLUGIN_INFO(REPORTER, + CReporterRHticket, + "RHticket", + "0.0.4", + _("Reports bugs to Red Hat support"), + "Denys Vlasenko <dvlasenk@redhat.com>", + "https://fedorahosted.org/abrt/wiki", + PLUGINS_LIB_DIR"/RHTSupport.glade"); diff --git a/src/plugins/RHTSupport.glade b/src/plugins/RHTSupport.glade new file mode 100644 index 00000000..64fd6c26 --- /dev/null +++ b/src/plugins/RHTSupport.glade @@ -0,0 +1,213 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="2.14"/> + <!-- interface-naming-policy project-wide --> + <object class="GtkDialog" id="PluginDialog"> + <property name="border_width">12</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="window_position">center-on-parent</property> + <property name="icon_name">abrt</property> + <property name="type_hint">normal</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child> + <object class="GtkFrame" id="frame1"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">none</property> + <child> + <object class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <property name="top_padding">6</property> + <property name="left_padding">12</property> + <child> + <object class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="n_rows">5</property> + <property name="n_columns">2</property> + <property name="column_spacing">12</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="lURL"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">RHTSupport URL:</property> + </object> + <packing> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="lLogin"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Login:</property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="lPassword"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Password:</property> + </object> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_URL"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_Login"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_Password"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="visibility">False</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="cb_Password"> + <property name="label" translatable="yes">Show password</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="xalign">0</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label3"> + <property name="xalign">0</property> + </object> + <packing> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="conf_SSLVerify"> + <property name="label" translatable="yes">SSL verify</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="xalign">0</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="right_attach">2</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + </packing> + </child> + </object> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>RHTSupport plugin configuration</b></property> + <property name="use_markup">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="button2"> + <property name="label">gtk-cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="bApply"> + <property name="label">gtk-apply</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-6">button2</action-widget> + <action-widget response="-10">bApply</action-widget> + </action-widgets> + </object> + <object class="GtkAction" id="action1"/> +</interface> diff --git a/src/plugins/RHTSupport.h b/src/plugins/RHTSupport.h new file mode 100644 index 00000000..2338732f --- /dev/null +++ b/src/plugins/RHTSupport.h @@ -0,0 +1,38 @@ +/* + 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 RHTICKET_H_ +#define RHTICKET_H_ + +#include "plugin.h" +#include "reporter.h" + +class CReporterRHticket: public CReporter +{ + public: + CReporterRHticket(); + virtual ~CReporterRHticket(); + + virtual std::string Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs); + + virtual void SetSettings(const map_plugin_settings_t& pSettings); +}; + +#endif diff --git a/src/plugins/ReportUploader.conf b/src/plugins/ReportUploader.conf new file mode 100644 index 00000000..7a7b9133 --- /dev/null +++ b/src/plugins/ReportUploader.conf @@ -0,0 +1,23 @@ +# Description: Packs crash data into .tar.gz file, optionally uploads it via FTP/SCP/etc + +Enabled = yes + +# Customer = "Example Inc." +# Ticket = IT12345 +# Encrypt = yes +# If set to "no" or commented out, +# compressed ticket data will be copied to /tmp: +# Upload = yes + +# If "Upload = yes", URL to upload the files to. +# supported: ftp, ftps, http, https, scp, sftp, tftp, file +# for example: ftp://user:password@server.name/directory +# or: scp://user:password@server.name:port/directory etc. +# for testing: file:///tmp/test_directory +# URL = + +# How many times we try to upload the file +# RetryCount = 3 + +# How long we wait between we retry the upload (in seconds) +# RetryDelay = 20 diff --git a/src/plugins/ReportUploader.cpp b/src/plugins/ReportUploader.cpp new file mode 100644 index 00000000..4100e996 --- /dev/null +++ b/src/plugins/ReportUploader.cpp @@ -0,0 +1,517 @@ +/* + ReportUploader.cpp + + 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 "abrt_curl.h" +#include "ReportUploader.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" + +using namespace std; + + +CReportUploader::CReportUploader() : + m_bEncrypt(false), + m_bUpload(false), + m_nRetryCount(3), + m_nRetryDelay(20) +{} + +CReportUploader::~CReportUploader() +{} + + +static void RunCommand(const char *cmd) +{ + int retcode = system(cmd); + if (retcode) + { + throw CABRTException(EXCEP_PLUGIN, "'%s' exited with %d", cmd, retcode); + } +} + +static string ReadCommand(const char *cmd) +{ + FILE* fp = popen(cmd, "r"); + if (!fp) + { + throw CABRTException(EXCEP_PLUGIN, "Error running '%s'", cmd); + } + + string result; + char *buff; + while ((buff = xmalloc_fgetline(fp)) != NULL) + { + result += buff; + free(buff); + } + + int retcode = pclose(fp); + if (retcode) + { + throw CABRTException(EXCEP_PLUGIN, "'%s' exited with %d", cmd, retcode); + } + + return result; +} + +static void WriteCommand(const char *cmd, const char *input) +{ + FILE* fp = popen(cmd, "w"); + if (!fp) + { + throw CABRTException(EXCEP_PLUGIN, "error running '%s'", cmd); + } + + /* Hoping it's not too big to get us forever blocked... */ + fputs(input, fp); + + int retcode = pclose(fp); + if (retcode) + { + throw CABRTException(EXCEP_PLUGIN, "'%s' exited with %d", cmd, retcode); + } +} + +void CReportUploader::SendFile(const char *pURL, const char *pFilename, int retry_count, int retry_delay) +{ + if (pURL[0] == '\0') + { + error_msg(_("FileTransfer: URL not specified")); + return; + } + + update_client(_("Sending archive %s to %s"), pFilename, pURL); + + const char *base = (strrchr(pFilename, '/') ? : pFilename-1) + 1; + char *whole_url = concat_path_file(pURL, base); + int count = retry_count; + int result; + while (1) + { + FILE* f = fopen(pFilename, "r"); + if (!f) + { + free(whole_url); + throw CABRTException(EXCEP_PLUGIN, "Can't open archive file '%s'", pFilename); + } + struct stat buf; + fstat(fileno(f), &buf); /* never fails */ + CURL* curl = xcurl_easy_init(); + /* enable uploading */ + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + /* specify target */ + curl_easy_setopt(curl, CURLOPT_URL, whole_url); + curl_easy_setopt(curl, CURLOPT_READDATA, f); + curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)buf.st_size); + /* everything is done here; result 0 means success */ + result = curl_easy_perform(curl); + /* goodbye */ + curl_easy_cleanup(curl); + fclose(f); + if (result != 0) + { + update_client(_("Sending failed, trying again. %s"), curl_easy_strerror((CURLcode)result)); + } + if (result == 0 || --count <= 0) + break; + /* retry the upload if not succesful, wait a bit before next try */ + sleep(retry_delay); + } + free(whole_url); + + if (count <= 0 && result != 0) + { + throw CABRTException(EXCEP_PLUGIN, "Curl can not send a ticket"); + } +} + + +static void write_str_to_file(const char *str, const char *path, const char *fname) +{ + char *ofile_name = concat_path_file(path, fname); + FILE *ofile = fopen(ofile_name, "w"); + if (!ofile) + { + CABRTException e(EXCEP_PLUGIN, "Can't open '%s'", ofile_name); + free(ofile_name); + throw e; + } + free(ofile_name); + fputs(str, ofile); + fclose(ofile); +} + +string CReportUploader::Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs) +{ + string customer_name; + string ticket_name; + string upload_url; + bool do_encrypt; + bool do_upload; + int retry_count; + int retry_delay; + + /* if parse_settings fails it returns an empty map so we need to use defaults */ + map_plugin_settings_t settings = parse_settings(pSettings); + // Get ticket name, customer name, and do_encrypt from config settings + if (!settings.empty()) + { + customer_name = settings["Customer"]; + ticket_name = settings["Ticket"]; + upload_url = settings["URL"]; + do_encrypt = string_to_bool(settings["Encrypt"].c_str()); + do_upload = string_to_bool(settings["Upload"].c_str()); + retry_count = xatoi_u(settings["RetryCount"].c_str()); + retry_delay = xatoi_u(settings["RetryDelay"].c_str()); + } + else + { + customer_name = m_sCustomer; + ticket_name = m_sTicket; + upload_url = m_sURL; + do_encrypt = m_bEncrypt; + do_upload = m_bUpload; + retry_count = m_nRetryCount; + retry_delay = m_nRetryDelay; + } + update_client(_("Creating a ReportUploader report...")); + + bool have_ticket_name = (ticket_name != ""); + if (!have_ticket_name) + { + ticket_name = "ReportUploader-newticket"; + } + + // Format the time to add to the file name + char timebuf[256]; + time_t curtime = time(NULL); + strftime(timebuf, sizeof(timebuf), "-%Y%m%d%H%M%S", gmtime(&curtime)); + + // Create a tmp work directory, and within that + // create the "<ticketname>-yyyymmddhhmmss" directory + // which will be the root of the tarball + string file_name = ticket_name + timebuf; + + char tmpdir_name[] = "/tmp/abrtuploadXXXXXX"; + if (mkdtemp(tmpdir_name) == NULL) + { + throw CABRTException(EXCEP_PLUGIN, "Can't mkdir a temporary directory in /tmp"); + } + + char *tmptar_name = concat_path_file(tmpdir_name, file_name.c_str()); + if (mkdir(tmptar_name, 0700)) + { + CABRTException e(EXCEP_PLUGIN, "Can't mkdir '%s'", tmptar_name); + free(tmptar_name); + throw e; + } + + // Copy each entry into the tarball root. + // Files are simply copied, strings are written to a file + // TODO: some files are totally useless: + // "Reported", "Message" (plugin's output), "DumpDir", + // "Description" (package description) - maybe skip those? + map_crash_data_t::const_iterator it; + for (it = pCrashData.begin(); it != pCrashData.end(); it++) + { + const char *content = it->second[CD_CONTENT].c_str(); + if (it->second[CD_TYPE] == CD_TXT) + { + write_str_to_file(content, tmptar_name, it->first.c_str()); + } + else if (it->second[CD_TYPE] == CD_BIN) + { + char *ofile_name = concat_path_file(tmptar_name, it->first.c_str()); + if (copy_file(content, ofile_name, 0644) < 0) + { + CABRTException e(EXCEP_PLUGIN, + "Can't copy '%s' to '%s'", + content, ofile_name + ); + free(tmptar_name); + free(ofile_name); + throw e; + } + free(ofile_name); + } + } + + // add ticket_name and customer name to tarball + if (have_ticket_name) + { + write_str_to_file(ticket_name.c_str(), tmptar_name, "TICKET"); + } + if (customer_name != "") + { + write_str_to_file(customer_name.c_str(), tmptar_name, "CUSTOMER"); + } + + // Create the compressed tarball + string outfile_basename = file_name + ".tar.gz"; + char *outfile_name = concat_path_file(tmpdir_name, outfile_basename.c_str()); + string cmd = ssprintf("tar -C %s --create --gzip --file=%s %s", tmpdir_name, outfile_name, file_name.c_str()); + RunCommand(cmd.c_str()); + + // encrypt if requested + string key; + if (do_encrypt) + { + key = ReadCommand("openssl rand -base64 48"); + + string infile_name = outfile_name; + outfile_basename += ".aes"; + outfile_name = append_to_malloced_string(outfile_name, ".aes"); + + cmd = ssprintf("openssl aes-128-cbc -in %s -out %s -pass stdin", infile_name.c_str(), outfile_name); + WriteCommand(cmd.c_str(), key.c_str()); + } + + // generate md5sum + cmd = ssprintf("cd %s; md5sum <%s", tmpdir_name, outfile_basename.c_str()); + string md5sum = ReadCommand(cmd.c_str()); + + // upload or cp to /tmp + if (do_upload) + { + // FIXME: SendFile isn't working sometime (scp) + SendFile(upload_url.c_str(), outfile_name, retry_count, retry_delay); + } + else + { + cmd = ssprintf("cp %s /tmp/", outfile_name); + RunCommand(cmd.c_str()); + } + + // generate a reciept telling md5sum and encryption key + // note: do not internationalize these strings! + string msg; + if (have_ticket_name) + { + msg += "Please copy this into ticket: "; + msg += ticket_name; + msg += '\n'; + msg += "========cut here========\n"; + } + else + { + msg += "Please send this to your technical support:\n"; + msg += "========cut here========\n"; + } + if (do_upload) + { + msg += "RHUPLOAD: This report was sent to "; + msg += upload_url; + msg += '\n'; + } + else + { + msg += "RHUPLOAD: This report was copied into /tmp/:\n"; + } + if (have_ticket_name) + { + msg += "TICKET: "; + msg += ticket_name; + msg += '\n'; + } + msg += "FILE: "; + msg += outfile_basename; + msg += "\nMD5SUM: "; + msg += md5sum; + msg += '\n'; + if (do_encrypt) + { + msg += "KEY: aes-128-cbc\n"; + msg += key; + msg += '\n'; + } + msg += "==========end===========\n"; + + // warn the client (why _warn_? it's not an error, maybe update_client?): + //error_msg("%s", msg.c_str()); + + // delete the temporary directory + cmd = ssprintf("rm -rf %s", tmpdir_name); + RunCommand(cmd.c_str()); + + free(tmptar_name); + free(outfile_name); + + return msg; +} + +static bool is_string_safe(const char *str) +{ + const char *p = str; + while (*p) + { + unsigned char c = *p; + if ((c < '0' || c > '9') + && c != '_' + && c != '-' + ) { + c |= 0x20; // tolower + if (c < 'a' || c > 'z') + { + return false; + } + } + // only 0-9, -, _, A-Z, a-z reach this point + p++; + } + return true; +} + +void CReportUploader::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("Customer"); + if (it != end) + { + m_sCustomer = it->second; + } + // We use m_sTicket as part of filename, + // and we use resulting filename in system("cd %s; ...", filename) etc, + // so we are very paraniod about allowed chars + it = pSettings.find("Ticket"); + if (it != end && is_string_safe(it->second.c_str())) + { + m_sTicket = it->second; + } + it = pSettings.find("URL"); + if (it != end) + { + m_sURL = it->second; + } + it = pSettings.find("Encrypt"); + if (it != end) + { + m_bEncrypt = string_to_bool(it->second.c_str()); + } + it = pSettings.find("Upload"); + if (it != end) + { + m_bUpload = string_to_bool(it->second.c_str()); + } + it = pSettings.find("RetryCount"); + if (it != end) + { + m_nRetryCount = xatoi_u(it->second.c_str()); + } + it = pSettings.find("RetryDelay"); + if (it != end) + { + m_nRetryDelay = xatoi_u(it->second.c_str()); + } +} + +const map_plugin_settings_t& CReportUploader::GetSettings() +{ + m_pSettings["Customer"] = m_sCustomer; + m_pSettings["Ticket"] = m_sTicket; + m_pSettings["URL"] = m_sURL; + m_pSettings["Encrypt"] = m_bEncrypt ? "yes" : "no"; + m_pSettings["Upload"] = m_bUpload ? "yes" : "no"; + m_pSettings["RetryCount"] = to_string(m_nRetryCount); + m_pSettings["RetryDelay"] = to_string(m_nRetryDelay); + + return m_pSettings; +} + +//todo: make static +map_plugin_settings_t CReportUploader::parse_settings(const map_plugin_settings_t& pSettings) +{ + map_plugin_settings_t plugin_settings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + + it = pSettings.find("Customer"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["Customer"] = it->second; + + it = pSettings.find("Ticket"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["Ticket"] = it->second; + + it = pSettings.find("URL"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["URL"] = it->second; + + it = pSettings.find("Encrypt"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["Encrypt"] = it->second; + + it = pSettings.find("Upload"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["Upload"] = it->second; + + it = pSettings.find("RetryCount"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["RetryCount"] = it->second; + + it = pSettings.find("RetryDelay"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["RetryDelay"] = it->second; + + VERB1 log("User settings ok, using them instead of defaults"); + return plugin_settings; +} + +PLUGIN_INFO(REPORTER, + CReportUploader, + "ReportUploader", + "0.0.1", + _("Packs crash data into .tar.gz file, optionally uploads it via FTP/SCP/etc"), + "gavin@redhat.com", + "https://fedorahosted.org/abrt/wiki", + PLUGINS_LIB_DIR"/ReportUploader.glade"); diff --git a/src/plugins/ReportUploader.glade b/src/plugins/ReportUploader.glade new file mode 100644 index 00000000..c2bbd470 --- /dev/null +++ b/src/plugins/ReportUploader.glade @@ -0,0 +1,249 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="2.14"/> + <!-- interface-naming-policy project-wide --> + <object class="GtkDialog" id="PluginDialog"> + <property name="border_width">5</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="window_position">center-on-parent</property> + <property name="icon_name">abrt</property> + <property name="type_hint">normal</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child> + <object class="GtkFrame" id="frame1"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">none</property> + <child> + <object class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <property name="top_padding">6</property> + <property name="left_padding">12</property> + <child> + <object class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="n_rows">7</property> + <property name="n_columns">2</property> + <property name="column_spacing">12</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Customer:</property> + </object> + <packing> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Ticket:</property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label5"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">URL:</property> + </object> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_Customer"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_Ticket"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_URL"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label6"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Retry count:</property> + </object> + <packing> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_RetryCount"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label7"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Retry delay:</property> + </object> + <packing> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + <property name="x_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="conf_RetryDelay"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="conf_Encrypt"> + <property name="label" translatable="yes">Use encryption</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="right_attach">2</property> + <property name="top_attach">5</property> + <property name="bottom_attach">6</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="conf_Upload"> + <property name="label" translatable="yes">Upload</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="right_attach">2</property> + <property name="top_attach">6</property> + <property name="bottom_attach">7</property> + </packing> + </child> + </object> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Report Uploader plugin configuration</b></property> + <property name="use_markup">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="button2"> + <property name="label">gtk-cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="bApply"> + <property name="label">gtk-apply</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-6">button2</action-widget> + <action-widget response="-10">bApply</action-widget> + </action-widgets> + </object> + <object class="GtkAction" id="action1"/> +</interface> diff --git a/src/plugins/ReportUploader.h b/src/plugins/ReportUploader.h new file mode 100644 index 00000000..4ff780b8 --- /dev/null +++ b/src/plugins/ReportUploader.h @@ -0,0 +1,55 @@ +/* + ReportUploader.h + + Attach a configureable Ticket Number and Customer name to a report. + Create a compressed, optionally encrypted, tarball. + Upload tarball to configureable URL. + + 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 REPORTUPLOADER_H_ +#define REPORTUPLAODER_H_ + +#include "plugin.h" +#include "reporter.h" + +class CReportUploader : public CReporter +{ + private: + std::string m_sCustomer; + std::string m_sTicket; + std::string m_sURL; + bool m_bEncrypt; + bool m_bUpload; + int m_nRetryCount; + int m_nRetryDelay; + + void SendFile(const char *pURL, const char *pFilename, int retry_count, int retry_delay); + map_plugin_settings_t parse_settings(const map_plugin_settings_t& pSettings); + + public: + CReportUploader(); + virtual ~CReportUploader(); + virtual const map_plugin_settings_t& GetSettings(); + virtual void SetSettings(const map_plugin_settings_t& pSettings); + + virtual std::string Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs); +}; + +#endif diff --git a/src/plugins/SOSreport.conf b/src/plugins/SOSreport.conf new file mode 100644 index 00000000..3201c6da --- /dev/null +++ b/src/plugins/SOSreport.conf @@ -0,0 +1 @@ +Enabled = yes diff --git a/src/plugins/SOSreport.cpp b/src/plugins/SOSreport.cpp new file mode 100644 index 00000000..36a768fd --- /dev/null +++ b/src/plugins/SOSreport.cpp @@ -0,0 +1,169 @@ +/* + SOSreport.cpp + + 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 "abrt_exception.h" +#include "SOSreport.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" + +using namespace std; + +static char *ParseFilename(char *p) +{ + /* + the sosreport's filename is embedded in sosreport's output. + It appears on the line after the string in 'sosreport_filename_marker', + it has leading spaces, and a trailing newline. This function trims + any leading and trailing whitespace from the filename. + */ + static const char sosreport_filename_marker[] = + "Your sosreport has been generated and saved in:"; + + p = strstr(p, sosreport_filename_marker); + if (!p) + return p; + p = skip_whitespace(p + sizeof(sosreport_filename_marker)-1); + char *end = strchrnul(p, '\n'); + while (end > p && isspace(*end)) + *end-- = '\0'; + return p[0] == '/' ? p : NULL; +} + +void CActionSOSreport::Run(const char *pActionDir, const char *pArgs, int force) +{ + if (!force) + { + struct dump_dir *dd = dd_opendir(pActionDir, /*flags:*/ 0); + if (!dd) + return; + + bool bt_exists = dd_exist(dd, "sosreport.tar.bz2") || dd_exist(dd, "sosreport.tar.xz"); + if (bt_exists) + { + VERB3 log("%s already exists, not regenerating", "sosreport.tar.bz2"); + return; + } + dd_close(dd); + } + + static const char command_default[] = + "cd -- '%s' || exit 1;" + "nice sosreport --tmp-dir . --batch" + " --only=anaconda --only=bootloader" + " --only=devicemapper --only=filesys --only=hardware --only=kernel" + " --only=libraries --only=memory --only=networking --only=nfsserver" + " --only=pam --only=process --only=rpm -k rpm.rpmva=off --only=ssh" + " --only=startup --only=yum 2>&1;" + "rm sosreport*.md5 2>/dev/null;" + "mv sosreport*.tar.bz2 sosreport.tar.bz2 2>/dev/null;" + "mv sosreport*.tar.xz sosreport.tar.xz 2>/dev/null;" + ; + static const char command_prefix[] = + "cd -- '%s' || exit 1;" + "nice sosreport --tmp-dir . --batch %s 2>&1;" + "rm sosreport*.md5 2>/dev/null;" + "mv sosreport*.tar.bz2 sosreport.tar.bz2 2>/dev/null;" + "mv sosreport*.tar.xz sosreport.tar.xz 2>/dev/null;" + ; + string command; + + vector_string_t args; + parse_args(pArgs, args, '"'); + + if (args.size() == 0 || args[0] == "") + { + command = ssprintf(command_default, pActionDir); + } + else + { + command = ssprintf(command_prefix, pActionDir, args[0].c_str()); + } + + update_client(_("Running sosreport: %s"), command.c_str()); + string output = command; + output += '\n'; + char *command_out = run_in_shell_and_save_output(/*flags:*/ 0, command.c_str(), /*dir:*/ NULL, /*size_p:*/ NULL); + output += command_out; + update_client(_("Finished running sosreport")); + VERB3 log("sosreport output:'%s'", output.c_str()); + +// Not needed: now we use "sosreport --tmp-dir DUMPDIR" +#if 0 + // Parse: + // "Your sosreport has been generated and saved in: + // /tmp/sosreport-XXXX.tar.bz2" + // Note: ParseFilename modifies its parameter and returns pointer + // which points somewhere inside it. + char *sosreport_filename = xstrdup(ParseFilename(command_out)); + free(command_out); + if (!sosreport_filename) + { + throw CABRTException(EXCEP_PLUGIN, "Can't find filename in sosreport output"); + } + + char *sosreport_dd_filename = concat_path_file(pActionDir, "sosreport.tar"); + char *ext = strrchr(sosreport_filename, '.'); + if (ext && strcmp(ext, ".tar") != 0) + { + // Assuming it's .bz2, .gz or some such + sosreport_dd_filename = append_to_malloced_string(sosreport_dd_filename, ext); + } + + CDebugDump dd; + if (!dd.Open(pActionDir)) + { + VERB1 log(_("Unable to open debug dump '%s'"), pDebugDumpDir); + free(sosreport_filename); + free(sosreport_dd_filename); + return; + } + //Not useful: dd.SaveText("sosreportoutput", output); + off_t sz = copy_file(sosreport_filename, sosreport_dd_filename, 0644); + + // don't want to leave sosreport-XXXX.tar.bz2 in /tmp + unlink(sosreport_filename); + // sosreport-XXXX.tar.bz2.md5 too + sosreport_filename = append_to_malloced_string(sosreport_filename, ".md5"); + unlink(sosreport_filename); + + dd.Close(); + if (sz < 0) + { + CABRTException e(EXCEP_PLUGIN, + "Can't copy '%s' to '%s'", + sosreport_filename, sosreport_dd_filename + ); + free(sosreport_filename); + free(sosreport_dd_filename); + throw e; + } + free(sosreport_filename); + free(sosreport_dd_filename); +#endif +} + +PLUGIN_INFO(ACTION, + CActionSOSreport, + "SOSreport", + "0.0.2", + _("Runs sosreport, saves the output"), + "gavin@redhat.com", + "https://fedorahosted.org/abrt/wiki", + ""); diff --git a/src/plugins/SOSreport.h b/src/plugins/SOSreport.h new file mode 100644 index 00000000..4b32940f --- /dev/null +++ b/src/plugins/SOSreport.h @@ -0,0 +1,31 @@ +/* + SOSreport.h - Attach an sosreport to a crash dump + + 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 SOSREPORT_H_ +#define SOSREPORT_H_ + +#include "action.h" + +class CActionSOSreport : public CAction +{ + public: + virtual void Run(const char *pActionDir, const char *pArgs, int force); +}; + +#endif diff --git a/src/plugins/SQLite3.conf b/src/plugins/SQLite3.conf new file mode 100644 index 00000000..a7617a90 --- /dev/null +++ b/src/plugins/SQLite3.conf @@ -0,0 +1,4 @@ +# Configuration file for database plugin SQLite3 + +# DB path +DBPath = /var/spool/abrt/abrt-db diff --git a/src/plugins/SQLite3.cpp b/src/plugins/SQLite3.cpp new file mode 100644 index 00000000..2ed3f9fd --- /dev/null +++ b/src/plugins/SQLite3.cpp @@ -0,0 +1,742 @@ +/* + SQLite3.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 <sqlite3.h> +#include "abrtlib.h" +#include "SQLite3.h" + +using namespace std; + +#define ABRT_TABLE_VERSION 4 +#define ABRT_TABLE_VERSION_STR "4" +#define ABRT_TABLE "abrt_v"ABRT_TABLE_VERSION_STR +#define ABRT_REPRESULT_TABLE "abrt_v"ABRT_TABLE_VERSION_STR"_reportresult" +#define SQLITE3_MASTER_TABLE "sqlite_master" + +#define COL_UID "UID" +#define COL_UUID "UUID" +#define COL_INFORMALL "InformAll" +#define COL_DEBUG_DUMP_PATH "DebugDumpPath" +#define COL_COUNT "Count" +#define COL_REPORTED "Reported" +#define COL_TIME "Time" +#define COL_MESSAGE "Message" + +#define COL_REPORTER "Reporter" + +#define NUM_COL 8 + +/* Is this string safe wrt SQL injection? + * PHP's mysql_real_escape_string() treats \, ', ", \x00, \n, \r, and \x1a as special. + * We are a bit more paranoid and disallow any control chars. + */ +static bool is_string_safe(const char *str) +{ +// Apparently SQLite allows unescaped newlines. More surprisingly, +// it does not unescape escaped ones - I see lines ending with \ when I do it. +// I wonder whether this is a bug in SQLite, and whether using unescaped +// newlines is a danger with other SQL servers. +// For now, I disabled newline escaping, and also allowed double quote. + const char *p = str; + while (*p) + { + unsigned char c = *p; +// if (c == '\\' && p[1] != '\0') +// { +// p += 2; +// continue; +// } + if ((c < ' ' && c != '\n') + || strchr("\\\'", c) //was: "\\\"\'" + ) { + error_msg("Probable SQL injection: '%s'", str); + return false; + } + p++; + } + return true; +} + +#ifdef UNUSED_FOR_NOW +/* Escape \n */ +static string sql_escape(const char *str) +{ + const char *s = str; + unsigned len = 0; + do + { + if (*s == '\n') + len++; + len++; + } while (*s++); + + char buf[len]; + s = str; + char *d = buf; + do + { + if (*s == '\n') + *d++ = '\\'; + *d++ = *s; + } while (*s++); + + return buf; +} +#endif + +/* Note: + * expects "SELECT * FROM ...", not "SELECT <only some fields> FROM ..." + */ +static GList *vget_table(sqlite3 *db, const char *fmt, va_list p) +{ + char *sql = xvasprintf(fmt, p); + + char **table; + int ncol, nrow; + char *err = NULL; + int ret = sqlite3_get_table(db, sql, &table, &nrow, &ncol, &err); + if (ret != SQLITE_OK) + { + error_msg("Error in SQL:'%s' error: %s", sql, err); + free(sql); + sqlite3_free(err); + return (GList*)ERR_PTR; + } + VERB2 log("%s: %d rows returned by SQL:%s", __func__, nrow, sql); + free(sql); + + if (nrow > 0 && ncol < NUM_COL) + error_msg_and_die("Unexpected number of columns: %d", ncol); + + GList *rows = NULL; + int ii; + for (ii = 0; ii < nrow; ii++) + { + int jj; + struct db_row *row = (struct db_row*)xzalloc(sizeof(struct db_row)); + for (jj = 0; jj < ncol; jj++) + { + char *val = table[jj + (ncol*ii) + ncol]; + switch (jj) + { + case 0: row->db_uuid = xstrdup(val); break; + case 1: row->db_uid = xstrdup(val); break; + case 2: row->db_inform_all = xstrdup(val); break; + case 3: row->db_dump_dir = xstrdup(val); break; + case 4: row->db_count = xstrdup(val); break; + case 5: row->db_reported = xstrdup(val); break; + case 6: row->db_time = xstrdup(val); break; + case 7: row->db_message = xstrdup(val); break; + } + } + + VERB3 log("%s: row->db_uuid = '%s'", __func__, row->db_uuid); + VERB3 log("%s: row->db_uid = '%s'", __func__, row->db_uid); + VERB3 log("%s: row->db_inform_all = '%s'", __func__, row->db_inform_all); + VERB3 log("%s: row->db_dump_dir = '%s'", __func__, row->db_dump_dir); + VERB3 log("%s: row->db_count = '%s'", __func__, row->db_count); + VERB3 log("%s: row->db_reported = '%s'", __func__, row->db_reported); + VERB3 log("%s: row->db_time = '%s'", __func__, row->db_time); + VERB3 log("%s: row->db_message = '%s'", __func__, row->db_message); + rows = g_list_append(rows, row); + + } + sqlite3_free_table(table); + + return rows; +} + +static GList *get_table_or_die(sqlite3 *db, const char *fmt, ...) +{ + va_list p; + va_start(p, fmt); + GList *table = vget_table(db, fmt, p); + va_end(p); + + if (table == (GList*)ERR_PTR) + xfunc_die(); + + return table; +} + +static int vexecute_sql(sqlite3 *db, const char *fmt, va_list p) +{ + char *sql = xvasprintf(fmt, p); + + char *err = NULL; + int ret = sqlite3_exec(db, sql, /*callback:*/ NULL, /*callback param:*/ NULL, &err); + if (ret != SQLITE_OK) + { + error_msg("Error in SQL:'%s' error: %s", sql, err); + free(sql); + sqlite3_free(err); + return -1; + } + int affected = sqlite3_changes(db); + VERB2 log("%d rows affected by SQL:%s", affected, sql); + free(sql); + + return affected; +} + +static int execute_sql_or_die(sqlite3 *db, const char *fmt, ...) +{ + va_list p; + va_start(p, fmt); + int ret = vexecute_sql(db, fmt, p); + va_end(p); + + if (ret < 0) + xfunc_die(); + + return ret; +} + +static bool exists_uuid_uid(sqlite3 *db, const char *UUID, const char *UID) +{ + GList *table = get_table_or_die(db, "SELECT * FROM "ABRT_TABLE + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + UUID, UID + ); + + if (!table) + return false; + + db_list_free(table); + + return true; +} + +static void update_from_old_ver(sqlite3 *db, int old_version) +{ + static const char *const update_sql_commands[] = { + // v0 -> v1 + NULL, + // v1 -> v2 + "BEGIN TRANSACTION;" + "CREATE TABLE abrt_v2 (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_DEBUG_DUMP_PATH" VARCHAR NOT NULL," + COL_COUNT" INT NOT NULL DEFAULT 1," + COL_REPORTED" INT NOT NULL DEFAULT 0," + COL_TIME" VARCHAR NOT NULL DEFAULT 0," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID"));" + "INSERT INTO abrt_v2 " + "SELECT "COL_UUID"," + COL_UID"," + COL_DEBUG_DUMP_PATH"," + COL_COUNT"," + COL_REPORTED"," + COL_TIME"," + COL_MESSAGE + " FROM abrt;" + "DROP TABLE abrt;" + "COMMIT;", + // v2 -> v3 + "BEGIN TRANSACTION;" + "CREATE TABLE abrt_v3 (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_DEBUG_DUMP_PATH" VARCHAR NOT NULL," + COL_COUNT" INT NOT NULL DEFAULT 1," + COL_REPORTED" INT NOT NULL DEFAULT 0," + COL_TIME" VARCHAR NOT NULL DEFAULT 0," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID"));" + "INSERT INTO abrt_v3 " + "SELECT "COL_UUID"," + COL_UID"," + COL_DEBUG_DUMP_PATH"," + COL_COUNT"," + COL_REPORTED"," + COL_TIME"," + COL_MESSAGE + " FROM abrt_v2;" + "DROP TABLE abrt_v2;" + "CREATE TABLE abrt_v3_reportresult (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_REPORTER" VARCHAR NOT NULL," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID","COL_REPORTER"));" + "COMMIT;", + // v3-> v4 + "BEGIN TRANSACTION;" + "CREATE TABLE abrt_v4(" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_INFORMALL" INT NOT NULL DEFAULT 0," + COL_DEBUG_DUMP_PATH" VARCHAR NOT NULL," + COL_COUNT" INT NOT NULL DEFAULT 1," + COL_REPORTED" INT NOT NULL DEFAULT 0," + COL_TIME" VARCHAR NOT NULL DEFAULT 0," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID"));" + "INSERT INTO abrt_v4 " + "SELECT "COL_UUID"," + COL_UID"," + "0," /* COL_INFORMALL */ + COL_DEBUG_DUMP_PATH"," + COL_COUNT"," + COL_REPORTED"," + COL_TIME"," + COL_MESSAGE + " FROM abrt_v3;" + "DROP TABLE abrt_v3;" + "UPDATE abrt_v4" + " SET "COL_UID"='0', "COL_INFORMALL"=1" + " WHERE "COL_UID"='-1';" + "CREATE TABLE abrt_v4_reportresult (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_REPORTER" VARCHAR NOT NULL," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID","COL_REPORTER"));" + "INSERT INTO abrt_v4_reportresult " + "SELECT * FROM abrt_v3_reportresult;" + "DROP TABLE abrt_v3_reportresult;" + "COMMIT;", + }; + + while (old_version < ABRT_TABLE_VERSION) + { + execute_sql_or_die(db, update_sql_commands[old_version]); + old_version++; + } +} + +static bool check_table(sqlite3 *db) +{ + const char *command = "SELECT NAME FROM "SQLITE3_MASTER_TABLE" " + "WHERE TYPE='table' AND NAME like 'abrt_v%';"; + char **table; + int ncol, nrow; + char *err; + int ret = sqlite3_get_table(db, command, &table, &nrow, &ncol, &err); + if (ret != SQLITE_OK) + { + /* Should never happen */ + error_msg_and_die("SQLite3 database is corrupted"); + } + if (!nrow) + { + sqlite3_free_table(table); + return false; + } + + // table format: + // table[0]:"NAME" // table[1]:"SQL" <== field names from SELECT + // table[2]:"abrt_vNN" // table[3]:"sql" + char *tableName = table[0 + ncol]; + char *underscore = strchr(tableName, '_'); + if (underscore) + { + // It can be "abrt_vNN_something", thus using atoi(), not xatoi() + int tableVersion = atoi(underscore + 2); + sqlite3_free_table(table); + if (tableVersion < ABRT_TABLE_VERSION) + { + update_from_old_ver(db, tableVersion); + } + return true; + } + sqlite3_free_table(table); + update_from_old_ver(db, 1); + return true; +} + + +CSQLite3::CSQLite3() : + m_sDBPath(LOCALSTATEDIR "/spool/abrt/abrt-db"), + m_pDB(NULL) +{} + +CSQLite3::~CSQLite3() +{ + /* Paranoia. In C++, destructor will abort() if it was called while unwinding + * the stack and it throws an exception. + */ + try + { + DisConnect(); + m_sDBPath.clear(); + } + catch (...) + { + error_msg_and_die("Internal error"); + } +} + +void CSQLite3::DisConnect() +{ + if (m_pDB) + { + sqlite3_close(m_pDB); + m_pDB = NULL; + } +} + +void CSQLite3::Connect() +{ + int ret = sqlite3_open_v2(m_sDBPath.c_str(), + &m_pDB, + SQLITE_OPEN_READWRITE, + NULL + ); + + if (ret != SQLITE_OK) + { + if (ret != SQLITE_CANTOPEN) + { + error_msg_and_die("Can't open database '%s': %s", m_sDBPath.c_str(), sqlite3_errmsg(m_pDB)); + } + + ret = sqlite3_open_v2(m_sDBPath.c_str(), + &m_pDB, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + NULL + ); + if (ret != SQLITE_OK) + { + error_msg_and_die("Can't create database '%s': %s", m_sDBPath.c_str(), sqlite3_errmsg(m_pDB)); + } + } + + if (!check_table(m_pDB)) + { + execute_sql_or_die(m_pDB, + "CREATE TABLE "ABRT_TABLE" (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_INFORMALL" INT NOT NULL DEFAULT 0," + COL_DEBUG_DUMP_PATH" VARCHAR NOT NULL," + COL_COUNT" INT NOT NULL DEFAULT 1," + COL_REPORTED" INT NOT NULL DEFAULT 0," + COL_TIME" VARCHAR NOT NULL DEFAULT 0," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID"));" + ); + execute_sql_or_die(m_pDB, + "CREATE TABLE "ABRT_REPRESULT_TABLE" (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_REPORTER" VARCHAR NOT NULL," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID","COL_REPORTER"));" + ); + } +} + +void CSQLite3::Insert_or_Update(const char *crash_id, + bool inform_all_users, + const char *pDebugDumpPath, + const char *pTime) +{ + const char *UUID = strchr(crash_id, ':'); + if (!UUID + || !is_string_safe(crash_id) + || !is_string_safe(pDebugDumpPath) + || !is_string_safe(pTime) + ) { + return; + } + + /* Split crash_id into UID:UUID */ + unsigned uid_len = UUID - crash_id; + UUID++; + char UID[uid_len + 1]; + strncpy(UID, crash_id, uid_len); + UID[uid_len] = '\0'; + + if (!exists_uuid_uid(m_pDB, UUID, UID)) + { + execute_sql_or_die(m_pDB, + "INSERT INTO "ABRT_TABLE" (" + COL_UUID"," + COL_UID"," + COL_INFORMALL"," + COL_DEBUG_DUMP_PATH"," + COL_TIME + ")" + " VALUES ('%s','%s',%u,'%s','%s');", + UUID, UID, (unsigned)inform_all_users, pDebugDumpPath, pTime + ); + } + else + { + execute_sql_or_die(m_pDB, + "UPDATE "ABRT_TABLE + " SET "COL_COUNT"="COL_COUNT"+1,"COL_TIME"='%s'" + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + pTime, + UUID, UID + ); + } +} + +void CSQLite3::DeleteRow(const char *crash_id) +{ + const char *UUID = strchr(crash_id, ':'); + if (!UUID + || !is_string_safe(crash_id) + ) { + return; + } + + /* Split crash_id into UID:UUID */ + unsigned uid_len = UUID - crash_id; + UUID++; + char UID[uid_len + 1]; + strncpy(UID, crash_id, uid_len); + UID[uid_len] = '\0'; + + if (exists_uuid_uid(m_pDB, UUID, UID)) + { + execute_sql_or_die(m_pDB, "DELETE FROM "ABRT_TABLE + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + UUID, UID + ); + execute_sql_or_die(m_pDB, "DELETE FROM "ABRT_REPRESULT_TABLE + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + UUID, UID + ); + } + else + { + error_msg("crash_id %s is not found in DB", crash_id); + } +} + +void CSQLite3::DeleteRows_by_dir(const char *dump_dir) +{ + if (!is_string_safe(dump_dir)) + { + return; + } + + /* Get UID:UUID pair(s) to delete */ + GList *table = get_table_or_die(m_pDB, "SELECT * FROM "ABRT_TABLE + " WHERE "COL_DEBUG_DUMP_PATH"='%s';", + dump_dir + ); + + if (!table) + { + return; + } + + struct db_row *row = NULL; + /* Delete from both tables */ + for (GList *li = table; li != NULL; li = g_list_next(li)) + { + row = (struct db_row*)li->data; + execute_sql_or_die(m_pDB, + "DELETE FROM "ABRT_REPRESULT_TABLE + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + row->db_uuid, row->db_uid + ); + } + execute_sql_or_die(m_pDB, + "DELETE FROM "ABRT_TABLE + " WHERE "COL_DEBUG_DUMP_PATH"='%s'", + dump_dir + ); +} + +void CSQLite3::SetReported(const char *crash_id, const char *pMessage) +{ + const char *UUID = strchr(crash_id, ':'); + if (!UUID + || !is_string_safe(crash_id) + || !is_string_safe(pMessage) + ) { + return; + } + + /* Split crash_id into UID:UUID */ + unsigned uid_len = UUID - crash_id; + UUID++; + char UID[uid_len + 1]; + strncpy(UID, crash_id, uid_len); + UID[uid_len] = '\0'; + + if (exists_uuid_uid(m_pDB, UUID, UID)) + { + execute_sql_or_die(m_pDB, + "UPDATE "ABRT_TABLE + " SET "COL_REPORTED"=1" + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + UUID, UID + ); + execute_sql_or_die(m_pDB, + "UPDATE "ABRT_TABLE + " SET "COL_MESSAGE"='%s'" + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + pMessage, UUID, UID + ); + } + else + { + error_msg("crash_id %s is not found in DB", crash_id); + } +} + +void CSQLite3::SetReportedPerReporter(const char *crash_id, + const char *reporter, + const char *pMessage) +{ + const char *UUID = strchr(crash_id, ':'); + if (!UUID + || !is_string_safe(crash_id) + || !is_string_safe(reporter) + || !is_string_safe(pMessage) + ) { + return; + } + + /* Split crash_id into UID:UUID */ + unsigned uid_len = UUID - crash_id; + UUID++; + char UID[uid_len + 1]; + strncpy(UID, crash_id, uid_len); + UID[uid_len] = '\0'; + + int affected_rows = execute_sql_or_die(m_pDB, + "UPDATE "ABRT_REPRESULT_TABLE + " SET "COL_MESSAGE"='%s'" + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s' AND "COL_REPORTER"='%s'", + pMessage, + UUID, UID, reporter + ); + if (!affected_rows) + { + execute_sql_or_die(m_pDB, + "INSERT INTO "ABRT_REPRESULT_TABLE + " ("COL_UUID","COL_UID","COL_REPORTER","COL_MESSAGE")" + " VALUES ('%s','%s','%s','%s');", + UUID, UID, reporter, pMessage + ); + } +} + +GList *CSQLite3::GetUIDData(long caller_uid) +{ + GList *table = NULL; + + if (caller_uid == 0) + { + table = get_table_or_die(m_pDB, "SELECT * FROM "ABRT_TABLE";"); + } + else + { + table = get_table_or_die(m_pDB, "SELECT * FROM "ABRT_TABLE + " WHERE "COL_UID"='%ld' OR "COL_INFORMALL"=1;", + caller_uid + ); + } + return table; +} + +struct db_row *CSQLite3::GetRow(const char *crash_id) +{ + const char *UUID = strchr(crash_id, ':'); + if (!UUID + || !is_string_safe(crash_id) + ) { + return NULL; + } + + /* Split crash_id into UID:UUID */ + unsigned uid_len = UUID - crash_id; + UUID++; + char UID[uid_len + 1]; + strncpy(UID, crash_id, uid_len); + UID[uid_len] = '\0'; + + GList *table = get_table_or_die(m_pDB, "SELECT * FROM "ABRT_TABLE + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + UUID, UID + ); + + if (!table) + { + return NULL; + } + + GList *first = g_list_first(table); + struct db_row *row = db_rowcpy_from_list(first); + + db_list_free(table); + + return row; +} + +struct db_row *CSQLite3::GetRow_by_dir(const char *dir) +{ + if (!is_string_safe(dir)) + return NULL; + + GList *table = get_table_or_die(m_pDB, "SELECT * FROM "ABRT_TABLE + " WHERE "COL_DEBUG_DUMP_PATH"='%s';", + dir + ); + + if (!table) + return NULL; + + GList *first = g_list_first(table); + struct db_row *row = db_rowcpy_from_list(first); + + db_list_free(table); + + return row; +} + +void CSQLite3::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("DBPath"); + if (it != end) + { + m_sDBPath = it->second; + } +} + +//ok to delete? +//const map_plugin_settings_t& CSQLite3::GetSettings() +//{ +// m_pSettings["DBPath"] = m_sDBPath; +// +// return m_pSettings; +//} + +PLUGIN_INFO(DATABASE, + CSQLite3, + "SQLite3", + "0.0.2", + _("Keeps SQLite3 database about all crashes"), + "zprikryl@redhat.com,jmoskovc@redhat.com", + "https://fedorahosted.org/abrt/wiki", + ""); diff --git a/src/plugins/SQLite3.h b/src/plugins/SQLite3.h new file mode 100644 index 00000000..5750d92e --- /dev/null +++ b/src/plugins/SQLite3.h @@ -0,0 +1,58 @@ +/* + SQLite3.h + + 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 SQLITE3_H_ +#define SQLITE3_H_ + +#include "plugin.h" +#include "database.h" + +class CSQLite3 : public CDatabase +{ + private: + std::string m_sDBPath; + sqlite3* m_pDB; + + public: + CSQLite3(); + ~CSQLite3(); + + virtual void Connect(); + virtual void DisConnect(); + + virtual void Insert_or_Update(const char *crash_id, + bool inform_all_users, + const char *pDebugDumpPath, + const char *pTime); + virtual void DeleteRow(const char *crash_id); + virtual void DeleteRows_by_dir(const char *dump_dir); + virtual void SetReported(const char *crash_id, const char *pMessage); + virtual void SetReportedPerReporter(const char *crash_id, + const char *reporter, + const char *pMessage); + virtual GList *GetUIDData(long caller_uid); + virtual struct db_row *GetRow(const char *crash_id); + virtual struct db_row *GetRow_by_dir(const char *dir); + + virtual void SetSettings(const map_plugin_settings_t& pSettings); +}; + + +#endif diff --git a/src/plugins/abrt-Bugzilla.7 b/src/plugins/abrt-Bugzilla.7 new file mode 100644 index 00000000..99bb60d1 --- /dev/null +++ b/src/plugins/abrt-Bugzilla.7 @@ -0,0 +1,43 @@ +.TH abrt "7" "1 Jun 2009" "" +.SH NAME +Bugzilla plugin for abrt(8) +.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 the \fIBugzilla\fP plugin +for \fIabrt\fP. +.P +This plugin is used to report the crash to a Bugzilla instance. The +plugin will determine the package name and distribution version. The +crash data is attached to the bug report. +.SH INVOCATION +The plugin is invoked in the \fIabrt.conf\fP configuration file. +No parameters are necessary. +.SH CONFIGURATION +The \fIBugzilla.conf\fP configuration file contains several +entries in the format "Option = Value". The options are: +.SS BugzillaURL +The URL of the Bugzilla instance that you want to use, including the +path to the xmlrpc. The default is https://bugzilla.redhat.com/xmlrpc.cgi +.SS Login +Your Bugzilla login. If you have no Bugzilla account, you cannot +use the plugin. +.SS Password +Your Bugzilla password. +.SH EXAMPLES +.P +This is a snippet from the \fIabrt.conf\fP configuration file. +When something crashes, use the Bugzilla plugin: +.P +[common] +.br +ActionsAndReporters = Bugzilla +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Zdenek Prikryl <zprikryl@redhat.com>. +Manual page written by Daniel Novotny <dnovotny@redhat.com>. diff --git a/src/plugins/abrt-FileTransfer.7 b/src/plugins/abrt-FileTransfer.7 new file mode 100644 index 00000000..a721dd81 --- /dev/null +++ b/src/plugins/abrt-FileTransfer.7 @@ -0,0 +1,72 @@ +.TH abrt "7" "1 Jun 2009" "" +.SH NAME +FileTransfer plugin for abrt(8) +.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 the \fIFileTransfer\fP plugin +for \fIabrt\fP. +.P +This plugin is used to transfer the crash report to another +machine using a file transfer protocol. The protocols supported +are FTP, FTPS, HTTP, HTTPS, SCP, SFTP, and TFTP. +.SH INVOCATION +.P +The plugin is invoked in the \fIabrt.conf\fP file, usually in the +\fIActionsAndReporters\fP option and/or the \fI[cron]\fP section. +There are two modes of invocation: +.P +* Specify \fIFileTransfer(one)\fP in ActionsAndReporters directive. +Immediately after crash is detected, the plugin transfers crash data +to the server specified in the \fIFileTransfer.conf\fP configuration file. +.P +* Specify \fIFileTransfer(store)\fP in ActionsAndReporters directive +and add \fIHH:MM = FileTransfer\fP line in [cron] section. +At the time of the crash, +the plugin stores a record of it in its internal list. +When specified time is reached, the plugin iterates through +its internal list and sends every recorded crash to the specified URL. +After that, the internal list is cleared. +.SH CONFIGURATION +The \fIFileTransfer.conf\fP configuration file contains +several entries in the format "Option = Value". The options are: +.SS URL +The URL of the server, where the crash should +be transfered, specifying the protocol, the path, +the user name and the password, for example: +.br +URL = ftp://user:passwd@server.com/path +.SS ArchiveType +The type of the archive in which to pack the crash data. +Currently, \fI.tar\fP, \fI.tar.gz\fP, \fI.tar.bz2\fP and \fI.zip\fP +are supported. The default is \fI.tar.gz\fP +.SS RetryCount +This specifies how many times the plugin will try to resend +the file if the transfer was not succesful. The plugin +waits a while before it retries the transfer: see \fIRetryDelay\fP. +The default is 3 +.SS RetryDelay +If the transfer was not succesful, the plugin will +wait some time before sending the file again. This configuration +option specifies the time in seconds. The default is 20. +.SH EXAMPLES +.P +Typical configuration in \fIabrt.conf\fP. The crash is stored +each time it happens and at midnight, all the crash data +is transferred to a central server. +.P +[common] +.br +ActionsAndReporters = FileTransfer(store) +.br +[cron] +.br +00:00 = FileTransfer +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Daniel Novotny <dnovotny@redhat.com>. diff --git a/src/plugins/abrt-KerneloopsReporter.7 b/src/plugins/abrt-KerneloopsReporter.7 new file mode 100644 index 00000000..98bd3874 --- /dev/null +++ b/src/plugins/abrt-KerneloopsReporter.7 @@ -0,0 +1,40 @@ +.TH abrt "7" "1 Jun 2009" "" +.SH NAME +KerneloopsReporter plugin for abrt(8) +.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 the \fIKerneloopsReporter\fP +plugin for \fIabrt\fP. +.P +This plugin is used to report the crash to the Kerneloops tracker. +.SH INVOCATION +The plugin is invoked in the \fIabrt.conf\fP configuration file. +No parameters are necessary. +.SH CONFIGURATION +The \fIKerneloopsReporter.conf\fP configuration file contains one entry: +.SS SubmitURL +The URL of the kerneloops tracker, the default is +.br +SubmitURL = http://submit.kerneloops.org/submitoops.php +.SH EXAMPLES +.P +This is a snippet from the \fIabrt.conf\fP configuration file. +Each time a kernel oops is detected, run KerneloopsReporter: +.P +[common] +.br +ActionsAndReporters = Kerneloops, KerneloopsReporter +.br +[AnalyzerActionsAndReporters] +.br +Kerneloops = KerneloopsReporter +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Anton Arapov <anton@redhat.com>. Manual +page by Daniel Novotny <dnovotny@redhat.com>. diff --git a/src/plugins/abrt-KerneloopsScanner.7 b/src/plugins/abrt-KerneloopsScanner.7 new file mode 100644 index 00000000..ff094847 --- /dev/null +++ b/src/plugins/abrt-KerneloopsScanner.7 @@ -0,0 +1,46 @@ +.TH abrt "7" "1 Jun 2009" "" +.SH NAME +KerneloopsScanner plugin for abrt(8) +.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 the \fIKerneloopsScanner\fP +plugin for \fIabrt\fP. +.P +This plugin reads the system log file (default /var/log/messages) +and stores the kernel oops crashes, which were not already +reported, to abrt's debug dump directory. +.P +To distinguish between new crashes and crashes +that were already reported, the plugin makes its own entry +in the log file, which acts as a separator. +.SH INVOCATION +The plugin is invoked in the \fIabrt.conf\fP configuration file. +No parameters are necessary. +.SH CONFIGURATION +The \fIKerneloopsScanner.conf\fP configuration file contains one entry: +.SS SysLogFile +The file to scan. The default is +.br +SysLogFile = /var/log/messages +.SH EXAMPLES +.P +This is a snippet from the \fIabrt.conf\fP configuration file. +Every 10 seconds look if there were any kernel crashes: +.P +[common] +.br +ActionsAndReporters = Kerneloops, KerneloopsScanner +.br +[cron] +.br +10 = KerneloopsScanner +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Anton Arapov <anton@redhat.com>. Manual +page by Daniel Novotny <dnovotny@redhat.com>. diff --git a/src/plugins/abrt-Logger.7 b/src/plugins/abrt-Logger.7 new file mode 100644 index 00000000..8ae679f8 --- /dev/null +++ b/src/plugins/abrt-Logger.7 @@ -0,0 +1,44 @@ +.TH abrt "7" "1 Jun 2009" "" +.SH NAME +Logger plugin for abrt(8) +.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 the \fILogger\fP plugin +for \fIabrt\fP. +.P +This plugin is used to log the crash to a file. +.P +The log will contain all the file names as well as their +content. It also contains "duplicity check": the ID +of the crash, which is used to tell whether the same +crash has happened previously. +.SH INVOCATION +The plugin is invoked in the \fIabrt.conf\fP configuration file. +No parameters are necessary. +.SH CONFIGURATION +The \fILogger.conf\fP configuration file contains +several entries in a format "Option = Value". The options are: +.SS LogPath +The path to the log file. +.SS AppendLogs +If set to "yes" (the default) \fILogger\fP will append +the report to the file, otherwise it will overwrite the file (so +only the last crash will be stored). +.SH EXAMPLES +.P +This is a snippet from the \fIabrt.conf\fP configuration file. +Log all the C/C++ application crashes: +.P +[AnalyzerActionsAndReporters] +.br +CCpp = Logger +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Zdenek Prikryl <zprikryl@redhat.com>. Manual +page by Daniel Novotny <dnovotny@redhat.com>. diff --git a/src/plugins/abrt-Mailx.7 b/src/plugins/abrt-Mailx.7 new file mode 100644 index 00000000..90a8bbce --- /dev/null +++ b/src/plugins/abrt-Mailx.7 @@ -0,0 +1,57 @@ +.TH abrt "7" "1 Jun 2009" "" +.SH NAME +Mailx plugin for abrt(8) +.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 the \fIMailx\fP plugin +for \fIabrt\fP. +.P +This plugin is used to mail the data about the crash +to a specified mail address. +.SH INVOCATION +The plugin is invoked in the \fIabrt.conf\fP configuration file. It can take +one parameter, a subject of the mail (if it differs from the +one specified in the \fIMailx.conf\fP configuration file). +.SH CONFIGURATION +The \fIMailx.conf\fP configuration file contains +several entries in a format "Option = Value". The options are: +.SS Subject +The subject of the mail. +.SS Parameters +The \fIMailx\fP plugin executes the external "mailx" command to +send the mail. This option defines some additional command line +parameters, which should be added to the program invocation, if any. +.SS EmailFrom +The address from which the email is sent. +.SS EmailTo +The address to which the email is sent. +.SS SendBinaryData +Can be "yes" or "no". If set to "yes", the email will also +contain the binary files associated with the crash. Warning: +this can cause the emails to be large! (several MB) +.SH EXAMPLES +.P +These are snippets from the \fIabrt.conf\fP configuration file. +.P +1) Each time a crash happens, a mail is sent +.PP +[common] +.br +ActionsAndReporters = Mailx("[abrt] a crash occurs") +.P +2) When a program in a specific package (in this case "httpd") crashes, +send a mail about it. +.PP +[AnalyzerActionsAndReporters] +.br +CCpp:httpd = Mailx("[abrt] Apache crash") +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Zdenek Prikryl <zprikryl@redhat.com>. Manual +page by Daniel Novotny <dnovotny@redhat.com>. diff --git a/src/plugins/abrt-ReportUploader.7 b/src/plugins/abrt-ReportUploader.7 new file mode 100644 index 00000000..bd91f266 --- /dev/null +++ b/src/plugins/abrt-ReportUploader.7 @@ -0,0 +1,55 @@ +.TH abrt "7" "9 July 2009" "" +.SH NAME +ReportUploader plugin for abrt(8) +.SH DESCRIPTION +.P +.I abrt +is a daemon which watches for application crashes. When a crash occurs, +it collects the crash data and performs some actions according to +the configuration. This manual page describes the \fIReportUploader\fP plugin +for \fIabrt\fP. +.P +This plugin will send a report to an anonymous FTP site. It's intended +for use in cases where a ticketing system is associated with the FTP site, +but the ticketing system has no way to automatically create new tickets, +or add to existing tickets. Customer Name is put in config file. +Ticket name (or number) is also put in config file. If no ticket +name is configured, assume ticketing system should create a new ticket. +This information is added to the report, the report is copied into a +compressed, optionally encrypted, tarball. Then the tarball is FTP'd +to the upload site. Then a status string is displayed to the user +showing the name of the file on the FTP site, it's MD5SUM, and +it's encryption key. This information can be pasted into a ticket +in the ticketing system. +.SH INVOCATION +The plugin is invoked in the \fIabrt.conf\fP configuration file. +No parameters are necessary. +.SH CONFIGURATION +The \fIReportUploader.conf\fP configuration file contains +entries in a format "Option = Value". The options are: +.SS Customer +This is the customer's name or other customer identifier. +.SS Ticket +This is the ticket name or number. +.SS Encrypt +"yes" for encrypt upload, anything else for not. +.SS Upload +"yes" for for upload to FTP site, anything else for copy to local /tmp. +.SS URL +URL of upload site (ie. ftp://support.com/upload). +.SH EXAMPLES +.P +This is a snippet from the \fIabrt.conf\fP configuration file. +Log all the C/C++ application crashes: +.P +[AnalyzerActionsAndReporters] +.br +CCpp = RHUpload +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Plugin and man page by Gavin Romig-Koch <gavin@redhat.com>. + + diff --git a/src/plugins/abrt-SQLite3.7 b/src/plugins/abrt-SQLite3.7 new file mode 100644 index 00000000..c2b39d86 --- /dev/null +++ b/src/plugins/abrt-SQLite3.7 @@ -0,0 +1,36 @@ +.TH abrt "7" "1 Jun 2009" "" +.SH NAME +SQLite3 database plugin for abrt(8) +.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 the \fISQLite3\fP database plugin +for \fIabrt\fP. +.P +This is a database plugin: \fIabrt\fP needs a database in which to store +its metadata. You can choose one by specifying "Database" in +the \fIabrt.conf\fP configuration file. Currently SQLite3 is +the only choice supported. +.SH INVOCATION +The plugin is invoked in the \fIabrt.conf\fP configuration file, like +this: +.br +[common] +.br +Database = SQLite3 +.SH CONFIGURATION +The \fISQLite3.conf\fP configuration file contains one entry: +.SS DBPath +The path to the database. +.SH EXAMPLES +see \fBINVOCATION\fP +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Zdenek Prikryl <zprikryl@redhat.com> and Jiri +Moskovcak <jmoskovc@redhat.com>. Manual +page by Daniel Novotny <dnovotny@redhat.com>. diff --git a/src/plugins/abrt-action-analyze-c.c b/src/plugins/abrt-action-analyze-c.c new file mode 100644 index 00000000..60e08372 --- /dev/null +++ b/src/plugins/abrt-action-analyze-c.c @@ -0,0 +1,238 @@ +/* + 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 "parse_options.h" + +#define PROGNAME "abrt-action-analyze-c" + +static void create_hash(char hash_str[SHA1_RESULT_LEN*2 + 1], const char *pInput) +{ + unsigned len; + unsigned char hash2[SHA1_RESULT_LEN]; + sha1_ctx_t sha1ctx; + + sha1_begin(&sha1ctx); + sha1_hash(pInput, strlen(pInput), &sha1ctx); + sha1_end(hash2, &sha1ctx); + len = SHA1_RESULT_LEN; + + char *d = hash_str; + unsigned char *s = hash2; + while (len) + { + *d++ = "0123456789abcdef"[*s >> 4]; + *d++ = "0123456789abcdef"[*s & 0xf]; + s++; + len--; + } + *d = '\0'; + //log("hash2:%s str:'%s'", hash_str, pInput); +} + +static char *run_unstrip_n(const char *dump_dir_name, unsigned timeout_sec) +{ + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + return NULL; + char *uid_str = dd_load_text(dd, CD_UID); + dd_close(dd); + unsigned uid = xatoi_u(uid_str); + free(uid_str); + + int flags = EXECFLG_INPUT_NUL | EXECFLG_OUTPUT | EXECFLG_SETGUID | EXECFLG_SETSID | EXECFLG_QUIET; + VERB1 flags &= ~EXECFLG_QUIET; + int pipeout[2]; + char* args[4]; + args[0] = (char*)"eu-unstrip"; + args[1] = xasprintf("--core=%s/"FILENAME_COREDUMP, dump_dir_name); + args[2] = (char*)"-n"; + args[3] = NULL; + pid_t child = fork_execv_on_steroids(flags, args, pipeout, /*unsetenv_vec:*/ NULL, /*dir:*/ NULL, uid); + free(args[1]); + + /* Bugs in unstrip or corrupted coredumps can cause it to enter infinite loop. + * Therefore we have a (largish) timeout, after which we kill the child. + */ + int t = time(NULL); /* int is enough, no need to use time_t */ + int endtime = t + timeout_sec; + struct strbuf *buf_out = strbuf_new(); + while (1) + { + int timeout = endtime - t; + if (timeout < 0) + { + kill(child, SIGKILL); + strbuf_append_strf(buf_out, "\nTimeout exceeded: %u seconds, killing %s\n", timeout_sec, args[0]); + break; + } + + /* We don't check poll result - checking read result is enough */ + struct pollfd pfd; + pfd.fd = pipeout[0]; + pfd.events = POLLIN; + poll(&pfd, 1, timeout * 1000); + + char buff[1024]; + int r = read(pipeout[0], buff, sizeof(buff) - 1); + if (r <= 0) + break; + buff[r] = '\0'; + strbuf_append_str(buf_out, buff); + t = time(NULL); + } + close(pipeout[0]); + + /* Prevent having zombie child process */ + int status; + waitpid(child, &status, 0); + + if (status != 0) + { + /* unstrip didnt exit with exitcode 0 */ + strbuf_free(buf_out); + return NULL; + } + + return strbuf_free_nobuf(buf_out); +} + +static void trim_unstrip_output(char *result, const char *unstrip_n_output) +{ + // lines look like this: + // 0x400000+0x209000 23c77451cf6adff77fc1f5ee2a01d75de6511dda@0x40024c - - [exe] + // 0x400000+0x209000 ab3c8286aac6c043fd1bb1cc2a0b88ec29517d3e@0x40024c /bin/sleep /usr/lib/debug/bin/sleep.debug [exe] + // 0x7fff313ff000+0x1000 389c7475e3d5401c55953a425a2042ef62c4c7df@0x7fff313ff2f8 . - linux-vdso.so.1 + // ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // we drop everything except the marked part ^ + + char *dst = result; + const char *line = unstrip_n_output; + while (*line) + { + const char *eol = strchrnul(line, '\n'); + const char *plus = (char*)memchr(line, '+', eol - line); + if (plus) + { + while (++plus < eol && *plus != '@') + { + if (!isspace(*plus)) + { + *dst++ = *plus; + } + } + } + if (*eol != '\n') break; + line = eol + 1; + } + *dst = '\0'; +} + +int main(int argc, char **argv) +{ + char *env_verbose = getenv("ABRT_VERBOSE"); + if (env_verbose) + g_verbose = atoi(env_verbose); + + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( + PROGNAME" [-vs] -d DIR\n\n" + "Calculates and saves UUID of coredumps" + ); + const char *dump_dir_name = "."; + enum { + OPT_v = 1 << 0, + OPT_d = 1 << 1, + OPT_s = 1 << 2, + }; + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_STRING('d', NULL, &dump_dir_name, "DIR", _("Crash dump directory")), + OPT_BOOL( 's', NULL, NULL, _("Log to syslog" )), + OPT_END() + }; + /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); + + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + + msg_prefix = PROGNAME; +//Maybe we will want this... later +// if (opts & OPT_s) +// { +// openlog(msg_prefix, 0, LOG_DAEMON); +// logmode = LOGMODE_SYSLOG; +// } + + /* Run unstrip -n and trim its output, leaving only sizes and build ids */ + + char *unstrip_n_output = run_unstrip_n(dump_dir_name, /*timeout_sec:*/ 30); + if (!unstrip_n_output) + return 1; /* bad dump_dir_name, can't run unstrip, etc... */ + /* modifies unstrip_n_output in-place: */ + trim_unstrip_output(unstrip_n_output, unstrip_n_output); + + /* Hash package + executable + unstrip_n_output and save it as UUID */ + + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + return 1; + + char *executable = dd_load_text(dd, FILENAME_EXECUTABLE); + char *package = dd_load_text(dd, FILENAME_PACKAGE); + /* Package variable has "firefox-3.5.6-1.fc11[.1]" format */ + /* Remove distro suffix and maybe least significant version number */ + char *p = package; + while (*p) + { + if (*p == '.' && (p[1] < '0' || p[1] > '9')) + { + /* We found "XXXX.nondigitXXXX", trim this part */ + *p = '\0'; + break; + } + p++; + } + char *first_dot = strchr(package, '.'); + if (first_dot) + { + char *last_dot = strrchr(first_dot, '.'); + if (last_dot != first_dot) + { + /* There are more than one dot: "1.2.3" + * Strip last part, we don't want to distinquish crashes + * in packages which differ only by minor release number. + */ + *last_dot = '\0'; + } + } + + char *string_to_hash = xasprintf("%s%s%s", package, executable, unstrip_n_output); + /*free(package);*/ + /*free(executable);*/ + /*free(unstrip_n_output);*/ + + char hash_str[SHA1_RESULT_LEN*2 + 1]; + create_hash(hash_str, string_to_hash); + /*free(hash_str);*/ + + dd_save_text(dd, CD_UUID, hash_str); + dd_close(dd); + + return 0; +} diff --git a/src/plugins/abrt-action-analyze-oops.c b/src/plugins/abrt-action-analyze-oops.c new file mode 100644 index 00000000..354ec6fd --- /dev/null +++ b/src/plugins/abrt-action-analyze-oops.c @@ -0,0 +1,176 @@ +/* + 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 "parse_options.h" + +#define PROGNAME "abrt-action-analyze-oops" + +static unsigned hash_oops_str(const char *oops_ptr) +{ + unsigned char old_c; + unsigned char c = 0; + unsigned hash = 0; + + /* Special-case: if the first line is of form: + * WARNING: at net/wireless/core.c:614 wdev_cleanup_work+0xe9/0x120 [cfg80211]() (Not tainted) + * then hash only "file:line func+ofs/len" part. + */ + if (strncmp(oops_ptr, "WARNING: at ", sizeof("WARNING: at ")-1) == 0) + { + const char *p = oops_ptr + sizeof("WARNING: at ")-1; + p = strchr(p, ' '); /* skip filename:NNN */ + if (p) + { + p = strchrnul(p + 1, ' '); /* skip function_name+0xNN/0xNNN */ + oops_ptr += sizeof("WARNING: at ")-1; + while (oops_ptr < p) + { + c = *oops_ptr++; + hash = ((hash << 5) ^ (hash >> 27)) ^ c; + } + return hash; + } + } + + while (1) + { + old_c = c; + c = *oops_ptr++; + if (!c) + break; + if (c == '\n') + { + // Exclude some lines which have process name - in some oops classes + // process name is irrelevant and changes with every oops. + // Lines we filter out: + // Pid: 8003, comm: Xorg Not tainted (2.6.27.9-159.fc10.i686 #1) + // Process Xorg (pid: 8003, ti=f0a0c000 task=f2380000 task.ti=f0a0c000) + if (strncmp(oops_ptr, "Pid: ", 5) == 0 + || strncmp(oops_ptr, "Process ", 8) == 0 + ) { + while (*oops_ptr && *oops_ptr != '\n') + oops_ptr++; + continue; + } + } + if (!isalnum(old_c)) + { + if (c >= '0' && c <= '9') + { + // Convert all (possibly hex) numbers to just one '0' + if (c == '0' && *oops_ptr == 'x') // "0xSOMETHING" + oops_ptr++; + while (isxdigit(*oops_ptr)) + oops_ptr++; + c = '0'; + } + else if ((c|0x20) >= 'a' && (c|0x20) <= 'f') + { + // This *may be* a hex number without 0x prefix: "f0a0c000" + // Check that it indeed is, and replace with '0' + const char *oops_ptr2 = oops_ptr; + while (isxdigit(*oops_ptr2)) + oops_ptr2++; + // Does it end in a letter which is not a hex digit? + // (Example: "abcw" is not a hex number, "abc " is) + if (!isalpha(*oops_ptr2)) + { + // It's "abc " case. Skip the "abc" string + oops_ptr = oops_ptr2; + c = '0'; + } + // else: hash the string as-is + } + } + // TODO: Drop call trace tail - in interrupt-driven oopses, + // everything before interrupt is irrelevant. + // Example of call trace part of oops: + // Call Trace: + // [<f88e11c7>] ? radeon_cp_resume+0x7d/0xbc [radeon] + // [<f88745f8>] ? drm_ioctl+0x1b0/0x225 [drm] + // [<f88e114a>] ? radeon_cp_resume+0x0/0xbc [radeon] + // [<c049b1c0>] ? vfs_ioctl+0x50/0x69 + // [<c049b414>] ? do_vfs_ioctl+0x23b/0x247 + // [<c0460a56>] ? audit_syscall_entry+0xf9/0x123 + // [<c049b460>] ? sys_ioctl+0x40/0x5c + // [<c0403c76>] ? syscall_call+0x7/0xb + + /* An algorithm proposed by Donald E. Knuth in The Art Of Computer + * Programming Volume 3, under the topic of sorting and search + * chapter 6.4. + */ + hash = ((hash << 5) ^ (hash >> 27)) ^ c; + } + return hash; +} + +int main(int argc, char **argv) +{ + char *env_verbose = getenv("ABRT_VERBOSE"); + if (env_verbose) + g_verbose = atoi(env_verbose); + + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( + PROGNAME" [-vs] -d DIR\n\n" + "Calculates and saves UUID and DUPHASH of oops crash dumps" + ); + const char *dump_dir_name = "."; + enum { + OPT_v = 1 << 0, + OPT_d = 1 << 1, + OPT_s = 1 << 2, + }; + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_STRING('d', NULL, &dump_dir_name, "DIR", _("Crash dump directory")), + OPT_BOOL( 's', NULL, NULL, _("Log to syslog" )), + OPT_END() + }; + /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); + + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + + msg_prefix = PROGNAME; +//Maybe we will want this... later +// if (opts & OPT_s) +// { +// openlog(msg_prefix, 0, LOG_DAEMON); +// logmode = LOGMODE_SYSLOG; +// } + + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + return 1; + + char *oops = dd_load_text(dd, FILENAME_BACKTRACE); + unsigned hash = hash_oops_str(oops); + /* free(oops); */ + + hash &= 0x7FFFFFFF; + char hash_str[sizeof(int)*3 + 2]; + sprintf(hash_str, "%u", hash); + dd_save_text(dd, CD_UUID, hash_str); + dd_save_text(dd, FILENAME_DUPHASH, hash_str); + + dd_close(dd); + + return 0; +} diff --git a/src/plugins/abrt-action-analyze-python.c b/src/plugins/abrt-action-analyze-python.c new file mode 100644 index 00000000..bb5722ec --- /dev/null +++ b/src/plugins/abrt-action-analyze-python.c @@ -0,0 +1,119 @@ +/* + 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 "parse_options.h" + +#define PROGNAME "abrt-action-analyze-python" + +// Hash is MD5_RESULT_LEN bytes long, but we use only first 4 +// (I don't know why old Python code was using only 4, I mimic that) +#define HASH_STRING_HEX_DIGITS 4 + +int main(int argc, char **argv) +{ + char *env_verbose = getenv("ABRT_VERBOSE"); + if (env_verbose) + g_verbose = atoi(env_verbose); + + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( + PROGNAME" [-vs] -d DIR\n\n" + "Calculates and saves UUID and DUPHASH of python crash dumps" + ); + const char *dump_dir_name = "."; + enum { + OPT_v = 1 << 0, + OPT_d = 1 << 1, + OPT_s = 1 << 2, + }; + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_STRING('d', NULL, &dump_dir_name, "DIR", _("Crash dump directory")), + OPT_BOOL( 's', NULL, NULL, _("Log to syslog" )), + OPT_END() + }; + /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); + + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + + msg_prefix = PROGNAME; +//Maybe we will want this... later +// if (opts & OPT_s) +// { +// openlog(msg_prefix, 0, LOG_DAEMON); +// logmode = LOGMODE_SYSLOG; +// } + + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + return 1; + char *bt = dd_load_text(dd, FILENAME_BACKTRACE); + + /* Hash 1st line of backtrace and save it as UUID and DUPHASH */ + + const char *bt_end = strchrnul(bt, '\n'); + unsigned char hash_bytes[MD5_RESULT_LEN]; + md5_ctx_t md5ctx; + md5_begin(&md5ctx); + // Better: + // "example.py:1:<module>:ZeroDivisionError: integer division or modulo by zero" + //md5_hash(bt_str, bt_end - bt_str, &md5ctx); + //free(bt); + // For now using compat version: + { + char *copy = xstrndup(bt, bt_end - bt); + free(bt); + char *s = copy; + char *d = copy; + unsigned colon_cnt = 0; + while (*s && colon_cnt < 3) + { + if (*s != ':') + *d++ = *s; + else + colon_cnt++; + s++; + } + // copy = "example.py1<module>" + md5_hash(copy, d - copy, &md5ctx); + free(copy); + } + // end of compat version + md5_end(hash_bytes, &md5ctx); + + char hash_str[HASH_STRING_HEX_DIGITS*2 + 1]; + unsigned len = HASH_STRING_HEX_DIGITS; + char *d = hash_str; + unsigned char *s = hash_bytes; + while (len) + { + *d++ = "0123456789abcdef"[*s >> 4]; + *d++ = "0123456789abcdef"[*s & 0xf]; + s++; + len--; + } + *d = '\0'; + + dd_save_text(dd, CD_UUID, hash_str); + dd_save_text(dd, FILENAME_DUPHASH, hash_str); + dd_close(dd); + + return 0; +} diff --git a/src/plugins/abrt-action-bugzilla.cpp b/src/plugins/abrt-action-bugzilla.cpp new file mode 100644 index 00000000..a3c2f0b0 --- /dev/null +++ b/src/plugins/abrt-action-bugzilla.cpp @@ -0,0 +1,908 @@ +/* + 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 "abrt_xmlrpc.h" +#include "crash_types.h" +#include "abrt_exception.h" + +#include "plugin.h" /* make_description_bz */ + +#define XML_RPC_SUFFIX "/xmlrpc.cgi" +#define MAX_HOPS 5 + +/* + * TODO: npajkovs: better deallocation of xmlrpc value + * npajkovs: better gathering function which collects all information from bugzilla + * npajkovs: figure out how to deal with cloning bugs + * npajkovs: check if attachment was uploaded successul an if not try it again(max 3 times) + * and if it still fails. retrun successful, but mention that attaching failed + * npajkovs: add option to set comment privat + */ + +struct bug_info { + const char* bug_status; + const char* bug_resolution; + const char* bug_reporter; + const char* bug_product; + xmlrpc_int32 bug_dup_id; + std::vector<char*> bug_cc; +}; + +static void bug_info_init(struct bug_info* bz) +{ + bz->bug_status = NULL; + bz->bug_resolution = NULL; + bz->bug_reporter = NULL; + bz->bug_product = NULL; + bz->bug_dup_id = -1; +} + +static void bug_info_destroy(struct bug_info* bz) +{ + free((void*)bz->bug_status); + free((void*)bz->bug_resolution); + free((void*)bz->bug_reporter); + free((void*)bz->bug_product); + + if (!bz->bug_cc.empty()) + { + for (unsigned ii = 0; ii < bz->bug_cc.size(); ii++) + free(bz->bug_cc[ii]); + + bz->bug_cc.clear(); + } +} + +static int am_i_in_cc(const struct bug_info* bz, const char* login) +{ + if (bz->bug_cc.empty()) + return -1; + + int size = bz->bug_cc.size(); + for (int ii = 0; ii < size; ii++) + { + if (strcmp(login, bz->bug_cc[ii]) == 0) + return 0; + } + return -1; +} + +/* + * Static namespace for xmlrpc stuff. + * Used mainly to ensure we always destroy xmlrpc client and server_info. + */ + +namespace { + +struct ctx: public abrt_xmlrpc_conn { + xmlrpc_env env; + + ctx(const char* url, bool ssl_verify): abrt_xmlrpc_conn(url, ssl_verify) + { xmlrpc_env_init(&env); } + ~ctx() { xmlrpc_env_clean(&env); } + + void login(const char* login, const char* passwd); + void logout(); + + const char* get_bug_status(xmlrpc_value* result_xml); + const char* get_bug_resolution(xmlrpc_value* result_xml); + const char* get_bug_reporter(xmlrpc_value* result_xml); + const char* get_bug_product(xmlrpc_value* relult_xml); + + xmlrpc_value* call_quicksearch_duphash(const char* component, const char* release, const char* duphash); + xmlrpc_value* get_cc_member(xmlrpc_value* result_xml); + xmlrpc_value* get_member(const char* member, xmlrpc_value* result_xml); + + int get_array_size(xmlrpc_value* result_xml); + xmlrpc_int32 get_bug_id(xmlrpc_value* result_xml); + xmlrpc_int32 get_bug_dup_id(xmlrpc_value* result_xml); + void get_bug_cc(xmlrpc_value* result_xml, struct bug_info* bz); + int add_plus_one_cc(xmlrpc_int32 bug_id, const char* login); + xmlrpc_int32 new_bug(const map_crash_data_t& pCrashData, int depend_on_bugno); + int add_attachments(const char* bug_id_str, const map_crash_data_t& pCrashData); + int get_bug_info(struct bug_info* bz, xmlrpc_int32 bug_id); + int add_comment(xmlrpc_int32 bug_id, const char* comment, bool is_private); + + xmlrpc_value* call(const char* method, const char* format, ...); +}; + +xmlrpc_value* ctx::call(const char* method, const char* format, ...) +{ + va_list args; + xmlrpc_value* param = NULL; + xmlrpc_value* result = NULL; + const char* suffix; + + va_start(args, format); + xmlrpc_build_value_va(&env, format, args, ¶m, &suffix); + va_end(args); + + if (!env.fault_occurred) + { + if (*suffix != '\0') + { + xmlrpc_env_set_fault_formatted( + &env, XMLRPC_INTERNAL_ERROR, "Junk after the argument " + "specifier: '%s'. There must be exactly one arument.", + suffix); + + xmlrpc_DECREF(param); + return NULL; + } + + xmlrpc_client_call2(&env, m_pClient, m_pServer_info, method, param, &result); + xmlrpc_DECREF(param); + if (env.fault_occurred) + return NULL; + } + + + return result; +} + +xmlrpc_value* ctx::get_member(const char* member, xmlrpc_value* result_xml) +{ + xmlrpc_value* cc_member = NULL; + xmlrpc_struct_find_value(&env, result_xml, member, &cc_member); + if (env.fault_occurred) + return NULL; + + return cc_member; +} + +int ctx::get_array_size(xmlrpc_value* result_xml) +{ + int size = xmlrpc_array_size(&env, result_xml); + if (env.fault_occurred) + return -1; + + return size; +} + +xmlrpc_int32 ctx::get_bug_dup_id(xmlrpc_value* result_xml) +{ + xmlrpc_value* dup_id = get_member("dup_id", result_xml); + if (!dup_id) + return -1; + + xmlrpc_int32 dup_id_int = -1; + xmlrpc_read_int(&env, dup_id, &dup_id_int); + xmlrpc_DECREF(dup_id); + if (env.fault_occurred) + return -1; + + VERB3 log("got dup_id: %i", dup_id_int); + return dup_id_int; +} + +const char* ctx::get_bug_product(xmlrpc_value* result_xml) +{ + xmlrpc_value* product_member = get_member("product", result_xml); + if (!product_member) //should never happend. Each bug has to set up product + return NULL; + + const char* product = NULL; + xmlrpc_read_string(&env, product_member, &product); + xmlrpc_DECREF(product_member); + if (env.fault_occurred) + return NULL; + + if (*product != '\0') + { + VERB3 log("got bug product: %s", product); + return product; + } + + free((void*)product); + return NULL; +} + +const char* ctx::get_bug_reporter(xmlrpc_value* result_xml) +{ + xmlrpc_value* reporter_member = get_member("reporter", result_xml); + if (!reporter_member) + return NULL; + + const char* reporter = NULL; + xmlrpc_read_string(&env, reporter_member, &reporter); + xmlrpc_DECREF(reporter_member); + if (env.fault_occurred) + return NULL; + + if (*reporter != '\0') + { + VERB3 log("got bug reporter: %s", reporter); + return reporter; + } + free((void*)reporter); + return NULL; +} + +const char* ctx::get_bug_resolution(xmlrpc_value* result_xml) +{ + xmlrpc_value* bug_resolution = get_member("resolution", result_xml); + if (!bug_resolution) + return NULL; + + const char* resolution_str = NULL; + xmlrpc_read_string(&env, bug_resolution, &resolution_str); + xmlrpc_DECREF(bug_resolution); + if (env.fault_occurred) + return NULL; + + if (*resolution_str != '\0') + { + VERB3 log("got resolution: %s", resolution_str); + return resolution_str; + } + free((void*)resolution_str); + return NULL; +} + +const char* ctx::get_bug_status(xmlrpc_value* result_xml) +{ + xmlrpc_value* bug_status = get_member("bug_status", result_xml); + if (!bug_status) + return NULL; + + const char* status_str = NULL; + xmlrpc_read_string(&env, bug_status, &status_str); + xmlrpc_DECREF(bug_status); + if (env.fault_occurred) + return NULL; + + if (*status_str != '\0') + { + VERB3 log("got bug_status: %s", status_str); + return status_str; + } + free((void*)status_str); + return NULL; +} + +void ctx::get_bug_cc(xmlrpc_value* result_xml, struct bug_info* bz) +{ + xmlrpc_value* cc_member = get_member("cc", result_xml); + if (!cc_member) + return; + + int array_size = xmlrpc_array_size(&env, cc_member); + if (array_size == -1) + return; + + VERB3 log("count members on cc %i", array_size); + + for (int i = 0; i < array_size; i++) + { + xmlrpc_value* item = NULL; + xmlrpc_array_read_item(&env, cc_member, i, &item); + if (env.fault_occurred) + return; + + if (item) + { + const char* cc = NULL; + xmlrpc_read_string(&env, item, &cc); + xmlrpc_DECREF(item); + if (env.fault_occurred) + { + xmlrpc_DECREF(cc_member); + return; + } + + if (*cc != '\0') + { + bz->bug_cc.push_back((char*)cc); + VERB3 log("member on cc is %s", cc); + continue; + } + free((char*)cc); + } + } + xmlrpc_DECREF(cc_member); + return; +} + +xmlrpc_value* ctx::call_quicksearch_duphash(const char* component, const char* release, const char* duphash) +{ + char *query = NULL; + if (!release) + query = xasprintf("ALL component:\"%s\" whiteboard:\"%s\"", component, duphash); + else + { + char *product = NULL; + char *version = NULL; + parse_release(release, &product, &version); + query = xasprintf("ALL component:\"%s\" whiteboard:\"%s\" product:\"%s\"", + component, duphash, product + ); + free(product); + free(version); + } + + VERB3 log("quicksearch for `%s'", query); + xmlrpc_value *ret = call("Bug.search", "({s:s})", "quicksearch", query); + free(query); + return ret; +} + +xmlrpc_int32 ctx::get_bug_id(xmlrpc_value* result_xml) +{ + xmlrpc_value* item = NULL; + xmlrpc_array_read_item(&env, result_xml, 0, &item); + if (env.fault_occurred) + return -1; + + xmlrpc_value* bug = get_member("bug_id", item); + xmlrpc_DECREF(item); + if (!bug) + return -1; + + xmlrpc_int32 bug_id = -1; + xmlrpc_read_int(&env, bug, &bug_id); + xmlrpc_DECREF(bug); + if (env.fault_occurred) + return -1; + + VERB3 log("got bug_id %d", (int)bug_id); + return bug_id; +} + +int ctx::add_plus_one_cc(xmlrpc_int32 bug_id, const char* login) +{ + xmlrpc_value* result = call("Bug.update", "({s:i,s:{s:(s)}})", "ids", (int)bug_id, "updates", "add_cc", login); + if (result) + xmlrpc_DECREF(result); + return result ? 0 : -1; +} + +int ctx::add_comment(xmlrpc_int32 bug_id, const char* comment, bool is_private) +{ + xmlrpc_value* result = call("Bug.add_comment", "({s:i,s:s,s:b})", "id", (int)bug_id, + "comment", comment, + "private", is_private); + if (result) + xmlrpc_DECREF(result); + return result ? 0 : -1; +} + +xmlrpc_int32 ctx::new_bug(const map_crash_data_t& pCrashData, int depend_on_bugno) +{ + const char *package = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_PACKAGE); + const char *component = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_COMPONENT); + const char *release = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_RELEASE); + const char *arch = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_ARCHITECTURE); + const char *duphash = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_DUPHASH); + const char *reason = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_REASON); + const char *function = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_CRASH_FUNCTION); + + struct strbuf *buf_summary = strbuf_new(); + strbuf_append_strf(buf_summary, "[abrt] %s", package); + + if (function != NULL && strlen(function) < 30) + strbuf_append_strf(buf_summary, ": %s", function); + + if (reason != NULL) + strbuf_append_strf(buf_summary, ": %s", reason); + + char *status_whiteboard = xasprintf("abrt_hash:%s", duphash); + + char *bz_dsc = make_description_bz(pCrashData); + char *full_dsc = xasprintf("abrt version: "VERSION"\n%s", bz_dsc); + free(bz_dsc); + + char *product = NULL; + char *version = NULL; + parse_release(release, &product, &version); + + xmlrpc_value* result = NULL; + char *summary = strbuf_free_nobuf(buf_summary); + if (depend_on_bugno > -1) + { + result = call("Bug.create", "({s:s,s:s,s:s,s:s,s:s,s:s,s:s,s:i})", + "product", product, + "component", component, + "version", version, + "summary", summary, + "description", full_dsc, + "status_whiteboard", status_whiteboard, + "platform", arch, + "dependson", depend_on_bugno + ); + } + else + { + result = call("Bug.create", "({s:s,s:s,s:s,s:s,s:s,s:s,s:s})", + "product", product, + "component", component, + "version", version, + "summary", summary, + "description", full_dsc, + "status_whiteboard", status_whiteboard, + "platform", arch + ); + } + free(status_whiteboard); + free(product); + free(version); + free(summary); + free(full_dsc); + + if (!result) + return -1; + + xmlrpc_value* id = get_member("id", result); + xmlrpc_DECREF(result); + if (!id) + return -1; + + xmlrpc_int32 bug_id = -1; + xmlrpc_read_int(&env, id, &bug_id); + xmlrpc_DECREF(id); + if (env.fault_occurred) + return -1; + + log(_("New bug id: %i"), (int)bug_id); + + return bug_id; +} + +int ctx::add_attachments(const char* bug_id_str, const map_crash_data_t& pCrashData) +{ + map_crash_data_t::const_iterator it = pCrashData.begin(); + for (; it != pCrashData.end(); it++) + { + const char *itemname = it->first.c_str(); + const char *type = it->second[CD_TYPE].c_str(); + const char *content = it->second[CD_CONTENT].c_str(); + + if ((strcmp(type, CD_TXT) == 0) + && (strlen(content) > CD_TEXT_ATT_SIZE || (strcmp(itemname, FILENAME_BACKTRACE) == 0)) + ) { + char *encoded64 = encode_base64(content, strlen(content)); + char *filename = xasprintf("File: %s", itemname); + xmlrpc_value* result = call("bugzilla.addAttachment", "(s{s:s,s:s,s:s,s:s})", bug_id_str, + "description", filename, + "filename", itemname, + "contenttype", "text/plain", + "data", encoded64 + ); + free(encoded64); + free(filename); + if (!result) + return -1; + + xmlrpc_DECREF(result); + } + } + return 0; +} + +int ctx::get_bug_info(struct bug_info* bz, xmlrpc_int32 bug_id) +{ + xmlrpc_value* result = call("bugzilla.getBug", "(s)", to_string(bug_id).c_str()); + if (!result) + return -1; + + bz->bug_product = get_bug_product(result); + if (bz->bug_product == NULL) + return -1; + + bz->bug_status = get_bug_status(result); + if (bz->bug_status == NULL) + return -1; + + bz->bug_reporter = get_bug_reporter(result); + if (bz->bug_reporter == NULL) + return -1; + + // mandatory when bug status is CLOSED + if (strcmp(bz->bug_status, "CLOSED") == 0) + { + bz->bug_resolution = get_bug_resolution(result); + if ((env.fault_occurred) && (bz->bug_resolution == NULL)) + return -1; + } + + // mandatory when bug status is CLOSED and resolution is DUPLICATE + if ((strcmp(bz->bug_status, "CLOSED") == 0) + && (strcmp(bz->bug_resolution, "DUPLICATE") == 0) + ) { + bz->bug_dup_id = get_bug_dup_id(result); + if (env.fault_occurred) + return -1; + } + + get_bug_cc(result, bz); + if (env.fault_occurred) + return -1; + + xmlrpc_DECREF(result); + return 0; +} + +void ctx::login(const char* login, const char* passwd) +{ + xmlrpc_value* result = call("User.login", "({s:s,s:s})", "login", login, "password", passwd); + + if (!result) + { + char *errmsg = xasprintf("Can't login. Check Edit->Plugins->Bugzilla and /etc/abrt/plugins/Bugzilla.conf. Server said: %s", env.fault_string); + error_msg("%s", errmsg); // show error in daemon log + CABRTException e(EXCEP_PLUGIN, errmsg); + free(errmsg); + throw e; + } + xmlrpc_DECREF(result); +} + +void ctx::logout() +{ + xmlrpc_value* result = call("User.logout", "(s)", ""); + if (result) + xmlrpc_DECREF(result); + + throw_if_xml_fault_occurred(&env); +} + +} /* namespace */ + + +static void report_to_bugzilla( + const char *dump_dir_name, + /*const*/ map_plugin_settings_t& settings) +{ + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + { + throw CABRTException(EXCEP_PLUGIN, _("Can't open '%s'"), dump_dir_name); + } + map_crash_data_t pCrashData; + load_crash_data_from_debug_dump(dd, pCrashData); + dd_close(dd); + + const char *env; + const char *login; + const char *password; + const char *bugzilla_xmlrpc; + const char *bugzilla_url; + bool ssl_verify; + + env = getenv("Bugzilla_Login"); + login = env ? env : settings["Login"].c_str(); + env = getenv("Bugzilla_Password"); + password = env ? env : settings["Password"].c_str(); + if (!login[0] || !password[0]) + { + VERB3 log("Empty login and password"); + throw CABRTException(EXCEP_PLUGIN, _("Empty login or password, please check %s"), PLUGINS_CONF_DIR"/Bugzilla.conf"); + } + + env = getenv("Bugzilla_BugzillaURL"); + bugzilla_url = env ? env : settings["BugzillaURL"].c_str(); + if (!bugzilla_url[0]) + bugzilla_url = "https://bugzilla.redhat.com"; + bugzilla_xmlrpc = xasprintf("%s"XML_RPC_SUFFIX, bugzilla_url); + + env = getenv("Bugzilla_SSLVerify"); + ssl_verify = string_to_bool(env ? env : settings["SSLVerify"].c_str()); + + const char *component = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_COMPONENT); + const char *duphash = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_DUPHASH); + const char *release = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_RELEASE); + + ctx bz_server(bugzilla_xmlrpc, ssl_verify); + + log(_("Logging into bugzilla...")); + bz_server.login(login, password); + + log(_("Checking for duplicates...")); + + char *product = NULL; + char *version = NULL; + parse_release(release, &product, &version); + + xmlrpc_value *result; + if (strcmp(product, "Fedora") == 0) + result = bz_server.call_quicksearch_duphash(component, product, duphash); + else + result = bz_server.call_quicksearch_duphash(component, NULL, duphash); + + if (!result) + throw_if_xml_fault_occurred(&bz_server.env); + + xmlrpc_value *all_bugs = bz_server.get_member("bugs", result); + xmlrpc_DECREF(result); + + if (!all_bugs) + { + throw_if_xml_fault_occurred(&bz_server.env); + throw CABRTException(EXCEP_PLUGIN, _("Missing mandatory member 'bugs'")); + } + + xmlrpc_int32 bug_id = -1; + int all_bugs_size = bz_server.get_array_size(all_bugs); + struct bug_info bz; + int depend_on_bugno = -1; + if (all_bugs_size > 0) + { + bug_id = bz_server.get_bug_id(all_bugs); + xmlrpc_DECREF(all_bugs); + if (bug_id == -1) + throw_if_xml_fault_occurred(&bz_server.env); + + bug_info_init(&bz); + if (bz_server.get_bug_info(&bz, bug_id) == -1) + { + bug_info_destroy(&bz); + throw_if_xml_fault_occurred(&bz_server.env); + throw CABRTException(EXCEP_PLUGIN, _("get_bug_info() failed. Could not collect all mandatory information")); + } + + if (strcmp(bz.bug_product, product) != 0) + { + depend_on_bugno = bug_id; + bug_info_destroy(&bz); + result = bz_server.call_quicksearch_duphash(component, release, duphash); + if (!result) + throw_if_xml_fault_occurred(&bz_server.env); + + all_bugs = bz_server.get_member("bugs", result); + xmlrpc_DECREF(result); + + if (!all_bugs) + { + throw_if_xml_fault_occurred(&bz_server.env); + throw CABRTException(EXCEP_PLUGIN, _("Missing mandatory member 'bugs'")); + } + + all_bugs_size = bz_server.get_array_size(all_bugs); + if (all_bugs_size > 0) + { + bug_id = bz_server.get_bug_id(all_bugs); + xmlrpc_DECREF(all_bugs); + if (bug_id == -1) + throw_if_xml_fault_occurred(&bz_server.env); + + bug_info_init(&bz); + if (bz_server.get_bug_info(&bz, bug_id) == -1) + { + bug_info_destroy(&bz); + throw_if_xml_fault_occurred(&bz_server.env); + throw CABRTException(EXCEP_PLUGIN, _("get_bug_info() failed. Could not collect all mandatory information")); + } + } + else + xmlrpc_DECREF(all_bugs); + } + } + free(product); + free(version); + + if (all_bugs_size < 0) + { + throw_if_xml_fault_occurred(&bz_server.env); + } + else if (all_bugs_size == 0) // Create new bug + { + log(_("Creating a new bug...")); + bug_id = bz_server.new_bug(pCrashData, depend_on_bugno); + if (bug_id < 0) + { + throw_if_xml_fault_occurred(&bz_server.env); + throw CABRTException(EXCEP_PLUGIN, _("Bugzilla entry creation failed")); + } + + log("Adding attachments to bug %d...", bug_id); + int ret = bz_server.add_attachments(to_string(bug_id).c_str(), pCrashData); + if (ret == -1) + { + throw_if_xml_fault_occurred(&bz_server.env); + } + + log(_("Logging out...")); + bz_server.logout(); + + log("Status: NEW %s/show_bug.cgi?id=%u", + bugzilla_url, + (int)bug_id + ); + return; + } + + if (all_bugs_size > 1) + { + // When someone clones bug it has same duphash, so we can find more than 1. + // Need to be checked if component is same. + VERB3 log("Bugzilla has %u reports with same duphash '%s'", all_bugs_size, duphash); + } + + // decision based on state + log(_("Bug is already reported: %i"), bug_id); + + xmlrpc_int32 original_bug_id = bug_id; + if ((strcmp(bz.bug_status, "CLOSED") == 0) && (strcmp(bz.bug_resolution, "DUPLICATE") == 0)) + { + for (int ii = 0; ii <= MAX_HOPS; ii++) + { + if (ii == MAX_HOPS) + { + VERB3 log("Bugzilla could not find a parent of bug %d", (int)original_bug_id); + bug_info_destroy(&bz); + throw CABRTException(EXCEP_PLUGIN, _("Bugzilla couldn't find parent of bug %d"), (int)original_bug_id); + } + + log("Bug %d is a duplicate, using parent bug %d", bug_id, (int)bz.bug_dup_id); + bug_id = bz.bug_dup_id; + bug_info_destroy(&bz); + bug_info_init(&bz); + + if (bz_server.get_bug_info(&bz, bug_id) == -1) + { + bug_info_destroy(&bz); + if (bz_server.env.fault_occurred) + { + throw_if_xml_fault_occurred(&bz_server.env); + } + throw CABRTException(EXCEP_PLUGIN, _("get_bug_info() failed. Could not collect all mandatory information")); + } + + // found a bug which is not CLOSED as DUPLICATE + if (bz.bug_dup_id == -1) + break; + } + } + + if (strcmp(bz.bug_status, "CLOSED") != 0) + { + int status = 0; + if ((strcmp(bz.bug_reporter, login) != 0) && (am_i_in_cc(&bz, login))) + { + log(_("Add %s to CC list"), login); + status = bz_server.add_plus_one_cc(bug_id, login); + } + + if (status == -1) + { + bug_info_destroy(&bz); + throw_if_xml_fault_occurred(&bz_server.env); + } + + char *dsc = make_description_reproduce_comment(pCrashData); + if (dsc) + { + const char* package = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_PACKAGE); + const char* release = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_RELEASE); + const char* arch = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_ARCHITECTURE); + const char* is_private = get_crash_data_item_content_or_NULL(pCrashData, "is_private"); + + char *full_dsc = xasprintf("Package: %s\n" + "Architecture: %s\n" + "OS Release: %s\n" + "%s", package, arch, release, dsc + ); + + log(_("Adding new comment to bug %d"), (int)bug_id); + + free(dsc); + + bool is_priv = is_private && (is_private[0] == '1'); + if (bz_server.add_comment(bug_id, full_dsc, is_priv) == -1) + { + free(full_dsc); + bug_info_destroy(&bz); + throw_xml_fault(&bz_server.env); + } + free(full_dsc); + } + } + + log(_("Logging out...")); + bz_server.logout(); + + log("Status: %s%s%s %s/show_bug.cgi?id=%u", + bz.bug_status, + bz.bug_resolution ? " " : "", + bz.bug_resolution ? bz.bug_resolution : "", + bugzilla_url, + (int)bug_id + ); + + bug_info_destroy(&bz); +} + +int main(int argc, char **argv) +{ + char *env_verbose = getenv("ABRT_VERBOSE"); + if (env_verbose) + g_verbose = atoi(env_verbose); + + map_plugin_settings_t settings; + + const char *dump_dir_name = "."; + enum { + OPT_s = (1 << 0), + }; + int optflags = 0; + int opt; + while ((opt = getopt(argc, argv, "c:d:vs")) != -1) + { + switch (opt) + { + case 'c': + dump_dir_name = optarg; + VERB1 log("Loading settings from '%s'", optarg); + LoadPluginSettings(optarg, settings); + VERB3 log("Loaded '%s'", optarg); + break; + case 'd': + dump_dir_name = optarg; + break; + case 'v': + g_verbose++; + break; + case 's': + optflags |= OPT_s; + break; + default: + /* Careful: the string below contains tabs, dont replace with spaces */ + error_msg_and_die( + "Usage: abrt-action-bugzilla -c CONFFILE -d DIR [-vs]" + "\n" + "\nReport a crash to Bugzilla" + "\n" + "\nOptions:" + "\n -c FILE Configuration file (may be given many times)" + "\n -d DIR Crash dump directory" + "\n -v Verbose" + "\n -s Log to syslog" + ); + } + } + + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + +//DONT! our stdout/stderr goes directly to daemon, don't want to have prefix there. +// msg_prefix = xasprintf("abrt-action-bugzilla[%u]", getpid()); + + if (optflags & OPT_s) + { + openlog(msg_prefix, 0, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + } + + 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); + xmlrpc_env_clean(&env); + + try + { + report_to_bugzilla(dump_dir_name, settings); + } + catch (CABRTException& e) + { + error_msg_and_die("%s", e.what()); + } + + return 0; +} diff --git a/src/plugins/abrt-action-generate-backtrace.c b/src/plugins/abrt-action-generate-backtrace.c new file mode 100644 index 00000000..8f1642d7 --- /dev/null +++ b/src/plugins/abrt-action-generate-backtrace.c @@ -0,0 +1,386 @@ +/* + 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 "../btparser/backtrace.h" +#include "../btparser/frame.h" +#include "../btparser/location.h" +#include "parse_options.h" + + +#define PROGNAME "abrt-action-generate-backtrace" + +#define DEBUGINFO_CACHE_DIR LOCALSTATEDIR"/cache/abrt-di" + +static const char *dump_dir_name = "."; +static const char *debuginfo_dirs; +static int exec_timeout_sec = 60; + + +static void create_hash(char hash_str[SHA1_RESULT_LEN*2 + 1], const char *pInput) +{ + unsigned len; + unsigned char hash2[SHA1_RESULT_LEN]; + sha1_ctx_t sha1ctx; + + sha1_begin(&sha1ctx); + sha1_hash(pInput, strlen(pInput), &sha1ctx); + sha1_end(hash2, &sha1ctx); + len = SHA1_RESULT_LEN; + + char *d = hash_str; + unsigned char *s = hash2; + while (len) + { + *d++ = "0123456789abcdef"[*s >> 4]; + *d++ = "0123456789abcdef"[*s & 0xf]; + s++; + len--; + } + *d = '\0'; + //log("hash2:%s str:'%s'", hash_str, pInput); +} + +/** + * + * @param[out] status See `man 2 wait` for status information. + * @return Malloc'ed string + */ +static char* exec_vp(char **args, uid_t uid, int redirect_stderr, int *status) +{ + /* Nuke everything which may make setlocale() switch to non-POSIX locale: + * we need to avoid having gdb output in some obscure language. + */ + static const char *const unsetenv_vec[] = { + "LANG", + "LC_ALL", + "LC_COLLATE", + "LC_CTYPE", + "LC_MESSAGES", + "LC_MONETARY", + "LC_NUMERIC", + "LC_TIME", + NULL + }; + + int flags = EXECFLG_INPUT_NUL | EXECFLG_OUTPUT | EXECFLG_SETGUID | EXECFLG_SETSID | EXECFLG_QUIET; + if (redirect_stderr) + flags |= EXECFLG_ERR2OUT; + VERB1 flags &= ~EXECFLG_QUIET; + + int pipeout[2]; + pid_t child = fork_execv_on_steroids(flags, args, pipeout, (char**)unsetenv_vec, /*dir:*/ NULL, uid); + + /* We use this function to run gdb and unstrip. Bugs in gdb or corrupted + * coredumps were observed to cause gdb to enter infinite loop. + * Therefore we have a (largish) timeout, after which we kill the child. + */ + int t = time(NULL); /* int is enough, no need to use time_t */ + int endtime = t + exec_timeout_sec; + + struct strbuf *buf_out = strbuf_new(); + + while (1) + { + int timeout = endtime - t; + if (timeout < 0) + { + kill(child, SIGKILL); + strbuf_append_strf(buf_out, "\nTimeout exceeded: %u seconds, killing %s\n", exec_timeout_sec, args[0]); + break; + } + + /* We don't check poll result - checking read result is enough */ + struct pollfd pfd; + pfd.fd = pipeout[0]; + pfd.events = POLLIN; + poll(&pfd, 1, timeout * 1000); + + char buff[1024]; + int r = read(pipeout[0], buff, sizeof(buff) - 1); + if (r <= 0) + break; + buff[r] = '\0'; + strbuf_append_str(buf_out, buff); + t = time(NULL); + } + close(pipeout[0]); + + /* Prevent having zombie child process, and maybe collect status + * (note that status == NULL is ok too) */ + waitpid(child, status, 0); + + return strbuf_free_nobuf(buf_out); +} + +static char *get_backtrace(struct dump_dir *dd) +{ + char *uid_str = dd_load_text(dd, CD_UID); + uid_t uid = xatoi_u(uid_str); + free(uid_str); + char *executable = dd_load_text(dd, FILENAME_EXECUTABLE); + dd_close(dd); + + // Workaround for + // http://sourceware.org/bugzilla/show_bug.cgi?id=9622 + unsetenv("TERM"); + // This is not necessary + //putenv((char*)"TERM=dumb"); + + char *args[21]; + args[0] = (char*)"gdb"; + args[1] = (char*)"-batch"; + + // when/if gdb supports "set debug-file-directory DIR1:DIR2": + // (https://bugzilla.redhat.com/show_bug.cgi?id=528668): + args[2] = (char*)"-ex"; + struct strbuf *set_debug_file_directory = strbuf_new(); + strbuf_append_str(set_debug_file_directory, "set debug-file-directory /usr/lib/debug"); + const char *p = debuginfo_dirs; + while (1) + { + while (*p == ':') + p++; + if (*p == '\0') + break; + const char *colon_or_nul = strchrnul(p, ':'); + strbuf_append_strf(set_debug_file_directory, ":%.*s/usr/lib/debug", (int)(colon_or_nul - p), p); + p = colon_or_nul; + } + args[3] = strbuf_free_nobuf(set_debug_file_directory); + + /* "file BINARY_FILE" is needed, without it gdb cannot properly + * unwind the stack. Currently the unwind information is located + * in .eh_frame which is stored only in binary, not in coredump + * or debuginfo. + * + * Fedora GDB does not strictly need it, it will find the binary + * by its build-id. But for binaries either without build-id + * (= built on non-Fedora GCC) or which do not have + * their debuginfo rpm installed gdb would not find BINARY_FILE + * so it is still makes sense to supply "file BINARY_FILE". + * + * Unfortunately, "file BINARY_FILE" doesn't work well if BINARY_FILE + * was deleted (as often happens during system updates): + * gdb uses specified BINARY_FILE + * even if it is completely unrelated to the coredump. + * See https://bugzilla.redhat.com/show_bug.cgi?id=525721 + * + * TODO: check mtimes on COREFILE and BINARY_FILE and not supply + * BINARY_FILE if it is newer (to at least avoid gdb complaining). + */ + args[4] = (char*)"-ex"; + args[5] = xasprintf("file %s", executable); + free(executable); + + args[6] = (char*)"-ex"; + args[7] = xasprintf("core-file %s/"FILENAME_COREDUMP, dump_dir_name); + + args[8] = (char*)"-ex"; + /*args[9] = ... see below */ + args[10] = (char*)"-ex"; + args[11] = (char*)"info sharedlib"; + /* glibc's abort() stores its message in __abort_msg variable */ + args[12] = (char*)"-ex"; + args[13] = (char*)"print (char*)__abort_msg"; + args[14] = (char*)"-ex"; + args[15] = (char*)"print (char*)__glib_assert_msg"; + args[16] = (char*)"-ex"; + args[17] = (char*)"info registers"; + args[18] = (char*)"-ex"; + args[19] = (char*)"disassemble"; + args[20] = NULL; + + /* Get the backtrace, but try to cap its size */ + /* Limit bt depth. With no limit, gdb sometimes OOMs the machine */ + unsigned bt_depth = 2048; + const char *thread_apply_all = "thread apply all"; + const char *full = " full"; + char *bt = NULL; + while (1) + { + args[9] = xasprintf("%s backtrace %u%s", thread_apply_all, bt_depth, full); + bt = exec_vp(args, uid, /*redirect_stderr:*/ 1, NULL); + free(args[9]); + if ((bt && strnlen(bt, 256*1024) < 256*1024) || bt_depth <= 32) + { + break; + } + + free(bt); + bt_depth /= 2; + if (bt_depth <= 64 && thread_apply_all[0] != '\0') + { + /* This program likely has gazillion threads, dont try to bt them all */ + bt_depth = 256; + thread_apply_all = ""; + } + if (bt_depth <= 64 && full[0] != '\0') + { + /* Looks like there are gigantic local structures or arrays, disable "full" bt */ + bt_depth = 256; + full = ""; + } + } + + free(args[3]); + free(args[5]); + free(args[7]); + return bt; +} + +static char *i_opt; +static const char abrt_action_generage_backtrace_usage[] = PROGNAME" [options] -d DIR"; +enum { + OPT_v = 1 << 0, + OPT_d = 1 << 1, + OPT_i = 1 << 2, + OPT_t = 1 << 3, + OPT_s = 1 << 4, +}; +/* Keep enum above and order of options below in sync! */ +static struct options abrt_action_generate_backtrace_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_STRING( 'd', NULL, &dump_dir_name, "DIR", "Crash dump directory"), + OPT_STRING( 'i', NULL, &i_opt, "dir1[:dir2]...", "Additional debuginfo directories"), + OPT_INTEGER('t', NULL, &exec_timeout_sec, "Kill gdb if it runs for more than N seconds"), + OPT_BOOL( 's', NULL, NULL, "Log to syslog"), + OPT_END() +}; + +int main(int argc, char **argv) +{ + char *env_verbose = getenv("ABRT_VERBOSE"); + if (env_verbose) + g_verbose = atoi(env_verbose); + + unsigned opts = parse_opts(argc, argv, abrt_action_generate_backtrace_options, + abrt_action_generage_backtrace_usage); + + debuginfo_dirs = DEBUGINFO_CACHE_DIR; + if (i_opt) + { + debuginfo_dirs = xasprintf("%s:%s", DEBUGINFO_CACHE_DIR, i_opt); + } + + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + msg_prefix = PROGNAME; + + if (opts & OPT_s) + { + openlog(msg_prefix, 0, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + } + + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + return 1; + + char *package = dd_load_text(dd, FILENAME_PACKAGE); + char *executable = dd_load_text(dd, FILENAME_EXECUTABLE); + + /* Create and store backtrace */ + /* NB: get_backtrace() closes dd */ + char *backtrace_str = get_backtrace(dd); + if (!backtrace_str) + { + backtrace_str = xstrdup(""); + VERB3 log("get_backtrace() returns NULL, broken core/gdb?"); + } + + dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + return 1; + + dd_save_text(dd, FILENAME_BACKTRACE, backtrace_str); + + /* Compute and store backtrace hash. */ + struct btp_location location; + btp_location_init(&location); + char *backtrace_str_ptr = backtrace_str; + struct btp_backtrace *backtrace = btp_backtrace_parse(&backtrace_str_ptr, &location); + if (!backtrace) + { + VERB1 log(_("Backtrace parsing failed for %s"), dump_dir_name); + VERB1 log("%d:%d: %s", location.line, location.column, location.message); + /* If the parser failed compute the UUID from the executable + and package only. This is not supposed to happen often. + Do not store the rating, as we do not know how good the + backtrace is. */ + struct strbuf *emptybt = strbuf_new(); + strbuf_prepend_str(emptybt, executable); + strbuf_prepend_str(emptybt, package); + char hash_str[SHA1_RESULT_LEN*2 + 1]; + create_hash(hash_str, emptybt->buf); + dd_save_text(dd, FILENAME_DUPHASH, hash_str); + + strbuf_free(emptybt); + free(backtrace_str); + free(package); + free(executable); + dd_close(dd); + return 2; + } + free(backtrace_str); + + /* Compute duplication hash. */ + char *str_hash_core = btp_backtrace_get_duplication_hash(backtrace); + struct strbuf *str_hash = strbuf_new(); + strbuf_append_str(str_hash, package); + strbuf_append_str(str_hash, executable); + strbuf_append_str(str_hash, str_hash_core); + char hash_str[SHA1_RESULT_LEN*2 + 1]; + create_hash(hash_str, str_hash->buf); + dd_save_text(dd, FILENAME_DUPHASH, hash_str); + strbuf_free(str_hash); + free(str_hash_core); + + /* Compute the backtrace rating. */ + float quality = btp_backtrace_quality_complex(backtrace); + const char *rating; + if (quality < 0.6f) + rating = "0"; + else if (quality < 0.7f) + rating = "1"; + else if (quality < 0.8f) + rating = "2"; + else if (quality < 0.9f) + rating = "3"; + else + rating = "4"; + dd_save_text(dd, FILENAME_RATING, rating); + + /* Get the function name from the crash frame. */ + struct btp_frame *crash_frame = btp_backtrace_get_crash_frame(backtrace); + if (crash_frame) + { + if (crash_frame->function_name && + 0 != strcmp(crash_frame->function_name, "??")) + { + dd_save_text(dd, FILENAME_CRASH_FUNCTION, crash_frame->function_name); + } + btp_frame_free(crash_frame); + } + btp_backtrace_free(backtrace); + dd_close(dd); + + free(executable); + free(package); + + return 0; +} diff --git a/src/plugins/abrt-action-install-debuginfo b/src/plugins/abrt-action-install-debuginfo new file mode 100755 index 00000000..c1b8fdb9 --- /dev/null +++ b/src/plugins/abrt-action-install-debuginfo @@ -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-action-install-debuginfo 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-action-install-debuginfo [-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/plugins/abrt-action-kerneloops.cpp b/src/plugins/abrt-action-kerneloops.cpp new file mode 100644 index 00000000..4c820081 --- /dev/null +++ b/src/plugins/abrt-action-kerneloops.cpp @@ -0,0 +1,197 @@ +/* + 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. + + Authors: + Anton Arapov <anton@redhat.com> + Arjan van de Ven <arjan@linux.intel.com> + */ + +#include <curl/curl.h> +#include "abrtlib.h" +#include "crash_types.h" +#include "abrt_exception.h" + +#include "plugin.h" /* LoadPluginSettings */ + +#define PROGNAME "abrt-action-kerneloops" + +/* helpers */ +static size_t writefunction(void *ptr, size_t size, size_t nmemb, void *stream) +{ + size *= nmemb; +/* + char *c, *c1, *c2; + + log("received: '%*.*s'\n", (int)size, (int)size, (char*)ptr); + c = (char*)xzalloc(size + 1); + memcpy(c, ptr, size); + c1 = strstr(c, "201 "); + if (c1) + { + c1 += 4; + c2 = strchr(c1, '\n'); + if (c2) + *c2 = 0; + } + free(c); +*/ + + return size; +} + +/* Send oops data to kerneloops.org-style site, using HTTP POST */ +/* Returns 0 on success */ +static CURLcode http_post_to_kerneloops_site(const char *url, const char *oopsdata) +{ + CURLcode ret; + CURL *handle; + struct curl_httppost *post = NULL; + struct curl_httppost *last = NULL; + + handle = curl_easy_init(); + if (!handle) + error_msg_and_die("Can't create curl handle"); + + curl_easy_setopt(handle, CURLOPT_URL, url); + + curl_formadd(&post, &last, + CURLFORM_COPYNAME, "oopsdata", + CURLFORM_COPYCONTENTS, oopsdata, + CURLFORM_END); + curl_formadd(&post, &last, + CURLFORM_COPYNAME, "pass_on_allowed", + CURLFORM_COPYCONTENTS, "yes", + CURLFORM_END); + + curl_easy_setopt(handle, CURLOPT_HTTPPOST, post); + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writefunction); + + ret = curl_easy_perform(handle); + + curl_formfree(post); + curl_easy_cleanup(handle); + + return ret; +} + +static void report_to_kerneloops( + const char *dump_dir_name, + const map_plugin_settings_t& settings) +{ + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + exit(1); /* error msg is already logged */ + + map_crash_data_t pCrashData; + load_crash_data_from_debug_dump(dd, pCrashData); + dd_close(dd); + + const char *backtrace = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_BACKTRACE); + if (!backtrace) + error_msg_and_die("Error sending kernel oops due to missing backtrace"); + + map_plugin_settings_t::const_iterator end = settings.end(); + map_plugin_settings_t::const_iterator it; + + const char *env = getenv("KerneloopsReporter_SubmitURL"); + it = settings.find("SubmitURL"); + const char *submitURL = (env ? env : it == end ? "" : it->second.c_str()); + if (!submitURL[0]) + submitURL = "http://submit.kerneloops.org/submitoops.php"; + + log(_("Submitting oops report to %s"), submitURL); + + CURLcode ret = http_post_to_kerneloops_site(submitURL, backtrace); + if (ret != CURLE_OK) + error_msg_and_die("Kernel oops has not been sent due to %s", curl_easy_strerror(ret)); + + /* Server replies with: + * 200 thank you for submitting the kernel oops information + * RemoteIP: 34192fd15e34bf60fac6a5f01bba04ddbd3f0558 + * - no URL or bug ID apparently... + */ + log("Kernel oops report was uploaded"); +} + +int main(int argc, char **argv) +{ + char *env_verbose = getenv("ABRT_VERBOSE"); + if (env_verbose) + g_verbose = atoi(env_verbose); + + map_plugin_settings_t settings; + + const char *dump_dir_name = "."; + enum { + OPT_s = (1 << 0), + }; + int optflags = 0; + int opt; + while ((opt = getopt(argc, argv, "c:d:vs")) != -1) + { + switch (opt) + { + case 'c': + dump_dir_name = optarg; + VERB1 log("Loading settings from '%s'", optarg); + LoadPluginSettings(optarg, settings); + VERB3 log("Loaded '%s'", optarg); + break; + case 'd': + dump_dir_name = optarg; + break; + case 'v': + g_verbose++; + break; + case 's': + optflags |= OPT_s; + break; + default: + /* Careful: the string below contains tabs, dont replace with spaces */ + error_msg_and_die( + "Usage: "PROGNAME" -c CONFFILE -d DIR [-vs]" + "\n" + "\nReport a kernel oops to kerneloops.org (or similar) site" + "\n" + "\nOptions:" + "\n -c FILE Configuration file (may be given many times)" + "\n -d DIR Crash dump directory" + "\n -v Verbose" + "\n -s Log to syslog" + ); + } + } + + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + +//DONT! our stdout/stderr goes directly to daemon, don't want to have prefix there. +// msg_prefix = xasprintf(PROGNAME"[%u]", getpid()); + + if (optflags & OPT_s) + { + openlog(msg_prefix, 0, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + } + + try + { + report_to_kerneloops(dump_dir_name, settings); + } + catch (CABRTException& e) + { + error_msg_and_die("%s", e.what()); + } + + return 0; +} diff --git a/src/plugins/abrt-action-print.cpp b/src/plugins/abrt-action-print.cpp new file mode 100644 index 00000000..a4db373a --- /dev/null +++ b/src/plugins/abrt-action-print.cpp @@ -0,0 +1,106 @@ +/* + Write crash dump to stdout in text form. + + 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 "parse_options.h" +#include "crash_types.h" +#include "abrt_exception.h" +#include "plugin.h" /* make_description_logger */ + +#define PROGNAME "abrt-action-print" + +static const char *dump_dir_name = "."; +static const char *output_file = NULL; +static const char *open_mode = "w"; + +int main(int argc, char **argv) +{ + char *env_verbose = getenv("ABRT_VERBOSE"); + if (env_verbose) + g_verbose = atoi(env_verbose); + + const char *program_usage = _( + PROGNAME" [-v] [-o FILE] -d DIR\n" + "\n" + "Print information about the crash to standard output"); + enum { + OPT_v = 1 << 0, + OPT_d = 1 << 1, + OPT_o = 1 << 2, + }; + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_STRING('d', NULL, &dump_dir_name, "DIR" , _("Crash dump directory")), + OPT_STRING('o', NULL, &output_file , "FILE", _("Output file")), + OPT_END() + }; + + /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage); + + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + //msg_prefix = PROGNAME; + + char *env = getenv("Logger_LogPath"); + VERB3 log("output_file:'%s' Logger_LogPath env:'%s'", output_file, env); + if (env) + output_file = env; + + env = getenv("Logger_AppendLogs"); + VERB3 log("Logger_AppendLogs env:'%s'", env); + if (env && string_to_bool(env)) + open_mode = "a"; + + if (output_file) + { + if (!freopen(output_file, open_mode, stdout)) + { + perror_msg_and_die("Can't open '%s'", output_file); + } + } + + try + { + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + return 1; /* error message is already logged */ + + map_crash_data_t pCrashData; + load_crash_data_from_debug_dump(dd, pCrashData); + dd_close(dd); + + char *dsc = make_description_logger(pCrashData); + fputs(dsc, stdout); + free(dsc); + } + catch (CABRTException& e) + { + log("%s", e.what()); + return 1; + } + + if (output_file) + { + const char *format = (open_mode[0] == 'a' ? _("The report was appended to %s") : _("The report was stored to %s")); + log(format, output_file); + } + + return 0; +} diff --git a/src/plugins/abrt-action-rhtsupport.cpp b/src/plugins/abrt-action-rhtsupport.cpp new file mode 100644 index 00000000..d1854541 --- /dev/null +++ b/src/plugins/abrt-action-rhtsupport.cpp @@ -0,0 +1,338 @@ +/* + 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 <libtar.h> +#include "abrtlib.h" +#include "abrt_curl.h" +#include "abrt_xmlrpc.h" +#include "abrt_rh_support.h" +#include "crash_types.h" +#include "abrt_exception.h" + +#include "plugin.h" /* make_description_bz */ + + +#define PROGNAME "abrt-action-rhtsupport" + +static void report_to_rhtsupport( + const char *dump_dir_name, + const map_plugin_settings_t& settings) +{ + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + exit(1); /* error msg is already logged by dd_opendir */ + + map_crash_data_t pCrashData; + load_crash_data_from_debug_dump(dd, pCrashData); + dd_close(dd); + + /* Gzipping e.g. 0.5gig coredump takes a while. Let client know what we are doing */ + log(_("Compressing data")); + + const char* errmsg = NULL; + TAR* tar = NULL; + pid_t child; + char* tempfile = NULL; + reportfile_t* file = NULL; + char* dsc = NULL; + char* summary = NULL; + const char* function; + const char* reason; + const char* package; + + char* env; + map_plugin_settings_t::const_iterator end = settings.end(); + map_plugin_settings_t::const_iterator it; + + env = getenv("RHTSupport_URL"); + it = settings.find("URL"); + char *url = xstrdup(env ? env : it == end ? "https://api.access.redhat.com/rs" : it->second.c_str()); + + env = getenv("RHTSupport_Login"); + it = settings.find("Login"); + char *login = xstrdup(env ? env : it == end ? "" : it->second.c_str()); + + env = getenv("RHTSupport_Password"); + it = settings.find("Password"); + char *password = xstrdup(env ? env : it == end ? "" : it->second.c_str()); + + env = getenv("RHTSupport_SSLVerify"); + it = settings.find("SSLVerify"); + bool ssl_verify = string_to_bool(env ? env : it == end ? "1" : it->second.c_str()); + + if (!login[0] || !password[0]) + { + errmsg = _("Empty login or password, please check RHTSupport.conf"); + goto ret; + } + + package = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_PACKAGE); + reason = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_REASON); + function = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_CRASH_FUNCTION); + + { + struct strbuf *buf_summary = strbuf_new(); + strbuf_append_strf(buf_summary, "[abrt] %s", package); + if (function && strlen(function) < 30) + strbuf_append_strf(buf_summary, ": %s", function); + if (reason) + strbuf_append_strf(buf_summary, ": %s", reason); + summary = strbuf_free_nobuf(buf_summary); + + char *bz_dsc = make_description_bz(pCrashData); + dsc = xasprintf("abrt version: "VERSION"\n%s", bz_dsc); + free(bz_dsc); + } + + file = new_reportfile(); + + /* SELinux guys are not happy with /tmp, using /var/run/abrt */ + tempfile = xasprintf(LOCALSTATEDIR"/run/abrt/tmp-%lu-%lu.tar.gz", (long)getpid(), (long)time(NULL)); + + int pipe_from_parent_to_child[2]; + xpipe(pipe_from_parent_to_child); + child = fork(); + if (child == 0) + { + /* child */ + close(pipe_from_parent_to_child[1]); + xmove_fd(xopen3(tempfile, O_WRONLY | O_CREAT | O_EXCL, 0600), 1); + xmove_fd(pipe_from_parent_to_child[0], 0); + execlp("gzip", "gzip", NULL); + perror_msg_and_die("can't execute '%s'", "gzip"); + } + close(pipe_from_parent_to_child[0]); + + if (tar_fdopen(&tar, pipe_from_parent_to_child[1], tempfile, + /*fileops:(standard)*/ NULL, O_WRONLY | O_CREAT, 0644, TAR_GNU) != 0) + { + errmsg = "can't create temporary file in "LOCALSTATEDIR"/run/abrt"; + goto ret; + } + + { + map_crash_data_t::const_iterator it = pCrashData.begin(); + for (; it != pCrashData.end(); it++) + { + if (it->first == CD_COUNT) continue; + if (it->first == CD_DUMPDIR) continue; + if (it->first == CD_INFORMALL) continue; + if (it->first == CD_REPORTED) continue; + if (it->first == CD_MESSAGE) continue; // plugin's status message (if we already reported it yesterday) + if (it->first == FILENAME_DESCRIPTION) continue; // package description + + const char *content = it->second[CD_CONTENT].c_str(); + if (it->second[CD_TYPE] == CD_TXT) + { + reportfile_add_binding_from_string(file, it->first.c_str(), content); + } + else if (it->second[CD_TYPE] == CD_BIN) + { + const char *basename = strrchr(content, '/'); + if (basename) + basename++; + else + basename = content; + char *xml_name = concat_path_file("content", basename); + reportfile_add_binding_from_namedfile(file, + /*on_disk_filename */ content, + /*binding_name */ it->first.c_str(), + /*recorded_filename*/ xml_name, + /*binary */ 1); + if (tar_append_file(tar, (char*)content, xml_name) != 0) + { + errmsg = "can't create temporary file in "LOCALSTATEDIR"/run/abrt"; + free(xml_name); + goto ret; + } + free(xml_name); + } + } + } + + /* Write out content.xml in the tarball's root */ + { + const char *signature = reportfile_as_string(file); + unsigned len = strlen(signature); + unsigned len512 = (len + 511) & ~511; + char *block = (char*)memcpy(xzalloc(len512), signature, len); + th_set_type(tar, S_IFREG | 0644); + th_set_mode(tar, S_IFREG | 0644); + //th_set_link(tar, char *linkname); + //th_set_device(tar, dev_t device); + //th_set_user(tar, uid_t uid); + //th_set_group(tar, gid_t gid); + //th_set_mtime(tar, time_t fmtime); + th_set_path(tar, (char*)"content.xml"); + th_set_size(tar, len); + th_finish(tar); /* caclulate and store th xsum etc */ + if (th_write(tar) != 0 + || full_write(tar_fd(tar), block, len512) != len512 + || tar_close(tar) != 0 + ) { + free(block); + errmsg = "can't create temporary file in "LOCALSTATEDIR"/run/abrt"; + goto ret; + } + tar = NULL; + free(block); + } + + { + log(_("Creating a new case...")); + char* result = send_report_to_new_case(url, + login, + password, + ssl_verify, + summary, + dsc, + package, + tempfile + ); + /* Temporary hackish detection of errors. Ideally, + * send_report_to_new_case needs to have better error reporting. + */ + if (strncasecmp(result, "error", 5) == 0) + { + /* + * result can contain "...server says: 'multi-line <html> text'" + * Replace all '\n' with spaces: + * we want this message to be, logically, one log entry. + * IOW: one line, not many lines. + */ + char *src, *dst; + dst = src = result; + while (1) + { + unsigned char c = *src++; + if (c == '\n') + c = ' '; + *dst++ = c; + if (c == '\0') + break; + } + /* Use sanitized string as error message */ + error_msg_and_die("%s", result); + } + /* No error */ + log("%s", result); + free(result); + } + + ret: + // Damn, selinux does not allow SIGKILLing our own child! wtf?? + //kill(child, SIGKILL); /* just in case */ + waitpid(child, NULL, 0); + if (tar) + tar_close(tar); + //close(pipe_from_parent_to_child[1]); - tar_close() does it itself + unlink(tempfile); + free(tempfile); + reportfile_free(file); + + free(summary); + free(dsc); + + free(url); + free(login); + free(password); + + if (errmsg) + error_msg_and_die("%s", errmsg); +} + +int main(int argc, char **argv) +{ + char *env_verbose = getenv("ABRT_VERBOSE"); + if (env_verbose) + g_verbose = atoi(env_verbose); + + map_plugin_settings_t settings; + + const char *dump_dir_name = "."; + enum { + OPT_s = (1 << 0), + }; + int optflags = 0; + int opt; + while ((opt = getopt(argc, argv, "c:d:vs")) != -1) + { + switch (opt) + { + case 'c': + dump_dir_name = optarg; + VERB1 log("Loading settings from '%s'", optarg); + LoadPluginSettings(optarg, settings); + VERB3 log("Loaded '%s'", optarg); + break; + case 'd': + dump_dir_name = optarg; + break; + case 'v': + g_verbose++; + break; + case 's': + optflags |= OPT_s; + break; + default: + /* Careful: the string below contains tabs, dont replace with spaces */ + error_msg_and_die( + "Usage: "PROGNAME" -c CONFFILE -d DIR [-vs]" + "\n" + "\nReport a crash to RHTSupport" + "\n" + "\nOptions:" + "\n -c FILE Configuration file (may be given many times)" + "\n -d DIR Crash dump directory" + "\n -v Verbose" + "\n -s Log to syslog" + ); + } + } + + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + +//DONT! our stdout/stderr goes directly to daemon, don't want to have prefix there. +// msg_prefix = xasprintf(PROGNAME"[%u]", getpid()); + + if (optflags & OPT_s) + { + openlog(msg_prefix, 0, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + } + + 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); + xmlrpc_env_clean(&env); + + try + { + report_to_rhtsupport(dump_dir_name, settings); + } + catch (CABRTException& e) + { + error_msg_and_die("%s", e.what()); + } + + return 0; +} diff --git a/src/plugins/abrt-plugins.7 b/src/plugins/abrt-plugins.7 new file mode 100644 index 00000000..6c07e65a --- /dev/null +++ b/src/plugins/abrt-plugins.7 @@ -0,0 +1,43 @@ +.TH abrt "8" "28 May 2009" "" +.SH NAME +abrt-plugins \- plugins for the abrt crash reporter program +.SH DESCRIPTION +.P +.I abrt +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 specified in the +.I abrt.conf +configuration file. +.P +Plugins allow abrt to perform various actions: for example, +to report the crash to Bugzilla, to mail the report, to transfer +the report via FTP or SCP, or to run a program that you specify. +.P +This manual page provides a list of all the manual pages for +these plugins. +.P +If you want to create your own plugin, refer to the PLUGINS-HOWTO +file in the documentation directory. +.SH INVOCATION +Each plugin is invoked in the \fIabrt.conf\fP configuration +file, in the section that is appropriate to what you +want abrt to do. +.SH CONFIGURATION +Almost every plugin has its configuration file, +stored in the \fI/etc/abrt/plugins\fP directory. +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-Bugzilla (7), +.IR abrt-FileTransfer (7), +.IR abrt-KerneloopsReporter (7), +.IR abrt-KerneloopsScanner (7), +.IR abrt-Logger (7), +.IR abrt-Mailx (7), +.IR abrt-SQLite3 (7) +.SH AUTHOR +\fIabrt\fP written by Zdeněk Přikryl <zprikryl@redhat.com> and +Jiří Moskovčák <jmoskovc@redhat.com>. Manual page written by Daniel +Novotný <dnovotny@redhat.com>. diff --git a/src/plugins/abrt_rh_support.c b/src/plugins/abrt_rh_support.c new file mode 100644 index 00000000..04e2c8ef --- /dev/null +++ b/src/plugins/abrt_rh_support.c @@ -0,0 +1,519 @@ +/* + 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 <libxml/encoding.h> +#include <libxml/xmlwriter.h> +#include <curl/curl.h> +#include "abrtlib.h" +#include "abrt_curl.h" +#include "abrt_rh_support.h" + +struct reportfile { + xmlTextWriterPtr writer; + xmlBufferPtr buf; +}; + +static void __attribute__((__noreturn__)) +die_xml_oom(void) +{ + error_msg_and_die("can't create XML attribute (out of memory?)"); +} + +static xmlBufferPtr +xxmlBufferCreate(void) +{ + xmlBufferPtr r = xmlBufferCreate(); + if (!r) + die_xml_oom(); + return r; +} + +static xmlTextWriterPtr +xxmlNewTextWriterMemory(xmlBufferPtr buf /*, int compression*/) +{ + xmlTextWriterPtr r = xmlNewTextWriterMemory(buf, /*compression:*/ 0); + if (!r) + die_xml_oom(); + return r; +} + +static void +xxmlTextWriterStartDocument(xmlTextWriterPtr writer, + const char * version, + const char * encoding, + const char * standalone) +{ + if (xmlTextWriterStartDocument(writer, version, encoding, standalone) < 0) + die_xml_oom(); +} + +static void +xxmlTextWriterEndDocument(xmlTextWriterPtr writer) +{ + if (xmlTextWriterEndDocument(writer) < 0) + die_xml_oom(); +} + +static void +xxmlTextWriterStartElement(xmlTextWriterPtr writer, const char *name) +{ + // these bright guys REDEFINED CHAR (!) to unsigned char... + if (xmlTextWriterStartElement(writer, (unsigned char*)name) < 0) + die_xml_oom(); +} + +static void +xxmlTextWriterEndElement(xmlTextWriterPtr writer) +{ + if (xmlTextWriterEndElement(writer) < 0) + die_xml_oom(); +} + +static void +xxmlTextWriterWriteElement(xmlTextWriterPtr writer, const char *name, const char *content) +{ + if (xmlTextWriterWriteElement(writer, (unsigned char*)name, (unsigned char*)content) < 0) + die_xml_oom(); +} + +static void +xxmlTextWriterWriteAttribute(xmlTextWriterPtr writer, const char *name, const char *content) +{ + if (xmlTextWriterWriteAttribute(writer, (unsigned char*)name, (unsigned char*)content) < 0) + die_xml_oom(); +} + +#if 0 //unused +static void +xxmlTextWriterWriteString(xmlTextWriterPtr writer, const char *content) +{ + if (xmlTextWriterWriteString(writer, (unsigned char*)content) < 0) + die_xml_oom(); +} +#endif + +// +// End the reportfile, and prepare it for delivery. +// No more bindings can be added after this. +// +static void +close_writer(reportfile_t* file) +{ + if (!file->writer) + return; + + // close off the end of the xml file + xxmlTextWriterEndDocument(file->writer); + xmlFreeTextWriter(file->writer); + file->writer = NULL; +} + +// +// This allocates a reportfile_t structure and initializes it. +// +reportfile_t* +new_reportfile(void) +{ + // create a new reportfile_t + reportfile_t* file = (reportfile_t*)xmalloc(sizeof(*file)); + + // set up a libxml 'buffer' and 'writer' to that buffer + file->buf = xxmlBufferCreate(); + file->writer = xxmlNewTextWriterMemory(file->buf); + + // start a new xml document: + // <report xmlns="http://www.redhat.com/gss/strata">... + xxmlTextWriterStartDocument(file->writer, /*version:*/ NULL, /*encoding:*/ NULL, /*standalone:*/ NULL); + xxmlTextWriterStartElement(file->writer, "report"); + xxmlTextWriterWriteAttribute(file->writer, "xmlns", "http://www.redhat.com/gss/strata"); + + return file; +} + +static void +internal_reportfile_start_binding(reportfile_t* file, const char* name, int isbinary, const char* filename) +{ + // <binding name=NAME [fileName=FILENAME] type=text/binary... + xxmlTextWriterStartElement(file->writer, "binding"); + xxmlTextWriterWriteAttribute(file->writer, "name", name); + if (filename) + xxmlTextWriterWriteAttribute(file->writer, "fileName", filename); + if (isbinary) + xxmlTextWriterWriteAttribute(file->writer, "type", "binary"); + else + xxmlTextWriterWriteAttribute(file->writer, "type", "text"); +} + +// +// Add a new text binding +// +void +reportfile_add_binding_from_string(reportfile_t* file, const char* name, const char* value) +{ + // <binding name=NAME type=text value=VALUE> + internal_reportfile_start_binding(file, name, /*isbinary:*/ 0, /*filename:*/ NULL); + xxmlTextWriterWriteAttribute(file->writer, "value", value); + xxmlTextWriterEndElement(file->writer); +} + +// +// Add a new binding to a report whose value is represented as a file. +// +void +reportfile_add_binding_from_namedfile(reportfile_t* file, + const char* on_disk_filename, /* unused so far */ + const char* binding_name, + const char* recorded_filename, + int isbinary) +{ + // <binding name=NAME fileName=FILENAME type=text/binary... + internal_reportfile_start_binding(file, binding_name, isbinary, recorded_filename); + // ... href=content/NAME> + char *href_name = concat_path_file("content", binding_name); + xxmlTextWriterWriteAttribute(file->writer, "href", href_name); + free(href_name); +} + +// +// Return the contents of the reportfile as a string. +// +const char* +reportfile_as_string(reportfile_t* file) +{ + close_writer(file); + // unsigned char -> char + return (char*)file->buf->content; +} + +void +reportfile_free(reportfile_t* file) +{ + if (!file) + return; + close_writer(file); + xmlBufferFree(file->buf); + free(file); +} + + +// +// post_signature() +// +char* +post_signature(const char* baseURL, bool ssl_verify, const char* signature) +{ + char *URL = concat_path_file(baseURL, "/signatures"); + + abrt_post_state_t *state = new_abrt_post_state(0 + + ABRT_POST_WANT_HEADERS + + ABRT_POST_WANT_BODY + + ABRT_POST_WANT_ERROR_MSG + + (ssl_verify ? ABRT_POST_WANT_SSL_VERIFY : 0) + ); + int http_resp_code = abrt_post_string(state, URL, "application/xml", signature); + free(URL); + + char *retval; + const char *strata_msg; + switch (http_resp_code) + { + case 200: + case 201: + if (state->body) + { + retval = state->body; + state->body = NULL; + break; + } + strata_msg = find_header_in_abrt_post_state(state, "Strata-Message:"); + if (strata_msg && strcmp(strata_msg, "CREATED") != 0) { + retval = xstrdup(strata_msg); + break; + } + retval = xstrdup("Signature submitted successfully"); + break; + + default: + strata_msg = find_header_in_abrt_post_state(state, "Strata-Message:"); + if (strata_msg) + { + retval = xasprintf("Error (HTTP response %d): %s", + http_resp_code, + strata_msg); + break; + } + if (state->curl_error_msg) + { + if (http_resp_code >= 0) + retval = xasprintf("Error (HTTP response %d): %s", http_resp_code, state->curl_error_msg); + else + retval = xasprintf("Error in HTTP transaction: %s", state->curl_error_msg); + break; + } + retval = xasprintf("Error (HTTP response %d), body:\n%s", http_resp_code, state->body); + break; + } + + free_abrt_post_state(state); + return retval; +} + + +// +// send_report_to_new_case() +// + +static char* +make_case_data(const char* summary, const char* description, + const char* product, const char* version, + const char* component) +{ + char* retval; + xmlTextWriterPtr writer; + xmlBufferPtr buf; + + buf = xxmlBufferCreate(); + writer = xxmlNewTextWriterMemory(buf); + + xxmlTextWriterStartDocument(writer, NULL, "UTF-8", "yes"); + xxmlTextWriterStartElement(writer, "case"); + xxmlTextWriterWriteAttribute(writer, "xmlns", + "http://www.redhat.com/gss/strata"); + + xxmlTextWriterWriteElement(writer, "summary", summary); + xxmlTextWriterWriteElement(writer, "description", description); + if (product) { + xxmlTextWriterWriteElement(writer, "product", product); + } + if (version) { + xxmlTextWriterWriteElement(writer, "version", version); + } + if (component) { + xxmlTextWriterWriteElement(writer, "component", component); + } + + xxmlTextWriterEndDocument(writer); + retval = xstrdup((const char*)buf->content); + xmlFreeTextWriter(writer); + xmlBufferFree(buf); + return retval; +} + +#if 0 //unused +static char* +make_response(const char* title, const char* body, + const char* actualURL, const char* displayURL) +{ + char* retval; + xmlTextWriterPtr writer; + xmlBufferPtr buf; + + buf = xxmlBufferCreate(); + writer = xxmlNewTextWriterMemory(buf); + + xxmlTextWriterStartDocument(writer, NULL, "UTF-8", "yes"); + xxmlTextWriterStartElement(writer, "response"); + if (title) { + xxmlTextWriterWriteElement(writer, "title", title); + } + if (body) { + xxmlTextWriterWriteElement(writer, "body", body); + } + if (actualURL || displayURL) { + xxmlTextWriterStartElement(writer, "URL"); + if (actualURL) { + xxmlTextWriterWriteAttribute(writer, "href", actualURL); + } + if (displayURL) { + xxmlTextWriterWriteString(writer, displayURL); + } + } + + xxmlTextWriterEndDocument(writer); + retval = xstrdup((const char*)buf->content); + xmlFreeTextWriter(writer); + xmlBufferFree(buf); + return retval; +} +//Example: +//<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +//<response><title>Case Created and Report Attached</title><body></body><URL href="http://support-services-devel.gss.redhat.com:8080/Strata/cases/00005129/attachments/ccbf3e65-b941-3db7-a016-6a3831691a32">New Case URL</URL></response> +#endif + +char* +send_report_to_new_case(const char* baseURL, + const char* username, + const char* password, + bool ssl_verify, + const char* summary, + const char* description, + const char* component, + const char* report_file_name) +{ + char *case_url = concat_path_file(baseURL, "/cases"); + + char *case_data = make_case_data(summary, description, + "Red Hat Enterprise Linux", "6.0", + component); + + int redirect_count = 0; + char *errmsg; + char *allocated = NULL; + char* retval = NULL; + abrt_post_state_t *case_state; + + redirect_case: + case_state = new_abrt_post_state(0 + + ABRT_POST_WANT_HEADERS + + ABRT_POST_WANT_BODY + + ABRT_POST_WANT_ERROR_MSG + + (ssl_verify ? ABRT_POST_WANT_SSL_VERIFY : 0) + ); + case_state->username = username; + case_state->password = password; + abrt_post_string(case_state, case_url, "application/xml", case_data); + + char *case_location = find_header_in_abrt_post_state(case_state, "Location:"); + switch (case_state->http_resp_code) + { + case 301: /* "301 Moved Permanently" (for example, used to move http:// to https://) */ + case 302: /* "302 Found" (just in case) */ + case 305: /* "305 Use Proxy" */ + if (++redirect_count < 10 && case_location) + { + free(case_url); + case_url = xstrdup(case_location); + free_abrt_post_state(case_state); + goto redirect_case; + } + goto bad_resp_code; + + case 404: + /* Not strictly necessary, but makes this typical error less cryptic: + * instead of returning html-encoded body, we show short concise message, + * and show offending URL (typos in which is a typical cause) */ + retval = xasprintf("error in case creation, " + "HTTP code: 404 (Not found), URL:'%s'", case_url); + break; + + default: + bad_resp_code: + errmsg = case_state->curl_error_msg; + if (errmsg) + retval = xasprintf("error in case creation: %s", errmsg); + else + { + errmsg = find_header_in_abrt_post_state(case_state, "Strata-Message:"); + if ((!errmsg || !errmsg[0]) && case_state->body && case_state->body[0]) + errmsg = case_state->body; + if (errmsg) + retval = xasprintf("error in case creation, HTTP code: %d, server says: '%s'", + case_state->http_resp_code, errmsg); + else + retval = xasprintf("error in case creation, HTTP code: %d", + case_state->http_resp_code); + } + break; + + case 200: + case 201: { + if (!case_location) { + /* Case Creation returned valid code, but no location */ + retval = xasprintf("error in case creation: no Location URL, HTTP code: %d", + case_state->http_resp_code); + break; + } + + char *atch_url = concat_path_file(case_location, "/attachments"); + abrt_post_state_t *atch_state; + redirect_attach: + atch_state = new_abrt_post_state(0 + + ABRT_POST_WANT_HEADERS + + ABRT_POST_WANT_BODY + + ABRT_POST_WANT_ERROR_MSG + + (ssl_verify ? ABRT_POST_WANT_SSL_VERIFY : 0) + ); + atch_state->username = username; + atch_state->password = password; + abrt_post_file_as_form(atch_state, atch_url, "application/binary", report_file_name); + + char *atch_location = find_header_in_abrt_post_state(atch_state, "Location:"); + switch (atch_state->http_resp_code) + { + case 305: /* "305 Use Proxy" */ + if (++redirect_count < 10 && atch_location) + { + free(atch_url); + atch_url = xstrdup(atch_location); + free_abrt_post_state(atch_state); + goto redirect_attach; + } + /* fall through */ + + default: + /* Case Creation Succeeded, attachement FAILED */ + errmsg = find_header_in_abrt_post_state(atch_state, "Strata-Message:"); + if (!errmsg || !errmsg[0]) + errmsg = atch_state->curl_error_msg; + if (atch_state->body && atch_state->body[0]) + { + if (errmsg && errmsg[0] + && strcmp(errmsg, atch_state->body) != 0 + ) /* both strata/curl error and body are present (and aren't the same) */ + allocated = errmsg = xasprintf("%s. %s", + atch_state->body, + errmsg); + else /* only body exists */ + errmsg = atch_state->body; + } + /* Note: to prevent URL misparsing, make sure to delimit + * case_location only using spaces */ + retval = xasprintf("Case created: %s but report attachment failed (HTTP code %d)%s%s", + case_location, + atch_state->http_resp_code, + errmsg ? ": " : "", + errmsg ? errmsg : "" + ); + break; + + case 200: + case 201: + // unused + //char *body = atch_state->body; + //if (case_state->body && case_state->body[0]) + //{ + // body = case_state->body; + // if (atch_state->body && atch_state->body[0]) + // allocated = body = xasprintf("%s\n%s", + // case_state->body, + // atch_state->body); + //} + retval = xasprintf("Case created: %s", /*body,*/ case_location); + } /* switch (attach HTTP code) */ + + free_abrt_post_state(atch_state); + free(atch_url); + } /* case 200/201 */ + + } /* switch (case HTTP code) */ + + free_abrt_post_state(case_state); + free(allocated); + free(case_url); + return retval; +} diff --git a/src/plugins/abrt_rh_support.h b/src/plugins/abrt_rh_support.h new file mode 100644 index 00000000..db6e9cd7 --- /dev/null +++ b/src/plugins/abrt_rh_support.h @@ -0,0 +1,55 @@ +/* + 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 ABRT_RH_SUPPORT_H_ +#define ABRT_RH_SUPPORT_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct reportfile reportfile_t; + +reportfile_t *new_reportfile(void); +void reportfile_free(reportfile_t* file); + +void reportfile_add_binding_from_string(reportfile_t* file, const char* name, const char* value); +void reportfile_add_binding_from_namedfile(reportfile_t* file, + const char* on_disk_filename, /* unused so far */ + const char* binding_name, + const char* recorded_filename, + int isbinary); + +const char* reportfile_as_string(reportfile_t* file); + +char* post_signature(const char* baseURL, bool ssl_verify, const char* signature); +char* +send_report_to_new_case(const char* baseURL, + const char* username, + const char* password, + bool ssl_verify, + const char* summary, + const char* description, + const char* component, + const char* report_file_name); + +#ifdef __cplusplus +} +#endif + +#endif |