diff options
Diffstat (limited to 'src/cli/CLI.cpp')
-rw-r--r-- | src/cli/CLI.cpp | 436 |
1 files changed, 436 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; +} |