From d9cd2ab6ef3ebe4bb7883690efeb1b8dcb4c3c65 Mon Sep 17 00:00:00 2001 From: Nikola Pajkovsky Date: Fri, 11 Mar 2011 13:18:03 +0100 Subject: CLI.cpp -> cli.c Signed-off-by: Nikola Pajkovsky --- src/cli/cli.c | 432 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 432 insertions(+) create mode 100644 src/cli/cli.c (limited to 'src/cli/cli.c') diff --git a/src/cli/cli.c b/src/cli/cli.c new file mode 100644 index 00000000..49c08279 --- /dev/null +++ b/src/cli/cli.c @@ -0,0 +1,432 @@ +/* + 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. +*/ +#if HAVE_LOCALE_H +# include +#endif +#include +#include "abrtlib.h" +#include "abrt_dbus.h" +#include "report.h" + +/** 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, sizeof(timeloc), "%c", localtime(&time)); + if (!success) + error_msg_and_die("Error while converting time '%s' to string", timestr); + return xstrdup(timeloc); +} + +static crash_data_t *FillCrashInfo(const char *dump_dir_name) +{ + int sv_logmode = logmode; + logmode = 0; /* suppress EPERM/EACCES errors in opendir */ + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ DD_OPEN_READONLY); + logmode = sv_logmode; + + if (!dd) + return NULL; + + crash_data_t *crash_data = create_crash_data_from_dump_dir(dd); + dd_close(dd); + add_to_crash_data_ext(crash_data, CD_DUMPDIR, dump_dir_name, CD_FLAG_TXT + CD_FLAG_ISNOTEDITABLE); + + return crash_data; +} + +static void GetCrashInfos(vector_of_crash_data_t *retval, const char *dir_name) +{ + VERB1 log("Loading dumps from '%s'", dir_name); + + DIR *dir = opendir(dir_name); + if (dir != NULL) + { + struct dirent *dent; + while ((dent = readdir(dir)) != NULL) + { + if (dot_or_dotdot(dent->d_name)) + continue; /* skip "." and ".." */ + + char *dump_dir_name = concat_path_file(dir_name, dent->d_name); + + struct stat statbuf; + if (stat(dump_dir_name, &statbuf) == 0 + && S_ISDIR(statbuf.st_mode) + ) { + crash_data_t *crash_data = FillCrashInfo(dump_dir_name); + if (crash_data) + g_ptr_array_add(retval, crash_data); + } + free(dump_dir_name); + } + closedir(dir); + } +} + +/** Prints basic information about a crash to stdout. */ +static void print_crash(crash_data_t *crash_data) +{ + /* Create a localized string from crash time. */ + const char *timestr = get_crash_item_content_or_die(crash_data, FILENAME_TIME); + char *timeloc = localize_crash_time(timestr); + + printf(_("\tCrash dump : %s\n" + "\tUID : %s\n" + "\tPackage : %s\n" + "\tExecutable : %s\n" + "\tCrash Time : %s\n" + "\tCrash Count: %s\n"), + get_crash_item_content_or_NULL(crash_data, CD_DUMPDIR), + get_crash_item_content_or_NULL(crash_data, FILENAME_UID), + get_crash_item_content_or_NULL(crash_data, FILENAME_PACKAGE), + get_crash_item_content_or_NULL(crash_data, FILENAME_EXECUTABLE), + timeloc, + get_crash_item_content_or_NULL(crash_data, FILENAME_COUNT) + ); + + free(timeloc); + + /* Print the hostname if it's available. */ + const char *hostname = get_crash_item_content_or_NULL(crash_data, 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(vector_of_crash_data_t *crash_list, bool include_reported) +{ + for (unsigned i = 0; i < crash_list->len; ++i) + { + crash_data_t *crash = get_crash_data(crash_list, i); + if (!include_reported) + { + const char *msg = get_crash_item_content_or_NULL(crash, FILENAME_MESSAGE); + if (!msg || !msg[0]) + continue; + } + + printf("%u.\n", i); + print_crash(crash); + } +} + +/** + * Prints full information about a crash + */ +static void print_crash_info(crash_data_t *crash_data, bool show_backtrace) +{ + const char *timestr = get_crash_item_content_or_die(crash_data, FILENAME_TIME); + char *timeloc = localize_crash_time(timestr); + + printf(_("Dump directory: %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" + "Reason: %s\n"), + get_crash_item_content_or_die(crash_data, CD_DUMPDIR), + timeloc, + get_crash_item_content_or_die(crash_data, FILENAME_ANALYZER), + get_crash_item_content_or_die(crash_data, FILENAME_COMPONENT), + get_crash_item_content_or_die(crash_data, FILENAME_PACKAGE), + get_crash_item_content_or_die(crash_data, FILENAME_CMDLINE), + get_crash_item_content_or_die(crash_data, FILENAME_EXECUTABLE), + get_crash_item_content_or_die(crash_data, FILENAME_OS_RELEASE), + get_crash_item_content_or_die(crash_data, FILENAME_KERNEL), + get_crash_item_content_or_die(crash_data, FILENAME_REASON) + ); + + free(timeloc); + + /* Print optional fields only if they are available */ + + /* Coredump is not present in kerneloopses and Python exceptions. */ + const char *coredump = get_crash_item_content_or_NULL(crash_data, FILENAME_COREDUMP); + if (coredump) + printf(_("Coredump file: %s\n"), coredump); + + const char *rating = get_crash_item_content_or_NULL(crash_data, FILENAME_RATING); + if (rating) + printf(_("Rating: %s\n"), rating); + + /* Crash function is not present in kerneloopses, and before the full report is created.*/ + const char *crash_function = get_crash_item_content_or_NULL(crash_data, FILENAME_CRASH_FUNCTION); + if (crash_function) + printf(_("Crash function: %s\n"), crash_function); + + const char *hostname = get_crash_item_content_or_NULL(crash_data, FILENAME_HOSTNAME); + if (hostname) + printf(_("Hostname: %s\n"), hostname); + + const char *comment = get_crash_item_content_or_NULL(crash_data, FILENAME_COMMENT); + if (comment) + printf(_("\nComment:\n%s\n"), comment); + + if (show_backtrace) + { + const char *backtrace = get_crash_item_content_or_NULL(crash_data, FILENAME_BACKTRACE); + if (backtrace) + printf(_("\nBacktrace:\n%s\n"), backtrace); + } +} + +/* 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, '?' }, + { "verbose" , no_argument, NULL, 'v' }, + { "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 print_usage_and_die(char *argv0) +{ + const char *name = progname(argv0); + printf("%s "VERSION"\n\n", name); + + /* Message has embedded tabs. */ + printf(_( + "Usage: %s -l[f] [-D BASE_DIR]...]\n" + " or: %s -r[y] CRASH_DIR\n" + " or: %s -i[b] CRASH_DIR\n" + " or: %s -d CRASH_DIR\n" + "\n" + " -l, --list List not yet reported crashes\n" + " -f, --full List all crashes\n" + " -D BASE_DIR Directory to list crashes from\n" + " (default: -D $HOME/.abrt/spool -D %s)\n" + "\n" + " -r, --report Send a report about CRASH_DIR\n" + " -y, --always ...without editing and asking\n" + " -i, --info Print detailed information about CRASH_DIR\n" + " -b, --backtrace ...including backtrace\n" + " -d, --delete Remove CRASH_DIR\n" + "\n" + " -V, --version Display version and exit\n" + " -v, --verbose Be verbose\n" + ), + name, name, name, name, + DEBUG_DUMPS_DIR + ); + exit(1); +} + +int main(int argc, char** argv) +{ + GList *D_list = NULL; + char *dump_dir_name = 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) + { + /* Do not use colons, arguments are handled after parsing all options. */ + int c = getopt_long(argc, argv, "?Vvrdlfyib", longopts, NULL); + +#define SET_OP(newop) \ + do { \ + if (op != -1 && op != newop) \ + error_msg_and_die(_("You must specify exactly one operation")); \ + op = newop; \ + } while (0) + + switch (c) + { + case -1: goto end_of_arg_parsing; + 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 'v': g_verbose++; break; + case 'D': + D_list = g_list_append(D_list, optarg); + break; + case 'V': + printf("%s "VERSION"\n", progname(argv[0])); + return 0; + case '?': + default: /* some error */ + print_usage_and_die(argv[0]); /* exits app */ + } +#undef SET_OP + } + end_of_arg_parsing: ; + + if (!D_list) + { + char *home = getenv("HOME"); + if (home) + D_list = g_list_append(D_list, concat_path_file(home, ".abrt/spool")); + D_list = g_list_append(D_list, (void*)DEBUG_DUMPS_DIR); + } + + /* Handle option arguments. */ + argc -= optind; + switch (argc) + { + case 0: + if (op == OPT_REPORT || op == OPT_DELETE || op == OPT_INFO) + print_usage_and_die(argv[0]); + break; + case 1: + if (op != OPT_REPORT && op != OPT_DELETE && op != OPT_INFO) + print_usage_and_die(argv[0]); + dump_dir_name = argv[optind]; + break; + default: + print_usage_and_die(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) + { + print_usage_and_die(argv[0]); + } + + /* Do the selected operation. */ + int exitcode = 0; + switch (op) + { + case OPT_GET_LIST: + { + vector_of_crash_data_t *ci = new_vector_of_crash_data(); + while (D_list) + { + char *dir = (char *)D_list->data; + GetCrashInfos(ci, dir); + D_list = g_list_remove(D_list, dir); + } + print_crash_list(ci, full); + free_vector_of_crash_data(ci); + break; + } + case OPT_REPORT: + { + struct dump_dir *dd = dd_opendir(dump_dir_name, DD_OPEN_READONLY); + if (!dd) + break; + int readonly = !dd->locked; + dd_close(dd); + if (readonly) + { + log("'%s' is not writable", dump_dir_name); + /* D_list can't be NULL here */ + struct dump_dir *dd_copy = steal_directory((char *)D_list->data, dump_dir_name); + if (dd_copy) + { + delete_dump_dir_possibly_using_abrtd(dump_dir_name); + dump_dir_name = xstrdup(dd_copy->dd_dirname); + dd_close(dd_copy); + } + } + + exitcode = report(dump_dir_name, (always ? CLI_REPORT_BATCH : 0)); + if (exitcode == -1) + error_msg_and_die("Crash '%s' not found", dump_dir_name); + break; + } + case OPT_DELETE: + { + exitcode = delete_dump_dir_possibly_using_abrtd(dump_dir_name); + break; + } + case OPT_INFO: + { + if (run_analyze_event(dump_dir_name) != 0) + return 1; + + /* Load crash_data from (possibly updated by analyze) dump dir */ + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + return -1; + crash_data_t *crash_data = create_crash_data_from_dump_dir(dd); + dd_close(dd); + add_to_crash_data_ext(crash_data, CD_DUMPDIR, dump_dir_name, + CD_FLAG_TXT + CD_FLAG_ISNOTEDITABLE); + + print_crash_info(crash_data, backtrace); + free_crash_data(crash_data); + + break; + } + } + + return exitcode; +} -- cgit