summaryrefslogtreecommitdiffstats
path: root/src/CLI
diff options
context:
space:
mode:
authorNikola Pajkovsky <npajkovs@redhat.com>2009-10-21 16:45:34 +0200
committerNikola Pajkovsky <npajkovs@redhat.com>2009-10-21 16:45:34 +0200
commit1821d3811b019c3c7ffaaf875cdfe52d33954cb9 (patch)
tree5ae9593044b3deabd2bbf4c0ec5a9aabe530108a /src/CLI
parent4bebcf3bae780d5de960ae8279d93cf90447e729 (diff)
parent3588b74b1445c33071edac67577599bc6915ab23 (diff)
downloadabrt-1821d3811b019c3c7ffaaf875cdfe52d33954cb9.tar.gz
abrt-1821d3811b019c3c7ffaaf875cdfe52d33954cb9.tar.xz
abrt-1821d3811b019c3c7ffaaf875cdfe52d33954cb9.zip
Merge branch 'master' into experimental
Diffstat (limited to 'src/CLI')
-rw-r--r--src/CLI/ABRTSocket.cpp9
-rw-r--r--src/CLI/ABRTSocket.h2
-rw-r--r--src/CLI/CLI.cpp256
-rw-r--r--src/CLI/Makefile.am19
-rw-r--r--src/CLI/abrt-cli.116
-rw-r--r--src/CLI/abrt-cli.bash34
-rw-r--r--src/CLI/dbus.cpp130
-rw-r--r--src/CLI/dbus.h32
-rw-r--r--src/CLI/report.cpp423
-rw-r--r--src/CLI/report.h24
-rw-r--r--src/CLI/run-command.cpp87
-rw-r--r--src/CLI/run-command.h23
12 files changed, 862 insertions, 193 deletions
diff --git a/src/CLI/ABRTSocket.cpp b/src/CLI/ABRTSocket.cpp
index 1f90f63..d31c7a4 100644
--- a/src/CLI/ABRTSocket.cpp
+++ b/src/CLI/ABRTSocket.cpp
@@ -8,13 +8,12 @@
#include <sys/un.h>
#include <string.h>
-CABRTSocket::CABRTSocket() :
- m_nSocket(-1)
+CABRTSocket::CABRTSocket() : m_nSocket(-1)
{}
CABRTSocket::~CABRTSocket()
{
- DisConnect();
+ Disconnect();
}
void CABRTSocket::Send(const std::string& pMessage)
@@ -91,12 +90,10 @@ void CABRTSocket::Connect(const std::string& pPath)
}
}
-void CABRTSocket::DisConnect()
+void CABRTSocket::Disconnect()
{
if (m_nSocket != -1)
- {
close(m_nSocket);
- }
}
vector_crash_infos_t CABRTSocket::GetCrashInfos()
diff --git a/src/CLI/ABRTSocket.h b/src/CLI/ABRTSocket.h
index d476411..5d5383f 100644
--- a/src/CLI/ABRTSocket.h
+++ b/src/CLI/ABRTSocket.h
@@ -18,7 +18,7 @@ class CABRTSocket
~CABRTSocket();
void Connect(const std::string& pPath);
- void DisConnect();
+ void Disconnect();
vector_crash_infos_t GetCrashInfos();
map_crash_report_t CreateReport(const std::string& pUUID);
diff --git a/src/CLI/CLI.cpp b/src/CLI/CLI.cpp
index 33272df..18e99dc 100644
--- a/src/CLI/CLI.cpp
+++ b/src/CLI/CLI.cpp
@@ -21,8 +21,19 @@
#include "abrtlib.h"
#include "abrt_dbus.h"
#include "DBusCommon.h"
+#include "report.h"
+#include "dbus.h"
#if HAVE_CONFIG_H
- #include <config.h>
+#include <config.h>
+#endif
+#if HAVE_LOCALE_H
+#include <locale.h>
+#endif
+#if ENABLE_NLS
+#include <libintl.h>
+#define _(S) gettext(S)
+#else
+#define _(S) (S)
#endif
/* Program options */
@@ -37,8 +48,6 @@ enum
OPT_DELETE
};
-static DBusConnection* s_dbus_conn;
-
static void print_crash_infos(vector_crash_infos_t& pCrashInfos, int pMode)
{
unsigned int ii;
@@ -47,148 +56,35 @@ static void print_crash_infos(vector_crash_infos_t& pCrashInfos, int pMode)
map_crash_info_t& info = pCrashInfos[ii];
if (pMode == OPT_GET_LIST_FULL || info.find(CD_REPORTED)->second[CD_CONTENT] != "1")
{
- printf("%u.\n"
- "\tUID : %s\n"
- "\tUUID : %s\n"
- "\tPackage : %s\n"
- "\tExecutable: %s\n"
- "\tCrash time: %s\n"
- "\tCrash Rate: %s\n",
- ii,
- info[CD_UID][CD_CONTENT].c_str(),
- info[CD_UUID][CD_CONTENT].c_str(),
- info[CD_PACKAGE][CD_CONTENT].c_str(),
- info[CD_EXECUTABLE][CD_CONTENT].c_str(),
- info[CD_TIME][CD_CONTENT].c_str(),
- info[CD_COUNT][CD_CONTENT].c_str()
- );
+ const char *timestr = info[CD_TIME][CD_CONTENT].c_str();
+ long time = strtol(timestr, 0, 10);
+ if (time == 0)
+ error_msg_and_die("Error while converting time string.");
+
+ char timeloc[256];
+ int success = strftime(timeloc, 128, "%c", localtime(&time));
+ if (!success)
+ error_msg_and_die("Error while converting time to string.");
+
+ printf(_("%u.\n"
+ "\tUID : %s\n"
+ "\tUUID : %s\n"
+ "\tPackage : %s\n"
+ "\tExecutable : %s\n"
+ "\tCrash Time : %s\n"
+ "\tCrash Count: %s\n"),
+ ii,
+ info[CD_UID][CD_CONTENT].c_str(),
+ info[CD_UUID][CD_CONTENT].c_str(),
+ info[CD_PACKAGE][CD_CONTENT].c_str(),
+ info[CD_EXECUTABLE][CD_CONTENT].c_str(),
+ timeloc,
+ info[CD_COUNT][CD_CONTENT].c_str()
+ );
}
}
}
-static void print_crash_report(const map_crash_report_t& pCrashReport)
-{
- map_crash_report_t::const_iterator it = pCrashReport.begin();
- for (; it != pCrashReport.end(); it++)
- {
- if (it->second[CD_TYPE] != CD_SYS)
- {
- printf("\n%s\n"
- "-----\n"
- "%s\n", it->first.c_str(), it->second[CD_CONTENT].c_str());
- }
- }
-}
-
-/*
- * DBus member calls
- */
-
-/* helpers */
-static DBusMessage* new_call_msg(const char* method)
-{
- DBusMessage* msg = dbus_message_new_method_call(CC_DBUS_NAME, CC_DBUS_PATH, CC_DBUS_IFACE, method);
- if (!msg)
- die_out_of_memory();
- return msg;
-}
-static DBusMessage* send_get_reply_and_unref(DBusMessage* msg)
-{
- DBusError err;
- dbus_error_init(&err);
- DBusMessage *reply = dbus_connection_send_with_reply_and_block(s_dbus_conn, msg, /*timeout*/ -1, &err);
- if (reply == NULL)
- {
-//TODO: analyse err
- error_msg_and_die("Error sending DBus message");
- }
- dbus_message_unref(msg);
- return reply;
-}
-
-static vector_crash_infos_t call_GetCrashInfos()
-{
- DBusMessage* msg = new_call_msg("GetCrashInfos");
-
- DBusMessage *reply = send_get_reply_and_unref(msg);
-
- vector_crash_infos_t argout;
- DBusMessageIter in_iter;
- dbus_message_iter_init(reply, &in_iter);
- int r = load_val(&in_iter, argout);
- if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */
- error_msg_and_die("dbus call %s: return type mismatch", "GetCrashInfos");
- dbus_message_unref(reply);
- return argout;
-}
-
-static map_crash_report_t call_CreateReport(const char* uuid)
-{
- /* Yes, call name is not "CreateReport" but "GetJobResult".
- * We need to clean up the names one day. */
- DBusMessage* msg = new_call_msg("GetJobResult");
- dbus_message_append_args(msg,
- DBUS_TYPE_STRING, &uuid,
- DBUS_TYPE_INVALID);
-
- DBusMessage *reply = send_get_reply_and_unref(msg);
-
- map_crash_report_t argout;
- DBusMessageIter in_iter;
- dbus_message_iter_init(reply, &in_iter);
- int r = load_val(&in_iter, argout);
- if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */
- error_msg_and_die("dbus call %s: return type mismatch", "GetJobResult");
- dbus_message_unref(reply);
- return argout;
-}
-
-static void call_Report(const map_crash_report_t& report)
-{
- DBusMessage* msg = new_call_msg("Report");
- DBusMessageIter out_iter;
- dbus_message_iter_init_append(msg, &out_iter);
- store_val(&out_iter, report);
-
- DBusMessage *reply = send_get_reply_and_unref(msg);
- //it returns a single value of report_status_t type,
- //but we don't use it (yet?)
-
- dbus_message_unref(reply);
- return;
-}
-
-static void call_DeleteDebugDump(const char* uuid)
-{
- DBusMessage* msg = new_call_msg("DeleteDebugDump");
- dbus_message_append_args(msg,
- DBUS_TYPE_STRING, &uuid,
- DBUS_TYPE_INVALID);
-
- DBusMessage *reply = send_get_reply_and_unref(msg);
- //it returns a single boolean value,
- //but we don't use it (yet?)
-
- dbus_message_unref(reply);
- return;
-}
-
-static void handle_dbus_err(bool error_flag, DBusError *err)
-{
- if (dbus_error_is_set(err))
- {
- error_msg("dbus error: %s", err->message);
- /* dbus_error_free(&err); */
- error_flag = true;
- }
- if (!error_flag)
- return;
- error_msg_and_die(
- "error requesting DBus name %s, possible reasons: "
- "abrt run by non-root; dbus config is incorrect",
- CC_DBUS_NAME);
-}
-
static const struct option longopts[] =
{
/* name, has_arg, flag, val */
@@ -202,7 +98,7 @@ static const struct option longopts[] =
{ 0, 0, 0, 0 } /* prevents crashes for unknown options*/
};
-/* Gets program name from command line argument. */
+/* Gets the program name from the first command line argument. */
static char *progname(char *argv0)
{
char* name = strrchr(argv0, '/');
@@ -212,11 +108,36 @@ static char *progname(char *argv0)
return argv0;
}
+/* Prints abrt-cli version and some help text. */
+static void usage(char *argv0)
+{
+ char *name = progname(argv0);
+ printf("%s " VERSION "\n\n", name);
+
+ /* Message has embedded tabs. */
+ printf(_("Usage: %s [OPTION]\n\n"
+ "Startup:\n"
+ " -V, --version display the version of %s and exit\n"
+ " -?, --help print this help\n\n"
+ "Actions:\n"
+ " --get-list print list of crashes which are not reported yet\n"
+ " --get-list-full print list of all crashes\n"
+ " --report UUID create and send a report\n"
+ " --report-always UUID create and send a report without asking\n"
+ " --delete UUID remove crash\n"),
+ name, name);
+}
+
int main(int argc, char** argv)
{
char* uuid = NULL;
int op = -1;
- char *name;
+
+ setlocale(LC_ALL,"");
+#if ENABLE_NLS
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+#endif
while (1)
{
@@ -233,7 +154,7 @@ int main(int argc, char** argv)
case OPT_GET_LIST_FULL:
if (op == -1)
break;
- error_msg("You must specify exactly one operation.");
+ error_msg(_("You must specify exactly one operation."));
return 1;
case -1: /* end of options */
if (op != -1) /* if some operation was specified... */
@@ -242,25 +163,12 @@ int main(int argc, char** argv)
default:
case '?':
case OPT_HELP:
- name = progname(argv[0]);
- printf("%s " VERSION "\n\n", name);
- /* note: message has embedded tabs */
- printf("Usage: %s [OPTION]\n\n"
- "Startup:\n"
- " -V, --version display the version of %s and exit\n"
- " -?, --help print this help\n\n"
- "Actions:\n"
- " --get-list print list of crashes which are not reported yet\n"
- " --get-list-full print list of all crashes\n"
- " --report UUID create and send a report\n"
- " --report-always UUID create and send a report without asking\n"
- " --delete UUID remove crash\n",
- name, name);
- return 1;
+ usage(argv[0]);
+ return 1;
case 'V':
case OPT_VERSION:
- printf("%s " VERSION "\n", progname(argv[0]));
- return 0;
+ printf("%s " VERSION "\n", progname(argv[0]));
+ return 0;
}
if (c == -1)
break;
@@ -276,6 +184,7 @@ int main(int argc, char** argv)
CABRTSocket ABRTDaemon;
ABRTDaemon.Connect(VAR_RUN"/abrt.socket");
#endif
+
switch (op)
{
case OPT_GET_LIST:
@@ -286,33 +195,20 @@ int main(int argc, char** argv)
break;
}
case OPT_REPORT:
- {
- map_crash_report_t cr = call_CreateReport(uuid);
- print_crash_report(cr);
- printf("\nDo you want to send the report? [y/n]: ");
- fflush(NULL);
- char answer[16] = "n";
- fgets(answer, sizeof(answer), stdin);
- if (answer[0] == 'Y' || answer[0] == 'y')
- {
- call_Report(cr);
- }
- break;
- }
+ report(uuid, false);
+ break;
case OPT_REPORT_ALWAYS:
- {
- map_crash_report_t cr = call_CreateReport(uuid);
- call_Report(cr);
- break;
- }
+ report(uuid, true);
+ break;
case OPT_DELETE:
{
call_DeleteDebugDump(uuid);
break;
}
}
+
#if ENABLE_SOCKET
- ABRTDaemon.DisConnect();
+ ABRTDaemon.Disconnect();
#endif
return 0;
diff --git a/src/CLI/Makefile.am b/src/CLI/Makefile.am
index 9447791..7b10bfb 100644
--- a/src/CLI/Makefile.am
+++ b/src/CLI/Makefile.am
@@ -2,7 +2,15 @@ bin_PROGRAMS = abrt-cli
abrt_cli_SOURCES = \
CLI.cpp \
- ABRTSocket.h ABRTSocket.cpp
+ ABRTSocket.h \
+ ABRTSocket.cpp \
+ run-command.h \
+ run-command.cpp \
+ report.h \
+ report.cpp \
+ dbus.h \
+ dbus.cpp
+
abrt_cli_CPPFLAGS = \
-I$(srcdir)/../../inc \
-I$(srcdir)/../../lib/Utils \
@@ -11,5 +19,14 @@ abrt_cli_CPPFLAGS = \
$(DBUS_CFLAGS) \
-D_GNU_SOURCE
# $(GTK_CFLAGS)
+
abrt_cli_LDADD = \
../../lib/Utils/libABRTUtils.la
+
+man_MANS = abrt-cli.1
+EXTRA_DIST = $(man_MANS)
+
+completiondir = $(sysconfdir)/bash_completion.d
+completion_DATA = abrt-cli.bash
+
+DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@
diff --git a/src/CLI/abrt-cli.1 b/src/CLI/abrt-cli.1
index fb0a7c8..4fe4bb4 100644
--- a/src/CLI/abrt-cli.1
+++ b/src/CLI/abrt-cli.1
@@ -2,12 +2,12 @@
.SH NAME
abrt\-cli \- a command line interface to abrt
.SH SYNOPSIS
-.B abrt\-cli
+.B abrt\-cli
[option]
.SH DESCRIPTION
.I abrt\-cli
is a command line tool that manages application crashes catched by
-.I abrtd
+.I abrtd
daemon. It enables access to crash data, and allows to report
crashes depending on active abrt plugins.
.SH OPTIONS
@@ -24,11 +24,17 @@ Prints list of crashes which are not reported yet.
.IP "\-\-get-list-full"
Prints list of all crashes.
.IP "\-\-report \fIUUID\fR"
-Creates and sends a report.
+Creates a crash report and then the text editor is invoked on that
+report. When you are done with editing the report just exit the editor
+and then you will be asked if you want to send the report.
.IP "\-\-report-always \fIUUID\fR"
-Creates and sends a report without asking.
+Creates and sends the crash report without asking.
.IP "\-\-delete \fIUUID\fR"
-Removes crash.
+Removes data about particular crash.
+.SH ENVIRONMENT VARIABLES
+The editor used to edit the crash report is chosen from the ABRT_EDITOR
+environment variable, the VISUAL environment variable, or the EDITOR
+environment variable, in that order.
.SH "SEE ALSO"
.IR abrtd (8),
.IR abrt.conf (5),
diff --git a/src/CLI/abrt-cli.bash b/src/CLI/abrt-cli.bash
new file mode 100644
index 0000000..10b086a
--- /dev/null
+++ b/src/CLI/abrt-cli.bash
@@ -0,0 +1,34 @@
+# bash-completion add-on for abrt-cli(1)
+# http://bash-completion.alioth.debian.org/
+
+_abrt_cli()
+{
+ local cur prev opts
+ COMPREPLY=()
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+ opts="--help --version --get-list --get-list-full --report --report-always --delete"
+
+ #
+ # Complete the arguments to some of the basic commands.
+ #
+ case "${prev}" in
+ --report|--report-always|--delete)
+ local uuids=$(abrt-cli --get-list | grep UUID | awk '{print $3}')
+ COMPREPLY=( $(compgen -W "${uuids}" -- ${cur}) )
+ return 0
+ ;;
+ esac
+
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+}
+complete -F _abrt_cli abrt-cli
+
+# Local variables:
+# mode: shell-script
+# sh-basic-offset: 4
+# sh-indent-comment: t
+# indent-tabs-mode: nil
+# End:
+# ex: ts=4 sw=4 et filetype=sh \ No newline at end of file
diff --git a/src/CLI/dbus.cpp b/src/CLI/dbus.cpp
new file mode 100644
index 0000000..420fe70
--- /dev/null
+++ b/src/CLI/dbus.cpp
@@ -0,0 +1,130 @@
+/*
+ Copyright (C) 2009 RedHat inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#include "dbus.h"
+#include "DBusCommon.h"
+
+DBusConnection* s_dbus_conn;
+
+/*
+ * DBus member calls
+ */
+
+/* helpers */
+static DBusMessage* new_call_msg(const char* method)
+{
+ DBusMessage* msg = dbus_message_new_method_call(CC_DBUS_NAME, CC_DBUS_PATH, CC_DBUS_IFACE, method);
+ if (!msg)
+ die_out_of_memory();
+ return msg;
+}
+
+static DBusMessage* send_get_reply_and_unref(DBusMessage* msg)
+{
+ DBusError err;
+ dbus_error_init(&err);
+ DBusMessage *reply;
+ reply = dbus_connection_send_with_reply_and_block(s_dbus_conn,
+ msg, /*timeout*/ -1, &err);
+ if (reply == NULL)
+ {
+ //TODO: analyse err
+ error_msg_and_die("Error sending DBus message");
+ }
+ dbus_message_unref(msg);
+ return reply;
+}
+
+vector_crash_infos_t call_GetCrashInfos()
+{
+ DBusMessage* msg = new_call_msg("GetCrashInfos");
+ DBusMessage *reply = send_get_reply_and_unref(msg);
+
+ vector_crash_infos_t argout;
+ DBusMessageIter in_iter;
+ dbus_message_iter_init(reply, &in_iter);
+ int r = load_val(&in_iter, argout);
+ if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */
+ error_msg_and_die("dbus call %s: return type mismatch", "GetCrashInfos");
+ dbus_message_unref(reply);
+ return argout;
+}
+
+map_crash_report_t call_CreateReport(const char* uuid)
+{
+ /* Yes, call name is not "CreateReport" but "GetJobResult".
+ * We need to clean up the names one day. */
+ DBusMessage* msg = new_call_msg("GetJobResult");
+ dbus_message_append_args(msg,
+ DBUS_TYPE_STRING, &uuid,
+ DBUS_TYPE_INVALID);
+
+ DBusMessage *reply = send_get_reply_and_unref(msg);
+
+ map_crash_report_t argout;
+ DBusMessageIter in_iter;
+ dbus_message_iter_init(reply, &in_iter);
+ int r = load_val(&in_iter, argout);
+ if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */
+ error_msg_and_die("dbus call %s: return type mismatch", "GetJobResult");
+ dbus_message_unref(reply);
+ return argout;
+}
+
+void call_Report(const map_crash_report_t& report)
+{
+ DBusMessage* msg = new_call_msg("Report");
+ DBusMessageIter out_iter;
+ dbus_message_iter_init_append(msg, &out_iter);
+ store_val(&out_iter, report);
+
+ DBusMessage *reply = send_get_reply_and_unref(msg);
+ //it returns a single value of report_status_t type,
+ //but we don't use it (yet?)
+
+ dbus_message_unref(reply);
+}
+
+void call_DeleteDebugDump(const char* uuid)
+{
+ DBusMessage* msg = new_call_msg("DeleteDebugDump");
+ dbus_message_append_args(msg,
+ DBUS_TYPE_STRING, &uuid,
+ DBUS_TYPE_INVALID);
+
+ DBusMessage *reply = send_get_reply_and_unref(msg);
+ //it returns a single boolean value,
+ //but we don't use it (yet?)
+
+ dbus_message_unref(reply);
+}
+
+void handle_dbus_err(bool error_flag, DBusError *err)
+{
+ if (dbus_error_is_set(err))
+ {
+ error_msg("dbus error: %s", err->message);
+ /* dbus_error_free(&err); */
+ error_flag = true;
+ }
+ if (!error_flag)
+ return;
+ error_msg_and_die(
+ "error requesting DBus name %s, possible reasons: "
+ "abrt run by non-root; dbus config is incorrect",
+ CC_DBUS_NAME);
+}
diff --git a/src/CLI/dbus.h b/src/CLI/dbus.h
new file mode 100644
index 0000000..c6cb82a
--- /dev/null
+++ b/src/CLI/dbus.h
@@ -0,0 +1,32 @@
+/*
+ Copyright (C) 2009 RedHat inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#ifndef ABRT_CLI_DBUS_H
+#define ABRT_CLI_DBUS_H
+
+#include "abrt_dbus.h"
+#include "CrashTypes.h"
+
+extern DBusConnection* s_dbus_conn;
+
+extern vector_crash_infos_t call_GetCrashInfos();
+extern map_crash_report_t call_CreateReport(const char* uuid);
+extern void call_Report(const map_crash_report_t& report);
+extern void call_DeleteDebugDump(const char* uuid);
+extern void handle_dbus_err(bool error_flag, DBusError *err);
+
+#endif
diff --git a/src/CLI/report.cpp b/src/CLI/report.cpp
new file mode 100644
index 0000000..b9a1391
--- /dev/null
+++ b/src/CLI/report.cpp
@@ -0,0 +1,423 @@
+/*
+ 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 "report.h"
+#include "run-command.h"
+#include "dbus.h"
+#include "abrtlib.h"
+#include "DebugDump.h" // FILENAME_* defines
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+#if ENABLE_NLS
+#include <libintl.h>
+#define _(S) gettext(S)
+#else
+#define _(S) (S)
+#endif
+
+/* Field separator for the crash report file that is edited by user. */
+#define FIELD_SEP "%----"
+
+/*
+ * Trims whitespace characters both from left and right side of a string.
+ * Modifies the string in-place. Returns the trimmed string.
+ */
+char *trim(char *str)
+{
+ if (!str)
+ return NULL;
+
+ // Remove leading spaces.
+ char *ibuf;
+ for (ibuf = str; *ibuf && isspace(*ibuf); ++ibuf)
+ ;
+ if (str != ibuf)
+ memmove(str, ibuf, ibuf - str);
+
+ // Remove trailing spaces.
+ int i = strlen(str);
+ while (--i >= 0)
+ {
+ if (!isspace(str[i]))
+ break;
+ }
+ str[++i] = NULL;
+ return str;
+}
+
+/*
+ * Escapes the field content string to avoid confusion with file comments.
+ * Returned field must be free()d by caller.
+ */
+static char *escape(const char *str)
+{
+ // Determine the size of resultant string.
+ // Count the required number of escape characters.
+ // 1. NEWLINE followed by #
+ // 2. NEWLINE followed by \# (escaped version)
+ const char *ptr = str;
+ bool newline = true;
+ int count = 0;
+ while (*ptr)
+ {
+ if (newline)
+ {
+ if (*ptr == '#')
+ ++count;
+ if (*ptr == '\\' && *(ptr + 1) == '#')
+ ++count;
+ }
+
+ newline = (*ptr == '\n');
+ ++ptr;
+ }
+
+ // Copy the input string to the resultant string, and escape all
+ // occurences of \# and #.
+ char *result = (char*)malloc(strlen(str) + 1 + count);
+ if (!result)
+ error_msg_and_die("Memory error while escaping a field.");
+
+ const char *src = str;
+ char *dest = result;
+ newline = true;
+ while (*src)
+ {
+ if (newline)
+ {
+ if (*src == '#')
+ *dest++ = '\\';
+ else if (*src == '\\' && *(src + 1) == '#')
+ *dest++ = '\\';
+ }
+
+ newline = (*src == '\n');
+ *dest++ = *src++;
+ }
+ *dest = '\0';
+ return result;
+}
+
+/*
+ * Removes all comment lines, and unescapes the string previously escaped
+ * by escape(). Works in-place.
+ */
+static void remove_comments_and_unescape(char *str)
+{
+ char *src = str, *dest = str;
+ bool newline = true;
+ while (*src)
+ {
+ if (newline)
+ {
+ if (*src == '#')
+ { // Skip the comment line!
+ while (*src && *src != '\n')
+ ++src;
+
+ if (*src == '\0')
+ break;
+
+ ++src;
+ continue;
+ }
+ else if (*src == '\\' &&
+ (*(src + 1) == '#' ||
+ (*(src + 1) == '\\' && *(src + 2) == '#')))
+ {
+ ++src; // Unescape escaped char.
+ }
+ }
+
+ newline = (*src == '\n');
+ *dest++ = *src++;
+ }
+ *dest = '\0';
+}
+
+/*
+ * Writes a field of crash report to a file.
+ * Field must be writable.
+ */
+static void write_crash_report_field(FILE *fp, const map_crash_report_t &report,
+ const char *field, const char *description)
+{
+ const map_crash_report_t::const_iterator it = report.find(field);
+ if (it == report.end())
+ {
+ // exit silently, all fields are optional for now
+ //error_msg("Field %s not found.\n", field);
+ return;
+ }
+
+ if (it->second[CD_TYPE] == CD_SYS)
+ {
+ error_msg("Cannot write field %s because it is a system value\n", field);
+ return;
+ }
+
+ fprintf(fp, "%s%s\n", FIELD_SEP, it->first.c_str());
+
+ fprintf(fp, "%s\n", description);
+ if (it->second[CD_EDITABLE] != CD_ISEDITABLE)
+ fprintf(fp, _("# This field is read only.\n"));
+
+ char *escaped_content = escape(it->second[CD_CONTENT].c_str());
+ fprintf(fp, "%s\n", escaped_content);
+ free(escaped_content);
+}
+
+/*
+ * Saves the crash report to a file.
+ * Parameter 'fp' must be opened before write_crash_report is called.
+ * Returned value:
+ * If the report is successfully stored to the file, a zero value is returned.
+ * On failure, nonzero value is returned.
+ */
+static int write_crash_report(const map_crash_report_t &report, FILE *fp)
+{
+ fprintf(fp, "# Please check this report. Lines starting with '#' will be ignored.\n"
+ "# Lines starting with '%%----' separate fields, please do not delete them.\n\n");
+
+ write_crash_report_field(fp, report, CD_COMMENT,
+ _("# Describe the circumstances of this crash below."));
+ write_crash_report_field(fp, report, CD_REPRODUCE,
+ _("# How to reproduce the crash?"));
+ write_crash_report_field(fp, report, "backtrace",
+ _("# Stack trace: a list of active stack frames at the time the crash occurred\n# Check that it does not contain any sensitive data such as passwords."));
+ write_crash_report_field(fp, report, CD_UUID, _("# UUID"));
+ write_crash_report_field(fp, report, FILENAME_ARCHITECTURE, _("# Architecture"));
+ write_crash_report_field(fp, report, "cmdline", _("# Command line"));
+ write_crash_report_field(fp, report, FILENAME_COMPONENT, _("# Component"));
+ write_crash_report_field(fp, report, "coredump", _("# Core dump"));
+ write_crash_report_field(fp, report, FILENAME_EXECUTABLE, _("# Executable"));
+ write_crash_report_field(fp, report, FILENAME_KERNEL, _("# Kernel version"));
+ write_crash_report_field(fp, report, FILENAME_PACKAGE, _("# Package"));
+ write_crash_report_field(fp, report, FILENAME_REASON, _("# Reason of crash"));
+ write_crash_report_field(fp, report, FILENAME_RELEASE, _("# Release string of the operating system"));
+
+ return 0;
+}
+
+/*
+ * Updates appropriate field in the report from the text. The text can
+ * contain multiple fields.
+ * Returns:
+ * 0 if no change to the field was detected.
+ * 1 if the field was changed.
+ * Changes to read-only fields are ignored.
+ */
+static int read_crash_report_field(const char *text, map_crash_report_t &report,
+ const char *field)
+{
+ char separator[strlen("\n" FIELD_SEP) + strlen(field) + 2]; // 2 = '\n\0'
+ sprintf(separator, "\n%s%s\n", FIELD_SEP, field);
+ const char *textfield = strstr(text, separator);
+ if (!textfield)
+ return 0; // exit silently because all fields are optional
+
+ textfield += strlen(separator);
+ int length = 0;
+ const char *end = strstr(textfield, "\n" FIELD_SEP);
+ if (!end)
+ length = strlen(textfield);
+ else
+ length = end - textfield;
+
+ const map_crash_report_t::iterator it = report.find(field);
+ if (it == report.end())
+ {
+ error_msg("Field %s not found.\n", field);
+ return 0;
+ }
+
+ if (it->second[CD_TYPE] == CD_SYS)
+ {
+ error_msg("Cannot update field %s because it is a system value.\n", field);
+ return 0;
+ }
+
+ // Do not change noneditable fields.
+ if (it->second[CD_EDITABLE] != CD_ISEDITABLE)
+ return 0;
+
+ // Compare the old field contents with the new field contents.
+ char newvalue[length + 1];
+ strncpy(newvalue, textfield, length);
+ newvalue[length] = '\0';
+ trim(newvalue);
+
+ char oldvalue[it->second[CD_CONTENT].length() + 1];
+ strcpy(oldvalue, it->second[CD_CONTENT].c_str());
+ trim(oldvalue);
+
+ // Return if no change in the contents detected.
+ int cmp = strcmp(newvalue, oldvalue);
+ if (!cmp)
+ return 0;
+
+ it->second[CD_CONTENT].assign(newvalue);
+ return 1;
+}
+
+/*
+ * Updates the crash report 'report' from the text. The text must not contain
+ * any comments.
+ * Returns:
+ * 0 if no field was changed.
+ * 1 if any field was changed.
+ * Changes to read-only fields are ignored.
+ */
+static int read_crash_report(map_crash_report_t &report, const char *text)
+{
+ int result = 0;
+ result |= read_crash_report_field(text, report, CD_COMMENT);
+ result |= read_crash_report_field(text, report, CD_REPRODUCE);
+ result |= read_crash_report_field(text, report, "backtrace");
+ result |= read_crash_report_field(text, report, CD_UUID);
+ result |= read_crash_report_field(text, report, FILENAME_ARCHITECTURE);
+ result |= read_crash_report_field(text, report, "cmdline");
+ result |= read_crash_report_field(text, report, FILENAME_COMPONENT);
+ result |= read_crash_report_field(text, report, "coredump");
+ result |= read_crash_report_field(text, report, FILENAME_EXECUTABLE);
+ result |= read_crash_report_field(text, report, FILENAME_KERNEL);
+ result |= read_crash_report_field(text, report, FILENAME_PACKAGE);
+ result |= read_crash_report_field(text, report, FILENAME_REASON);
+ result |= read_crash_report_field(text, report, FILENAME_RELEASE);
+ return result;
+}
+
+/* Runs external editor. */
+int launch_editor(const char *path)
+{
+ const char *editor, *terminal;
+
+ editor = getenv("ABRT_EDITOR");
+ if (!editor)
+ editor = getenv("VISUAL");
+ if (!editor)
+ editor = getenv("EDITOR");
+
+ terminal = getenv("TERM");
+ if (!editor && (!terminal || !strcmp(terminal, "dumb")))
+ {
+ error_msg(_("Terminal is dumb but no VISUAL nor EDITOR defined."));
+ return 1;
+ }
+
+ if (!editor)
+ editor = "vi";
+
+ const char *args[6];
+ args[0] = editor;
+ args[1] = path;
+ run_command(args);
+
+ return 0;
+}
+
+/* Reports the crash with corresponding uuid over DBus. */
+int report(const char *uuid, bool always)
+{
+ // Ask for an initial report.
+ map_crash_report_t cr = call_CreateReport(uuid);
+
+ if (always)
+ {
+ // Send the report immediately.
+ call_Report(cr);
+ return 0;
+ }
+
+ /* Open a temporary file and write the crash report to it. */
+ char filename[] = "/tmp/abrt-report.XXXXXX";
+ int fd = mkstemp(filename);
+ if (fd == -1)
+ {
+ error_msg("could not generate temporary file name");
+ return 1;
+ }
+
+ FILE *fp = fdopen(fd, "w");
+ if (!fp)
+ {
+ error_msg("could not open '%s' to save the crash report", filename);
+ return 1;
+ }
+
+ write_crash_report(cr, fp);
+
+ if (fclose(fp))
+ {
+ error_msg("could not close '%s'", filename);
+ return 2;
+ }
+
+ // Start a text editor on the temporary file.
+ launch_editor(filename);
+
+ // Read the file back and update the report from the file.
+ fp = fopen(filename, "r");
+ if (!fp)
+ {
+ error_msg("could not open '%s' to read the crash report", filename);
+ return 1;
+ }
+
+ fseek(fp, 0, SEEK_END);
+ long size = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+
+ char *text = (char*)malloc(size + 1);
+ if (fread(text, 1, size, fp) != size)
+ {
+ error_msg("could not read '%s'", filename);
+ return 1;
+ }
+ text[size] = '\0';
+ fclose(fp);
+
+ remove_comments_and_unescape(text);
+ // Updates the crash report from the file text.
+ int report_changed = read_crash_report(cr, text);
+ if (report_changed)
+ puts(_("\nThe report has been updated."));
+ else
+ puts(_("\nNo changes were detected in the report."));
+
+ free(text);
+
+ if (unlink(filename) != 0) // Delete the tempfile.
+ error_msg("could not unlink %s: %s", filename, strerror(errno));
+
+ // Report only if the user is sure.
+ printf(_("Do you want to send the report? [y/N]: "));
+ fflush(NULL);
+ char answer[16] = "n";
+ fgets(answer, sizeof(answer), stdin);
+ if (answer[0] == 'Y' || answer[0] == 'y')
+ {
+ puts(_("Reporting..."));
+ call_Report(cr);
+ puts(_("Crash report was successfully sent."));
+ }
+ else
+ puts(_("Crash report was not sent."));
+
+ return 0;
+}
diff --git a/src/CLI/report.h b/src/CLI/report.h
new file mode 100644
index 0000000..888babf
--- /dev/null
+++ b/src/CLI/report.h
@@ -0,0 +1,24 @@
+/*
+ Copyright (C) 2009 RedHat inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#ifndef ABRT_CLI_REPORT_H
+#define ABRT_CLI_REPORT_H
+
+/* Reports the crash with corresponding uuid over DBus. */
+extern int report(const char *uuid, bool always);
+
+#endif
diff --git a/src/CLI/run-command.cpp b/src/CLI/run-command.cpp
new file mode 100644
index 0000000..80184cf
--- /dev/null
+++ b/src/CLI/run-command.cpp
@@ -0,0 +1,87 @@
+/*
+ Copyright (C) 2009 RedHat inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#include "run-command.h"
+#include "abrtlib.h"
+
+/*
+ Inspired by git code.
+ http://git.kernel.org/?p=git/git.git;a=blob;f=run-command.c;hb=HEAD
+*/
+
+struct child_process
+{
+ const char **argv;
+ pid_t pid;
+};
+
+static int start_command(struct child_process *cmd)
+{
+ cmd->pid = fork();
+ if (cmd->pid == 0)
+ { // new process
+ execvp(cmd->argv[0], (char *const*)cmd->argv);
+ exit(127);
+ }
+ if (cmd->pid < 0)
+ {
+ error_msg_and_die("Unable to fork for %s: %s", cmd->argv[0], strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+static int finish_command(struct child_process *cmd)
+{
+ pid_t waiting;
+ int status, code = -1;
+ while ((waiting = waitpid(cmd->pid, &status, 0)) < 0 && errno == EINTR)
+ ; /* nothing */
+
+ if (waiting < 0)
+ error_msg_and_die("waitpid for %s failed: %s", cmd->argv[0], strerror(errno));
+ else if (waiting != cmd->pid)
+ error_msg_and_die("waitpid is confused (%s)", cmd->argv[0]);
+ else if (WIFSIGNALED(status))
+ {
+ code = WTERMSIG(status);
+ error_msg("%s died of signal %d", cmd->argv[0], code);
+ }
+ else if (WIFEXITED(status))
+ {
+ code = WEXITSTATUS(status);
+ if (code == 127)
+ {
+ code = -1;
+ error_msg_and_die("cannot run %s: %s", cmd->argv[0], strerror(ENOENT));
+ }
+ }
+ else
+ error_msg_and_die("waitpid is confused (%s)", cmd->argv[0]);
+
+ return code;
+}
+
+int run_command(const char **argv)
+{
+ struct child_process cmd;
+ cmd.argv = argv;
+ int code = start_command(&cmd);
+ if (code)
+ return code;
+ return finish_command(&cmd);
+}
diff --git a/src/CLI/run-command.h b/src/CLI/run-command.h
new file mode 100644
index 0000000..45a85af
--- /dev/null
+++ b/src/CLI/run-command.h
@@ -0,0 +1,23 @@
+/*
+ Copyright (C) 2009 RedHat inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#ifndef ABRT_CLI_RUN_COMMAND_H
+#define ABRT_CLI_RUN_COMMAND_H
+
+int run_command(const char **argv);
+
+#endif