summaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/Bugzilla.conf12
-rw-r--r--src/plugins/Bugzilla.cpp155
-rw-r--r--src/plugins/Bugzilla.glade246
-rw-r--r--src/plugins/Bugzilla.h37
-rw-r--r--src/plugins/CCpp.conf44
-rw-r--r--src/plugins/CCpp.cpp279
-rw-r--r--src/plugins/CCpp.h49
-rw-r--r--src/plugins/FileTransfer.conf35
-rw-r--r--src/plugins/FileTransfer.cpp367
-rw-r--r--src/plugins/FileTransfer.h46
-rw-r--r--src/plugins/Kerneloops.conf13
-rw-r--r--src/plugins/Kerneloops.cpp31
-rw-r--r--src/plugins/Kerneloops.h38
-rw-r--r--src/plugins/KerneloopsReporter.cpp143
-rw-r--r--src/plugins/KerneloopsReporter.glade118
-rw-r--r--src/plugins/KerneloopsReporter.h47
-rw-r--r--src/plugins/KerneloopsScanner.cpp214
-rw-r--r--src/plugins/KerneloopsScanner.h42
-rw-r--r--src/plugins/KerneloopsSysLog.cpp383
-rw-r--r--src/plugins/KerneloopsSysLog.h35
-rw-r--r--src/plugins/Logger.conf8
-rw-r--r--src/plugins/Logger.glade135
-rw-r--r--src/plugins/Mailx.conf17
-rw-r--r--src/plugins/Mailx.cpp153
-rw-r--r--src/plugins/Mailx.glade184
-rw-r--r--src/plugins/Mailx.h48
-rw-r--r--src/plugins/Makefile.am316
-rw-r--r--src/plugins/Python.conf1
-rw-r--r--src/plugins/Python.cpp30
-rw-r--r--src/plugins/Python.h30
-rw-r--r--src/plugins/RHTSupport.conf11
-rw-r--r--src/plugins/RHTSupport.cpp147
-rw-r--r--src/plugins/RHTSupport.glade213
-rw-r--r--src/plugins/RHTSupport.h38
-rw-r--r--src/plugins/ReportUploader.conf23
-rw-r--r--src/plugins/ReportUploader.cpp517
-rw-r--r--src/plugins/ReportUploader.glade249
-rw-r--r--src/plugins/ReportUploader.h55
-rw-r--r--src/plugins/SOSreport.conf1
-rw-r--r--src/plugins/SOSreport.cpp169
-rw-r--r--src/plugins/SOSreport.h31
-rw-r--r--src/plugins/SQLite3.conf4
-rw-r--r--src/plugins/SQLite3.cpp742
-rw-r--r--src/plugins/SQLite3.h58
-rw-r--r--src/plugins/abrt-Bugzilla.743
-rw-r--r--src/plugins/abrt-FileTransfer.772
-rw-r--r--src/plugins/abrt-KerneloopsReporter.740
-rw-r--r--src/plugins/abrt-KerneloopsScanner.746
-rw-r--r--src/plugins/abrt-Logger.744
-rw-r--r--src/plugins/abrt-Mailx.757
-rw-r--r--src/plugins/abrt-ReportUploader.755
-rw-r--r--src/plugins/abrt-SQLite3.736
-rw-r--r--src/plugins/abrt-action-analyze-c.c238
-rw-r--r--src/plugins/abrt-action-analyze-oops.c176
-rw-r--r--src/plugins/abrt-action-analyze-python.c119
-rw-r--r--src/plugins/abrt-action-bugzilla.cpp908
-rw-r--r--src/plugins/abrt-action-generate-backtrace.c386
-rwxr-xr-xsrc/plugins/abrt-action-install-debuginfo418
-rw-r--r--src/plugins/abrt-action-kerneloops.cpp197
-rw-r--r--src/plugins/abrt-action-print.cpp106
-rw-r--r--src/plugins/abrt-action-rhtsupport.cpp338
-rw-r--r--src/plugins/abrt-plugins.743
-rw-r--r--src/plugins/abrt_rh_support.c519
-rw-r--r--src/plugins/abrt_rh_support.h55
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">&#x25CF;</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">&#x25CF;</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">&#x25CF;</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">&lt;b&gt;Bugzilla plugin configuration&lt;/b&gt;</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 &lt;a href="https://bugzilla.redhat.com/createaccount.cgi"&gt;here&lt;/a&gt;</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">&#x25CF;</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">&lt;b&gt;Kerneloops Reporter plugin configuration&lt;/b&gt;</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">&#x25CF;</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">&lt;b&gt;Logger plugin configuration&lt;/b&gt;</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">&#x25CF;</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">&#x25CF;</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">&#x25CF;</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">&lt;b&gt;Mailx plugin configuration&lt;/b&gt;</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">&#x25CF;</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">&#x25CF;</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">&#x25CF;</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">&lt;b&gt;RHTSupport plugin configuration&lt;/b&gt;</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">&#x25CF;</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">&#x25CF;</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">&#x25CF;</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">&#x25CF;</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">&#x25CF;</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">&lt;b&gt;Report Uploader plugin configuration&lt;/b&gt;</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, &param, &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