diff options
author | Nikola Pajkovsky <npajkovs@redhat.com> | 2010-08-10 10:21:25 +0200 |
---|---|---|
committer | Nikola Pajkovsky <npajkovs@redhat.com> | 2010-08-10 10:21:56 +0200 |
commit | 83a6ce9ad4b1828e163dc7172ef603201b748473 (patch) | |
tree | 9d0580eba6c01cb5964655df42bafab9de91329b /src/cli | |
parent | e84ab7783d05eb7b5f1b55ab44e7c23c85e50516 (diff) | |
download | abrt-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.cpp | 436 | ||||
-rw-r--r-- | src/cli/Makefile.am | 28 | ||||
-rw-r--r-- | src/cli/abrt-cli.1 | 58 | ||||
-rw-r--r-- | src/cli/abrt-cli.bash | 50 | ||||
-rw-r--r-- | src/cli/dbus.cpp | 282 | ||||
-rw-r--r-- | src/cli/dbus.h | 68 | ||||
-rw-r--r-- | src/cli/report.cpp | 743 | ||||
-rw-r--r-- | src/cli/report.h | 28 | ||||
-rw-r--r-- | src/cli/run-command.cpp | 73 | ||||
-rw-r--r-- | src/cli/run-command.h | 23 |
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 |