summaryrefslogtreecommitdiffstats
path: root/src/cli
diff options
context:
space:
mode:
authorNikola Pajkovsky <npajkovs@redhat.com>2010-08-10 10:21:25 +0200
committerNikola Pajkovsky <npajkovs@redhat.com>2010-08-10 10:21:56 +0200
commit83a6ce9ad4b1828e163dc7172ef603201b748473 (patch)
tree9d0580eba6c01cb5964655df42bafab9de91329b /src/cli
parente84ab7783d05eb7b5f1b55ab44e7c23c85e50516 (diff)
downloadabrt-83a6ce9ad4b1828e163dc7172ef603201b748473.tar.gz
abrt-83a6ce9ad4b1828e163dc7172ef603201b748473.tar.xz
abrt-83a6ce9ad4b1828e163dc7172ef603201b748473.zip
lower case direcotry(no code changed)
Signed-off-by: Nikola Pajkovsky <npajkovs@redhat.com>
Diffstat (limited to 'src/cli')
-rw-r--r--src/cli/CLI.cpp436
-rw-r--r--src/cli/Makefile.am28
-rw-r--r--src/cli/abrt-cli.158
-rw-r--r--src/cli/abrt-cli.bash50
-rw-r--r--src/cli/dbus.cpp282
-rw-r--r--src/cli/dbus.h68
-rw-r--r--src/cli/report.cpp743
-rw-r--r--src/cli/report.h28
-rw-r--r--src/cli/run-command.cpp73
-rw-r--r--src/cli/run-command.h23
10 files changed, 1789 insertions, 0 deletions
diff --git a/src/cli/CLI.cpp b/src/cli/CLI.cpp
new file mode 100644
index 00000000..276703dc
--- /dev/null
+++ b/src/cli/CLI.cpp
@@ -0,0 +1,436 @@
+/*
+ Copyright (C) 2009, 2010 Red Hat, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#include <getopt.h>
+#include "abrt_exception.h"
+#include "abrtlib.h"
+#include "abrt_dbus.h"
+#include "dbus_common.h"
+#include "report.h"
+#include "dbus.h"
+#if HAVE_CONFIG_H
+# include <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
+
+/** Creates a localized string from crash time. */
+static char *localize_crash_time(const char *timestr)
+{
+ long time = xatou(timestr);
+ char timeloc[256];
+ int success = strftime(timeloc, 128, "%c", localtime(&time));
+ if (!success)
+ error_msg_and_die("Error while converting time to string");
+ return xasprintf("%s", timeloc);
+}
+
+/** Prints basic information about a crash to stdout. */
+static void print_crash(const map_crash_data_t &crash)
+{
+ /* Create a localized string from crash time. */
+ const char *timestr = get_crash_data_item_content(crash, FILENAME_TIME).c_str();
+ const char *timeloc = localize_crash_time(timestr);
+
+ printf(_("\tUID : %s\n"
+ "\tUUID : %s\n"
+ "\tPackage : %s\n"
+ "\tExecutable : %s\n"
+ "\tCrash Time : %s\n"
+ "\tCrash Count: %s\n"),
+ get_crash_data_item_content(crash, CD_UID).c_str(),
+ get_crash_data_item_content(crash, CD_UUID).c_str(),
+ get_crash_data_item_content(crash, FILENAME_PACKAGE).c_str(),
+ get_crash_data_item_content(crash, FILENAME_EXECUTABLE).c_str(),
+ timeloc,
+ get_crash_data_item_content(crash, CD_COUNT).c_str());
+
+ free((void *)timeloc);
+
+ /* Print the hostname if it's available. */
+ const char *hostname = get_crash_data_item_content_or_NULL(crash, FILENAME_HOSTNAME);
+ if (hostname)
+ printf(_("\tHostname : %s\n"), hostname);
+}
+
+/**
+ * Prints a list containing "crashes" to stdout.
+ * @param include_reported
+ * Do not skip entries marked as already reported.
+ */
+static void print_crash_list(const vector_map_crash_data_t& crash_list, bool include_reported)
+{
+ for (unsigned i = 0; i < crash_list.size(); ++i)
+ {
+ const map_crash_data_t& crash = crash_list[i];
+ if (get_crash_data_item_content(crash, CD_REPORTED) == "1" && !include_reported)
+ continue;
+
+ printf("%u.\n", i);
+ print_crash(crash);
+ }
+}
+
+/**
+ * Prints full information about a crash
+ */
+static void print_crash_info(const map_crash_data_t& crash, bool show_backtrace)
+{
+ const char *timestr = get_crash_data_item_content(crash, FILENAME_TIME).c_str();
+ const char *timeloc = localize_crash_time(timestr);
+
+ printf(_("Crash ID: %s:%s\n"
+ "Last crash: %s\n"
+ "Analyzer: %s\n"
+ "Component: %s\n"
+ "Package: %s\n"
+ "Command: %s\n"
+ "Executable: %s\n"
+ "System: %s, kernel %s\n"
+ "Rating: %s\n"
+ "Coredump file: %s\n"
+ "Reason: %s\n"),
+ get_crash_data_item_content(crash, CD_UID).c_str(),
+ get_crash_data_item_content(crash, CD_UUID).c_str(),
+ timeloc,
+ get_crash_data_item_content(crash, FILENAME_ANALYZER).c_str(),
+ get_crash_data_item_content(crash, FILENAME_COMPONENT).c_str(),
+ get_crash_data_item_content(crash, FILENAME_PACKAGE).c_str(),
+ get_crash_data_item_content(crash, FILENAME_CMDLINE).c_str(),
+ get_crash_data_item_content(crash, FILENAME_EXECUTABLE).c_str(),
+ get_crash_data_item_content(crash, FILENAME_RELEASE).c_str(),
+ get_crash_data_item_content(crash, FILENAME_KERNEL).c_str(),
+ get_crash_data_item_content(crash, FILENAME_RATING).c_str(),
+ get_crash_data_item_content(crash, FILENAME_COREDUMP).c_str(),
+ get_crash_data_item_content(crash, FILENAME_REASON).c_str());
+
+ free((void *)timeloc);
+
+ /* print only if available */
+ const char *crash_function = get_crash_data_item_content_or_NULL(crash, FILENAME_CRASH_FUNCTION);
+ if (crash_function)
+ printf(_("Crash function: %s\n"), crash_function);
+
+ const char *hostname = get_crash_data_item_content_or_NULL(crash, FILENAME_HOSTNAME);
+ if (hostname)
+ printf(_("Hostname: %s\n"), hostname);
+
+ const char *reproduce = get_crash_data_item_content_or_NULL(crash, FILENAME_REPRODUCE);
+ if (reproduce)
+ printf(_("\nHow to reproduce:\n%s\n"), reproduce);
+
+ const char *comment = get_crash_data_item_content_or_NULL(crash, FILENAME_COMMENT);
+ if (comment)
+ printf(_("\nComment:\n%s\n"), comment);
+
+ if (show_backtrace)
+ {
+ const char *backtrace = get_crash_data_item_content_or_NULL(crash, FILENAME_BACKTRACE);
+ if (backtrace)
+ printf(_("\nBacktrace:\n%s\n"), backtrace);
+ }
+}
+
+/**
+ * Converts crash reference from user's input to unique crash identification
+ * in form UID:UUID.
+ * The returned string must be released by caller.
+ */
+static char *guess_crash_id(const char *str)
+{
+ vector_map_crash_data_t ci = call_GetCrashInfos();
+ unsigned num_crashinfos = ci.size();
+ if (str[0] == '@') /* "--report @N" syntax */
+ {
+ unsigned position = xatoi_u(str + 1);
+ if (position >= num_crashinfos)
+ error_msg_and_die("There are only %u crash infos", num_crashinfos);
+ map_crash_data_t& info = ci[position];
+ return xasprintf("%s:%s",
+ get_crash_data_item_content(info, CD_UID).c_str(),
+ get_crash_data_item_content(info, CD_UUID).c_str()
+ );
+ }
+
+ unsigned len = strlen(str);
+ unsigned ii;
+ char *result = NULL;
+ for (ii = 0; ii < num_crashinfos; ii++)
+ {
+ map_crash_data_t& info = ci[ii];
+ const char *this_uuid = get_crash_data_item_content(info, CD_UUID).c_str();
+ if (strncmp(str, this_uuid, len) == 0)
+ {
+ if (result)
+ error_msg_and_die("Crash prefix '%s' is not unique", str);
+ result = xasprintf("%s:%s",
+ get_crash_data_item_content(info, CD_UID).c_str(),
+ this_uuid
+ );
+ }
+ }
+ if (!result)
+ error_msg_and_die("Crash '%s' not found", str);
+ return result;
+}
+
+/* Program options */
+enum
+{
+ OPT_GET_LIST,
+ OPT_REPORT,
+ OPT_DELETE,
+ OPT_INFO
+};
+
+/**
+ * Long options.
+ * Do not use the has_arg field. Arguments are handled after parsing all options.
+ * The reason is that we want to use all the following combinations:
+ * --report ID
+ * --report ID --always
+ * --report --always ID
+ */
+static const struct option longopts[] =
+{
+ /* name, has_arg, flag, val */
+ { "help" , no_argument, NULL, '?' },
+ { "version" , no_argument, NULL, 'V' },
+ { "list" , no_argument, NULL, 'l' },
+ { "full" , no_argument, NULL, 'f' },
+ { "always" , no_argument, NULL, 'y' },
+ { "report" , no_argument, NULL, 'r' },
+ { "delete" , no_argument, NULL, 'd' },
+ { "info" , no_argument, NULL, 'i' },
+ { "backtrace", no_argument, NULL, 'b' },
+ { 0, 0, 0, 0 } /* prevents crashes for unknown options*/
+};
+
+/* Gets the program name from the first command line argument. */
+static const char *progname(const char *argv0)
+{
+ const char* name = strrchr(argv0, '/');
+ if (name)
+ return ++name;
+ return argv0;
+}
+
+/**
+ * Prints abrt-cli version and some help text.
+ * Then exits the program with return value 1.
+ */
+static void usage(char *argv0)
+{
+ const char *name = progname(argv0);
+ printf("%s "VERSION"\n\n", name);
+
+ /* Message has embedded tabs. */
+ printf(_("Usage: %s [OPTION]\n\n"
+ "Startup:\n"
+ " -V, --version display the version of %s and exit\n"
+ " -?, --help print this help\n\n"
+ "Actions:\n"
+ " -l, --list print a list of all crashes which are not yet reported\n"
+ " -f, --full print a list of all crashes, including the already reported ones\n"
+ " -r, --report CRASH_ID create and send a report\n"
+ " -y, --always create and send a report without asking\n"
+ " -d, --delete CRASH_ID remove a crash\n"
+ " -i, --info CRASH_ID print detailed information about a crash\n"
+ " -b, --backtrace print detailed information about a crash including backtrace\n"
+ "CRASH_ID can be:\n"
+ " UID:UUID pair,\n"
+ " unique UUID prefix - the crash with matching UUID will be acted upon\n"
+ " @N - N'th crash (as displayed by --list --full) will be acted upon\n"
+ ),
+ name, name);
+
+ exit(1);
+}
+
+int main(int argc, char** argv)
+{
+ const char* crash_id = NULL;
+ int op = -1;
+ bool full = false;
+ bool always = false;
+ bool backtrace = false;
+
+ setlocale(LC_ALL, "");
+#if ENABLE_NLS
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+#endif
+
+ while (1)
+ {
+ int option_index;
+ /* Do not use colons, arguments are handled after parsing all options. */
+ int c = getopt_long_only(argc, argv, "?Vrdlfyib",
+ longopts, &option_index);
+
+#define SET_OP(newop) \
+ if (op != -1 && op != newop) \
+ { \
+ error_msg(_("You must specify exactly one operation")); \
+ return 1; \
+ } \
+ op = newop;
+
+ switch (c)
+ {
+ case 'r': SET_OP(OPT_REPORT); break;
+ case 'd': SET_OP(OPT_DELETE); break;
+ case 'l': SET_OP(OPT_GET_LIST); break;
+ case 'i': SET_OP(OPT_INFO); break;
+ case 'f': full = true; break;
+ case 'y': always = true; break;
+ case 'b': backtrace = true; break;
+ case -1: /* end of options */ break;
+ default: /* some error */
+ case '?':
+ usage(argv[0]); /* exits app */
+ case 'V':
+ printf("%s "VERSION"\n", progname(argv[0]));
+ return 0;
+ }
+#undef SET_OP
+ if (c == -1)
+ break;
+ }
+
+ /* Handle option arguments. */
+ int arg_count = argc - optind;
+ switch (arg_count)
+ {
+ case 0:
+ if (op == OPT_REPORT || op == OPT_DELETE || op == OPT_INFO)
+ usage(argv[0]);
+ break;
+ case 1:
+ if (op != OPT_REPORT && op != OPT_DELETE && op != OPT_INFO)
+ usage(argv[0]);
+ crash_id = argv[optind];
+ break;
+ default:
+ usage(argv[0]);
+ }
+
+ /* Check if we have an operation.
+ * Limit --full and --always to certain operations.
+ */
+ if ((full && op != OPT_GET_LIST) ||
+ (always && op != OPT_REPORT) ||
+ (backtrace && op != OPT_INFO) ||
+ op == -1)
+ {
+ usage(argv[0]);
+ return 1;
+ }
+
+ DBusError err;
+ dbus_error_init(&err);
+ s_dbus_conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
+ handle_dbus_err(s_dbus_conn == NULL, &err);
+
+ /* Do the selected operation. */
+ int exitcode = 0;
+ switch (op)
+ {
+ case OPT_GET_LIST:
+ {
+ vector_map_crash_data_t ci = call_GetCrashInfos();
+ print_crash_list(ci, full);
+ break;
+ }
+ case OPT_REPORT:
+ {
+ int flags = CLI_REPORT_SILENT_IF_NOT_FOUND;
+ if (always)
+ flags |= CLI_REPORT_BATCH;
+ exitcode = report(crash_id, flags);
+ if (exitcode == -1) /* no such crash_id */
+ {
+ crash_id = guess_crash_id(crash_id);
+ exitcode = report(crash_id, always ? CLI_REPORT_BATCH : 0);
+ if (exitcode == -1)
+ {
+ error_msg("Crash '%s' not found", crash_id);
+ free((void *)crash_id);
+ xfunc_die();
+ }
+
+ free((void *)crash_id);
+ }
+ break;
+ }
+ case OPT_DELETE:
+ {
+ exitcode = call_DeleteDebugDump(crash_id);
+ if (exitcode == ENOENT)
+ {
+ crash_id = guess_crash_id(crash_id);
+ exitcode = call_DeleteDebugDump(crash_id);
+ if (exitcode == ENOENT)
+ {
+ error_msg("Crash '%s' not found", crash_id);
+ free((void *)crash_id);
+ xfunc_die();
+ }
+
+ free((void *)crash_id);
+ }
+ if (exitcode != 0)
+ error_msg_and_die("Can't delete debug dump '%s'", crash_id);
+ break;
+ }
+ case OPT_INFO:
+ {
+ int old_logmode = logmode;
+ logmode = 0;
+
+ map_crash_data_t crashData = call_CreateReport(crash_id);
+ if (crashData.empty()) /* no such crash_id */
+ {
+ crash_id = guess_crash_id(crash_id);
+ crashData = call_CreateReport(crash_id);
+ if (crashData.empty())
+ {
+ error_msg("Crash '%s' not found", crash_id);
+ free((void *)crash_id);
+ xfunc_die();
+ }
+
+ free((void *)crash_id);
+ }
+
+ logmode = old_logmode;
+
+ print_crash_info(crashData, backtrace);
+
+ break;
+ }
+ }
+
+ return exitcode;
+}
diff --git a/src/cli/Makefile.am b/src/cli/Makefile.am
new file mode 100644
index 00000000..14d98a01
--- /dev/null
+++ b/src/cli/Makefile.am
@@ -0,0 +1,28 @@
+bin_PROGRAMS = abrt-cli
+
+abrt_cli_SOURCES = \
+ CLI.cpp \
+ run-command.h run-command.cpp \
+ report.h report.cpp \
+ dbus.h dbus.cpp
+
+abrt_cli_CPPFLAGS = \
+ -I$(srcdir)/../../inc \
+ -I$(srcdir)/../../lib/utils \
+ -DVAR_RUN=\"$(VAR_RUN)\" \
+ $(ENABLE_SOCKET_OR_DBUS) \
+ $(DBUS_CFLAGS) \
+ -D_GNU_SOURCE
+# $(GTK_CFLAGS)
+
+abrt_cli_LDADD = \
+ ../../lib/utils/libABRTUtils.la \
+ ../../lib/utils/libABRTdUtils.la
+
+man_MANS = abrt-cli.1
+EXTRA_DIST = $(man_MANS)
+
+completiondir = $(sysconfdir)/bash_completion.d
+dist_completion_DATA = abrt-cli.bash
+
+DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@
diff --git a/src/cli/abrt-cli.1 b/src/cli/abrt-cli.1
new file mode 100644
index 00000000..3ad858b3
--- /dev/null
+++ b/src/cli/abrt-cli.1
@@ -0,0 +1,58 @@
+.TH abrt\-cli "1" "12 Oct 2009" ""
+.SH NAME
+abrt\-cli \- a command line interface to abrt
+.SH SYNOPSIS
+.B abrt\-cli
+[option]
+.SH DESCRIPTION
+.I abrt\-cli
+is a command line tool that manages application crashes catched by
+.I abrtd
+daemon. It enables access to crash data, and allows to report
+crashes depending on active abrt plugins.
+.SH OPTIONS
+.B Basic startup options
+.IP "\-V, \-\-version"
+Displays version of abrt\-cli.
+.IP "\-?, \-\-help"
+Print a help message describing all of abrt-cli’s command-line options.
+
+.PP
+.B Crash action options
+.IP "\-l, \-\-list"
+Prints list of crashes which are not reported yet.
+.IP "\-r, \-\-report \fIUUID\fR"
+Creates a crash report and then the text editor is invoked on that
+report. When you are done with editing the report just exit the editor
+and then you will be asked if you want to send the report.
+.IP "\-d, \-\-delete \fIUUID\fR"
+Removes data about particular crash.
+.IP "\-i, \-\-info \fIUUID\fR"
+Prints detailed information about particular crash.
+
+.PP
+.B Listing options
+.IP "\-f, \-\-full"
+List all crashes, including already reported.
+
+.PP
+.B Report options
+.IP "\-y, \-\-always"
+Creates and sends the crash report automatically, without asking
+any questions.
+
+.PP
+.B Info options
+.IP "\-b, \-\-backtrace"
+Includes the crash backtrace in the info output if the backtrace is
+available.
+
+.SH ENVIRONMENT VARIABLES
+The editor used to edit the crash report is chosen from the
+ABRT_EDITOR environment variable, the VISUAL environment variable, or
+the EDITOR environment variable, in that order.
+
+.SH SEE ALSO
+.IR abrtd (8),
+.IR abrt.conf (5),
+.IR abrt-plugins (7)
diff --git a/src/cli/abrt-cli.bash b/src/cli/abrt-cli.bash
new file mode 100644
index 00000000..fd0a85f3
--- /dev/null
+++ b/src/cli/abrt-cli.bash
@@ -0,0 +1,50 @@
+# bash-completion add-on for abrt-cli(1)
+# http://bash-completion.alioth.debian.org/
+
+# $1 = additional options for abrt-cli
+_abrt_list()
+{
+ echo $(abrt-cli --list $1 | grep UUID | awk '{print $3}')
+ return 0
+}
+
+_abrt_cli()
+{
+ local cur prev opts
+ COMPREPLY=()
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+ opts="--help --version --list --report --delete"
+
+ #
+ # Complete the arguments to some of the basic commands.
+ #
+ case "${prev}" in
+ --list)
+ opts="--full"
+ ;;
+ --report)
+ # Include only not-yet-reported crashes.
+ opts="--always $(_abrt_list)"
+ ;;
+ --always) # This is for --report --always
+ # Include only not-yet-reported crashes.
+ opts=$(_abrt_list)
+ ;;
+ --delete)
+ opts=$(_abrt_list "--full")
+ ;;
+ esac
+
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+}
+complete -F _abrt_cli abrt-cli
+
+# Local variables:
+# mode: shell-script
+# sh-basic-offset: 4
+# sh-indent-comment: t
+# indent-tabs-mode: nil
+# End:
+# ex: ts=4 sw=4 et filetype=sh \ No newline at end of file
diff --git a/src/cli/dbus.cpp b/src/cli/dbus.cpp
new file mode 100644
index 00000000..7565d5bc
--- /dev/null
+++ b/src/cli/dbus.cpp
@@ -0,0 +1,282 @@
+/*
+ Copyright (C) 2009 RedHat inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#include "dbus.h"
+#include "dbus_common.h"
+
+DBusConnection* s_dbus_conn;
+
+/*
+ * DBus member calls
+ */
+
+/* helpers */
+static DBusMessage* new_call_msg(const char* method)
+{
+ DBusMessage* msg = dbus_message_new_method_call(ABRTD_DBUS_NAME, ABRTD_DBUS_PATH, ABRTD_DBUS_IFACE, method);
+ if (!msg)
+ die_out_of_memory();
+ return msg;
+}
+
+static DBusMessage* send_get_reply_and_unref(DBusMessage* msg)
+{
+ dbus_uint32_t serial;
+ if (TRUE != dbus_connection_send(s_dbus_conn, msg, &serial))
+ error_msg_and_die("Error sending DBus message");
+ dbus_message_unref(msg);
+
+ while (true)
+ {
+ DBusMessage *received = dbus_connection_pop_message(s_dbus_conn);
+ if (!received)
+ {
+ if (FALSE == dbus_connection_read_write(s_dbus_conn, -1))
+ error_msg_and_die("dbus connection closed");
+ continue;
+ }
+
+ int tp = dbus_message_get_type(received);
+ const char *error_str = dbus_message_get_error_name(received);
+#if 0
+ /* Debugging */
+ printf("type:%u (CALL:%u, RETURN:%u, ERROR:%u, SIGNAL:%u)\n", tp,
+ DBUS_MESSAGE_TYPE_METHOD_CALL,
+ DBUS_MESSAGE_TYPE_METHOD_RETURN,
+ DBUS_MESSAGE_TYPE_ERROR,
+ DBUS_MESSAGE_TYPE_SIGNAL
+ );
+ const char *sender = dbus_message_get_sender(received);
+ if (sender)
+ printf("sender: %s\n", sender);
+ const char *path = dbus_message_get_path(received);
+ if (path)
+ printf("path: %s\n", path);
+ const char *member = dbus_message_get_member(received);
+ if (member)
+ printf("member: %s\n", member);
+ const char *interface = dbus_message_get_interface(received);
+ if (interface)
+ printf("interface: %s\n", interface);
+ const char *destination = dbus_message_get_destination(received);
+ if (destination)
+ printf("destination: %s\n", destination);
+ if (error_str)
+ printf("error: '%s'\n", error_str);
+#endif
+
+ DBusError err;
+ dbus_error_init(&err);
+
+ if (dbus_message_is_signal(received, ABRTD_DBUS_IFACE, "Update"))
+ {
+ const char *update_msg;
+ if (!dbus_message_get_args(received, &err,
+ DBUS_TYPE_STRING, &update_msg,
+ DBUS_TYPE_INVALID))
+ {
+ error_msg_and_die("dbus Update message: arguments mismatch");
+ }
+ printf(">> %s\n", update_msg);
+ }
+ else if (dbus_message_is_signal(received, ABRTD_DBUS_IFACE, "Warning"))
+ {
+ const char *warning_msg;
+ if (!dbus_message_get_args(received, &err,
+ DBUS_TYPE_STRING, &warning_msg,
+ DBUS_TYPE_INVALID))
+ {
+ error_msg_and_die("dbus Warning message: arguments mismatch");
+ }
+ log(">! %s\n", warning_msg);
+ }
+ else
+ if (tp == DBUS_MESSAGE_TYPE_METHOD_RETURN
+ && dbus_message_get_reply_serial(received) == serial
+ ) {
+ return received;
+ }
+ else
+ if (tp == DBUS_MESSAGE_TYPE_ERROR
+ && dbus_message_get_reply_serial(received) == serial
+ ) {
+ error_msg_and_die("dbus call returned error: '%s'", error_str);
+ }
+
+ dbus_message_unref(received);
+ }
+}
+
+vector_map_crash_data_t call_GetCrashInfos()
+{
+ DBusMessage* msg = new_call_msg(__func__ + 5);
+ DBusMessage *reply = send_get_reply_and_unref(msg);
+
+ DBusMessageIter in_iter;
+ dbus_message_iter_init(reply, &in_iter);
+
+ vector_map_crash_data_t argout;
+ int r = load_val(&in_iter, argout);
+ if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */
+ error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5);
+
+ dbus_message_unref(reply);
+ return argout;
+}
+
+map_crash_data_t call_CreateReport(const char* crash_id)
+{
+ DBusMessage* msg = new_call_msg(__func__ + 5);
+ dbus_message_append_args(msg,
+ DBUS_TYPE_STRING, &crash_id,
+ DBUS_TYPE_INVALID);
+
+ DBusMessage *reply = send_get_reply_and_unref(msg);
+
+ DBusMessageIter in_iter;
+ dbus_message_iter_init(reply, &in_iter);
+
+ map_crash_data_t argout;
+ int r = load_val(&in_iter, argout);
+ if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */
+ error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5);
+
+ dbus_message_unref(reply);
+ return argout;
+}
+
+report_status_t call_Report(const map_crash_data_t& report,
+ const vector_string_t& reporters,
+ const map_map_string_t &plugins)
+{
+ DBusMessage* msg = new_call_msg(__func__ + 5);
+ DBusMessageIter out_iter;
+ dbus_message_iter_init_append(msg, &out_iter);
+
+ /* parameter #1: report data */
+ store_val(&out_iter, report);
+ /* parameter #2: reporters to use */
+ store_val(&out_iter, reporters);
+ /* parameter #3 (opt): plugin config */
+ if (!plugins.empty())
+ store_val(&out_iter, plugins);
+
+ DBusMessage *reply = send_get_reply_and_unref(msg);
+
+ DBusMessageIter in_iter;
+ dbus_message_iter_init(reply, &in_iter);
+
+ report_status_t result;
+ int r = load_val(&in_iter, result);
+ if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */
+ error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5);
+
+ dbus_message_unref(reply);
+ return result;
+}
+
+int32_t call_DeleteDebugDump(const char* crash_id)
+{
+ DBusMessage* msg = new_call_msg(__func__ + 5);
+ dbus_message_append_args(msg,
+ DBUS_TYPE_STRING, &crash_id,
+ DBUS_TYPE_INVALID);
+
+ DBusMessage *reply = send_get_reply_and_unref(msg);
+
+ DBusMessageIter in_iter;
+ dbus_message_iter_init(reply, &in_iter);
+
+ int32_t result;
+ int r = load_val(&in_iter, result);
+ if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */
+ error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5);
+
+ dbus_message_unref(reply);
+ return result;
+}
+
+map_map_string_t call_GetPluginsInfo()
+{
+ DBusMessage *msg = new_call_msg(__func__ + 5);
+ DBusMessage *reply = send_get_reply_and_unref(msg);
+
+ DBusMessageIter in_iter;
+ dbus_message_iter_init(reply, &in_iter);
+
+ map_map_string_t argout;
+ int r = load_val(&in_iter, argout);
+ if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */
+ error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5);
+
+ dbus_message_unref(reply);
+ return argout;
+}
+
+map_plugin_settings_t call_GetPluginSettings(const char *name)
+{
+ DBusMessage *msg = new_call_msg(__func__ + 5);
+ dbus_message_append_args(msg,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID);
+
+ DBusMessage *reply = send_get_reply_and_unref(msg);
+
+ DBusMessageIter in_iter;
+ dbus_message_iter_init(reply, &in_iter);
+
+ map_string_t argout;
+ int r = load_val(&in_iter, argout);
+ if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */
+ error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5);
+
+ dbus_message_unref(reply);
+ return argout;
+}
+
+map_map_string_t call_GetSettings()
+{
+ DBusMessage *msg = new_call_msg(__func__ + 5);
+ DBusMessage *reply = send_get_reply_and_unref(msg);
+
+ DBusMessageIter in_iter;
+ dbus_message_iter_init(reply, &in_iter);
+ map_map_string_t argout;
+ int r = load_val(&in_iter, argout);
+ if (r != ABRT_DBUS_LAST_FIELD) /* more values present, or bad type */
+ error_msg_and_die("dbus call %s: return type mismatch", __func__ + 5);
+
+ dbus_message_unref(reply);
+ return argout;
+}
+
+void handle_dbus_err(bool error_flag, DBusError *err)
+{
+ if (dbus_error_is_set(err))
+ {
+ error_msg("dbus error: %s", err->message);
+ /* dbus_error_free(&err); */
+ error_flag = true;
+ }
+ if (!error_flag)
+ return;
+ error_msg_and_die(
+ "error requesting DBus name %s, possible reasons: "
+ "abrt run by non-root; dbus config is incorrect; "
+ "or dbus daemon needs to be restarted to reload dbus config",
+ ABRTD_DBUS_NAME);
+}
diff --git a/src/cli/dbus.h b/src/cli/dbus.h
new file mode 100644
index 00000000..8b56b152
--- /dev/null
+++ b/src/cli/dbus.h
@@ -0,0 +1,68 @@
+/*
+ Copyright (C) 2009 RedHat inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#ifndef ABRT_CLI_DBUS_H
+#define ABRT_CLI_DBUS_H
+
+#include "abrt_dbus.h"
+#include "crash_types.h"
+
+extern DBusConnection* s_dbus_conn;
+
+vector_map_crash_data_t call_GetCrashInfos();
+
+map_crash_data_t call_CreateReport(const char *crash_id);
+
+/** Sends report using enabled Reporter plugins.
+ * @param report
+ * The report sent to Reporter plugins.
+ * @param reporters
+ * List of names of Reporters which should be called.
+ * @param plugins
+ * Optional settings for Reporter plugins, can be empty.
+ * Format: plugins["PluginName"]["SettingsKey"] = "SettingsValue"
+ * If it contains settings for some plugin, it must contain _all fields_
+ * obtained by call_GetPluginSettings, otherwise the plugin might ignore
+ * the settings.
+ */
+report_status_t call_Report(const map_crash_data_t& report,
+ const vector_string_t& reporters,
+ const map_map_string_t &plugins);
+
+int32_t call_DeleteDebugDump(const char* crash_id);
+
+/* Gets basic data about all installed plugins.
+ * @todo
+ * Return more semantically structured output - maybe a struct instead of a map.
+ */
+map_map_string_t call_GetPluginsInfo();
+
+/** Gets default plugin settings.
+ * @param name
+ * Corresponds to name obtained from call_GetPluginsInfo.
+ */
+map_plugin_settings_t call_GetPluginSettings(const char *name);
+
+/** Gets global daemon settings.
+ * @todo
+ * Return more semantically structured output - maybe a struct instead of a map.
+ */
+map_map_string_t call_GetSettings();
+
+void handle_dbus_err(bool error_flag, DBusError *err);
+
+#endif
diff --git a/src/cli/report.cpp b/src/cli/report.cpp
new file mode 100644
index 00000000..96dec553
--- /dev/null
+++ b/src/cli/report.cpp
@@ -0,0 +1,743 @@
+/*
+ Copyright (C) 2009, 2010 Red Hat, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#include "report.h"
+#include "run-command.h"
+#include "dbus.h"
+#include "abrtlib.h"
+#include "debug_dump.h"
+#include "crash_types.h" // FILENAME_* defines
+#include "plugin.h" // LoadPluginSettings
+#include <cassert>
+#include <algorithm>
+#include <termios.h>
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+#if ENABLE_NLS
+# include <libintl.h>
+# define _(S) gettext(S)
+#else
+# define _(S) (S)
+#endif
+
+#include "abrt_packages.h"
+
+/* Field separator for the crash report file that is edited by user. */
+#define FIELD_SEP "%----"
+
+/*
+ * Trims whitespace characters both from left and right side of a string.
+ * Modifies the string in-place. Returns the trimmed string.
+ */
+static char *trim(char *str)
+{
+ if (!str)
+ return NULL;
+
+ // Remove leading spaces.
+ char *ibuf;
+ ibuf = skip_whitespace(str);
+ int i = strlen(ibuf);
+ if (str != ibuf)
+ memmove(str, ibuf, i + 1);
+
+ // Remove trailing spaces.
+ while (--i >= 0)
+ {
+ if (!isspace(str[i]))
+ break;
+ }
+ str[++i] = '\0';
+ return str;
+}
+
+/*
+ * Escapes the field content string to avoid confusion with file comments.
+ * Returned field must be free()d by caller.
+ */
+static char *escape(const char *str)
+{
+ // Determine the size of resultant string.
+ // Count the required number of escape characters.
+ // 1. NEWLINE followed by #
+ // 2. NEWLINE followed by \# (escaped version)
+ const char *ptr = str;
+ bool newline = true;
+ int count = 0;
+ while (*ptr)
+ {
+ if (newline)
+ {
+ if (*ptr == '#')
+ ++count;
+ if (*ptr == '\\' && ptr[1] == '#')
+ ++count;
+ }
+
+ newline = (*ptr == '\n');
+ ++ptr;
+ }
+
+ // Copy the input string to the resultant string, and escape all
+ // occurences of \# and #.
+ char *result = (char*)xmalloc(strlen(str) + 1 + count);
+
+ const char *src = str;
+ char *dest = result;
+ newline = true;
+ while (*src)
+ {
+ if (newline)
+ {
+ if (*src == '#')
+ *dest++ = '\\';
+ else if (*src == '\\' && *(src + 1) == '#')
+ *dest++ = '\\';
+ }
+
+ newline = (*src == '\n');
+ *dest++ = *src++;
+ }
+ *dest = '\0';
+ return result;
+}
+
+/*
+ * Removes all comment lines, and unescapes the string previously escaped
+ * by escape(). Works in-place.
+ */
+static void remove_comments_and_unescape(char *str)
+{
+ char *src = str, *dest = str;
+ bool newline = true;
+ while (*src)
+ {
+ if (newline)
+ {
+ if (*src == '#')
+ { // Skip the comment line!
+ while (*src && *src != '\n')
+ ++src;
+
+ if (*src == '\0')
+ break;
+
+ ++src;
+ continue;
+ }
+ if (*src == '\\'
+ && (src[1] == '#' || (src[1] == '\\' && src[2] == '#'))
+ ) {
+ ++src; // Unescape escaped char.
+ }
+ }
+
+ newline = (*src == '\n');
+ *dest++ = *src++;
+ }
+ *dest = '\0';
+}
+
+/*
+ * Writes a field of crash report to a file.
+ * Field must be writable.
+ */
+static void write_crash_report_field(FILE *fp, const map_crash_data_t &report,
+ const char *field, const char *description)
+{
+ const map_crash_data_t::const_iterator it = report.find(field);
+ if (it == report.end())
+ {
+ // exit silently, all fields are optional for now
+ //error_msg("Field %s not found", field);
+ return;
+ }
+
+ if (it->second[CD_TYPE] == CD_SYS)
+ {
+ error_msg("Cannot update field %s because it is a system value", field);
+ return;
+ }
+
+ fprintf(fp, "%s%s\n", FIELD_SEP, it->first.c_str());
+
+ fprintf(fp, "%s\n", description);
+ if (it->second[CD_EDITABLE] != CD_ISEDITABLE)
+ fprintf(fp, _("# This field is read only\n"));
+
+ char *escaped_content = escape(it->second[CD_CONTENT].c_str());
+ fprintf(fp, "%s\n", escaped_content);
+ free(escaped_content);
+}
+
+/*
+ * Saves the crash report to a file.
+ * Parameter 'fp' must be opened before write_crash_report is called.
+ * Returned value:
+ * If the report is successfully stored to the file, a zero value is returned.
+ * On failure, nonzero value is returned.
+ */
+static void write_crash_report(const map_crash_data_t &report, FILE *fp)
+{
+ fprintf(fp, "# Please check this report. Lines starting with '#' will be ignored.\n"
+ "# Lines starting with '%%----' separate fields, please do not delete them.\n\n");
+
+ write_crash_report_field(fp, report, FILENAME_COMMENT,
+ _("# Describe the circumstances of this crash below"));
+ write_crash_report_field(fp, report, FILENAME_REPRODUCE,
+ _("# How to reproduce the crash?"));
+ write_crash_report_field(fp, report, FILENAME_BACKTRACE,
+ _("# Backtrace\n# Check that it does not contain any sensitive data (passwords, etc.)"));
+ write_crash_report_field(fp, report, CD_DUPHASH, "# DUPHASH");
+ write_crash_report_field(fp, report, FILENAME_ARCHITECTURE, _("# Architecture"));
+ write_crash_report_field(fp, report, FILENAME_CMDLINE, _("# Command line"));
+ write_crash_report_field(fp, report, FILENAME_COMPONENT, _("# Component"));
+ write_crash_report_field(fp, report, FILENAME_COREDUMP, _("# Core dump"));
+ write_crash_report_field(fp, report, FILENAME_EXECUTABLE, _("# Executable"));
+ write_crash_report_field(fp, report, FILENAME_KERNEL, _("# Kernel version"));
+ write_crash_report_field(fp, report, FILENAME_PACKAGE, _("# Package"));
+ write_crash_report_field(fp, report, FILENAME_REASON, _("# Reason of crash"));
+ write_crash_report_field(fp, report, FILENAME_RELEASE, _("# Release string of the operating system"));
+}
+
+/*
+ * Updates appropriate field in the report from the text. The text can
+ * contain multiple fields.
+ * Returns:
+ * 0 if no change to the field was detected.
+ * 1 if the field was changed.
+ * Changes to read-only fields are ignored.
+ */
+static int read_crash_report_field(const char *text, map_crash_data_t &report,
+ const char *field)
+{
+ char separator[sizeof("\n" FIELD_SEP)-1 + strlen(field) + 2]; // 2 = '\n\0'
+ sprintf(separator, "\n%s%s\n", FIELD_SEP, field);
+ const char *textfield = strstr(text, separator);
+ if (!textfield)
+ return 0; // exit silently because all fields are optional
+
+ textfield += strlen(separator);
+ int length = 0;
+ const char *end = strstr(textfield, "\n" FIELD_SEP);
+ if (!end)
+ length = strlen(textfield);
+ else
+ length = end - textfield;
+
+ const map_crash_data_t::iterator it = report.find(field);
+ if (it == report.end())
+ {
+ error_msg("Field %s not found", field);
+ return 0;
+ }
+
+ if (it->second[CD_TYPE] == CD_SYS)
+ {
+ error_msg("Cannot update field %s because it is a system value", field);
+ return 0;
+ }
+
+ // Do not change noneditable fields.
+ if (it->second[CD_EDITABLE] != CD_ISEDITABLE)
+ return 0;
+
+ // Compare the old field contents with the new field contents.
+ char newvalue[length + 1];
+ strncpy(newvalue, textfield, length);
+ newvalue[length] = '\0';
+ trim(newvalue);
+
+ char oldvalue[it->second[CD_CONTENT].length() + 1];
+ strcpy(oldvalue, it->second[CD_CONTENT].c_str());
+ trim(oldvalue);
+
+ // Return if no change in the contents detected.
+ int cmp = strcmp(newvalue, oldvalue);
+ if (!cmp)
+ return 0;
+
+ it->second[CD_CONTENT].assign(newvalue);
+ return 1;
+}
+
+/*
+ * Updates the crash report 'report' from the text. The text must not contain
+ * any comments.
+ * Returns:
+ * 0 if no field was changed.
+ * 1 if any field was changed.
+ * Changes to read-only fields are ignored.
+ */
+static int read_crash_report(map_crash_data_t &report, const char *text)
+{
+ int result = 0;
+ result |= read_crash_report_field(text, report, FILENAME_COMMENT);
+ result |= read_crash_report_field(text, report, FILENAME_REPRODUCE);
+ result |= read_crash_report_field(text, report, FILENAME_BACKTRACE);
+ result |= read_crash_report_field(text, report, CD_DUPHASH);
+ result |= read_crash_report_field(text, report, FILENAME_ARCHITECTURE);
+ result |= read_crash_report_field(text, report, FILENAME_CMDLINE);
+ result |= read_crash_report_field(text, report, FILENAME_COMPONENT);
+ result |= read_crash_report_field(text, report, FILENAME_COREDUMP);
+ result |= read_crash_report_field(text, report, FILENAME_EXECUTABLE);
+ result |= read_crash_report_field(text, report, FILENAME_KERNEL);
+ result |= read_crash_report_field(text, report, FILENAME_PACKAGE);
+ result |= read_crash_report_field(text, report, FILENAME_REASON);
+ result |= read_crash_report_field(text, report, FILENAME_RELEASE);
+ return result;
+}
+
+/**
+ * Ensures that the fields needed for editor are present in the crash data.
+ * Fields: comments, how to reproduce.
+ */
+static void create_fields_for_editor(map_crash_data_t &crash_data)
+{
+ if (crash_data.find(FILENAME_COMMENT) == crash_data.end())
+ add_to_crash_data_ext(crash_data, FILENAME_COMMENT, CD_TXT, CD_ISEDITABLE, "");
+
+ if (crash_data.find(FILENAME_REPRODUCE) == crash_data.end())
+ add_to_crash_data_ext(crash_data, FILENAME_REPRODUCE, CD_TXT, CD_ISEDITABLE, "1. \n2. \n3. \n");
+}
+
+/**
+ * Runs external editor.
+ * Returns:
+ * 0 if the launch was successful
+ * 1 if it failed. The error reason is logged using error_msg()
+ */
+static int launch_editor(const char *path)
+{
+ const char *editor, *terminal;
+
+ editor = getenv("ABRT_EDITOR");
+ if (!editor)
+ editor = getenv("VISUAL");
+ if (!editor)
+ editor = getenv("EDITOR");
+
+ terminal = getenv("TERM");
+ if (!editor && (!terminal || strcmp(terminal, "dumb") == 0))
+ {
+ error_msg(_("Cannot run vi: $TERM, $VISUAL and $EDITOR are not set"));
+ return 1;
+ }
+
+ if (!editor)
+ editor = "vi";
+
+ char *args[3];
+ args[0] = (char*)editor;
+ args[1] = (char*)path;
+ args[2] = NULL;
+ run_command(args);
+
+ return 0;
+}
+
+/**
+ * Returns:
+ * 0 on success, crash data has been updated
+ * 2 on failure, unable to create, open, or close temporary file
+ * 3 on failure, cannot launch text editor
+ */
+static int run_report_editor(map_crash_data_t &cr)
+{
+ /* Open a temporary file and write the crash report to it. */
+ char filename[] = "/tmp/abrt-report.XXXXXX";
+ int fd = mkstemp(filename);
+ if (fd == -1) /* errno is set */
+ {
+ perror_msg("can't generate temporary file name");
+ return 2;
+ }
+
+ FILE *fp = fdopen(fd, "w");
+ if (!fp) /* errno is set */
+ {
+ perror_msg("can't open '%s' to save the crash report", filename);
+ return 2;
+ }
+
+ write_crash_report(cr, fp);
+
+ if (fclose(fp)) /* errno is set */
+ {
+ perror_msg("can't close '%s'", filename);
+ return 2;
+ }
+
+ // Start a text editor on the temporary file.
+ if (launch_editor(filename) != 0)
+ return 3; /* exit with error */
+
+ // Read the file back and update the report from the file.
+ fp = fopen(filename, "r");
+ if (!fp) /* errno is set */
+ {
+ perror_msg("can't open '%s' to read the crash report", filename);
+ return 2;
+ }
+
+ fseek(fp, 0, SEEK_END);
+ long size = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+
+ char *text = (char*)xmalloc(size + 1);
+ if (fread(text, 1, size, fp) != size)
+ {
+ error_msg("can't read '%s'", filename);
+ return 2;
+ }
+ text[size] = '\0';
+ if (fclose(fp) != 0) /* errno is set */
+ {
+ perror_msg("can't close '%s'", filename);
+ return 2;
+ }
+
+ // Delete the tempfile.
+ if (unlink(filename) == -1) /* errno is set */
+ {
+ perror_msg("can't unlink %s", filename);
+ }
+
+ remove_comments_and_unescape(text);
+ // Updates the crash report from the file text.
+ int report_changed = read_crash_report(cr, text);
+ free(text);
+ if (report_changed)
+ puts(_("\nThe report has been updated"));
+ else
+ puts(_("\nNo changes were detected in the report"));
+
+ return 0;
+}
+
+/**
+ * Asks user for a text response.
+ * @param question
+ * Question displayed to user.
+ * @param result
+ * Output array.
+ * @param result_size
+ * Maximum byte count to be written.
+ */
+static void read_from_stdin(const char *question, char *result, int result_size)
+{
+ assert(result_size > 1);
+ printf("%s", question);
+ fflush(NULL);
+ if (NULL == fgets(result, result_size, stdin))
+ result[0] = '\0';
+ // Remove the newline from the login.
+ strchrnul(result, '\n')[0] = '\0';
+}
+
+/** Splits a string into substrings using chosen delimiters.
+ * @param delim
+ * Specifies a set of characters that delimit the
+ * tokens in the parsed string
+ */
+static vector_string_t split(const char *s, const char delim)
+{
+ std::vector<std::string> elems;
+ while (1)
+ {
+ const char *end = strchrnul(s, delim);
+ elems.push_back(std::string(s, end - s));
+ if (*end == '\0')
+ break;
+ s = end + 1;
+ }
+ return elems;
+}
+
+/** Returns a list of enabled Reporter plugins, that are used to report
+ * a particular crash.
+ * @todo
+ * Very similar code is used in the GUI, and also in the Daemon.
+ * It should be shared.
+ */
+static vector_string_t get_enabled_reporters(map_crash_data_t &crash_data)
+{
+ vector_string_t result;
+
+ /* Get global daemon settings. Analyzer->Reporters mapping is stored there. */
+ map_map_string_t settings = call_GetSettings();
+ /* Reporters are separated by comma in the following map. */
+ map_string_t &analyzer_to_reporters = settings["AnalyzerActionsAndReporters"];
+
+ /* Get the analyzer from the crash. */
+ const char *analyzer = get_crash_data_item_content_or_NULL(crash_data, FILENAME_ANALYZER);
+ if (!analyzer)
+ return result; /* No analyzer found in the crash data. */
+
+ /* First try to find package name dependent analyzer.
+ * nvr = name-version-release
+ * TODO: Similar code is in MiddleWare.cpp. It should not be duplicated.
+ */
+ const char *package_nvr = get_crash_data_item_content_or_NULL(crash_data, FILENAME_PACKAGE);
+ if (!package_nvr)
+ return result; /* No package name found in the crash data. */
+ char * package_name = get_package_name_from_NVR_or_NULL(package_nvr);
+ // analyzer with package name (CCpp:xorg-x11-app) has higher priority
+ map_string_t::const_iterator reporters_iter;
+ if (package_name != NULL)
+ {
+ char* package_specific_analyzer = xasprintf("%s:%s", analyzer, package_name);
+ reporters_iter = analyzer_to_reporters.find(package_specific_analyzer);
+ free(package_specific_analyzer);
+ free(package_name);
+ }
+
+ if (analyzer_to_reporters.end() == reporters_iter)
+ {
+ reporters_iter = analyzer_to_reporters.find(analyzer);
+ if (analyzer_to_reporters.end() == reporters_iter)
+ return result; /* No reporters found for the analyzer. */
+ }
+
+ /* Reporters found, now parse the list. */
+ vector_string_t reporter_vec = split(reporters_iter->second.c_str(), ',');
+
+ // Get informations about all plugins.
+ map_map_string_t plugins = call_GetPluginsInfo();
+ // Check the configuration of each enabled Reporter plugin.
+ map_map_string_t::iterator it, itend = plugins.end();
+ for (it = plugins.begin(); it != itend; ++it)
+ {
+ // Skip disabled plugins.
+ if (string_to_bool(it->second["Enabled"].c_str()) != true)
+ continue;
+ // Skip nonReporter plugins.
+ if (0 != strcmp(it->second["Type"].c_str(), "Reporter"))
+ continue;
+ // Skip plugins not used in this particular crash.
+ if (reporter_vec.end() == std::find(reporter_vec.begin(), reporter_vec.end(), std::string(it->first)))
+ continue;
+ result.push_back(it->first);
+ }
+ return result;
+}
+
+/* Asks a [y/n] question on stdin/stdout.
+ * Returns true if the answer is yes, false otherwise.
+ */
+static bool ask_yesno(const char *question)
+{
+ printf(question);
+ fflush(NULL);
+ char answer[16] = "n";
+ fgets(answer, sizeof(answer), stdin);
+ /* TODO: localize 'y' */
+ return ((answer[0] | 0x20) == 'y');
+}
+
+/* Returns true if echo has been changed from another state. */
+static bool set_echo(bool enabled)
+{
+ if (isatty(STDIN_FILENO) == 0)
+ {
+ /* Clean errno, which is set by isatty. */
+ errno = 0;
+ return false;
+ }
+
+ struct termios t;
+ if (tcgetattr(STDIN_FILENO, &t) < 0)
+ return false;
+
+ /* No change needed. */
+ if ((bool)(t.c_lflag & ECHO) == enabled)
+ return false;
+
+ if (enabled)
+ t.c_lflag |= ECHO;
+ else
+ t.c_lflag &= ~ECHO;
+
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &t) < 0)
+ perror_msg_and_die("tcsetattr");
+
+ return true;
+}
+
+/**
+ * Gets reporter plugin settings.
+ * @param reporters
+ * List of reporter names. Settings of these reporters are handled.
+ * @param settings
+ * A structure filled with reporter plugin settings.
+ * @param ask_user
+ * If it's set to true and some reporter plugin settings are found to be missing
+ * (like login name or password), user is asked to provide the missing parts.
+ */
+static void get_reporter_plugin_settings(const vector_string_t& reporters,
+ map_map_string_t &settings,
+ bool ask_user)
+{
+ /* First of all, load system-wide report plugin settings. */
+ for (vector_string_t::const_iterator it = reporters.begin(); it != reporters.end(); ++it)
+ {
+ map_string_t single_plugin_settings = call_GetPluginSettings(it->c_str());
+ // Copy the received settings as defaults.
+ // Plugins won't work without it, if some value is missing
+ // they use their default values for all fields.
+ settings[it->c_str()] = single_plugin_settings;
+ }
+
+ /* Second, load user-specific settings, which override
+ the system-wide settings. */
+ struct passwd* pw = getpwuid(geteuid());
+ const char* homedir = pw ? pw->pw_dir : NULL;
+ if (homedir)
+ {
+ map_map_string_t::const_iterator itend = settings.end();
+ for (map_map_string_t::iterator it = settings.begin(); it != itend; ++it)
+ {
+ map_string_t single_plugin_settings;
+ std::string path = std::string(homedir) + "/.abrt/"
+ + it->first + "."PLUGINS_CONF_EXTENSION;
+ /* Load plugin config in the home dir. Do not skip lines with empty value (but containing a "key="),
+ because user may want to override password from /etc/abrt/plugins/\*.conf, but he prefers to
+ enter it every time he reports. */
+ bool success = LoadPluginSettings(path.c_str(), single_plugin_settings, false);
+ if (!success)
+ continue;
+ // Merge user's plugin settings into already loaded settings.
+ map_string_t::const_iterator valit, valitend = single_plugin_settings.end();
+ for (valit = single_plugin_settings.begin(); valit != valitend; ++valit)
+ it->second[valit->first] = valit->second;
+ }
+ }
+
+ if (!ask_user)
+ return;
+
+ /* Third, check if a login or password is missing, and ask for it. */
+ map_map_string_t::const_iterator itend = settings.end();
+ for (map_map_string_t::iterator it = settings.begin(); it != itend; ++it)
+ {
+ map_string_t &single_plugin_settings = it->second;
+ // Login information is missing.
+ bool loginMissing = single_plugin_settings.find("Login") != single_plugin_settings.end()
+ && 0 == strcmp(single_plugin_settings["Login"].c_str(), "");
+ bool passwordMissing = single_plugin_settings.find("Password") != single_plugin_settings.end()
+ && 0 == strcmp(single_plugin_settings["Password"].c_str(), "");
+ if (!loginMissing && !passwordMissing)
+ continue;
+
+ // Read the missing information and push it to plugin settings.
+ printf(_("Wrong settings were detected for plugin %s\n"), it->first.c_str());
+ char result[64];
+ if (loginMissing)
+ {
+ read_from_stdin(_("Enter your login: "), result, 64);
+ single_plugin_settings["Login"] = std::string(result);
+ }
+ if (passwordMissing)
+ {
+ bool changed = set_echo(false);
+ read_from_stdin(_("Enter your password: "), result, 64);
+ if (changed)
+ set_echo(true);
+
+ // Newline was not added by pressing Enter because ECHO was disabled, so add it now.
+ puts("");
+ single_plugin_settings["Password"] = std::string(result);
+ }
+ }
+}
+
+/* Reports the crash with corresponding crash_id over DBus. */
+int report(const char *crash_id, int flags)
+{
+ int old_logmode = logmode;
+ if (flags & CLI_REPORT_SILENT_IF_NOT_FOUND)
+ logmode = 0;
+ // Ask for an initial report.
+ map_crash_data_t cr = call_CreateReport(crash_id);
+ logmode = old_logmode;
+ if (cr.size() == 0)
+ {
+ return -1;
+ }
+
+ /* Open text editor and give a chance to review the backtrace etc. */
+ if (!(flags & CLI_REPORT_BATCH))
+ {
+ create_fields_for_editor(cr);
+ int result = run_report_editor(cr);
+ if (result != 0)
+ return result;
+ }
+
+ /* Get enabled reporters associated with this particular crash. */
+ vector_string_t reporters = get_enabled_reporters(cr);
+
+ int errors = 0;
+ int plugins = 0;
+ if (flags & CLI_REPORT_BATCH)
+ {
+ map_map_string_t reporters_settings; /* to be filled on the next line */
+ get_reporter_plugin_settings(reporters, reporters_settings, false);
+
+ puts(_("Reporting..."));
+ report_status_t r = call_Report(cr, reporters, reporters_settings);
+ report_status_t::iterator it = r.begin();
+ while (it != r.end())
+ {
+ vector_string_t &v = it->second;
+ printf("%s: %s\n", it->first.c_str(), v[REPORT_STATUS_IDX_MSG].c_str());
+ plugins++;
+ if (v[REPORT_STATUS_IDX_FLAG] == "0")
+ errors++;
+ it++;
+ }
+ }
+ else
+ {
+ /* For every reporter, ask if user really wants to report using it. */
+ for (vector_string_t::const_iterator it = reporters.begin(); it != reporters.end(); ++it)
+ {
+ char question[255];
+ snprintf(question, 255, _("Report using %s? [y/N]: "), it->c_str());
+ if (!ask_yesno(question))
+ {
+ puts(_("Skipping..."));
+ continue;
+ }
+
+ vector_string_t cur_reporter(1, *it);
+ map_map_string_t reporters_settings; /* to be filled on the next line */
+ get_reporter_plugin_settings(cur_reporter, reporters_settings, true);
+ report_status_t r = call_Report(cr, cur_reporter, reporters_settings);
+ assert(r.size() == 1); /* one reporter --> one report status */
+ vector_string_t &v = r.begin()->second;
+ printf("%s: %s\n", r.begin()->first.c_str(), v[REPORT_STATUS_IDX_MSG].c_str());
+ plugins++;
+ if (v[REPORT_STATUS_IDX_FLAG] == "0")
+ errors++;
+ }
+ }
+
+ printf(_("Crash reported via %d plugins (%d errors)\n"), plugins, errors);
+ return errors != 0;
+}
diff --git a/src/cli/report.h b/src/cli/report.h
new file mode 100644
index 00000000..8a851b8e
--- /dev/null
+++ b/src/cli/report.h
@@ -0,0 +1,28 @@
+/*
+ Copyright (C) 2009 RedHat inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#ifndef ABRT_CLI_REPORT_H
+#define ABRT_CLI_REPORT_H
+
+/* Reports the crash with corresponding uuid over DBus. */
+enum {
+ CLI_REPORT_BATCH = 1 << 0,
+ CLI_REPORT_SILENT_IF_NOT_FOUND = 1 << 1,
+};
+int report(const char *uuid, int flags);
+
+#endif
diff --git a/src/cli/run-command.cpp b/src/cli/run-command.cpp
new file mode 100644
index 00000000..1f6836d6
--- /dev/null
+++ b/src/cli/run-command.cpp
@@ -0,0 +1,73 @@
+/*
+ Copyright (C) 2009 RedHat inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#include "run-command.h"
+#include "abrtlib.h"
+
+/*
+ Inspired by git code.
+ http://git.kernel.org/?p=git/git.git;a=blob;f=run-command.c;hb=HEAD
+*/
+
+static pid_t start_command(char **argv)
+{
+ pid_t pid = vfork();
+ if (pid < 0)
+ {
+ perror_msg_and_die("vfork");
+ }
+ if (pid == 0)
+ { // new process
+ execvp(argv[0], argv);
+ exit(127);
+ }
+ return pid;
+}
+
+static int finish_command(pid_t pid, char **argv)
+{
+ pid_t waiting;
+ int status;
+ while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
+ continue;
+ if (waiting < 0)
+ perror_msg_and_die("waitpid");
+
+ int code = -1;
+ if (WIFSIGNALED(status))
+ {
+ code = WTERMSIG(status);
+ error_msg("'%s' killed by signal %d", argv[0], code);
+ code += 128; /* shells use this convention for deaths by signal */
+ }
+ else /* if (WIFEXITED(status)) */
+ {
+ code = WEXITSTATUS(status);
+ if (code == 127)
+ {
+ error_msg_and_die("Can't run '%s'", argv[0]);
+ }
+ }
+
+ return code;
+}
+
+int run_command(char **argv)
+{
+ pid_t pid = start_command(argv);
+ return finish_command(pid, argv);
+}
diff --git a/src/cli/run-command.h b/src/cli/run-command.h
new file mode 100644
index 00000000..71391579
--- /dev/null
+++ b/src/cli/run-command.h
@@ -0,0 +1,23 @@
+/*
+ Copyright (C) 2009 RedHat inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#ifndef ABRT_CLI_RUN_COMMAND_H
+#define ABRT_CLI_RUN_COMMAND_H
+
+int run_command(char **argv);
+
+#endif