diff options
Diffstat (limited to 'src')
155 files changed, 8692 insertions, 6390 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index e42f1aec..c6d3abd9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1 +1 @@ -SUBDIRS = include lib hooks btparser daemon applet gui cli plugins retrace +SUBDIRS = include lib report-python hooks btparser daemon applet gui gui-gtk cli plugins gui-wizard-gtk retrace diff --git a/src/applet/Makefile.am b/src/applet/Makefile.am index 4207e818..cd4a643f 100644 --- a/src/applet/Makefile.am +++ b/src/applet/Makefile.am @@ -1,14 +1,12 @@ bin_PROGRAMS = abrt-applet +#test-report abrt_applet_SOURCES = \ - Applet.cpp \ + applet.c \ applet_gtk.h applet_gtk.c abrt_applet_CPPFLAGS = \ - -Wall -Werror \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ - -I/usr/include/glib-2.0 \ - -I/usr/lib/glib-2.0/include \ -DBIN_DIR=\"$(bindir)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ -DCONF_DIR=\"$(CONF_DIR)\" \ @@ -18,11 +16,14 @@ abrt_applet_CPPFLAGS = \ -DICON_DIR=\"${datadir}/abrt/icons/hicolor/48x48/status\" \ $(GTK_CFLAGS) \ $(DBUS_CFLAGS) \ - -D_GNU_SOURCE + -D_GNU_SOURCE \ + -Wall -Werror +# -I/usr/include/glib-2.0 +# -I/usr/lib/glib-2.0/include # $(LIBNOTIFY_CFLAGS) # $(DBUS_GLIB_CFLAGS) abrt_applet_LDADD = \ - ../lib/libabrt.la \ + ../lib/libreport.la \ ../lib/libabrt_dbus.la \ -lglib-2.0 \ -lgthread-2.0 \ @@ -30,6 +31,16 @@ abrt_applet_LDADD = \ $(LIBNOTIFY_LIBS) \ $(GTK_LIBS) +#test_report_SOURCES = \ +# test_report.c +#test_report_CPPFLAGS = \ +# -I$(srcdir)/../include/report -I$(srcdir)/../include \ +# $(GLIB_CFLAGS) \ +# -D_GNU_SOURCE \ +# -Wall -Werror +#test_report_LDADD = \ +# ../lib/libreport.la + DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ @INTLTOOL_DESKTOP_RULE@ diff --git a/src/applet/Applet.cpp b/src/applet/applet.c index 4ce497cf..1a2bf5c0 100644 --- a/src/applet/Applet.cpp +++ b/src/applet/applet.c @@ -24,7 +24,6 @@ #include <dbus/dbus-glib-lowlevel.h> #include "abrtlib.h" #include "abrt_dbus.h" -#include "dbus_common.h" #include "applet_gtk.h" @@ -38,8 +37,8 @@ static void Crash(DBusMessage* signal) dbus_message_iter_init(signal, &in_iter); /* 1st param: package */ - const char* package_name; - r = load_val(&in_iter, package_name); + const char* package_name = NULL; + r = load_charp(&in_iter, &package_name); /* 2nd param: crash_id */ if (r != ABRT_DBUS_MORE_FIELDS) @@ -48,7 +47,7 @@ static void Crash(DBusMessage* signal) return; } const char* crash_id = NULL; - r = load_val(&in_iter, crash_id); + r = load_charp(&in_iter, &crash_id); /* 3rd param: dir */ //dir parameter is not used for now, use is planned in the future @@ -58,13 +57,13 @@ static void Crash(DBusMessage* signal) return; } const char* dir = NULL; - r = load_val(&in_iter, dir); + r = load_charp(&in_iter, &dir); /* Optional 4th param: uid */ const char* uid_str = NULL; if (r == ABRT_DBUS_MORE_FIELDS) { - r = load_val(&in_iter, uid_str); + r = load_charp(&in_iter, &uid_str); } if (r != ABRT_DBUS_LAST_FIELD) { @@ -113,13 +112,13 @@ static void Crash(DBusMessage* signal) show_crash_notification(applet, crash_id, message, package_name); } -static void QuotaExceed(DBusMessage* signal) +static void QuotaExceeded(DBusMessage* signal) { int r; DBusMessageIter in_iter; dbus_message_iter_init(signal, &in_iter); - const char* str; - r = load_val(&in_iter, str); + const char* str = NULL; + r = load_charp(&in_iter, &str); if (r != ABRT_DBUS_LAST_FIELD) { error_msg("dbus signal %s: parameter type mismatch", __func__); @@ -137,8 +136,8 @@ static void NameOwnerChanged(DBusMessage* signal) int r; DBusMessageIter in_iter; dbus_message_iter_init(signal, &in_iter); - const char* name; - r = load_val(&in_iter, name); + const char* name = NULL; + r = load_charp(&in_iter, &name); if (r != ABRT_DBUS_MORE_FIELDS) { error_msg("dbus signal %s: parameter type mismatch", __func__); @@ -149,15 +148,15 @@ static void NameOwnerChanged(DBusMessage* signal) if (strcmp(name, "com.redhat.abrt") != 0) return; - const char* old_owner; - r = load_val(&in_iter, old_owner); + const char* old_owner = NULL; + r = load_charp(&in_iter, &old_owner); if (r != ABRT_DBUS_MORE_FIELDS) { error_msg("dbus signal %s: parameter type mismatch", __func__); return; } - const char* new_owner; - r = load_val(&in_iter, new_owner); + const char* new_owner = NULL; + r = load_charp(&in_iter, &new_owner); if (r != ABRT_DBUS_LAST_FIELD) { error_msg("dbus signal %s: parameter type mismatch", __func__); @@ -186,8 +185,8 @@ static DBusHandlerResult handle_message(DBusConnection* conn, DBusMessage* msg, NameOwnerChanged(msg); else if (strcmp(member, "Crash") == 0) Crash(msg); - else if (strcmp(member, "QuotaExceed") == 0) - QuotaExceed(msg); + else if (strcmp(member, "QuotaExceeded") == 0) + QuotaExceeded(msg); return DBUS_HANDLER_RESULT_HANDLED; } @@ -234,7 +233,7 @@ int main(int argc, char** argv) error_msg_and_die( "Usage: abrt-applet [-v]\n" "\nOptions:" - "\n\t-v\tVerbose" + "\n\t-v\tBe verbose" ); } } diff --git a/src/applet/test_report.c b/src/applet/test_report.c new file mode 100644 index 00000000..f5e79d92 --- /dev/null +++ b/src/applet/test_report.c @@ -0,0 +1,62 @@ +/* + Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) + 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. +*/ +#if HAVE_LOCALE_H +# include <locale.h> +#endif +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include "crash_data.h" +#include "dump_dir.h" +#include "run_event.h" + +static char *do_log(char *log_line, void *param) +{ + printf("%s\n", log_line); + return log_line; +} + +int main(int argc, char** argv) +{ + crash_data_t *crash_data = new_crash_data(); + + add_to_crash_data(crash_data, "analyzer", "wow"); + const char *event = "report"; + + struct dump_dir *dd = create_dump_dir_from_crash_data(crash_data, "/tmp"); + free_crash_data(crash_data); + if (!dd) + return 1; + char *dir_name = strdup(dd->dd_dirname); + dd_close(dd); + + printf("Temp dump dir: '%s'\n", dir_name); + + struct run_event_state *run_state = new_run_event_state(); + run_state->logging_callback = do_log; + int r = run_event_on_dir_name(run_state, dir_name, event); + if (r == 0 && run_state->children_count == 0) + printf("No actions are found for event '%s'\n", event); + free_run_event_state(run_state); + +// delete_dump_dir(dir_name); + free(dir_name); + + return 0; +} diff --git a/src/btparser/Makefile.am b/src/btparser/Makefile.am index 349483a8..9f64f826 100644 --- a/src/btparser/Makefile.am +++ b/src/btparser/Makefile.am @@ -15,7 +15,7 @@ libbtparser_la_SOURCES = \ utils.h utils.c libbtparser_la_CFLAGS = -Wall -Werror -D_GNU_SOURCE -I../lib libbtparser_la_LDFLAGS = -version-info 1:1:0 -libbtparser_la_LIBADD = ../lib/libabrt.la +libbtparser_la_LIBADD = ../lib/libreport.la # From http://www.seul.org/docs/autotut/ # Version consists 3 numbers: CURRENT, REVISION, AGE. diff --git a/src/btparser/normalize_glibc.c b/src/btparser/normalize_glibc.c index ea40ba9d..3d0bca2b 100644 --- a/src/btparser/normalize_glibc.c +++ b/src/btparser/normalize_glibc.c @@ -71,9 +71,12 @@ btp_normalize_glibc_thread(struct btp_thread *thread) /* Normalize frame names. */ #define NORMALIZE_ARCH_SPECIFIC(func) \ - if (btp_frame_calls_func_in_file2(frame, "__" func "_sse2", func ".S", "libc.so") || \ - btp_frame_calls_func_in_file2(frame, "__" func "_ssse3", func ".S", "libc.so") || \ - btp_frame_calls_func_in_file2(frame, "__" func "_ia32", func ".S", "libc.so")) \ + if (btp_frame_calls_func_in_file3(frame, "__" func "_sse2", func, "/sysdeps/", "libc.so") || \ + btp_frame_calls_func_in_file3(frame, "__" func "_sse2_bsf", func, "/sysdeps/", "libc.so") || \ + btp_frame_calls_func_in_file3(frame, "__" func "_ssse3", func, "/sysdeps/", "libc.so") /* ssse3, not sse3! */ || \ + btp_frame_calls_func_in_file3(frame, "__" func "_ssse3_rep", func, "/sysdeps/", "libc.so") || \ + btp_frame_calls_func_in_file3(frame, "__" func "_sse42", func, "/sysdeps/", "libc.so") || \ + btp_frame_calls_func_in_file3(frame, "__" func "_ia32", func, "/sysdeps", "libc.so")) \ { \ strcpy(frame->function_name, func); \ } @@ -81,8 +84,11 @@ btp_normalize_glibc_thread(struct btp_thread *thread) NORMALIZE_ARCH_SPECIFIC("memchr"); NORMALIZE_ARCH_SPECIFIC("memcmp"); NORMALIZE_ARCH_SPECIFIC("memcpy"); + NORMALIZE_ARCH_SPECIFIC("memmove"); NORMALIZE_ARCH_SPECIFIC("memset"); NORMALIZE_ARCH_SPECIFIC("rawmemchr"); + NORMALIZE_ARCH_SPECIFIC("strcasecmp"); + NORMALIZE_ARCH_SPECIFIC("strcasecmp_l"); NORMALIZE_ARCH_SPECIFIC("strcat"); NORMALIZE_ARCH_SPECIFIC("strchr"); NORMALIZE_ARCH_SPECIFIC("strchrnul"); @@ -91,9 +97,11 @@ btp_normalize_glibc_thread(struct btp_thread *thread) NORMALIZE_ARCH_SPECIFIC("strcspn"); NORMALIZE_ARCH_SPECIFIC("strlen"); NORMALIZE_ARCH_SPECIFIC("strncmp"); + NORMALIZE_ARCH_SPECIFIC("strncpy"); NORMALIZE_ARCH_SPECIFIC("strpbrk"); NORMALIZE_ARCH_SPECIFIC("strrchr"); NORMALIZE_ARCH_SPECIFIC("strspn"); + NORMALIZE_ARCH_SPECIFIC("strstr"); NORMALIZE_ARCH_SPECIFIC("strtok"); /* Remove frames which are not a cause of the crash. */ @@ -106,7 +114,10 @@ btp_normalize_glibc_thread(struct btp_thread *thread) btp_frame_calls_func(frame, "___vsnprintf_chk") || btp_frame_calls_func(frame, "__snprintf_chk") || btp_frame_calls_func(frame, "___snprintf_chk") || - btp_frame_calls_func(frame, "__vasprintf_chk"); + btp_frame_calls_func(frame, "__vasprintf_chk") || + btp_frame_calls_func_in_file(frame, "malloc_consolidate", "malloc.c") || + btp_frame_calls_func_in_file(frame, "_int_malloc", "malloc.c") || + btp_frame_calls_func_in_file(frame, "__libc_calloc", "malloc.c"); if (removable) { bool success = btp_thread_remove_frames_above(thread, frame); diff --git a/src/btparser/thread.c b/src/btparser/thread.c index af480eb3..05f154d6 100644 --- a/src/btparser/thread.c +++ b/src/btparser/thread.c @@ -293,7 +293,7 @@ btp_thread_parse(char **input, return NULL; } - /* Skip spaces after the thread number and before the parenthesis. */ + /* Skip spaces after the thread number and before parentheses. */ spaces = btp_skip_char_sequence(&local_input, ' '); location->column += spaces; if (0 == spaces) @@ -303,31 +303,49 @@ btp_thread_parse(char **input, return NULL; } - /* Read the Thread keyword in parenthesis, which is mandatory. */ + /* Read the LWP section in parentheses, optional. */ + location->column += btp_thread_skip_lwp(&local_input); + + /* Read the Thread keyword in parentheses, optional. */ chars = btp_skip_string(&local_input, "(Thread "); location->column += chars; - if (0 == chars) + if (0 != chars) { - location->message = "Thread keyword in the parenthesis expected in the form '(Thread '."; - btp_thread_free(imthread); - return NULL; - } + /* Read the thread identification number. It can be either in + * decimal or hexadecimal form. + * Examples: + * "Thread 10 (Thread 2476):" + * "Thread 8 (Thread 0xb07fdb70 (LWP 6357)):" + */ + digits = btp_skip_hexadecimal_number(&local_input); + if (0 == digits) + digits = btp_skip_unsigned_integer(&local_input); + location->column += digits; + if (0 == digits) + { + location->message = "The thread identification number expected."; + btp_thread_free(imthread); + return NULL; + } - /* Read the thread identification number. */ - digits = btp_skip_unsigned_integer(&local_input); - location->column += digits; - if (0 == digits) - { - location->message = "The thread identification number expected."; - btp_thread_free(imthread); - return NULL; + /* Handle the optional " (LWP [0-9]+)" section. */ + location->column += btp_skip_char_sequence(&local_input, ' '); + location->column += btp_thread_skip_lwp(&local_input); + + /* Read the end of the parenthesis. */ + if (!btp_skip_char(&local_input, ')')) + { + location->message = "Closing parenthesis for Thread expected."; + btp_thread_free(imthread); + return NULL; + } } - /* Read the end of the parenthesis. */ - chars = btp_skip_string(&local_input, "):\n"); + /* Read the end of the header line. */ + chars = btp_skip_string(&local_input, ":\n"); if (0 == chars) { - location->message = "The end of the parenthesis expected in the form of '):\\n'."; + location->message = "Expected a colon followed by a newline ':\\n'."; btp_thread_free(imthread); return NULL; } @@ -362,3 +380,20 @@ btp_thread_parse(char **input, *input = local_input; return imthread; } + +int +btp_thread_skip_lwp(char **input) +{ + char *local_input = *input; + int count = btp_skip_string(&local_input, "(LWP "); + if (0 == count) + return 0; + int digits = btp_skip_unsigned_integer(&local_input); + if (0 == digits) + return 0; + count += digits; + if (!btp_skip_char(&local_input, ')')) + return 0; + *input = local_input; + return count + 1; +} diff --git a/src/btparser/thread.h b/src/btparser/thread.h index f7287385..47a0211b 100644 --- a/src/btparser/thread.h +++ b/src/btparser/thread.h @@ -197,6 +197,17 @@ struct btp_thread * btp_thread_parse(char **input, struct btp_location *location); +/** + * If the input contains a LWP section in form of "(LWP [0-9]+), move + * the input pointer after this section. Otherwise do not modify + * input. + * @returns + * The number of characters parsed from input. 0 if the input does not + * contain a LWP section. + */ +int +btp_thread_skip_lwp(char **input); + #ifdef __cplusplus } #endif diff --git a/src/cli/CLI.cpp b/src/cli/CLI.cpp index 44271329..5ece3171 100644 --- a/src/cli/CLI.cpp +++ b/src/cli/CLI.cpp @@ -19,30 +19,73 @@ # include <locale.h> #endif #include <getopt.h> -#include "abrt_exception.h" #include "abrtlib.h" #include "abrt_dbus.h" -#include "dbus_common.h" #include "report.h" -#include "dbus.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, 128, "%c", localtime(&time)); + int success = strftime(timeloc, sizeof(timeloc), "%c", localtime(&time)); if (!success) - error_msg_and_die("Error while converting time to string"); - return xasprintf("%s", timeloc); + 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(const map_crash_data_t &crash) +static void print_crash(crash_data_t *crash_data) { /* 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); + 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" @@ -50,17 +93,18 @@ static void print_crash(const map_crash_data_t &crash) "\tExecutable : %s\n" "\tCrash Time : %s\n" "\tCrash Count: %s\n"), - get_crash_data_item_content(crash, CD_DUMPDIR).c_str(), - get_crash_data_item_content(crash, FILENAME_UID).c_str(), - get_crash_data_item_content(crash, FILENAME_PACKAGE).c_str(), - get_crash_data_item_content(crash, FILENAME_EXECUTABLE).c_str(), + 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_data_item_content(crash, FILENAME_COUNT).c_str()); + get_crash_item_content_or_NULL(crash_data, FILENAME_COUNT) + ); - free((void *)timeloc); + free(timeloc); /* Print the hostname if it's available. */ - const char *hostname = get_crash_data_item_content_or_NULL(crash, FILENAME_HOSTNAME); + const char *hostname = get_crash_item_content_or_NULL(crash_data, FILENAME_HOSTNAME); if (hostname) printf(_("\tHostname : %s\n"), hostname); } @@ -70,14 +114,14 @@ static void print_crash(const map_crash_data_t &crash) * @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) +static void print_crash_list(vector_of_crash_data_t *crash_list, bool include_reported) { - for (unsigned i = 0; i < crash_list.size(); ++i) + for (unsigned i = 0; i < crash_list->len; ++i) { - const map_crash_data_t& crash = crash_list[i]; + crash_data_t *crash = get_crash_data(crash_list, i); if (!include_reported) { - const char *msg = get_crash_data_item_content_or_NULL(crash, FILENAME_MESSAGE); + const char *msg = get_crash_item_content_or_NULL(crash, FILENAME_MESSAGE); if (!msg || !msg[0]) continue; } @@ -90,10 +134,10 @@ static void print_crash_list(const vector_map_crash_data_t& crash_list, bool inc /** * Prints full information about a crash */ -static void print_crash_info(const map_crash_data_t& crash, bool show_backtrace) +static void print_crash_info(crash_data_t *crash_data, bool show_backtrace) { - const char *timestr = get_crash_data_item_content(crash, FILENAME_TIME).c_str(); - const char *timeloc = localize_crash_time(timestr); + 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" @@ -104,91 +148,56 @@ static void print_crash_info(const map_crash_data_t& crash, bool show_backtrace) "Executable: %s\n" "System: %s, kernel %s\n" "Reason: %s\n"), - get_crash_data_item_content(crash, CD_DUMPDIR).c_str(), + get_crash_item_content_or_die(crash_data, CD_DUMPDIR), 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_REASON).c_str()); - - free((void *)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_data_item_content_or_NULL(crash, FILENAME_COREDUMP); + 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_data_item_content_or_NULL(crash, FILENAME_RATING); + 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_data_item_content_or_NULL(crash, FILENAME_CRASH_FUNCTION); + 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_data_item_content_or_NULL(crash, FILENAME_HOSTNAME); + const char *hostname = get_crash_item_content_or_NULL(crash_data, FILENAME_HOSTNAME); if (hostname) printf(_("Hostname: %s\n"), hostname); - const char *reproduce = get_crash_data_item_content_or_NULL(crash, FILENAME_REPRODUCE); + const char *reproduce = get_crash_item_content_or_NULL(crash_data, 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); + 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_data_item_content_or_NULL(crash, FILENAME_BACKTRACE); + const char *backtrace = get_crash_item_content_or_NULL(crash_data, FILENAME_BACKTRACE); if (backtrace) printf(_("\nBacktrace:\n%s\n"), backtrace); } } -/** - * Converts crash reference from user's input to crash dump dir name. - * 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 xstrdup(get_crash_data_item_content(info, CD_DUMPDIR).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_dir = get_crash_data_item_content(info, CD_DUMPDIR).c_str(); - if (strncmp(str, this_dir, len) == 0) - { - if (result) - error_msg_and_die("Crash prefix '%s' is not unique", str); - result = xstrdup(this_dir); - } - } - if (!result) - error_msg_and_die("Crash dump directory '%s' not found", str); - return result; -} - /* Program options */ enum { @@ -210,6 +219,7 @@ 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' }, @@ -234,36 +244,42 @@ static const char *progname(const char *argv0) * Prints abrt-cli version and some help text. * Then exits the program with return value 1. */ -static void usage(char *argv0) +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 [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" - " a name of dump directory, or\n" - " @N - N'th crash (as displayed by --list --full) will be acted upon\n" + 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, name, name, + DEBUG_DUMPS_DIR + ); exit(1); } int main(int argc, char** argv) { - const char* crash_id = NULL; + GList *D_list = NULL; + char *dump_dir_name = NULL; int op = -1; bool full = false; bool always = false; @@ -278,19 +294,18 @@ int main(int argc, char** argv) while (1) { /* Do not use colons, arguments are handled after parsing all options. */ - int c = getopt_long_only(argc, argv, "?Vrdlfyib", - longopts, NULL); + int c = getopt_long(argc, argv, "?Vvrdlfyib", longopts, NULL); -#define SET_OP(newop) \ - if (op != -1 && op != newop) \ - { \ - error_msg(_("You must specify exactly one operation")); \ - return 1; \ - } \ - op = newop; +#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; @@ -298,34 +313,44 @@ int main(int argc, char** argv) 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': 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 - if (c == -1) - break; + } + 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. */ - int arg_count = argc - optind; - switch (arg_count) + argc -= optind; + switch (argc) { case 0: if (op == OPT_REPORT || op == OPT_DELETE || op == OPT_INFO) - usage(argv[0]); + print_usage_and_die(argv[0]); break; case 1: if (op != OPT_REPORT && op != OPT_DELETE && op != OPT_INFO) - usage(argv[0]); - crash_id = argv[optind]; + print_usage_and_die(argv[0]); + dump_dir_name = argv[optind]; break; default: - usage(argv[0]); + print_usage_and_die(argv[0]); } /* Check if we have an operation. @@ -336,89 +361,72 @@ int main(int argc, char** argv) (backtrace && op != OPT_INFO) || op == -1) { - usage(argv[0]); - return 1; + print_usage_and_die(argv[0]); } - 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(); + 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: { - 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 */ + struct dump_dir *dd = dd_opendir(dump_dir_name, DD_OPEN_READONLY); + if (!dd) + break; + int readonly = !dd->locked; + dd_close(dd); + if (readonly) { - crash_id = guess_crash_id(crash_id); - exitcode = report(crash_id, always ? CLI_REPORT_BATCH : 0); - if (exitcode == -1) + 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) { - error_msg("Crash '%s' not found", crash_id); - free((void *)crash_id); - xfunc_die(); + delete_dump_dir_possibly_using_abrtd(dump_dir_name); + dump_dir_name = xstrdup(dd_copy->dd_dirname); + dd_close(dd_copy); } - - free((void *)crash_id); } + + 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 = 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); + exitcode = delete_dump_dir_possibly_using_abrtd(dump_dir_name); 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); + 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; } diff --git a/src/cli/Makefile.am b/src/cli/Makefile.am index 3584fd6c..0a5c6e1a 100644 --- a/src/cli/Makefile.am +++ b/src/cli/Makefile.am @@ -3,20 +3,20 @@ bin_PROGRAMS = abrt-cli abrt_cli_SOURCES = \ CLI.cpp \ run-command.h run-command.c \ - report.h report.cpp \ - dbus.h dbus.cpp - + report.h report.cpp abrt_cli_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DVAR_RUN=\"$(VAR_RUN)\" \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ $(ENABLE_SOCKET_OR_DBUS) \ $(DBUS_CFLAGS) $(GLIB_CFLAGS) \ - -D_GNU_SOURCE + -D_GNU_SOURCE \ + -Wall -Werror # $(GTK_CFLAGS) - abrt_cli_LDADD = \ - ../lib/libabrt.la \ + ../lib/libreport.la \ ../lib/libabrt_dbus.la \ $(GLIB_LIBS) diff --git a/src/cli/dbus.cpp b/src/cli/dbus.cpp deleted file mode 100644 index 7565d5bc..00000000 --- a/src/cli/dbus.cpp +++ /dev/null @@ -1,282 +0,0 @@ -/* - 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 deleted file mode 100644 index 9c99c662..00000000 --- a/src/cli/dbus.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - 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 "abrt_crash_dump.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 index 78a38916..6b2bf2e2 100644 --- a/src/cli/report.cpp +++ b/src/cli/report.cpp @@ -15,11 +15,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include <termios.h> -#include <glib.h> #include "report.h" #include "run-command.h" -#include "dbus.h" #include "abrtlib.h" /* Field separator for the crash report file that is edited by user. */ @@ -35,13 +32,10 @@ static char *trim(char *str) return NULL; // Remove leading spaces. - char *ibuf; - ibuf = skip_whitespace(str); - int i = strlen(ibuf); - if (str != ibuf) - memmove(str, ibuf, i + 1); + overlapping_strcpy(str, skip_whitespace(str)); // Remove trailing spaces. + int i = strlen(str); while (--i >= 0) { if (!isspace(str[i])) @@ -142,30 +136,24 @@ static void remove_comments_and_unescape(char *str) * 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, +static void write_crash_report_field(FILE *fp, crash_data_t *crash_data, const char *field, const char *description) { - const map_crash_data_t::const_iterator it = report.find(field); - if (it == report.end()) + const struct crash_item *value = get_crash_data_item_or_NULL(crash_data, field); + if (!value) { // 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%s\n", FIELD_SEP, field); fprintf(fp, "%s\n", description); - if (it->second[CD_EDITABLE] != CD_ISEDITABLE) + if (!(value->flags & CD_FLAG_ISEDITABLE)) fprintf(fp, _("# This field is read only\n")); - char *escaped_content = escape(it->second[CD_CONTENT].c_str()); + char *escaped_content = escape(value->content); fprintf(fp, "%s\n", escaped_content); free(escaped_content); } @@ -177,7 +165,7 @@ static void write_crash_report_field(FILE *fp, const map_crash_data_t &report, * 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) +static void write_crash_report(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"); @@ -197,7 +185,7 @@ static void write_crash_report(const map_crash_data_t &report, FILE *fp) 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")); + write_crash_report_field(fp, report, FILENAME_OS_RELEASE, _("# Release string of the operating system")); } /* @@ -208,7 +196,7 @@ static void write_crash_report(const map_crash_data_t &report, FILE *fp) * 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, +static int read_crash_report_field(const char *text, crash_data_t *report, const char *field) { char separator[sizeof("\n" FIELD_SEP)-1 + strlen(field) + 2]; // 2 = '\n\0' @@ -225,21 +213,15 @@ static int read_crash_report_field(const char *text, map_crash_data_t &report, else length = end - textfield; - const map_crash_data_t::iterator it = report.find(field); - if (it == report.end()) + struct crash_item *value = get_crash_data_item_or_NULL(report, field); + if (!value) { 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) + if (!(value->flags & CD_FLAG_ISEDITABLE)) return 0; // Compare the old field contents with the new field contents. @@ -248,16 +230,16 @@ static int read_crash_report_field(const char *text, map_crash_data_t &report, newvalue[length] = '\0'; trim(newvalue); - char oldvalue[it->second[CD_CONTENT].length() + 1]; - strcpy(oldvalue, it->second[CD_CONTENT].c_str()); + char oldvalue[strlen(value->content) + 1]; + strcpy(oldvalue, value->content); trim(oldvalue); // Return if no change in the contents detected. - int cmp = strcmp(newvalue, oldvalue); - if (!cmp) + if (strcmp(newvalue, oldvalue) == 0) return 0; - it->second[CD_CONTENT].assign(newvalue); + free(value->content); + value->content = xstrdup(newvalue); return 1; } @@ -269,7 +251,7 @@ static int read_crash_report_field(const char *text, map_crash_data_t &report, * 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) +static int read_crash_report(crash_data_t *report, const char *text) { int result = 0; result |= read_crash_report_field(text, report, FILENAME_COMMENT); @@ -284,7 +266,7 @@ static int read_crash_report(map_crash_data_t &report, const char *text) 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); + result |= read_crash_report_field(text, report, FILENAME_OS_RELEASE); return result; } @@ -292,13 +274,13 @@ static int read_crash_report(map_crash_data_t &report, const char *text) * 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) +static void create_fields_for_editor(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 (!get_crash_data_item_or_NULL(crash_data, FILENAME_COMMENT)) + add_to_crash_data_ext(crash_data, FILENAME_COMMENT, "", CD_FLAG_TXT + CD_FLAG_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"); + if (!get_crash_data_item_or_NULL(crash_data, FILENAME_REPRODUCE)) + add_to_crash_data_ext(crash_data, FILENAME_REPRODUCE, "1. \n2. \n3. \n", CD_FLAG_TXT + CD_FLAG_ISEDITABLE); } /** @@ -342,7 +324,7 @@ static int launch_editor(const char *path) * 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) +static int run_report_editor(crash_data_t *crash_data) { /* Open a temporary file and write the crash report to it. */ char filename[] = "/tmp/abrt-report.XXXXXX"; @@ -356,15 +338,14 @@ static int run_report_editor(map_crash_data_t &cr) FILE *fp = fdopen(fd, "w"); if (!fp) /* errno is set */ { - perror_msg("can't open '%s' to save the crash report", filename); - return 2; + die_out_of_memory(); } - write_crash_report(cr, fp); + write_crash_report(crash_data, fp); if (fclose(fp)) /* errno is set */ { - perror_msg("can't close '%s'", filename); + perror_msg("can't write '%s'", filename); return 2; } @@ -381,21 +362,18 @@ static int run_report_editor(map_crash_data_t &cr) } fseek(fp, 0, SEEK_END); - long size = ftell(fp); + unsigned 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); + fclose(fp); return 2; } text[size] = '\0'; - if (fclose(fp) != 0) /* errno is set */ - { - perror_msg("can't close '%s'", filename); - return 2; - } + fclose(fp); // Delete the tempfile. if (unlink(filename) == -1) /* errno is set */ @@ -405,7 +383,7 @@ static int run_report_editor(map_crash_data_t &cr) remove_comments_and_unescape(text); // Updates the crash report from the file text. - int report_changed = read_crash_report(cr, text); + int report_changed = read_crash_report(crash_data, text); free(text); if (report_changed) puts(_("\nThe report has been updated")); @@ -435,25 +413,6 @@ static void read_from_stdin(const char *question, char *result, int result_size) 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 GList *split(const char *s, const char delim) -{ - GList *elems = NULL; - while (1) - { - const char *end = strchrnul(s, delim); - elems = g_list_append(elems, xstrndup(s, end - s)); - if (*end == '\0') - break; - s = end + 1; - } - return elems; -} - /** * Asks a [y/n] question on stdin/stdout. * Returns true if the answer is yes, false otherwise. @@ -467,97 +426,128 @@ static bool ask_yesno(const char *question) fflush(NULL); char answer[16]; - fgets(answer, sizeof(answer), stdin); + if (!fgets(answer, sizeof(answer), stdin)) + return false; /* Use strncmp here because the answer might contain a newline as the last char. */ return 0 == strncmp(answer, yes, strlen(yes)); } /* Returns true if echo has been changed from another state. */ -static bool set_echo(bool enabled) +static bool set_echo(bool enable) { - 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) + /* No change needed? */ + if ((bool)(t.c_lflag & ECHO) == enable) return false; - if (enabled) - t.c_lflag |= ECHO; - else - t.c_lflag &= ~ECHO; - + 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 + * @return settings * A structure filled with reporter plugin settings. + * It's GHashTable<char *, map_plugin_t *> and must be passed to + * g_hash_table_destroy(); */ -static void get_reporter_plugin_settings(const vector_string_t& reporters, - map_map_string_t &settings) +static void get_plugin_system_settings(GHashTable *settings) { - /* First of all, load system-wide report plugin settings. */ - for (vector_string_t::const_iterator it = reporters.begin(); it != reporters.end(); ++it) + DIR *dir = opendir(PLUGINS_CONF_DIR); + if (!dir) + return; + + struct dirent *dent; + while ((dent = readdir(dir)) != NULL) { - 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; + char *ext = strrchr(dent->d_name, '.'); + if (!ext || strcmp(ext + 1, "conf") != 0) + continue; + if (!is_regular_file(dent, PLUGINS_CONF_DIR)) + continue; + VERB3 log("Found %s", dent->d_name); + + char *conf_file = concat_path_file(PLUGINS_CONF_DIR, dent->d_name); + map_string_h *single_plugin_settings = new_map_string(); + if (load_conf_file(conf_file, single_plugin_settings, /*skip w/o value:*/ false)) + VERB3 log("Loaded %s", dent->d_name); + free(conf_file); + + *ext = '\0'; + g_hash_table_replace(settings, xstrdup(dent->d_name), single_plugin_settings); } + closedir(dir); +} + +static GHashTable *get_plugin_settings(void) +{ + /* First of all, load system-wide plugin settings. */ + GHashTable *settings = g_hash_table_new_full( + g_str_hash, g_str_equal, + free, (void (*)(void*))free_map_string + ); + + get_plugin_system_settings(settings); /* Second, load user-specific settings, which override - the system-wide settings. */ + * 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) + GHashTableIter iter; + char *plugin_name; + map_string_h *plugin_settings; + g_hash_table_iter_init(&iter, settings); + while (g_hash_table_iter_next(&iter, (void**)&plugin_name, (void**)&plugin_settings)) { - map_string_t single_plugin_settings; - std::string path = std::string(homedir) + "/.abrt/" - + it->first + ".conf"; - /* 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); + /* 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/foo.conf, but he prefers to + * enter it every time he reports. */ + map_string_h *single_plugin_settings = new_map_string(); + char *path = xasprintf("%s/.abrt/%s.conf", homedir, plugin_name); + bool success = load_conf_file(path, single_plugin_settings, /*skip key w/o values:*/ false); + free(path); if (!success) + { + free_map_string(single_plugin_settings); 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; + } + + /* Merge user's plugin settings into already loaded settings */ + GHashTableIter iter2; + char *key; + char *value; + g_hash_table_iter_init(&iter2, single_plugin_settings); + while (g_hash_table_iter_next(&iter2, (void**)&key, (void**)&value)) + g_hash_table_replace(plugin_settings, xstrdup(key), xstrdup(value)); + + free_map_string(single_plugin_settings); } } + return settings; } /** * Asks user for missing login information */ -static void ask_for_missing_settings(const char *plugin_name, map_string_t &single_plugin_settings) +static void ask_for_missing_settings(const char *plugin_name, map_string_h *single_plugin_settings) { // 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(), ""); + const char *login = get_map_string_item_or_NULL(single_plugin_settings, "Login"); + const char *password = get_map_string_item_or_NULL(single_plugin_settings, "Password"); + bool loginMissing = (login && login[0] == '\0'); + bool passwordMissing = (password && password[0] == '\0'); if (!loginMissing && !passwordMissing) return; @@ -567,7 +557,7 @@ static void ask_for_missing_settings(const char *plugin_name, map_string_t &sing if (loginMissing) { read_from_stdin(_("Enter your login: "), result, 64); - single_plugin_settings["Login"] = std::string(result); + g_hash_table_replace(single_plugin_settings, xstrdup("Login"), xstrdup(result)); } if (passwordMissing) { @@ -578,129 +568,266 @@ static void ask_for_missing_settings(const char *plugin_name, map_string_t &sing // Newline was not added by pressing Enter because ECHO was disabled, so add it now. puts(""); - single_plugin_settings["Password"] = std::string(result); + g_hash_table_replace(single_plugin_settings, xstrdup("Password"), xstrdup(result)); } } -/* Reports the crash with corresponding crash_id over DBus. */ -int report(const char *crash_id, int flags) + +struct logging_state { + char *last_line; +}; +static char *do_log_and_save_line(char *log_line, void *param) { - 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) + struct logging_state *l_state = (struct logging_state *)param; + log("%s", log_line); + free(l_state->last_line); + l_state->last_line = log_line; + return NULL; +} +static int run_events(const char *dump_dir_name, + const vector_string_t& events, + GHashTable *map_map_settings +) { + int error_cnt = 0; + GList *env_list = NULL; + + // Export overridden settings as environment variables + GHashTableIter iter; + char *plugin_name; + map_string_h *single_plugin_settings; + g_hash_table_iter_init(&iter, map_map_settings); + while (g_hash_table_iter_next(&iter, (void**)&plugin_name, (void**)&single_plugin_settings)) + { + GHashTableIter iter2; + char *key; + char *value; + g_hash_table_iter_init(&iter2, single_plugin_settings); + while (g_hash_table_iter_next(&iter2, (void**)&key, (void**)&value)) + { + char *s = xasprintf("%s_%s=%s", plugin_name, key, value); + VERB3 log("Exporting '%s'", s); + putenv(s); + env_list = g_list_append(env_list, s); + } + } + + // Run events + bool at_least_one_reporter_succeeded = false; + std::string message; + struct logging_state l_state; + l_state.last_line = NULL; + struct run_event_state *run_state = new_run_event_state(); + run_state->logging_callback = do_log_and_save_line; + run_state->logging_param = &l_state; + for (unsigned i = 0; i < events.size(); i++) + { + std::string event = events[i]; + + int r = run_event_on_dir_name(run_state, dump_dir_name, event.c_str()); + if (r == 0 && run_state->children_count == 0) + { + l_state.last_line = xasprintf("Error: no processing is specified for event '%s'", event.c_str()); + r = -1; + } + if (r == 0) + { + at_least_one_reporter_succeeded = true; + printf("%s: %s\n", event.c_str(), (l_state.last_line ? : "Reporting succeeded")); + if (message != "") + message += ";"; + message += (l_state.last_line ? : "Reporting succeeded"); + } + else + { + error_msg("Reporting via '%s' was not successful%s%s", + event.c_str(), + l_state.last_line ? ": " : "", + l_state.last_line ? l_state.last_line : "" + ); + error_cnt++; + } + free(l_state.last_line); + l_state.last_line = NULL; + } + free_run_event_state(run_state); + + // Unexport overridden settings + for (GList *li = env_list; li; li = g_list_next(li)) { - return -1; + char *s = (char*)li->data; + /* Need to make a copy: just cutting s at '=' and unsetenv'ing + * the result would be a bug! s _itself_ is in environment now, + * we must not modify it there! + */ + char *name = xstrndup(s, strchrnul(s, '=') - s); + VERB3 log("Unexporting '%s'", name); + unsetenv(name); + free(name); + free(s); } + g_list_free(env_list); + + // Save reporting results + if (at_least_one_reporter_succeeded) + { + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (dd) + { + dd_save_text(dd, FILENAME_MESSAGE, message.c_str()); + dd_close(dd); + } + } + + return error_cnt; +} + + +static char *do_log(char *log_line, void *param) +{ + log("%s", log_line); + return log_line; +} +int run_analyze_event(const char *dump_dir_name) +{ + VERB2 log("run_analyze_event('%s')", dump_dir_name); + + struct run_event_state *run_state = new_run_event_state(); + run_state->logging_callback = do_log; + int res = run_event_on_dir_name(run_state, dump_dir_name, "analyze"); + free_run_event_state(run_state); + return res; +} - const char *rating_str = get_crash_data_item_content_or_NULL(cr, FILENAME_RATING); - unsigned rating = rating_str ? xatou(rating_str) : 4; - /* Open text editor and give a chance to review the backtrace etc. */ +/* Report the crash */ +int report(const char *dump_dir_name, int flags) +{ + 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); + char *events_as_lines = list_possible_events(dd, NULL, ""); + dd_close(dd); + if (!(flags & CLI_REPORT_BATCH)) { - create_fields_for_editor(cr); - int result = run_report_editor(cr); + /* Open text editor and give a chance to review the backtrace etc */ + create_fields_for_editor(crash_data); + int result = run_report_editor(crash_data); if (result != 0) - return result; + { + free_crash_data(crash_data); + free(events_as_lines); + return 1; + } + /* Save comment, "how to reproduce", backtrace */ + dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (dd) + { +//TODO: we should iterate through crash_data and modify all modifiable fields + const char *comment = get_crash_item_content_or_NULL(crash_data, FILENAME_COMMENT); + const char *reproduce = get_crash_item_content_or_NULL(crash_data, FILENAME_REPRODUCE); + const char *backtrace = get_crash_item_content_or_NULL(crash_data, FILENAME_BACKTRACE); + if (comment) + dd_save_text(dd, FILENAME_COMMENT, comment); + if (reproduce) + dd_save_text(dd, FILENAME_REPRODUCE, reproduce); + if (backtrace) + dd_save_text(dd, FILENAME_BACKTRACE, backtrace); + dd_close(dd); + } } - /* Get possible reporters associated with this particular crash. */ - const char *events = get_crash_data_item_content_or_NULL(cr, CD_EVENTS); - vector_string_t reporters; - if (events) while (*events) + /* Get possible reporters associated with this particular crash */ + vector_string_t report_events; + if (events_as_lines) { - const char *end = strchrnul(events, '\n'); - if (strncmp(events, "report", 6) == 0 - && (events[6] == '\0' || events[6] == '_') - ) { - char *tmp = xstrndup(events, end - events); - reporters.push_back(tmp); - free(tmp); + char *events = events_as_lines; + while (*events) + { + char *end = strchrnul(events, '\n'); + if (strncmp(events, "report", 6) == 0 + && (events[6] == '\0' || events[6] == '_') + ) { + char *tmp = xstrndup(events, end - events); + report_events.push_back(tmp); + free(tmp); + } + events = end; + if (!*events) + break; + events++; } - events = end; - if (!*events) - break; - events++; } /* Get settings */ - map_map_string_t reporters_settings; - get_reporter_plugin_settings(reporters, reporters_settings); + GHashTable *map_map_settings = get_plugin_settings(); int errors = 0; int plugins = 0; if (flags & CLI_REPORT_BATCH) { 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++; - } + errors += run_events(dump_dir_name, report_events, map_map_settings); + plugins += report_events.size(); } else { + const char *rating_str = get_crash_item_content_or_NULL(crash_data, FILENAME_RATING); + unsigned rating = rating_str ? xatou(rating_str) : 4; + /* 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) + for (vector_string_t::const_iterator it = report_events.begin(); it != report_events.end(); ++it) { char question[255]; - snprintf(question, 255, _("Report using %s?"), it->c_str()); + snprintf(question, sizeof(question), _("Report using %s?"), it->c_str()); if (!ask_yesno(question)) { puts(_("Skipping...")); continue; } - map_map_string_t::iterator settings = reporters_settings.find(it->c_str()); - if (settings != reporters_settings.end()) +//TODO: rethink how we associate report events with configs + if (strncmp(it->c_str(), "report_", strlen("report_")) == 0) { - map_string_t::iterator rating_setting = settings->second.find("RatingRequired"); - if (rating_setting != settings->second.end() - && string_to_bool(rating_setting->second.c_str()) - && rating < 3) + const char *config_name = it->c_str() + strlen("report_"); + map_string_h *single_plugin_settings = (map_string_h *)g_hash_table_lookup(map_map_settings, config_name); + if (single_plugin_settings) { - puts(_("Reporting disabled because the backtrace is unusable")); - - const char *package = get_crash_data_item_content_or_NULL(cr, FILENAME_PACKAGE); - if (package[0]) - printf(_("Please try to install debuginfo manually using the command: \"debuginfo-install %s\" and try again\n"), package); - - plugins++; - errors++; - continue; + const char *rating_required = get_map_string_item_or_NULL(single_plugin_settings, "RatingRequired"); + if (rating_required + && string_to_bool(rating_required) == true + && rating < 3 + ) { + puts(_("Reporting disabled because the backtrace is unusable")); + + const char *package = get_crash_item_content_or_NULL(crash_data, FILENAME_PACKAGE); + if (package && package[0]) + printf(_("Please try to install debuginfo manually using the command: \"debuginfo-install %s\" and try again\n"), package); + + plugins++; + errors++; + continue; + } + ask_for_missing_settings(it->c_str(), single_plugin_settings); } } - else - { - puts(_("Error loading reporter settings")); - plugins++; - errors++; - continue; - } - ask_for_missing_settings(it->c_str(), settings->second); - - vector_string_t cur_reporter(1, *it); - 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()); + vector_string_t cur_event(1, *it); + errors += run_events(dump_dir_name, cur_event, map_map_settings); plugins++; - if (v[REPORT_STATUS_IDX_FLAG] == "0") - errors++; } } + g_hash_table_destroy(map_map_settings); + printf(_("Crash reported via %d report events (%d errors)\n"), plugins, errors); - return errors != 0; + free_crash_data(crash_data); + free(events_as_lines); + return errors; } diff --git a/src/cli/report.h b/src/cli/report.h index 8a851b8e..80c0b9f2 100644 --- a/src/cli/report.h +++ b/src/cli/report.h @@ -18,11 +18,12 @@ #ifndef ABRT_CLI_REPORT_H #define ABRT_CLI_REPORT_H -/* Reports the crash with corresponding uuid over DBus. */ +int run_analyze_event(const char *dump_dir_name); + +/* Report the crash */ enum { CLI_REPORT_BATCH = 1 << 0, - CLI_REPORT_SILENT_IF_NOT_FOUND = 1 << 1, }; -int report(const char *uuid, int flags); +int report(const char *dump_dir_name, int flags); #endif diff --git a/src/daemon/CommLayerServer.h b/src/daemon/CommLayerServer.h deleted file mode 100644 index 5b7ecf57..00000000 --- a/src/daemon/CommLayerServer.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 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 COMMLAYERSERVER_H_ -#define COMMLAYERSERVER_H_ - -#include "abrtlib.h" -#include "abrt_crash_dump.h" - -class CCommLayerServer { - public: - int m_init_error; - - CCommLayerServer(); - virtual ~CCommLayerServer(); - - /* just stubs to be called when not implemented in specific comm layer */ - virtual void Crash(const char *package_name, - const char *crash_id, - const char *dir, - const char *uid_str - ) {} - virtual void JobDone(const char* peer) = 0; - virtual void QuotaExceed(const char* str) {} - - virtual void Update(const char* pMessage, const char* peer) {}; - virtual void Warning(const char* pMessage, const char* peer) {}; -}; - -#endif diff --git a/src/daemon/CommLayerServerDBus.cpp b/src/daemon/CommLayerServerDBus.cpp index 28d6ee05..133feb7b 100644 --- a/src/daemon/CommLayerServerDBus.cpp +++ b/src/daemon/CommLayerServerDBus.cpp @@ -19,12 +19,9 @@ #include <dbus/dbus.h> #include "abrtlib.h" #include "abrt_dbus.h" -#include "abrt_exception.h" #include "comm_layer_inner.h" -#include "dbus_common.h" #include "MiddleWare.h" #include "Settings.h" -#include "Daemon.h" #include "CommLayerServerDBus.h" // 16kB message limit @@ -48,6 +45,11 @@ static DBusMessage* new_signal_msg(const char* member, const char* peer = NULL) } static void send_flush_and_unref(DBusMessage* msg) { + if (!g_dbus_conn) + { + /* Not logging this, it may recurse */ + return; + } if (!dbus_connection_send(g_dbus_conn, msg, NULL /* &serial */)) error_msg_and_die("Error sending DBus message"); dbus_connection_flush(g_dbus_conn); @@ -56,7 +58,7 @@ static void send_flush_and_unref(DBusMessage* msg) } /* Notify the clients (UI) about a new crash */ -void CCommLayerServerDBus::Crash(const char *package_name, +void send_dbus_sig_Crash(const char *package_name, const char *crash_id, const char *dir, const char *uid_str @@ -84,24 +86,24 @@ void CCommLayerServerDBus::Crash(const char *package_name, send_flush_and_unref(msg); } -void CCommLayerServerDBus::QuotaExceed(const char* str) +void send_dbus_sig_QuotaExceeded(const char* str) { - DBusMessage* msg = new_signal_msg("QuotaExceed"); + DBusMessage* msg = new_signal_msg("QuotaExceeded"); dbus_message_append_args(msg, DBUS_TYPE_STRING, &str, DBUS_TYPE_INVALID); - VERB2 log("Sending signal QuotaExceed('%s')", str); + VERB2 log("Sending signal QuotaExceeded('%s')", str); send_flush_and_unref(msg); } -void CCommLayerServerDBus::JobDone(const char* peer) +void send_dbus_sig_JobDone(const char* peer) { DBusMessage* msg = new_signal_msg("JobDone", peer); VERB2 log("Sending signal JobDone() to peer %s", peer); send_flush_and_unref(msg); } -void CCommLayerServerDBus::Update(const char* pMessage, const char* peer) +void send_dbus_sig_Update(const char* pMessage, const char* peer) { DBusMessage* msg = new_signal_msg("Update", peer); dbus_message_append_args(msg, @@ -110,7 +112,7 @@ void CCommLayerServerDBus::Update(const char* pMessage, const char* peer) send_flush_and_unref(msg); } -void CCommLayerServerDBus::Warning(const char* pMessage, const char* peer) +void send_dbus_sig_Warning(const char* pMessage, const char* peer) { DBusMessage* msg = new_signal_msg("Warning", peer); dbus_message_append_args(msg, @@ -144,11 +146,12 @@ static long get_remote_uid(DBusMessage* call, const char** ppSender = NULL) static int handle_GetCrashInfos(DBusMessage* call, DBusMessage* reply) { long unix_uid = get_remote_uid(call); - vector_map_crash_data_t argout1 = GetCrashInfos(unix_uid); + vector_of_crash_data_t *argout1 = GetCrashInfos(unix_uid); DBusMessageIter out_iter; dbus_message_iter_init_append(reply, &out_iter); - store_val(&out_iter, argout1); + store_vector_of_crash_data(&out_iter, argout1); + free_vector_of_crash_data(argout1); send_flush_and_unref(reply); return 0; @@ -197,12 +200,12 @@ static int handle_CreateReport(DBusMessage* call, DBusMessage* reply) } long unix_uid = get_remote_uid(call); - map_crash_data_t report; - CreateReport(crash_id, unix_uid, /*force:*/ 0, report); + crash_data_t *report = NULL; + CreateReport(crash_id, unix_uid, /*force:*/ 0, &report); DBusMessageIter out_iter; dbus_message_iter_init_append(reply, &out_iter); - store_val(&out_iter, report); + store_crash_data(&out_iter, report); send_flush_and_unref(reply); return 0; @@ -211,19 +214,28 @@ static int handle_CreateReport(DBusMessage* call, DBusMessage* reply) static int handle_Report(DBusMessage* call, DBusMessage* reply) { int r; + long unix_uid; + report_status_t argout1; + map_map_string_t user_conf_data; + vector_string_t events; + const char* comment = NULL; + const char* reproduce = NULL; + const char* errmsg = NULL; DBusMessageIter in_iter; + dbus_message_iter_init(call, &in_iter); - map_crash_data_t argin1; - r = load_val(&in_iter, argin1); + crash_data_t *crash_data = NULL; + r = load_crash_data(&in_iter, &crash_data); if (r != ABRT_DBUS_MORE_FIELDS) { error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; + r = -1; + goto ret; } - const char* comment = get_crash_data_item_content_or_NULL(argin1, FILENAME_COMMENT) ? : ""; - const char* reproduce = get_crash_data_item_content_or_NULL(argin1, FILENAME_REPRODUCE) ? : ""; - const char* errmsg = NULL; +//TODO? get_crash_item_content_or_die_or_empty? + comment = get_crash_item_content_or_NULL(crash_data, FILENAME_COMMENT) ? : ""; + reproduce = get_crash_item_content_or_NULL(crash_data, FILENAME_REPRODUCE) ? : ""; if (strlen(comment) > LIMIT_MESSAGE) { errmsg = _("Comment is too long"); @@ -239,52 +251,43 @@ static int handle_Report(DBusMessage* call, DBusMessage* reply) if (!reply) die_out_of_memory(); send_flush_and_unref(reply); - return 0; + r = 0; + goto ret; } /* Second parameter: list of events to run */ - vector_string_t events; r = load_val(&in_iter, events); if (r == ABRT_DBUS_ERROR) { error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; + r = -1; + goto ret; } /* Third parameter (optional): configuration data for plugins */ - map_map_string_t user_conf_data; if (r == ABRT_DBUS_MORE_FIELDS) { r = load_val(&in_iter, user_conf_data); if (r != ABRT_DBUS_LAST_FIELD) { error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; + r = -1; + goto ret; } } - long unix_uid = get_remote_uid(call); - report_status_t argout1; - try - { - argout1 = Report(argin1, events, user_conf_data, unix_uid); - } - catch (CABRTException &e) - { - dbus_message_unref(reply); - reply = dbus_message_new_error(call, DBUS_ERROR_FAILED, e.what()); - if (!reply) - die_out_of_memory(); - send_flush_and_unref(reply); - return 0; - } + unix_uid = get_remote_uid(call); + argout1 = Report(crash_data, events, user_conf_data, unix_uid); DBusMessageIter out_iter; dbus_message_iter_init_append(reply, &out_iter); store_val(&out_iter, argout1); send_flush_and_unref(reply); - return 0; + r = 0; + ret: + free_crash_data(crash_data); + return r; } static int handle_DeleteDebugDump(DBusMessage* call, DBusMessage* reply) @@ -339,12 +342,13 @@ static int handle_GetPluginSettings(DBusMessage* call, DBusMessage* reply) //long unix_uid = get_remote_uid(call); //VERB1 log("got %s('%s') call from uid %ld", "GetPluginSettings", PluginName, unix_uid); - map_plugin_settings_t plugin_settings; - GetPluginSettings(PluginName, plugin_settings); + map_string_h *plugin_settings = GetPluginSettings(PluginName); DBusMessageIter out_iter; dbus_message_iter_init_append(reply, &out_iter); - store_val(&out_iter, plugin_settings); + store_map_string(&out_iter, plugin_settings); + + free_map_string(plugin_settings); send_flush_and_unref(reply); return 0; @@ -362,25 +366,25 @@ static int handle_GetSettings(DBusMessage* call, DBusMessage* reply) return 0; } -static int handle_SetSettings(DBusMessage* call, DBusMessage* reply) -{ - int r; - DBusMessageIter in_iter; - dbus_message_iter_init(call, &in_iter); - map_abrt_settings_t param1; - r = load_val(&in_iter, param1); - if (r != ABRT_DBUS_LAST_FIELD) - { - error_msg("dbus call %s: parameter type mismatch", __func__ + 7); - return -1; - } - - const char * sender = dbus_message_get_sender(call); - SetSettings(param1, sender); - - send_flush_and_unref(reply); - return 0; -} +//static int handle_SetSettings(DBusMessage* call, DBusMessage* reply) +//{ +// int r; +// DBusMessageIter in_iter; +// dbus_message_iter_init(call, &in_iter); +// map_abrt_settings_t param1; +// r = load_val(&in_iter, param1); +// if (r != ABRT_DBUS_LAST_FIELD) +// { +// error_msg("dbus call %s: parameter type mismatch", __func__ + 7); +// return -1; +// } +// +// const char * sender = dbus_message_get_sender(call); +// SetSettings(param1, sender); +// +// send_flush_and_unref(reply); +// return 0; +//} /* @@ -413,8 +417,11 @@ static DBusHandlerResult message_received(DBusConnection* conn, DBusMessage* msg r = handle_GetPluginSettings(msg, reply); else if (strcmp(member, "GetSettings") == 0) r = handle_GetSettings(msg, reply); - else if (strcmp(member, "SetSettings") == 0) - r = handle_SetSettings(msg, reply); +// looks unused to me. +// Ok to grep for SetSettings and delete after 2011-04-01. +// else if (strcmp(member, "SetSettings") == 0) +// r = handle_SetSettings(msg, reply); + // NB: C++ binding also handles "Introspect" method, which returns a string. // It was sending "dummy" introspection answer whick looks like this: // "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n" @@ -460,7 +467,7 @@ static void handle_dbus_err(bool error_flag, DBusError *err) ABRTD_DBUS_NAME); } -CCommLayerServerDBus::CCommLayerServerDBus() +int init_dbus() { DBusConnection* conn; DBusError err; @@ -481,7 +488,7 @@ CCommLayerServerDBus::CCommLayerServerDBus() // // dbus-daemon drops connections if it recvs a malformed message // (we actually observed this when we sent bad UTF-8 string). - // Currently, in this case abrtd just exits with exitcode 1. + // Currently, in this case abrtd just exits with exit code 1. // (symptom: last two log messages are "abrtd: remove_watch()") // If we want to have better logging or other nontrivial handling, // here we need to do: @@ -510,9 +517,15 @@ CCommLayerServerDBus::CCommLayerServerDBus() int cnt = 10; while (dbus_connection_dispatch(conn) != DBUS_DISPATCH_COMPLETE && --cnt) VERB3 log("processed initial buffered dbus message"); + + return 0; } -CCommLayerServerDBus::~CCommLayerServerDBus() +void deinit_dbus() { - dbus_connection_unref(g_dbus_conn); + if (g_dbus_conn != NULL) + { + dbus_connection_unref(g_dbus_conn); + g_dbus_conn = NULL; + } } diff --git a/src/daemon/CommLayerServerDBus.h b/src/daemon/CommLayerServerDBus.h index df767436..979fef69 100644 --- a/src/daemon/CommLayerServerDBus.h +++ b/src/daemon/CommLayerServerDBus.h @@ -19,26 +19,26 @@ #ifndef COMMLAYERSERVERDBUS_H_ #define COMMLAYERSERVERDBUS_H_ -#include "CommLayerServer.h" +#ifdef __cplusplus +extern "C" { +#endif -class CCommLayerServerDBus -: public CCommLayerServer -{ - public: - CCommLayerServerDBus(); - virtual ~CCommLayerServerDBus(); +int init_dbus(void); +void deinit_dbus(void); - /* DBus signal senders */ - virtual void Crash(const char *package_name, +void send_dbus_sig_Crash(const char *package_name, const char *crash_id, const char *dir, const char *uid_str - ); - virtual void JobDone(const char* peer); - virtual void QuotaExceed(const char* str); +); +void send_dbus_sig_JobDone(const char* peer); +void send_dbus_sig_QuotaExceeded(const char* str); + +void send_dbus_sig_Update(const char* pMessage, const char* peer); +void send_dbus_sig_Warning(const char* pMessage, const char* peer); - virtual void Update(const char* pMessage, const char* peer); - virtual void Warning(const char* pMessage, const char* peer); -}; +#ifdef __cplusplus +} +#endif #endif diff --git a/src/daemon/CrashWatcher.cpp b/src/daemon/CrashWatcher.cpp deleted file mode 100644 index a74fa3aa..00000000 --- a/src/daemon/CrashWatcher.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) - 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 "abrtlib.h" -#include "Daemon.h" -#include "CommLayerServer.h" -#include "CrashWatcher.h" - -void CCrashWatcher::Status(const char *pMessage, const char* peer) -{ - VERB1 log("Update('%s'): %s", peer, pMessage); - if (g_pCommLayer != NULL) - g_pCommLayer->Update(pMessage, peer); -} - -void CCrashWatcher::Warning(const char *pMessage, const char* peer) -{ - VERB1 log("Warning('%s'): %s", peer, pMessage); - if (g_pCommLayer != NULL) - g_pCommLayer->Warning(pMessage, peer); -} - -CCrashWatcher::CCrashWatcher() -{ -} - -CCrashWatcher::~CCrashWatcher() -{ -} diff --git a/src/daemon/CrashWatcher.h b/src/daemon/CrashWatcher.h deleted file mode 100644 index 4f755a36..00000000 --- a/src/daemon/CrashWatcher.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) - 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 CRASHWATCHER_H_ -#define CRASHWATCHER_H_ - -#include "observer.h" - - -class CCrashWatcher -: public CObserver -{ - public: - CCrashWatcher(); - virtual ~CCrashWatcher(); - - public: - /* Observer methods */ - virtual void Status(const char *pMessage, const char* peer); - virtual void Warning(const char *pMessage, const char* peer); -}; - -#endif diff --git a/src/daemon/Daemon.cpp b/src/daemon/Daemon.cpp index 609b0b26..f791fb38 100644 --- a/src/daemon/Daemon.cpp +++ b/src/daemon/Daemon.cpp @@ -25,21 +25,18 @@ #include <string> #include <sys/inotify.h> #include <sys/ioctl.h> /* ioctl(FIONREAD) */ -#include <glib.h> #include "abrtlib.h" -#include "abrt_exception.h" #include "comm_layer_inner.h" #include "Settings.h" #include "CommLayerServerDBus.h" -#include "CrashWatcher.h" #include "MiddleWare.h" -#include "Daemon.h" #include "parse_options.h" +#define PROGNAME "abrtd" + using namespace std; -#define VAR_RUN_LOCK_FILE VAR_RUN"/abrt/abrtd.lock" #define VAR_RUN_PIDFILE VAR_RUN"/abrtd.pid" #define SOCKET_FILE VAR_RUN"/abrt/abrt.socket" @@ -55,15 +52,15 @@ using namespace std; * - signal: we got SIGTERM or SIGINT * * DBus methods we have: - * - GetCrashInfos(): returns a vector_map_crash_data_t (vector_map_vector_string_t) + * - GetCrashInfos(): returns a vector_of_crash_data * of crashes for given uid * v[N]["executable"/"uid"/"kernel"/"backtrace"][N] = "contents" * - StartJob(crash_id,force): starts creating a report for /var/spool/abrt/DIR with this UID:UUID. * Returns job id (uint64). * After thread returns, when report creation thread has finished, * JobDone() dbus signal is emitted. - * - CreateReport(crash_id): returns map_crash_data_t (map_vector_string_t) - * - Report(map_crash_data_t (map_vector_string_t[, map_map_string_t])): + * - CreateReport(crash_id): returns crash data (hash table of struct crash_item) + * - Report(crash_data[, map_map_string_t]): * "Please report this crash": calls Report() of all registered reporter plugins. * Returns report_status_t (map_vector_string_t) - the status of each call. * 2nd parameter is the contents of user's abrt.conf. @@ -85,12 +82,11 @@ using namespace std; * Both are sent as unicast to last client set by set_client_name(name). * If set_client_name(NULL) was done, they are not sent. */ -CCommLayerServer* g_pCommLayer; - static volatile sig_atomic_t s_sig_caught; static int s_signal_pipe[2]; static int s_signal_pipe_write = -1; static int s_upload_watch = -1; +static pid_t log_scanner_pid = -1; static unsigned s_timeout; static bool s_exiting; @@ -203,205 +199,36 @@ static void dumpsocket_shutdown() } } - -/* Cron handling */ - -typedef struct cron_callback_data_t -{ - std::string m_sPluginName; - std::string m_sPluginArgs; - unsigned int m_nTimeout; - - cron_callback_data_t( - const std::string& pPluginName, - const std::string& pPluginArgs, - const unsigned int& pTimeout) : - m_sPluginName(pPluginName), - m_sPluginArgs(pPluginArgs), - m_nTimeout(pTimeout) - {} -} cron_callback_data_t; - -static void cron_delete_callback_data_cb(gpointer data) -{ - cron_callback_data_t* cronDeleteCallbackData = static_cast<cron_callback_data_t*>(data); - delete cronDeleteCallbackData; -} - -static gboolean cron_activation_periodic_cb(gpointer data) +static int create_pidfile() { - cron_callback_data_t* cronPeriodicCallbackData = static_cast<cron_callback_data_t*>(data); - VERB1 log("Activating plugin: %s", cronPeriodicCallbackData->m_sPluginName.c_str()); - RunAction(DEBUG_DUMPS_DIR, - cronPeriodicCallbackData->m_sPluginName.c_str(), - cronPeriodicCallbackData->m_sPluginArgs.c_str() - ); - return TRUE; -} -static gboolean cron_activation_one_cb(gpointer data) -{ - cron_callback_data_t* cronOneCallbackData = static_cast<cron_callback_data_t*>(data); - VERB1 log("Activating plugin: %s", cronOneCallbackData->m_sPluginName.c_str()); - RunAction(DEBUG_DUMPS_DIR, - cronOneCallbackData->m_sPluginName.c_str(), - cronOneCallbackData->m_sPluginArgs.c_str() - ); - return FALSE; -} -static gboolean cron_activation_reshedule_cb(gpointer data) -{ - cron_callback_data_t* cronResheduleCallbackData = static_cast<cron_callback_data_t*>(data); - VERB1 log("Rescheduling plugin: %s", cronResheduleCallbackData->m_sPluginName.c_str()); - cron_callback_data_t* cronPeriodicCallbackData = new cron_callback_data_t(cronResheduleCallbackData->m_sPluginName, - cronResheduleCallbackData->m_sPluginArgs, - cronResheduleCallbackData->m_nTimeout); - g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, - cronPeriodicCallbackData->m_nTimeout, - cron_activation_periodic_cb, - static_cast<gpointer>(cronPeriodicCallbackData), - cron_delete_callback_data_cb - ); - return FALSE; -} - -static int SetUpCron() -{ - map_cron_t::iterator it_c = g_settings_mapCron.begin(); - for (; it_c != g_settings_mapCron.end(); it_c++) + /* Note: + * No O_EXCL: we would happily overwrite stale pidfile from previous boot. + * No O_TRUNC: we must first try to lock the file, and if lock fails, + * there is another live abrtd. O_TRUNCing the file in this case + * would be wrong - it'll erase the pid to empty string! + */ + int fd = open(VAR_RUN_PIDFILE, O_WRONLY|O_CREAT, 0644); + if (fd >= 0) { - std::string::size_type pos = it_c->first.find(":"); - int timeout = 0; - int nH = -1; - int nM = -1; - int nS = -1; - -//TODO: rewrite using good old sscanf? - - if (pos != std::string::npos) - { - std::string sH; - std::string sM; - - sH = it_c->first.substr(0, pos); - nH = xatou(sH.c_str()); - nH = nH > 23 ? 23 : nH; - nH = nH < 0 ? 0 : nH; - timeout += nH * 60 * 60; - sM = it_c->first.substr(pos + 1); - nM = xatou(sM.c_str()); - nM = nM > 59 ? 59 : nM; - nM = nM < 0 ? 0 : nM; - timeout += nM * 60; - } - else - { - std::string sS; - - sS = it_c->first; - nS = xatou(sS.c_str()); - nS = nS <= 0 ? 1 : nS; - timeout = nS; - } - - if (nS != -1) + if (lockf(fd, F_TLOCK, 0) < 0) { - vector_pair_string_string_t::iterator it_ar = it_c->second.begin(); - for (; it_ar != it_c->second.end(); it_ar++) - { - cron_callback_data_t* cronPeriodicCallbackData = new cron_callback_data_t(it_ar->first, it_ar->second, timeout); - g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, - timeout, - cron_activation_periodic_cb, - static_cast<gpointer>(cronPeriodicCallbackData), - cron_delete_callback_data_cb); - } + perror_msg("Can't lock file '%s'", VAR_RUN_PIDFILE); + return -1; } - else - { - time_t actTime = time(NULL); - struct tm locTime; - localtime_r(&actTime, &locTime); - locTime.tm_hour = nH; - locTime.tm_min = nM; - locTime.tm_sec = 0; - time_t nextTime = mktime(&locTime); - if (nextTime == ((time_t)-1)) - { - /* paranoia */ - perror_msg("Can't set up cron time"); - return -1; - } - if (actTime > nextTime) - { - timeout = 24*60*60 + (nextTime - actTime); - } - else - { - timeout = nextTime - actTime; - } - vector_pair_string_string_t::iterator it_ar = it_c->second.begin(); - for (; it_ar != it_c->second.end(); it_ar++) - { - cron_callback_data_t* cronOneCallbackData = new cron_callback_data_t(it_ar->first, it_ar->second, timeout); - g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, - timeout, - cron_activation_one_cb, - static_cast<gpointer>(cronOneCallbackData), - cron_delete_callback_data_cb); - cron_callback_data_t* cronResheduleCallbackData = new cron_callback_data_t(it_ar->first, it_ar->second, 24 * 60 * 60); - g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, - timeout, - cron_activation_reshedule_cb, - static_cast<gpointer>(cronResheduleCallbackData), - cron_delete_callback_data_cb); - } - } - } - return 0; -} - -static int CreatePidFile() -{ - int fd; - - /* JIC */ - unlink(VAR_RUN_PIDFILE); - - /* open the pidfile */ - fd = open(VAR_RUN_PIDFILE, O_WRONLY|O_CREAT|O_EXCL, 0644); - if (fd >= 0) - { + close_on_exec_on(fd); /* write our pid to it */ char buf[sizeof(long)*3 + 2]; int len = sprintf(buf, "%lu\n", (long)getpid()); write(fd, buf, len); - close(fd); + ftruncate(fd, len); + /* we leak opened+locked fd intentionally */ return 0; } - /* something went wrong */ perror_msg("Can't open '%s'", VAR_RUN_PIDFILE); return -1; } -static int Lock() -{ - int lfd = open(VAR_RUN_LOCK_FILE, O_RDWR|O_CREAT, 0640); - if (lfd < 0) - { - perror_msg("Can't open '%s'", VAR_RUN_LOCK_FILE); - return -1; - } - if (lockf(lfd, F_TLOCK, 0) < 0) - { - perror_msg("Can't lock file '%s'", VAR_RUN_LOCK_FILE); - return -1; - } - close_on_exec_on(lfd); - return 0; - /* we leak opened lfd intentionally */ -} - static void handle_signal(int signo) { int save_errno = errno; @@ -433,12 +260,22 @@ static gboolean handle_signal_cb(GIOChannel *gio, GIOCondition condition, gpoint s_exiting = 1; else { - if (socket_client_count) - socket_client_count--; - if (!socket_channel_cb_id) + pid_t pid; + while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) { - log("Accepting connections on '%s'", SOCKET_FILE); - socket_channel_cb_id = add_watch_or_die(socket_channel, G_IO_IN | G_IO_PRI, server_socket_cb); + if (pid == log_scanner_pid) + { + log("log scanner exited"); + log_scanner_pid = -1; + continue; + } + if (socket_client_count) + socket_client_count--; + if (!socket_channel_cb_id) + { + log("Accepting connections on '%s'", SOCKET_FILE); + socket_channel_cb_id = add_watch_or_die(socket_channel, G_IO_IN | G_IO_PRI, server_socket_cb); + } } } return TRUE; @@ -531,76 +368,63 @@ static gboolean handle_inotify_cb(GIOChannel *gio, GIOCondition condition, gpoin && worst_dir ) { log("Size of '%s' >= %u MB, deleting '%s'", DEBUG_DUMPS_DIR, g_settings_nMaxCrashReportsSize, worst_dir); - g_pCommLayer->QuotaExceed(_("The size of the report exceeded the quota. Please check system's MaxCrashReportsSize value in abrt.conf.")); + send_dbus_sig_QuotaExceeded(_("The size of the report exceeded the quota. Please check system's MaxCrashReportsSize value in abrt.conf.")); /* deletes both directory and DB record */ char *d = concat_path_file(DEBUG_DUMPS_DIR, worst_dir); free(worst_dir); worst_dir = NULL; - delete_crash_dump_dir(d); + delete_dump_dir(d); free(d); } } char *fullname = NULL; - try + crash_data_t *crash_data = NULL; + fullname = concat_path_file(DEBUG_DUMPS_DIR, name); + mw_result_t res = LoadDebugDump(fullname, &crash_data); + switch (res) { - fullname = concat_path_file(DEBUG_DUMPS_DIR, name); - map_crash_data_t crashinfo; - mw_result_t res = LoadDebugDump(fullname, crashinfo); - switch (res) - { - case MW_OK: - log("New crash %s, processing", fullname); - /* Fall through */ + case MW_OK: + log("New crash %s, processing", fullname); + /* Fall through */ - case MW_OCCURRED: /* dup */ + case MW_OCCURRED: /* dup */ + { + if (res != MW_OK) { - if (res != MW_OK) - { - const char *first = get_crash_data_item_content_or_NULL(crashinfo, CD_DUMPDIR); - log("Deleting crash %s (dup of %s), sending dbus signal", - strrchr(fullname, '/') + 1, - strrchr(first, '/') + 1); - delete_crash_dump_dir(fullname); - } - - const char *uid_str = get_crash_data_item_content_or_NULL(crashinfo, FILENAME_UID); - const char *inform_all = get_crash_data_item_content_or_NULL(crashinfo, FILENAME_INFORMALL); - - if (inform_all && string_to_bool(inform_all)) - uid_str = NULL; - char *crash_id = xasprintf("%s:%s", - get_crash_data_item_content_or_NULL(crashinfo, FILENAME_UID), - get_crash_data_item_content_or_NULL(crashinfo, FILENAME_UUID) - ); - /* Send dbus signal */ - g_pCommLayer->Crash(get_crash_data_item_content_or_NULL(crashinfo, FILENAME_PACKAGE), - crash_id, //TODO: stop passing this param, it is unused - fullname, - uid_str - ); - free(crash_id); - break; + const char *first = get_crash_item_content_or_NULL(crash_data, CD_DUMPDIR); + log("Deleting crash %s (dup of %s), sending dbus signal", + strrchr(fullname, '/') + 1, + strrchr(first, '/') + 1); + delete_dump_dir(fullname); } - case MW_CORRUPTED: - case MW_GPG_ERROR: - default: - log("Corrupted or bad crash %s (res:%d), deleting", fullname, (int)res); - delete_crash_dump_dir(fullname); - break; + + const char *uid_str = get_crash_item_content_or_NULL(crash_data, FILENAME_UID); + const char *inform_all = get_crash_item_content_or_NULL(crash_data, FILENAME_INFORMALL); + + if (inform_all && string_to_bool(inform_all)) + uid_str = NULL; + char *crash_id = xasprintf("%s:%s", + get_crash_item_content_or_NULL(crash_data, FILENAME_UID), + get_crash_item_content_or_NULL(crash_data, FILENAME_UUID) + ); + send_dbus_sig_Crash(get_crash_item_content_or_NULL(crash_data, FILENAME_PACKAGE), + crash_id, //TODO: stop passing this param, it is unused + fullname, + uid_str + ); + free(crash_id); + break; } - } - catch (CABRTException& e) - { - error_msg("%s", e.what()); - } - catch (...) - { - free(fullname); - free(buf); - throw; + case MW_CORRUPTED: + case MW_GPG_ERROR: + default: + log("Corrupted or bad crash %s (res:%d), deleting", fullname, (int)res); + delete_dump_dir(fullname); + break; } free(fullname); + free_crash_data(crash_data); } /* while */ free(buf); @@ -636,10 +460,10 @@ static void run_main_loop(GMainLoop* loop) fds = (GPollFD *)xrealloc(fds, fds_size * sizeof(fds[0])); } - if (s_timeout) + if (s_timeout != 0) alarm(s_timeout); g_poll(fds, nfds, timeout); - if (s_timeout) + if (s_timeout != 0) alarm(0); some_ready = g_main_context_check(context, max_priority, fds, nfds); @@ -659,8 +483,9 @@ static void start_syslog_logging() * Otherwise fprintf(stderr) dumps messages into random fds, etc. */ xdup2(STDIN_FILENO, STDOUT_FILENO); xdup2(STDIN_FILENO, STDERR_FILENO); - openlog("abrtd", 0, LOG_DAEMON); + openlog(PROGNAME, 0, LOG_DAEMON); logmode = LOGMODE_SYSLOG; + putenv((char*)"ABRT_SYSLOG=1"); } static void ensure_writable_dir(const char *dir, mode_t mode, const char *user) @@ -690,28 +515,11 @@ static void sanitize_dump_dir_rights() /* 00777 bits are usual "rwxrwxrwx" access rights */ ensure_writable_dir(DEBUG_DUMPS_DIR, 0755, "abrt"); /* debuginfo cache */ - ensure_writable_dir(DEBUG_INFO_DIR, 0755, "root"); + ensure_writable_dir(DEBUG_INFO_DIR, 0775, "abrt"); /* temp dir */ ensure_writable_dir(VAR_RUN"/abrt", 0755, "root"); } -static char *timeout_opt; -static const char* abrtd_usage = _("abrtd [options]"); -enum { - OPT_v = 1 << 0, - OPT_d = 1 << 1, - OPT_s = 1 << 2, - OPT_t = 1 << 3, -}; -/* Keep enum above and order of options below in sync! */ -static struct options abrtd_options[] = { - OPT__VERBOSE(&g_verbose), - OPT_BOOL( 'd' , 0, NULL, _("Do not daemonize")), - OPT_BOOL( 's' , 0, NULL, _("Log to syslog even with -d")), - OPT_INTEGER( 't' , 0, &timeout_opt, _("Exit after SEC seconds of inactivity")), - OPT_END() -}; - int main(int argc, char** argv) { int parent_pid = getpid(); @@ -730,26 +538,37 @@ int main(int argc, char** argv) if (env_verbose) g_verbose = atoi(env_verbose); - unsigned opts = parse_opts(argc, argv, abrtd_options, abrtd_usage); - - if (opts & OPT_s) - start_syslog_logging(); - + const char *program_usage_string = _( + PROGNAME" [options]" + ); + enum { + OPT_v = 1 << 0, + OPT_d = 1 << 1, + OPT_s = 1 << 2, + OPT_t = 1 << 3, + }; + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_BOOL( 'd', NULL, NULL , _("Do not daemonize")), + OPT_BOOL( 's', NULL, NULL , _("Log to syslog even with -d")), + OPT_INTEGER('t', NULL, &s_timeout, _("Exit after SEC seconds of inactivity")), + OPT_END() + }; + unsigned opts = parse_opts(argc, argv, program_options, program_usage_string); + + unsetenv("ABRT_SYSLOG"); + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); /* When dbus daemon starts us, it doesn't set PATH * (I saw it set only DBUS_STARTER_ADDRESS and DBUS_STARTER_BUS_TYPE). * In this case, set something sane: */ - /* Need to add LIBEXEC_DIR to PATH, because otherwise abrt-action-* - * are not found by exec() - */ const char *env_path = getenv("PATH"); if (!env_path || !env_path[0]) - env_path = "/usr/sbin:/usr/bin:/sbin:/bin"; - putenv(xasprintf("PATH=%s:%s", LIBEXEC_DIR, env_path)); - - putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); - - msg_prefix = "abrtd"; /* for log(), error_msg() and such */ + putenv((char*)"PATH=/usr/sbin:/usr/bin:/sbin:/bin"); + msg_prefix = PROGNAME; /* for log(), error_msg() and such */ + if (opts & OPT_s) + start_syslog_logging(); xpipe(s_signal_pipe); close_on_exec_on(s_signal_pipe[0]); @@ -757,7 +576,7 @@ int main(int argc, char** argv) signal(SIGTERM, handle_signal); signal(SIGINT, handle_signal); signal(SIGCHLD, handle_signal); - if (s_timeout) + if (s_timeout != 0) signal(SIGALRM, handle_signal); /* Daemonize unless -d */ @@ -799,37 +618,43 @@ int main(int argc, char** argv) guint channel_inotify_event_id = 0; GIOChannel* channel_signal = NULL; guint channel_signal_event_id = 0; - bool lockfile_created = false; bool pidfile_created = false; - CCrashWatcher watcher; /* Initialization */ try { - init_daemon_logging(&watcher); + init_daemon_logging(); VERB1 log("Loading settings"); - if (LoadSettings() != 0) + if (load_settings() != 0) throw 1; + sanitize_dump_dir_rights(); + VERB1 log("Creating glib main loop"); pMainloop = g_main_loop_new(NULL, FALSE); VERB1 log("Initializing inotify"); - sanitize_dump_dir_rights(); errno = 0; int inotify_fd = inotify_init(); if (inotify_fd == -1) perror_msg_and_die("inotify_init failed"); close_on_exec_on(inotify_fd); + /* Watching DEBUG_DUMPS_DIR for new files... */ if (inotify_add_watch(inotify_fd, DEBUG_DUMPS_DIR, IN_CREATE | IN_MOVED_TO) < 0) - perror_msg_and_die("inotify_add_watch failed on '%s'", DEBUG_DUMPS_DIR); + { + perror_msg("inotify_add_watch failed on '%s'", DEBUG_DUMPS_DIR); + throw 1; + } if (g_settings_sWatchCrashdumpArchiveDir) { s_upload_watch = inotify_add_watch(inotify_fd, g_settings_sWatchCrashdumpArchiveDir, IN_CLOSE_WRITE|IN_MOVED_TO); if (s_upload_watch < 0) - perror_msg_and_die("inotify_add_watch failed on '%s'", g_settings_sWatchCrashdumpArchiveDir); + { + perror_msg("inotify_add_watch failed on '%s'", g_settings_sWatchCrashdumpArchiveDir); + throw 1; + } } VERB1 log("Adding inotify watch to glib main loop"); channel_inotify = g_io_channel_unix_new(inotify_fd); @@ -838,13 +663,6 @@ int main(int argc, char** argv) handle_inotify_cb, NULL); - VERB1 log("Loading plugins from "PLUGINS_LIB_DIR); - g_pPluginManager = new CPluginManager(); - g_pPluginManager->LoadPlugins(); - - if (SetUpCron() != 0) - throw 1; - /* Add an event source which waits for INT/TERM signal */ VERB1 log("Adding signal pipe watch to glib main loop"); channel_signal = g_io_channel_unix_new(s_signal_pipe[0]); @@ -854,12 +672,8 @@ int main(int argc, char** argv) NULL); /* Mark the territory */ - VERB1 log("Creating lock file"); - if (Lock() != 0) - throw 1; - lockfile_created = true; VERB1 log("Creating pid file"); - if (CreatePidFile() != 0) + if (create_pidfile() != 0) throw 1; pidfile_created = true; @@ -870,8 +684,7 @@ int main(int argc, char** argv) * therefore it should be the last thing to initialize. */ VERB1 log("Initializing dbus"); - g_pCommLayer = new CCommLayerServerDBus(); - if (g_pCommLayer->m_init_error) + if (init_dbus() != 0) throw 1; } catch (...) @@ -896,21 +709,26 @@ int main(int argc, char** argv) /* Only now we want signal pipe to work */ s_signal_pipe_write = s_signal_pipe[1]; - /* Enter the event loop */ - try - { - log("Init complete, entering main loop"); - run_main_loop(pMainloop); - } - catch (CABRTException& e) + if (g_settings_sLogScanners) { - error_msg("Error: %s", e.what()); - } - catch (std::exception& e) - { - error_msg("Error: %s", e.what()); + const char *scanner_argv[] = { + "/bin/sh", "-c", + g_settings_sLogScanners, + NULL + }; + log_scanner_pid = fork_execv_on_steroids(EXECFLG_INPUT_NUL, + (char**)scanner_argv, + /*pipefds:*/ NULL, + /*unsetenv_vec:*/ NULL, + /*dir:*/ NULL, + /*uid:*/ 0); + VERB1 log("Started log scanner, pid:%d", (int)log_scanner_pid); } + /* Enter the event loop */ + log("Init complete, entering main loop"); + run_main_loop(pMainloop); + cleanup: /* Error or INT/TERM. Clean up, in reverse order. * Take care to not undo things we did not do. @@ -918,8 +736,6 @@ int main(int argc, char** argv) dumpsocket_shutdown(); if (pidfile_created) unlink(VAR_RUN_PIDFILE); - if (lockfile_created) - unlink(VAR_RUN_LOCK_FILE); if (channel_signal_event_id > 0) g_source_remove(channel_signal_event_id); @@ -930,21 +746,23 @@ int main(int argc, char** argv) if (channel_inotify) g_io_channel_unref(channel_inotify); - delete g_pCommLayer; - if (g_pPluginManager) - { - /* This restores /proc/sys/kernel/core_pattern, among other things: */ - g_pPluginManager->UnLoadPlugins(); - delete g_pPluginManager; - } + deinit_dbus(); + if (pMainloop) g_main_loop_unref(pMainloop); - settings_free(); + free_settings(); + + if (log_scanner_pid > 0) + { + VERB2 log("Sending SIGTERM to %d", log_scanner_pid); + kill(log_scanner_pid, SIGTERM); + } + /* Exiting */ if (s_sig_caught && s_sig_caught != SIGALRM && s_sig_caught != SIGCHLD) { - error_msg_and_die("Got signal %d, exiting", s_sig_caught); + error_msg("Got signal %d, exiting", s_sig_caught); signal(s_sig_caught, SIG_DFL); raise(s_sig_caught); } diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am index 34c7aa7b..6fe73065 100644 --- a/src/daemon/Makefile.am +++ b/src/daemon/Makefile.am @@ -1,24 +1,22 @@ bin_SCRIPTS = \ abrt-handle-upload -libexec_PROGRAMS = \ - abrt-action-save-package-data bin_PROGRAMS = \ - abrt-handle-crashdump + abrt-handle-crashdump \ + abrt-action-save-package-data -sbin_PROGRAMS = abrtd \ +sbin_PROGRAMS = \ + abrtd \ abrt-server abrtd_SOURCES = \ - PluginManager.h PluginManager.cpp \ MiddleWare.h MiddleWare.cpp \ - CrashWatcher.h CrashWatcher.cpp \ - CommLayerServer.h CommLayerServer.cpp \ CommLayerServerDBus.h CommLayerServerDBus.cpp \ Settings.h Settings.cpp \ - Daemon.h Daemon.cpp + comm_layer_inner.h comm_layer_inner.cpp \ + Daemon.cpp abrtd_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DBIN_DIR=\"$(bindir)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ @@ -36,14 +34,13 @@ abrtd_CPPFLAGS = \ abrtd_LDADD = \ $(DL_LIBS) \ $(DBUS_LIBS) \ - ../lib/libabrt.la \ - ../lib/libabrt_daemon.la \ + ../lib/libreport.la \ ../lib/libabrt_dbus.la abrt_server_SOURCES = \ abrt-server.c abrt_server_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DBIN_DIR=\"$(bindir)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ @@ -52,15 +49,16 @@ abrt_server_CPPFLAGS = \ -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + $(GLIB_CFLAGS) \ -D_GNU_SOURCE \ -Wall -Werror abrt_server_LDADD = \ - ../lib/libabrt.la + ../lib/libreport.la abrt_handle_crashdump_SOURCES = \ abrt-handle-crashdump.c abrt_handle_crashdump_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DBIN_DIR=\"$(bindir)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ @@ -71,17 +69,18 @@ abrt_handle_crashdump_CPPFLAGS = \ -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ -DLIBEXEC_DIR=\"$(LIBEXEC_DIR)\" \ + $(GLIB_CFLAGS) \ -D_GNU_SOURCE \ -Wall -Werror abrt_handle_crashdump_LDADD = \ - ../lib/libabrt.la + ../lib/libreport.la abrt_action_save_package_data_SOURCES = \ rpm.h rpm.c \ Settings.h Settings.cpp \ - abrt-action-save-package-data.cpp + abrt-action-save-package-data.c abrt_action_save_package_data_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DBIN_DIR=\"$(bindir)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ @@ -96,7 +95,7 @@ abrt_action_save_package_data_CPPFLAGS = \ -Wall -Werror abrt_action_save_package_data_LDADD = \ $(RPM_LIBS) \ - ../lib/libabrt.la + ../lib/libreport.la dbusabrtconfdir = ${sysconfdir}/dbus-1/system.d/ dist_dbusabrtconf_DATA = dbus-abrt.conf diff --git a/src/daemon/MiddleWare.cpp b/src/daemon/MiddleWare.cpp index ee2853b2..2757d84f 100644 --- a/src/daemon/MiddleWare.cpp +++ b/src/daemon/MiddleWare.cpp @@ -19,32 +19,21 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "abrtlib.h" -#include "Daemon.h" #include "Settings.h" -#include "abrt_exception.h" #include "comm_layer_inner.h" -#include "CommLayerServer.h" +#include "CommLayerServerDBus.h" #include "MiddleWare.h" using namespace std; /** - * An instance of CPluginManager. When MiddleWare wants to do something - * with plugins, it calls the plugin manager. - * @see PluginManager.h - */ -CPluginManager* g_pPluginManager; - - -/** * Get one crash info. If getting is successful, * then crash info is filled. * @param dump_dir_name A dump dir containing all necessary data. * @param pCrashData A crash info. * @return It return results of operation. See mw_result_t. */ -static mw_result_t FillCrashInfo(const char *dump_dir_name, - map_crash_data_t& pCrashData); +static crash_data_t *FillCrashInfo(const char *dump_dir_name); /** * Transforms a debugdump directory to inner crash @@ -52,36 +41,53 @@ static mw_result_t FillCrashInfo(const char *dump_dir_name, * @param dump_dir_name A debugdump dir containing all necessary data. * @param pCrashData A created crash report. */ -static bool DebugDumpToCrashReport(const char *dump_dir_name, map_crash_data_t& pCrashData) +static crash_data_t *DebugDumpToCrashReport(const char *dump_dir_name) { VERB3 log(" DebugDumpToCrashReport('%s')", dump_dir_name); struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) - return false; - + return NULL; + + static const char *const must_have_files[] = { + FILENAME_ARCHITECTURE, + FILENAME_KERNEL , + FILENAME_PACKAGE , + FILENAME_COMPONENT , + FILENAME_OS_RELEASE , + FILENAME_EXECUTABLE , + NULL + }; const char *const *v = must_have_files; while (*v) { if (!dd_exist(dd, *v)) { + /* Old dump dir format compat. Remove in abrt-2.1 */ + if (strcmp(*v, FILENAME_OS_RELEASE) == 0) + if (dd_exist(dd, "release")) + goto ok; + dd_close(dd); log("Important file '%s/%s' is missing", dump_dir_name, *v); - return false; + return NULL; } + ok: v++; } - load_crash_data_from_crash_dump_dir(dd, pCrashData); + crash_data_t *crash_data = create_crash_data_from_dump_dir(dd); char *events = list_possible_events(dd, NULL, ""); dd_close(dd); - add_to_crash_data_ext(pCrashData, CD_EVENTS, CD_SYS, CD_ISNOTEDITABLE, events); + add_to_crash_data_ext(crash_data, CD_EVENTS, events, + CD_FLAG_TXT + CD_FLAG_ISNOTEDITABLE); free(events); - add_to_crash_data_ext(pCrashData, CD_DUMPDIR, CD_SYS, CD_ISNOTEDITABLE, dump_dir_name); + add_to_crash_data_ext(crash_data, CD_DUMPDIR, dump_dir_name, + CD_FLAG_TXT + CD_FLAG_ISNOTEDITABLE); - return true; + return crash_data; } static char *do_log_and_update_client(char *log_line, void *param) @@ -91,22 +97,36 @@ static char *do_log_and_update_client(char *log_line, void *param) return log_line; } +/** + * Takes care of getting all additional data needed + * for computing UUIDs and creating a report for particular analyzer + * plugin. This report could be send somewhere afterwards. If a creation + * is successful, then a crash report is filled. + * @param pAnalyzer A name of an analyzer plugin. + * @param pDebugDumpPath A debugdump dir containing all necessary data. + * @param pCrashData A filled crash report. + * @return It return results of operation. See mw_result_t. + */ /* * Called in two cases: * (1) by StartJob dbus call -> CreateReportThread(), in the thread * (2) by CreateReport dbus call */ -mw_result_t CreateCrashReport(const char *dump_dir_name, +static mw_result_t CreateCrashReport(const char *dump_dir_name, long caller_uid, int force, - map_crash_data_t& pCrashData) + crash_data_t **crash_data) { - VERB2 log("CreateCrashReport('%s',%ld,result)", dump_dir_name, caller_uid); + VERB2 log("CreateCrashReport('%s',%ld)", dump_dir_name, caller_uid); + + *crash_data = NULL; struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) return MW_NOENT_ERROR; + struct run_event_state *run_state; + int res; mw_result_t r = MW_OK; if (caller_uid != 0) /* not called by root */ @@ -131,63 +151,33 @@ mw_result_t CreateCrashReport(const char *dump_dir_name, } dd_close(dd); - try + run_state = new_run_event_state(); + run_state->logging_callback = do_log_and_update_client; + res = run_event_on_dir_name(run_state, dump_dir_name, force ? "reanalyze" : "analyze"); + free_run_event_state(run_state); + if (res != 0) { - struct run_event_state *run_state = new_run_event_state(); - run_state->logging_callback = do_log_and_update_client; - int res = run_event(run_state, dump_dir_name, force ? "reanalyze" : "analyze"); - free_run_event_state(run_state); - if (res != 0 && res != -1) /* -1 is "nothing was done", here it is ok */ - { - r = MW_PLUGIN_ERROR; - goto ret; - } - - /* Do a load_crash_data_from_crash_dump_dir from (possibly updated) - * crash dump dir - */ - if (!DebugDumpToCrashReport(dump_dir_name, pCrashData)) - { - error_msg("Error loading crash data"); - r = MW_ERROR; - goto ret; - } + r = MW_PLUGIN_ERROR; + goto ret; } - catch (CABRTException& e) + + /* Do a create_crash_data_from_dump_dir from (possibly updated) + * crash dump dir + */ + *crash_data = DebugDumpToCrashReport(dump_dir_name); + if (!*crash_data) { - r = MW_CORRUPTED; - error_msg("%s", e.what()); - if (e.type() == EXCEP_PLUGIN) - { - r = MW_PLUGIN_ERROR; - } + error_msg("Error loading crash data"); + r = MW_ERROR; } ret: + if (*crash_data == NULL) + *crash_data = new_crash_data(); VERB3 log("CreateCrashReport() returns %d", r); return r; } -void RunAction(const char *pActionDir, - const char *pPluginName, - const char *pPluginArgs) -{ - CAction* action = g_pPluginManager->GetAction(pPluginName); - if (!action) - { - /* GetAction() already complained */ - return; - } - try - { - action->Run(pActionDir, pPluginArgs, /*force:*/ 0); - } - catch (CABRTException& e) - { - error_msg("Execution of '%s' was not successful: %s", pPluginName, e.what()); - } -} - struct logging_state { char *last_line; }; @@ -205,43 +195,55 @@ static char *do_log_and_save_line(char *log_line, void *param) // Do not trust client_report here! // dbus handler passes it from user without checking -report_status_t Report(const map_crash_data_t& client_report, +report_status_t Report(crash_data_t *client_report, const vector_string_t& events, const map_map_string_t& settings, long caller_uid) { - // Get ID fields - const char *UID = get_crash_data_item_content_or_NULL(client_report, FILENAME_UID); - const char *dump_dir_name = get_crash_data_item_content_or_NULL(client_report, CD_DUMPDIR); - if (!UID || !dump_dir_name) + report_status_t ret; + const char *dump_dir_name = get_crash_item_content_or_NULL(client_report, CD_DUMPDIR); + if (!dump_dir_name) { - throw CABRTException(EXCEP_ERROR, "Report(): UID or DUMPDIR is missing in client's report data"); + update_client("Reporting error: %s", "DUMPDIR is missing in client's report data"); + ret[""].push_back("0"); // REPORT_STATUS_IDX_FLAG + ret[""].push_back("DUMPDIR is missing in client's report data"); // REPORT_STATUS_IDX_MSG + return ret; } // Retrieve corresponding stored record - map_crash_data_t stored_report; - mw_result_t r = FillCrashInfo(dump_dir_name, stored_report); - if (r != MW_OK) + crash_data_t *stored_report = FillCrashInfo(dump_dir_name); + if (!stored_report) { return report_status_t(); } // Is it allowed for this user to report? - if (caller_uid != 0 // not called by root - && strcmp(to_string(caller_uid).c_str(), UID) != 0 - ) { - const char *inform_all = get_crash_data_item_content_or_NULL(stored_report, FILENAME_INFORMALL); - if (!inform_all || !string_to_bool(inform_all)) - throw CABRTException(EXCEP_ERROR, "Report(): user with uid %ld can't report crash %s", - caller_uid, dump_dir_name); + if (caller_uid != 0) // not called by root + { + char caller_uid_str[sizeof(long)*3 + 2]; + sprintf(caller_uid_str, "%ld", caller_uid); + if (strcmp(caller_uid_str, get_crash_item_content_or_die(stored_report, FILENAME_UID)) != 0) + { + const char *inform_all = get_crash_item_content_or_NULL(stored_report, FILENAME_INFORMALL); + if (!inform_all || !string_to_bool(inform_all)) + { + free_crash_data(stored_report); + char *errmsg = xasprintf("user with uid %ld can't report crash %s", caller_uid, dump_dir_name); + update_client("Reporting error: %s", errmsg); + ret[""].push_back("0"); // REPORT_STATUS_IDX_FLAG + ret[""].push_back(errmsg); // REPORT_STATUS_IDX_MSG + free(errmsg); + return ret; + } + } } // Save comment, "how to reproduce", backtrace //TODO: we should iterate through stored_report and modify all //modifiable fields which have new data in client_report - const char *comment = get_crash_data_item_content_or_NULL(client_report, FILENAME_COMMENT); - const char *reproduce = get_crash_data_item_content_or_NULL(client_report, FILENAME_REPRODUCE); - const char *backtrace = get_crash_data_item_content_or_NULL(client_report, FILENAME_BACKTRACE); + const char *comment = get_crash_item_content_or_NULL(client_report, FILENAME_COMMENT); + const char *reproduce = get_crash_item_content_or_NULL(client_report, FILENAME_REPRODUCE); + const char *backtrace = get_crash_item_content_or_NULL(client_report, FILENAME_BACKTRACE); if (comment || reproduce || backtrace) { struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); @@ -250,45 +252,47 @@ report_status_t Report(const map_crash_data_t& client_report, if (comment) { dd_save_text(dd, FILENAME_COMMENT, comment); - add_to_crash_data_ext(stored_report, FILENAME_COMMENT, CD_TXT, CD_ISEDITABLE, comment); + add_to_crash_data_ext(stored_report, FILENAME_COMMENT, comment, CD_FLAG_TXT + CD_FLAG_ISEDITABLE); } if (reproduce) { dd_save_text(dd, FILENAME_REPRODUCE, reproduce); - add_to_crash_data_ext(stored_report, FILENAME_REPRODUCE, CD_TXT, CD_ISEDITABLE, reproduce); + add_to_crash_data_ext(stored_report, FILENAME_REPRODUCE, reproduce, CD_FLAG_TXT + CD_FLAG_ISEDITABLE); } if (backtrace) { dd_save_text(dd, FILENAME_BACKTRACE, backtrace); - add_to_crash_data_ext(stored_report, FILENAME_BACKTRACE, CD_TXT, CD_ISEDITABLE, backtrace); + add_to_crash_data_ext(stored_report, FILENAME_BACKTRACE, backtrace, CD_FLAG_TXT + CD_FLAG_ISEDITABLE); } dd_close(dd); } } /* Remove BIN filenames from stored_report if they are not present in client's data */ - map_crash_data_t::const_iterator its = stored_report.begin(); - while (its != stored_report.end()) + GHashTableIter iter; + char *name; + struct crash_item *value; + g_hash_table_iter_init(&iter, stored_report); + while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value)) { - if (its->second[CD_TYPE] == CD_BIN) + if (value->flags & CD_FLAG_BIN) { - std::string key = its->first; - if (get_crash_data_item_content_or_NULL(client_report, key.c_str()) == NULL) + if (get_crash_item_content_or_NULL(client_report, name) == NULL) { /* client does not have it -> does not want it passed to events */ - VERB3 log("Won't report BIN file %s:'%s'", key.c_str(), its->second[CD_CONTENT].c_str()); - its++; /* move off the element we will erase */ - stored_report.erase(key); + VERB3 log("Won't report BIN file %s:'%s'", name, value->content); + g_hash_table_iter_remove(&iter); continue; } } - its++; } VERB3 { - log_map_crash_data(client_report, " client_report"); - log_map_crash_data(stored_report, " stored_report"); + log_crash_data(client_report, " client_report"); + log_crash_data(stored_report, " stored_report"); } + free_crash_data(stored_report); +#define stored_report stored_report_must_not_be_used_below #define client_report client_report_must_not_be_used_below // Export overridden settings as environment variables @@ -310,7 +314,6 @@ report_status_t Report(const map_crash_data_t& client_report, // Run events bool at_least_one_reporter_succeeded = false; - report_status_t ret; std::string message; struct logging_state l_state; struct run_event_state *run_state = new_run_event_state(); @@ -321,10 +324,11 @@ report_status_t Report(const map_crash_data_t& client_report, std::string event = events[i]; l_state.last_line = NULL; - int r = run_event(run_state, dump_dir_name, event.c_str()); - if (r == -1) + int r = run_event_on_dir_name(run_state, dump_dir_name, event.c_str()); + if (r == 0 && run_state->children_count == 0) { l_state.last_line = xasprintf("Error: no processing is specified for event '%s'", event.c_str()); + r = -1; } if (r == 0) { @@ -390,6 +394,7 @@ report_status_t Report(const map_crash_data_t& client_report, } return ret; +#undef stored_report #undef client_report } @@ -470,8 +475,7 @@ static char *do_log(char *log_line, void *param) return log_line; } -mw_result_t LoadDebugDump(const char *dump_dir_name, - map_crash_data_t& pCrashData) +mw_result_t LoadDebugDump(const char *dump_dir_name, crash_data_t **crash_data) { mw_result_t res; @@ -492,7 +496,7 @@ mw_result_t LoadDebugDump(const char *dump_dir_name, run_state->post_run_callback = is_crash_a_dup; run_state->post_run_param = &state; run_state->logging_callback = do_log; - int r = run_event(run_state, dump_dir_name, "post-create"); + int r = run_event_on_dir_name(run_state, dump_dir_name, "post-create"); free_run_event_state(run_state); //TODO: consider this case: @@ -527,7 +531,7 @@ mw_result_t LoadDebugDump(const char *dump_dir_name, dump_dir_name = state.crash_dump_dup_name; } - /* Loads pCrashData (from the *first debugdump dir* if this one is a dup) + /* Loads crash_data (from the *first debugdump dir* if this one is a dup) * Returns: * MW_OCCURRED: "crash count is != 1" (iow: it is > 1 - dup) * MW_OK: "crash count is 1" (iow: this is a new crash, not a dup) @@ -548,9 +552,10 @@ mw_result_t LoadDebugDump(const char *dump_dir_name, dd_save_text(dd, FILENAME_COUNT, new_count_str); dd_close(dd); - res = FillCrashInfo(dump_dir_name, pCrashData); - if (res == MW_OK) + *crash_data = FillCrashInfo(dump_dir_name); + if (*crash_data != NULL) { + res = MW_OK; if (count > 1) { log("Crash dump is a duplicate of %s", dump_dir_name); @@ -568,99 +573,89 @@ mw_result_t LoadDebugDump(const char *dump_dir_name, return res; } -static mw_result_t FillCrashInfo(const char *dump_dir_name, - map_crash_data_t& pCrashData) +static crash_data_t *FillCrashInfo(const char *dump_dir_name) { struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) - return MW_ERROR; + return NULL; - load_crash_data_from_crash_dump_dir(dd, pCrashData); + crash_data_t *crash_data = create_crash_data_from_dump_dir(dd); char *events = list_possible_events(dd, NULL, ""); dd_close(dd); - add_to_crash_data_ext(pCrashData, CD_EVENTS, CD_SYS, CD_ISNOTEDITABLE, events); + add_to_crash_data_ext(crash_data, CD_EVENTS, events, + CD_FLAG_TXT + CD_FLAG_ISNOTEDITABLE); free(events); - add_to_crash_data_ext(pCrashData, CD_DUMPDIR, CD_SYS, CD_ISNOTEDITABLE, dump_dir_name); + add_to_crash_data_ext(crash_data, CD_DUMPDIR, dump_dir_name, + CD_FLAG_TXT + CD_FLAG_ISNOTEDITABLE); - return MW_OK; + return crash_data; } -vector_map_crash_data_t GetCrashInfos(long caller_uid) +vector_of_crash_data_t *GetCrashInfos(long caller_uid) { - vector_map_crash_data_t retval; + vector_of_crash_data_t *retval = new_vector_of_crash_data(); log("Getting crash infos..."); DIR *dir = opendir(DEBUG_DUMPS_DIR); if (dir != NULL) { - try + struct dirent *dent; + while ((dent = readdir(dir)) != NULL) { - struct dirent *dent; - while ((dent = readdir(dir)) != NULL) - { - if (dot_or_dotdot(dent->d_name)) - continue; /* skip "." and ".." */ + if (dot_or_dotdot(dent->d_name)) + continue; /* skip "." and ".." */ - char *dump_dir_name = concat_path_file(DEBUG_DUMPS_DIR, dent->d_name); + char *dump_dir_name = concat_path_file(DEBUG_DUMPS_DIR, dent->d_name); - struct stat statbuf; - if (stat(dump_dir_name, &statbuf) != 0 - || !S_ISDIR(statbuf.st_mode) - ) { - goto next; /* not a dir, skip */ - } + struct stat statbuf; + if (stat(dump_dir_name, &statbuf) != 0 + || !S_ISDIR(statbuf.st_mode) + ) { + goto next; /* not a dir, skip */ + } - /* Skip directories which are not for this uid */ - if (caller_uid != 0) /* not called by root? */ - { - char *uid; - char caller_uid_str[sizeof(long) * 3 + 2]; + /* Skip directories which are not for this uid */ + if (caller_uid != 0) /* not called by root? */ + { + char *uid; + char caller_uid_str[sizeof(long) * 3 + 2]; - struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); - if (!dd) - goto next; + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + goto next; - sprintf(caller_uid_str, "%ld", caller_uid); - uid = dd_load_text(dd, FILENAME_UID); - if (strcmp(uid, caller_uid_str) != 0) + sprintf(caller_uid_str, "%ld", caller_uid); + uid = dd_load_text(dd, FILENAME_UID); + if (strcmp(uid, caller_uid_str) != 0) + { + char *inform_all = dd_load_text_ext(dd, FILENAME_INFORMALL, DD_FAIL_QUIETLY); + bool for_all = string_to_bool(inform_all); + free(inform_all); + if (!for_all) { - char *inform_all = dd_load_text_ext(dd, FILENAME_INFORMALL, DD_FAIL_QUIETLY); - bool for_all = string_to_bool(inform_all); - free(inform_all); - if (!for_all) - { - dd_close(dd); - goto next; - } + dd_close(dd); + goto next; } - dd_close(dd); } + dd_close(dd); + } + { + crash_data_t *crash_data = FillCrashInfo(dump_dir_name); + if (!crash_data) { - map_crash_data_t info; - mw_result_t res = FillCrashInfo(dump_dir_name, info); - switch (res) - { - case MW_OK: - retval.push_back(info); - break; - case MW_ERROR: - error_msg("Dump directory %s doesn't exist or misses crucial files, deleting", dump_dir_name); - delete_crash_dump_dir(dump_dir_name); - break; - default: - break; - } + error_msg("Dump directory %s doesn't exist or misses crucial files, deleting", dump_dir_name); + delete_dump_dir(dump_dir_name); + } + else + { + g_ptr_array_add(retval, crash_data); } - next: - free(dump_dir_name); } - } - catch (CABRTException& e) - { - error_msg("%s", e.what()); + next: + free(dump_dir_name); } closedir(dir); } @@ -676,14 +671,14 @@ vector_map_crash_data_t GetCrashInfos(long caller_uid) * StartJob dbus call already did all the processing, and we just retrieve * the result from dump directory, which is fast. */ -void CreateReport(const char* crash_id, long caller_uid, int force, map_crash_data_t& crashReport) +void CreateReport(const char* crash_id, long caller_uid, int force, crash_data_t **crash_data) { /* FIXME: starting from here, any shared data must be protected with a mutex. */ - mw_result_t res = CreateCrashReport(crash_id, caller_uid, force, crashReport); + mw_result_t res = CreateCrashReport(crash_id, caller_uid, force, crash_data); switch (res) { case MW_OK: - VERB2 log_map_crash_data(crashReport, "crashReport"); + VERB2 log_crash_data(*crash_data, "crashReport"); break; case MW_NOENT_ERROR: error_msg("Can't find crash with id '%s'", crash_id); @@ -715,18 +710,10 @@ static void* create_report(void* arg) /* Client name is per-thread, need to set it */ set_client_name(thread_data->peer); - try - { - log("Creating report..."); - map_crash_data_t crashReport; - CreateReport(thread_data->crash_id, thread_data->caller_uid, thread_data->force, crashReport); - g_pCommLayer->JobDone(thread_data->peer); - } - catch (CABRTException& e) - { - error_msg("%s", e.what()); - } - catch (...) {} + log("Creating report..."); + crash_data_t *crash_data = NULL; + CreateReport(thread_data->crash_id, thread_data->caller_uid, thread_data->force, &crash_data); + send_dbus_sig_JobDone(thread_data->peer); set_client_name(NULL); /* free strduped strings */ @@ -805,10 +792,10 @@ void GetPluginsInfo(map_map_string_t &map_of_plugin_info) struct dirent *dent; while ((dent = readdir(dir)) != NULL) { - if (!is_regular_file(dent, PLUGINS_CONF_DIR)) - continue; char *ext = strrchr(dent->d_name, '.'); - if (!ext || strcmp(ext + 1, PLUGINS_CONF_EXTENSION) != 0) + if (!ext || strcmp(ext + 1, "conf") != 0) + continue; + if (!is_regular_file(dent, PLUGINS_CONF_DIR)) continue; VERB3 log("Found %s", dent->d_name); *ext = '\0'; @@ -853,10 +840,12 @@ void GetPluginsInfo(map_map_string_t &map_of_plugin_info) closedir(dir); } -void GetPluginSettings(const char *plugin_name, map_plugin_settings_t &plugin_settings) +map_string_h *GetPluginSettings(const char *plugin_name) { char *conf_file = xasprintf(PLUGINS_CONF_DIR"/%s.conf", plugin_name); - if (LoadPluginSettings(conf_file, plugin_settings, /*skip w/o value:*/ false)) + map_string_h *settings = new_map_string(); + if (load_conf_file(conf_file, settings, /*skip w/o value:*/ false)) VERB3 log("Loaded %s.conf", plugin_name); free(conf_file); + return settings; } diff --git a/src/daemon/MiddleWare.h b/src/daemon/MiddleWare.h index 808d7be4..fde29978 100644 --- a/src/daemon/MiddleWare.h +++ b/src/daemon/MiddleWare.h @@ -23,7 +23,6 @@ #define MIDDLEWARE_H_ #include "abrt_types.h" -#include "PluginManager.h" /** * An enum contains all return codes. @@ -46,29 +45,6 @@ typedef enum { /** - * Takes care of getting all additional data needed - * for computing UUIDs and creating a report for particular analyzer - * plugin. This report could be send somewhere afterwards. If a creation - * is successful, then a crash report is filled. - * @param pAnalyzer A name of an analyzer plugin. - * @param pDebugDumpPath A debugdump dir containing all necessary data. - * @param pCrashData A filled crash report. - * @return It return results of operation. See mw_result_t. - */ -mw_result_t CreateCrashReport(const char *dump_dir_name, - long caller_uid, - int force, - map_crash_data_t& pCrashData); -/** - * Activates particular action plugin. - * @param pActionDir A directory, which is passed as working to a action plugin. - * @param pPluginName An action plugin name. - * @param pPluginArgs Action plugin's arguments. - */ -void RunAction(const char *pActionDir, - const char *pPluginName, - const char *pPluginArgs); -/** * Reports a crash report to particular receiver. It * takes an user uid, tries to find user config file and load it. If it * fails, then default config is used. If pUID is emply string, default @@ -76,16 +52,14 @@ void RunAction(const char *pActionDir, * ...). * @param crash_data * A crash report. - * @param reporters - * List of allowed reporters. Which reporters will be used depends - * on the analyzer of the crash_data. Reporters missing from this list - * will not be used. + * @param events + * List of events to run. * @param caller_uid * An user uid. * @return - * A report status, which reporters ends successfuly with messages. + * A report status: which events finished successfully, with messages. */ -report_status_t Report(const map_crash_data_t& crash_data, +report_status_t Report(crash_data_t *crash_data, const vector_string_t& events, const map_map_string_t& settings, long caller_uid); @@ -97,15 +71,14 @@ report_status_t Report(const map_crash_data_t& crash_data, * @param pCrashData A crash info. * @return It return results of operation. See mw_result_t. */ -mw_result_t LoadDebugDump(const char *dump_dir_name, - map_crash_data_t& pCrashData); +mw_result_t LoadDebugDump(const char *dump_dir_name, crash_data_t **crash_data); -vector_map_crash_data_t GetCrashInfos(long caller_uid); +vector_of_crash_data_t *GetCrashInfos(long caller_uid); int CreateReportThread(const char* dump_dir_name, long caller_uid, int force, const char* pSender); -void CreateReport(const char* dump_dir_name, long caller_uid, int force, map_crash_data_t&); +void CreateReport(const char* dump_dir_name, long caller_uid, int force, crash_data_t **crash_data); int DeleteDebugDump(const char *dump_dir_name, long caller_uid); void GetPluginsInfo(map_map_string_t &map_of_plugin_info); -void GetPluginSettings(const char *plugin_name, map_plugin_settings_t &plugin_settings); +map_string_h *GetPluginSettings(const char *plugin_name); #endif /*MIDDLEWARE_H_*/ diff --git a/src/daemon/PluginManager.cpp b/src/daemon/PluginManager.cpp deleted file mode 100644 index 665a4625..00000000 --- a/src/daemon/PluginManager.cpp +++ /dev/null @@ -1,259 +0,0 @@ -/* - PluginManager.cpp - - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) - 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 <dlfcn.h> -#include "abrtlib.h" -#include "abrt_exception.h" -#include "PluginManager.h" - -using namespace std; - - -/** - * CLoadedModule class. A class which contains a loaded plugin. - */ -class CLoadedModule -{ - private: - /* dlopen'ed library */ - void *m_pHandle; - const plugin_info_t *m_pPluginInfo; - CPlugin* (*m_pFnPluginNew)(); - - public: - CLoadedModule(void *handle, const char *mod_name); - ~CLoadedModule() { dlclose(m_pHandle); } - int GetMagicNumber() { return m_pPluginInfo->m_nMagicNumber; } - const char *GetVersion() { return m_pPluginInfo->m_sVersion; } - const char *GetName() { return m_pPluginInfo->m_sName; } - const char *GetDescription() { return m_pPluginInfo->m_sDescription; } - const char *GetEmail() { return m_pPluginInfo->m_sEmail; } - const char *GetWWW() { return m_pPluginInfo->m_sWWW; } - const char *GetGTKBuilder() { return m_pPluginInfo->m_sGTKBuilder; } - plugin_type_t GetType() { return m_pPluginInfo->m_Type; } - CPlugin *PluginNew() { return m_pFnPluginNew(); } -}; -CLoadedModule::CLoadedModule(void *handle, const char *mod_name) -{ - m_pHandle = handle; - /* All errors are fatal */ -#define LOADSYM(fp, handle, name) \ - do { \ - fp = (typeof(fp)) (dlsym(handle, name)); \ - if (!fp) \ - error_msg_and_die("'%s' has no %s entry", mod_name, name); \ - } while (0) - - LOADSYM(m_pPluginInfo, handle, "plugin_info"); - LOADSYM(m_pFnPluginNew, handle, "plugin_new"); -#undef LOADSYM -} - - -/** - * Text representation of plugin types. - */ -static const char *const plugin_type_str[] = { - "Analyzer", - "Action", - "Reporter", - "Database" -}; - - -CPluginManager::CPluginManager() -{} - -CPluginManager::~CPluginManager() -{} - -void CPluginManager::LoadPlugins() -{ - DIR *dir = opendir(PLUGINS_LIB_DIR); - if (dir != NULL) - { - struct dirent *dent; - while ((dent = readdir(dir)) != NULL) - { - if (!is_regular_file(dent, PLUGINS_LIB_DIR)) - continue; - char *ext = strrchr(dent->d_name, '.'); - if (!ext || strcmp(ext + 1, PLUGINS_LIB_EXTENSION) != 0) - continue; - *ext = '\0'; - if (strncmp(dent->d_name, PLUGINS_LIB_PREFIX, sizeof(PLUGINS_LIB_PREFIX)-1) != 0) - continue; - LoadPlugin(dent->d_name + sizeof(PLUGINS_LIB_PREFIX)-1, /*enabled_only:*/ true); - } - closedir(dir); - } -} - -void CPluginManager::UnLoadPlugins() -{ - map_loaded_module_t::iterator it_module; - while ((it_module = m_mapLoadedModules.begin()) != m_mapLoadedModules.end()) - { - UnLoadPlugin(it_module->first.c_str()); - } -} - -CPlugin* CPluginManager::LoadPlugin(const char *pName, bool enabled_only) -{ - map_plugin_t::iterator it_plugin = m_mapPlugins.find(pName); - if (it_plugin != m_mapPlugins.end()) - { - return it_plugin->second; /* ok */ - } - - map_string_t plugin_info; - plugin_info["Name"] = pName; - - const char *conf_name = pName; - if (strncmp(pName, "Kerneloops", sizeof("Kerneloops")-1) == 0) - { - /* Kerneloops{,Scanner,Reporter} share the same .conf file */ - conf_name = "Kerneloops"; - } - map_plugin_settings_t pluginSettings; - string conf_fullname = ssprintf(PLUGINS_CONF_DIR"/%s."PLUGINS_CONF_EXTENSION, conf_name); - LoadPluginSettings(conf_fullname.c_str(), pluginSettings); - m_map_plugin_settings[pName] = pluginSettings; - /* If settings are empty, most likely .conf file does not exist. - * Don't mislead the user: */ - VERB3 if (!pluginSettings.empty()) log("Loaded %s.conf", conf_name); - - if (enabled_only) - { - map_plugin_settings_t::iterator it = pluginSettings.find("Enabled"); - if (it == pluginSettings.end() || !string_to_bool(it->second.c_str())) - { - plugin_info["Enabled"] = "no"; - string empty; - plugin_info["Type"] = empty; - plugin_info["Version"] = empty; - plugin_info["Description"] = empty; - plugin_info["Email"] = empty; - plugin_info["WWW"] = empty; - plugin_info["GTKBuilder"] = empty; - VERB3 log("Plugin %s: 'Enabled' is not set, not loading it (yet)", pName); - return NULL; /* error */ - } - } - - string libPath = ssprintf(PLUGINS_LIB_DIR"/"PLUGINS_LIB_PREFIX"%s."PLUGINS_LIB_EXTENSION, pName); - void *handle = dlopen(libPath.c_str(), RTLD_NOW); - if (!handle) - { - error_msg("Can't load '%s': %s", libPath.c_str(), dlerror()); - return NULL; /* error */ - } - CLoadedModule *module = new CLoadedModule(handle, pName); - if (module->GetMagicNumber() != PLUGINS_MAGIC_NUMBER - || module->GetType() < 0 - || module->GetType() > MAX_PLUGIN_TYPE - ) { - error_msg("Can't load non-compatible plugin %s: magic %d != %d or type %d is not in [0,%d]", - pName, - module->GetMagicNumber(), PLUGINS_MAGIC_NUMBER, - module->GetType(), MAX_PLUGIN_TYPE); - delete module; - return NULL; /* error */ - } - VERB3 log("Loaded plugin %s v.%s", pName, module->GetVersion()); - - CPlugin *plugin = NULL; - try - { - plugin = module->PluginNew(); - plugin->Init(); - plugin->SetSettings(pluginSettings); - } - catch (CABRTException& e) - { - error_msg("Can't initialize plugin %s: %s", - pName, - e.what() - ); - delete plugin; - delete module; - return NULL; /* error */ - } - - plugin_info["Enabled"] = "yes"; - plugin_info["Type"] = plugin_type_str[module->GetType()]; - //plugin_info["Name"] = module->GetName(); - plugin_info["Version"] = module->GetVersion(); - plugin_info["Description"] = module->GetDescription(); - plugin_info["Email"] = module->GetEmail(); - plugin_info["WWW"] = module->GetWWW(); - plugin_info["GTKBuilder"] = module->GetGTKBuilder(); - - m_mapLoadedModules[pName] = module; - m_mapPlugins[pName] = plugin; - log("Registered %s plugin '%s'", plugin_type_str[module->GetType()], pName); - return plugin; /* ok */ -} - -void CPluginManager::UnLoadPlugin(const char *pName) -{ - map_loaded_module_t::iterator it_module = m_mapLoadedModules.find(pName); - if (it_module != m_mapLoadedModules.end()) - { - map_plugin_t::iterator it_plugin = m_mapPlugins.find(pName); - if (it_plugin != m_mapPlugins.end()) /* always true */ - { - it_plugin->second->DeInit(); - delete it_plugin->second; - m_mapPlugins.erase(it_plugin); - } - log("UnRegistered %s plugin %s", plugin_type_str[it_module->second->GetType()], pName); - delete it_module->second; - m_mapLoadedModules.erase(it_module); - } -} - -CAction* CPluginManager::GetAction(const char *pName, bool silent) -{ - CPlugin *plugin = LoadPlugin(pName); - if (!plugin) - { - error_msg("Plugin '%s' is not registered", pName); - return NULL; - } - if (m_mapLoadedModules[pName]->GetType() != ACTION) - { - if (!silent) - error_msg("Plugin '%s' is not an action plugin", pName); - return NULL; - } - return (CAction*)plugin; -} - -plugin_type_t CPluginManager::GetPluginType(const char *pName) -{ - CPlugin *plugin = LoadPlugin(pName); - if (!plugin) - { - throw CABRTException(EXCEP_PLUGIN, "Plugin '%s' is not registered", pName); - } - map_loaded_module_t::iterator it_module = m_mapLoadedModules.find(pName); - return it_module->second->GetType(); -} diff --git a/src/daemon/PluginManager.h b/src/daemon/PluginManager.h deleted file mode 100644 index c5036fbf..00000000 --- a/src/daemon/PluginManager.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - PluginManager.h - header file for plugin manager. it takes care about - (un)loading plugins - - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) - 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 PLUGINMANAGER_H_ -#define PLUGINMANAGER_H_ - -#include "abrt_types.h" -#include "plugin.h" -#include "analyzer.h" -#include "action.h" - -class CLoadedModule; /* opaque */ - -/** - * A class. It takes care of loading, registering and manipulating with - * plugins. When a plugin is loaded, its library is opened, but no plugin - * instance is created. It is possible after plugin registration. - */ -class CPluginManager -{ - private: - typedef std::map<std::string, CLoadedModule*> map_loaded_module_t; - typedef std::map<std::string, CPlugin*> map_plugin_t; - - /** - * Loaded plugins. A key is a plugin name. - */ - map_loaded_module_t m_mapLoadedModules; - /** - * Registered plugins. A key is a plugin name. - */ - map_plugin_t m_mapPlugins; - /** - * List of all possible plugins (loaded or not), with some attributes. - */ - map_map_string_t m_map_plugin_settings; - - public: - /** - * A constructor. - * @param pPluginsConfDir A plugins configuration directory. - * @param pPluginsLibDir A plugins library directory. - */ - CPluginManager(); - /** - * A destructor. - */ - ~CPluginManager(); - /** - * A method, which loads all plugins in plugins library direcotry. - */ - void LoadPlugins(); - /** - * A method, which unregister and unload all loaded plugins. - */ - void UnLoadPlugins(); - /** - * A method, which loads particular plugin. - * @param pName A plugin name. - */ - CPlugin* LoadPlugin(const char *pName, bool enabled_only = false); - /** - * A method, which unloads particular plugin. - * @param pName A plugin name. - */ - void UnLoadPlugin(const char *pName); - /** - * A method, which returns instance of particular action plugin. - * @param pName A plugin name. - * @return An action plugin. - */ - CAction* GetAction(const char *pName, bool silent = false); - /** - * A method, which returns type of particular plugin. - * @param pName A plugin name. - * @return A plugin type. - */ - plugin_type_t GetPluginType(const char *pName); -}; - -#endif /*PLUGINMANAGER_H_*/ diff --git a/src/daemon/Settings.cpp b/src/daemon/Settings.cpp index 5b9972ba..e25b7959 100644 --- a/src/daemon/Settings.cpp +++ b/src/daemon/Settings.cpp @@ -19,8 +19,8 @@ #include "abrtlib.h" #include "Settings.h" -#define SECTION_COMMON "Common" -#define SECTION_CRON "Cron" +#define SECTION_COMMON "Common" +#define SECTION_LOG_SCANNERS "LogScanners" /* Conf file has this format: * [ section_name1 ] @@ -31,32 +31,28 @@ */ /* Static data */ -/* Filled by LoadSettings() */ +/* Filled by load_settings() */ /* map["name"] = "value" strings from [ Common ] section. * If the same name found on more than one line, * the values are appended, separated by comma: map["name"] = "value1,value2" */ static map_string_t s_mapSectionCommon; -/* ... from [ Cron ] */ -static map_string_t s_mapSectionCron; /* Public data */ -/* Written out exactly in this order by SaveSettings() */ /* [ Common ] */ /* one line: "OpenGPGCheck = value" */ bool g_settings_bOpenGPGCheck = false; /* one line: "OpenGPGPublicKeys = value1,value2" */ -GList *g_settings_setOpenGPGPublicKeys = NULL; -GList *g_settings_setBlackListedPkgs = NULL; -GList *g_settings_setBlackListedPaths = NULL; -char *g_settings_sWatchCrashdumpArchiveDir = NULL; +GList * g_settings_setOpenGPGPublicKeys = NULL; +GList * g_settings_setBlackListedPkgs = NULL; +GList * g_settings_setBlackListedPaths = NULL; +char * g_settings_sWatchCrashdumpArchiveDir = NULL; unsigned int g_settings_nMaxCrashReportsSize = 1000; bool g_settings_bProcessUnpackaged = false; -/* [ Cron ] */ -/* many lines, one per key: "map_key = aa_first,bb_first(bb_second),cc_first" */ -map_cron_t g_settings_mapCron; +/* [ LogScanners ] */ +char * g_settings_sLogScanners = NULL; /* @@ -80,107 +76,12 @@ static GList *parse_list(const char* list) } if (item->len > 0) - l = g_list_append(l, xstrdup(item->buf)); + l = g_list_append(l, xstrdup(item->buf)); strbuf_free(item); return l; } -/* Format: name, name(param),name("param with spaces \"and quotes\"") */ -static vector_pair_string_string_t ParseListWithArgs(const char *pValue, int *err) -{ - VERB3 log(" ParseListWithArgs(%s)", pValue); - - vector_pair_string_string_t pluginsWithArgs; - std::string item; - std::string action; - bool is_quote = false; - bool is_arg = false; - for (int ii = 0; pValue[ii]; ii++) - { - if (is_quote && pValue[ii] == '\\' && pValue[ii + 1]) - { - ii++; - item += pValue[ii]; - continue; - } - if (pValue[ii] == '"') - { - is_quote = !is_quote; - /*item += pValue[ii]; - wrong! name("param") must be == name(param) */ - continue; - } - if (is_quote) - { - item += pValue[ii]; - continue; - } - if (pValue[ii] == '(') - { - if (!is_arg) - { - action = item; - item = ""; - is_arg = true; - } - else - { - *err = 1; - error_msg("Parser error: Invalid syntax on column %d in \"%s\"", ii, pValue); - } - - continue; - } - if (pValue[ii] == ')') - { - if (is_arg) - { - VERB3 log(" adding (%s,%s)", action.c_str(), item.c_str()); - pluginsWithArgs.push_back(make_pair(action, item)); - item = ""; - is_arg = false; - action = ""; - } - else - { - *err = 1; - error_msg("Parser error: Invalid syntax on column %d in \"%s\"", ii, pValue); - } - - continue; - } - if (pValue[ii] == ',' && !is_arg) - { - if (item != "") - { - VERB3 log(" adding (%s,%s)", item.c_str(), ""); - pluginsWithArgs.push_back(make_pair(item, "")); - item = ""; - } - continue; - } - item += pValue[ii]; - } - - if (is_quote) - { - *err = 1; - error_msg("Parser error: Unclosed quote in \"%s\"", pValue); - } - - if (is_arg) - { - *err = 1; - error_msg("Parser error: Unclosed argument in \"%s\"", pValue); - } - else if (item != "") - { - VERB3 log(" adding (%s,%s)", item.c_str(), ""); - pluginsWithArgs.push_back(make_pair(item, "")); - } - return pluginsWithArgs; -} - static int ParseCommon() { map_string_t::const_iterator end = s_mapSectionCommon.end(); @@ -207,7 +108,7 @@ static int ParseCommon() it = s_mapSectionCommon.find("MaxCrashReportsSize"); if (it != end) { - g_settings_nMaxCrashReportsSize = xatoi_u(it->second.c_str()); + g_settings_nMaxCrashReportsSize = xatoi_positive(it->second.c_str()); } it = s_mapSectionCommon.find("ProcessUnpackaged"); if (it != end) @@ -217,20 +118,6 @@ static int ParseCommon() return 0; /* no error */ } -static int ParseCron() -{ - map_string_t::iterator it = s_mapSectionCron.begin(); - for (; it != s_mapSectionCron.end(); it++) - { - int err = 0; - vector_pair_string_string_t actionsAndReporters = ParseListWithArgs(it->second.c_str(), &err); - if (err) - return err; - g_settings_mapCron[it->first] = actionsAndReporters; - } - return 0; /* no error */ -} - static void LoadGPGKeys() { FILE *fp = fopen(CONF_DIR"/gpg_keys", "r"); @@ -277,7 +164,7 @@ static int ReadConfigurationFromFile(FILE *fp) value += line[ii]; continue; } - if (isspace(line[ii]) && !is_quote) + if (isspace(line[ii]) && !is_quote && is_key) { continue; } @@ -304,8 +191,9 @@ static int ReadConfigurationFromFile(FILE *fp) section += line[ii]; continue; } - if (line[ii] == '=' && !is_quote) + if (is_key && line[ii] == '=' && !is_quote) { + while (isspace(line[ii + 1])) ii++; is_key = false; key = value; value = ""; @@ -351,11 +239,9 @@ static int ReadConfigurationFromFile(FILE *fp) s_mapSectionCommon[key] += ","; s_mapSectionCommon[key] += value; } - else if (section == SECTION_CRON) + else if (section == SECTION_LOG_SCANNERS) { - if (s_mapSectionCron[key] != "") - s_mapSectionCron[key] += ","; - s_mapSectionCron[key] += value; + g_settings_sLogScanners = xstrdup(value.c_str()); } else { @@ -372,7 +258,7 @@ static int ReadConfigurationFromFile(FILE *fp) } /* abrt daemon loads .conf file */ -int LoadSettings() +int load_settings() { int err = 0; @@ -387,8 +273,6 @@ int LoadSettings() if (err == 0) err = ParseCommon(); - if (err == 0) - err = ParseCron(); if (err == 0) { @@ -411,49 +295,36 @@ map_abrt_settings_t GetSettings() map_abrt_settings_t ABRTSettings; ABRTSettings[SECTION_COMMON] = s_mapSectionCommon; - ABRTSettings[SECTION_CRON] = s_mapSectionCron; return ABRTSettings; } -/* dbus call to change some .conf file data */ -void SetSettings(const map_abrt_settings_t& pSettings, const char *dbus_sender) +///* dbus call to change some .conf file data */ +//void SetSettings(const map_abrt_settings_t& pSettings, const char *dbus_sender) +//{ +// map_abrt_settings_t::const_iterator it = pSettings.find(SECTION_COMMON); +// map_abrt_settings_t::const_iterator end = pSettings.end(); +// if (it != end) +// { +// s_mapSectionCommon = it->second; +// ParseCommon(); +// } +//} + +void free_settings() { - map_abrt_settings_t::const_iterator it = pSettings.find(SECTION_COMMON); - map_abrt_settings_t::const_iterator end = pSettings.end(); - if (it != end) - { - s_mapSectionCommon = it->second; - ParseCommon(); - } - it = pSettings.find(SECTION_CRON); - if (it != end) - { - s_mapSectionCron = it->second; - ParseCron(); - } -} - -void settings_free() -{ - for (GList *li = g_settings_setOpenGPGPublicKeys; li != NULL; li = g_list_next(li)) - free((char*)li->data); - - g_list_free(g_settings_setOpenGPGPublicKeys); + list_free_with_free(g_settings_setOpenGPGPublicKeys); g_settings_setOpenGPGPublicKeys = NULL; - for (GList *li = g_settings_setBlackListedPkgs; li != NULL; li = g_list_next(li)) - free((char*)li->data); - - g_list_free(g_settings_setBlackListedPkgs); + list_free_with_free(g_settings_setBlackListedPkgs); g_settings_setBlackListedPkgs = NULL; - for (GList *li = g_settings_setBlackListedPaths; li != NULL; li = g_list_next(li)) - free((char*)li->data); - - g_list_free(g_settings_setBlackListedPaths); + list_free_with_free(g_settings_setBlackListedPaths); g_settings_setBlackListedPaths = NULL; free(g_settings_sWatchCrashdumpArchiveDir); g_settings_sWatchCrashdumpArchiveDir = NULL; + + free(g_settings_sLogScanners); + g_settings_sLogScanners = NULL; } diff --git a/src/daemon/Settings.h b/src/daemon/Settings.h index 71824c74..dce6407d 100644 --- a/src/daemon/Settings.h +++ b/src/daemon/Settings.h @@ -20,25 +20,33 @@ #define SETTINGS_H_ #include "abrt_types.h" -#include <glib.h> -typedef map_vector_pair_string_string_t map_cron_t; +#ifdef __cplusplus + typedef map_map_string_t map_abrt_settings_t; +// looks unused to me. +// Ok to grep for SetSettings and delete after 2011-04-01. +// void SetSettings(const map_abrt_settings_t& pSettings, const char *dbus_sender); +map_abrt_settings_t GetSettings(); + +extern "C" { +#endif -extern GList *g_settings_setOpenGPGPublicKeys; -extern GList *g_settings_setBlackListedPkgs; -extern GList *g_settings_setBlackListedPaths; +extern GList * g_settings_setOpenGPGPublicKeys; +extern GList * g_settings_setBlackListedPkgs; +extern GList * g_settings_setBlackListedPaths; extern unsigned int g_settings_nMaxCrashReportsSize; extern bool g_settings_bOpenGPGCheck; extern bool g_settings_bProcessUnpackaged; -extern char *g_settings_sWatchCrashdumpArchiveDir; -extern map_cron_t g_settings_mapCron; +extern char * g_settings_sWatchCrashdumpArchiveDir; -int LoadSettings(); -void SaveSettings(); -void SetSettings(const map_abrt_settings_t& pSettings, const char * dbus_sender); -map_abrt_settings_t GetSettings(); +extern char * g_settings_sLogScanners; + +int load_settings(); +void free_settings(); -void settings_free(); +#ifdef __cplusplus +} +#endif #endif diff --git a/src/daemon/abrt-action-save-package-data.cpp b/src/daemon/abrt-action-save-package-data.c index cb880bd3..5be712cf 100644 --- a/src/daemon/abrt-action-save-package-data.cpp +++ b/src/daemon/abrt-action-save-package-data.c @@ -91,7 +91,6 @@ static int SavePackageDescriptionToDebugDump(const char *dump_dir_name) char *package_short_name = NULL; char *component = NULL; char *script_name = NULL; /* only if "interpreter /path/to/script" */ - char *dsc = NULL; /* note: "goto ret" statements below free all the above variables, * but they don't dd_close(dd) */ @@ -100,7 +99,6 @@ static int SavePackageDescriptionToDebugDump(const char *dump_dir_name) component = xstrdup("kernel"); package_full_name = xstrdup("kernel"); package_short_name = xstrdup("kernel"); - dsc = rpm_get_description(package_short_name); } else { @@ -124,21 +122,7 @@ static int SavePackageDescriptionToDebugDump(const char *dump_dir_name) if (!dd) goto ret; /* return 1 (failure) */ dd_save_text(dd, FILENAME_PACKAGE, ""); - dd_save_text(dd, FILENAME_DESCRIPTION, "Crashed executable does not belong to any installed package"); dd_save_text(dd, FILENAME_COMPONENT, ""); -//TODO: move hostname saving to a more logical place - if (!remote) - { - char host[HOST_NAME_MAX + 1]; - int ret = gethostname(host, HOST_NAME_MAX); - if (ret < 0) - { - perror_msg("gethostname"); - host[0] = '\0'; - } - host[HOST_NAME_MAX] = '\0'; - dd_save_text(dd, FILENAME_HOSTNAME, host); - } goto ret0; /* no error */ } log("Executable '%s' doesn't belong to any package", executable); @@ -220,7 +204,6 @@ static int SavePackageDescriptionToDebugDump(const char *dump_dir_name) } component = rpm_get_component(executable); - dsc = rpm_get_description(package_short_name); dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) @@ -231,27 +214,10 @@ static int SavePackageDescriptionToDebugDump(const char *dump_dir_name) { dd_save_text(dd, FILENAME_PACKAGE, package_full_name); } - if (dsc) - { - dd_save_text(dd, FILENAME_DESCRIPTION, dsc); - } if (component) { dd_save_text(dd, FILENAME_COMPONENT, component); } -//TODO: move hostname saving to a more logical place - if (!remote) - { - char host[HOST_NAME_MAX + 1]; - int ret = gethostname(host, HOST_NAME_MAX); - if (ret < 0) - { - perror_msg("gethostname"); - host[0] = '\0'; - } - host[HOST_NAME_MAX] = '\0'; - dd_save_text(dd, FILENAME_HOSTNAME, host); - } dd_close(dd); @@ -262,41 +228,40 @@ static int SavePackageDescriptionToDebugDump(const char *dump_dir_name) free(package_short_name); free(component); free(script_name); - free(dsc); return error; } -static const char *dump_dir_name = "."; -static const char abrt_action_save_package_data_usage[] = - PROGNAME" [options] -d DIR\n" - "\n" - "Query package database and save package name, component, and description"; -enum { - OPT_v = 1 << 0, - OPT_d = 1 << 1, - OPT_s = 1 << 2, -}; -/* Keep enum above and order of options below in sync! */ -static struct options abrt_action_save_package_data_options[] = { - OPT__VERBOSE(&g_verbose), - OPT_STRING('d', NULL, &dump_dir_name, "DIR", "Crash dump directory"), - OPT_BOOL( 's', NULL, NULL, "Log to syslog"), - OPT_END() -}; - int main(int argc, char **argv) { char *env_verbose = getenv("ABRT_VERBOSE"); if (env_verbose) g_verbose = atoi(env_verbose); - unsigned opts = parse_opts(argc, argv, abrt_action_save_package_data_options, - abrt_action_save_package_data_usage); + const char *dump_dir_name = "."; + + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( + PROGNAME" [options] -d DIR\n" + "\n" + "Query package database and save package name, component, and description" + ); + enum { + OPT_v = 1 << 0, + OPT_d = 1 << 1, + OPT_s = 1 << 2, + }; + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_STRING('d', NULL, &dump_dir_name, "DIR", _("Crash dump directory")), + OPT_BOOL( 's', NULL, NULL , _("Log to syslog")), + OPT_END() + }; + unsigned opts = parse_opts(argc, argv, program_options, program_usage_string); putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); msg_prefix = PROGNAME; - if (opts & OPT_s) { openlog(msg_prefix, 0, LOG_DAEMON); @@ -304,8 +269,8 @@ int main(int argc, char **argv) } VERB1 log("Loading settings"); - if (LoadSettings() != 0) - return 1; /* syntax error (looged already by LoadSettings) */ + if (load_settings() != 0) + return 1; /* syntax error (logged already by load_settings) */ VERB1 log("Initializing rpm library"); rpm_init(); diff --git a/src/daemon/abrt-handle-crashdump.c b/src/daemon/abrt-handle-crashdump.c index 2217c67a..e7847c5f 100644 --- a/src/daemon/abrt-handle-crashdump.c +++ b/src/daemon/abrt-handle-crashdump.c @@ -38,11 +38,13 @@ int main(int argc, char **argv) if (env_verbose) g_verbose = atoi(env_verbose); - const char *program_usage = _( + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( PROGNAME" [-vs]" /*" [-c CONFFILE]"*/ " -d DIR -e EVENT\n" " or: "PROGNAME" [-vs]" /*" [-c CONFFILE]"*/ " [-d DIR] -l[PFX]\n" "\n" - "Handle crash dump according to rules in abrt_event.conf"); + "Handle crash dump according to rules in abrt_event.conf" + ); enum { OPT_v = 1 << 0, OPT_s = 1 << 1, @@ -61,11 +63,12 @@ int main(int argc, char **argv) // OPT_STRING( 'c', NULL, &conf_filename, "CONFFILE", _("Configuration file" )), OPT_END() }; - - unsigned opts = parse_opts(argc, argv, program_options, program_usage); + unsigned opts = parse_opts(argc, argv, program_options, program_usage_string); if (!(opts & (OPT_e|OPT_l))) - parse_usage_and_die(program_usage, program_options); + show_usage_and_die(program_usage_string, program_options); + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + msg_prefix = PROGNAME; if (opts & OPT_s) { openlog(msg_prefix, 0, LOG_DAEMON); @@ -87,17 +90,10 @@ int main(int argc, char **argv) /* -e EVENT: run event */ - /* Need to add LIBEXEC_DIR to PATH, because otherwise abrt-action-* - * are not found by exec() - */ - const char *env_path = getenv("PATH"); - if (!env_path) env_path = ""; - putenv(xasprintf("PATH=%s%s%s", LIBEXEC_DIR, (!env_path[0] ? "" : ":"), env_path)); - struct run_event_state *run_state = new_run_event_state(); run_state->logging_callback = do_log; - int r = run_event(run_state, dump_dir_name ? dump_dir_name : ".", event); - if (r == -1) + int r = run_event_on_dir_name(run_state, dump_dir_name ? dump_dir_name : ".", event); + if (r == 0 && run_state->children_count == 0) error_msg_and_die("No actions are found for event '%s'", event); free_run_event_state(run_state); diff --git a/src/daemon/abrt-server.c b/src/daemon/abrt-server.c index fdf66e59..de22f427 100644 --- a/src/daemon/abrt-server.c +++ b/src/daemon/abrt-server.c @@ -16,11 +16,10 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "abrtlib.h" -#include "dump_dir.h" -#include "abrt_crash_dump.h" /* FILENAME_foo */ #include "hooklib.h" #include "parse_options.h" +#define PROGNAME "abrt-server" /* Maximal length of backtrace. */ #define MAX_BACKTRACE_SIZE (1024*1024) @@ -108,9 +107,9 @@ static void create_debug_dump() This directory is renamed to final directory name after all files have been stored into it. */ - char *path = xasprintf(DEBUG_DUMPS_DIR"/%s-%ld-%u.new", + char *path = xasprintf(DEBUG_DUMPS_DIR"/%s-%s-%u.new", dir_basename, - (long)time(NULL), + iso_date_string(NULL), pid); /* No need to check the path length, as all variables used are limited, and dd_create() fails if the path is too long. */ @@ -120,6 +119,7 @@ static void create_debug_dump() { error_msg_and_die("Error creating crash dump %s", path); } + dd_create_basic_files(dd, client_uid); dd_save_text(dd, FILENAME_ANALYZER, analyzer); dd_save_text(dd, FILENAME_EXECUTABLE, executable); @@ -275,31 +275,32 @@ static void process_message(const char *message) static void dummy_handler(int sig_unused) {} -static const char abrt_server_usage[] = "abrt-server [options]"; -enum { - OPT_v = 1 << 0, - OPT_u = 1 << 1, - OPT_s = 1 << 2, -}; -/* Keep enum above and order of options below in sync! */ -static struct options abrt_server_options[] = { - OPT__VERBOSE(&g_verbose), - OPT_INTEGER( 'u' , 0, &client_uid, "Use UID as client uid"), - OPT_BOOL( 's' , 0, NULL, "Log to syslog"), - OPT_END() -}; - int main(int argc, char **argv) { char *env_verbose = getenv("ABRT_VERBOSE"); if (env_verbose) g_verbose = atoi(env_verbose); - unsigned opts = parse_opts(argc, argv, abrt_server_options, - abrt_server_usage); + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( + PROGNAME" [options]" + ); + enum { + OPT_v = 1 << 0, + OPT_u = 1 << 1, + OPT_s = 1 << 2, + }; + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_INTEGER('u', NULL, &client_uid, _("Use UID as client uid")), + OPT_BOOL( 's', NULL, NULL , _("Log to syslog")), + OPT_END() + }; + unsigned opts = parse_opts(argc, argv, program_options, program_usage_string); putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); - msg_prefix = xasprintf("abrt-server[%u]", getpid()); + msg_prefix = xasprintf(PROGNAME"[%u]", getpid()); if (opts & OPT_s) { openlog(msg_prefix, 0, LOG_DAEMON); diff --git a/src/daemon/abrt.conf b/src/daemon/abrt.conf index 07ea51a0..7f5a9c58 100644 --- a/src/daemon/abrt.conf +++ b/src/daemon/abrt.conf @@ -8,7 +8,7 @@ OpenGPGCheck = yes # Blacklisted packages # -BlackList = nspluginwrapper, valgrind, strace +BlackList = nspluginwrapper,valgrind,strace # Process crashes in executables which do not belong to any package? # @@ -16,7 +16,7 @@ ProcessUnpackaged = no # Blacklisted executable paths (shell patterns) # -BlackListedPaths = /usr/share/doc/*, */example* +BlackListedPaths = /usr/share/doc/*,*/example* # Enable this if you want abrtd to auto-unpack crashdump tarballs which appear # in this directory (for example, uploaded via ftp, scp etc). @@ -30,12 +30,7 @@ BlackListedPaths = /usr/share/doc/*, */example* MaxCrashReportsSize = 1000 -# Which Action plugins to run repeatedly +# So far we support only one line here # -[ Cron ] -# h:m - at h:m -# s - every s seconds - -120 = KerneloopsScanner - -#02:00 = FileTransfer +[ LogScanners ] +abrt-dump-oops = abrt-dump-oops -drw /var/log/messages diff --git a/src/daemon/abrt.conf.5 b/src/daemon/abrt.conf.5 index 3f9d8c39..84c8f002 100644 --- a/src/daemon/abrt.conf.5 +++ b/src/daemon/abrt.conf.5 @@ -65,18 +65,6 @@ all the C/C++ programs. .TP .B Kerneloops = \fIplugin\fP This plugin will be used in the case of kernel crashes. -.SS [ Cron ] -This section specifies tasks that will be run at some specified time. You can specify -either a time of day in the format -.br -.B hh:mm = \fIplugin\fP -.br -or an interval -.br -.B ss = \fIplugin2\fP -.br -in this case, \fIplugin2\fP will be run every \fIss\fP seconds (this number -can be greater than 60). .SH "SEE ALSO" .IR abrtd (8), .IR abrt-plugins (7) diff --git a/src/daemon/abrt_event.conf b/src/daemon/abrt_event.conf index 45c9017a..23765718 100644 --- a/src/daemon/abrt_event.conf +++ b/src/daemon/abrt_event.conf @@ -1,33 +1,49 @@ -# This table specifies which programs should be run +# This configuration file specifies which programs should be run # when the specified event occurs in crash dump lifetime. # # Example: # EVENT=post-create { pwd; date; }>/tmp/dt; echo $HOSTNAME `uname -r` # -# Each line may have conditions to be checked -# before the program is run. +# Rule starts with a line with non-space leading character. +# All subsequent lines which start with space or tab form one rule. +# Note that separating newline is *retained*. Example: +# EVENT=post-create date >/tmp/dt # semicolon is not needed here! +# echo $HOSTNAME `uname -r` +# +# Rules may be commented out with #. One # is sufficient to comment out +# even a multi-line rule (no need to comment out every line). +# +# Rule of the form "include GLOB_PATTERN" recurses to each file which matches +# GLOB_PATTERN. Example: "include post-create.d/*.conf" +# + +include events.d/*.conf + +# Any other rules specify which programs to run on the crash dump. +# +# Each rule may have conditions to be checked before the program is run. # # Conditions have form VAR=VAL, where VAR is either word "EVENT" # or a name of crash dump element to be checked (for example, # "executable", "package", hostname" etc). # -# If all conditions match, the program is run in the shell. +# If all conditions match, the remaining part of the rule +# (the "program" part) is run in the shell. # All shell language constructs are valid. # All stdout and stderr output is captured and passed to abrt # and possibly to abrt's frontends and shown to the user. # -# If the program terminates with nonzero exitcode, +# If the program terminates with nonzero exit code, # the event processing is considered unsuccessful and is stopped. # Last captured output line, if any, is considered to be # the error message indicating the reason of the failure, # and may be used by abrt as such. # -# If the program terminates successfully, next line is read +# If the program terminates successfully, next rule is read # and processed. This process is repeated until the end of this file. # abrt-action-analyze-c needs package name, save package data first EVENT=post-create abrt-action-save-package-data -EVENT=post-create analyzer=CCpp abrt-action-analyze-c EVENT=post-create analyzer=Python abrt-action-analyze-python EVENT=post-create analyzer=Kerneloops abrt-action-analyze-oops # If you want all users (not just root) to be able to see oopses: @@ -38,24 +54,20 @@ EVENT=post-create analyzer=Kerneloops abrt-action-analyze-oops # user interaction, uncomment this line: #EVENT=post-create analyzer=Kerneloops abrt-action-kerneloops # Example: if you want to save sosreport immediately at the moment of a crash: -#EVENT=post-create nice sosreport --tmp-dir "$DUMP_DIR" --batch --only=anaconda --only=bootloader --only=devicemapper --only=filesys --only=hardware --only=kernel --only=libraries --only=memory --only=networking --only=nfsserver --only=pam --only=process --only=rpm -k rpm.rpmva=off --only=ssh --only=startup --only=yum && { rm sosreport*.md5; mv sosreport*.tar.bz2 sosreport.tar.bz2; mv sosreport*.tar.xz sosreport.tar.xz; true; } 2>/dev/null - -#TODO: implement this (or add this functionality to abrt-action-install-debuginfo): -#EVENT=analyze analyzer=CCpp backtrace= trim-debuginfo-cache /var/cache/abrt-di 4096m -# Additional directories to search for debuginfos can be specified -# in the third argument (its format is CACHEDIR[:DEBUGINFODIR...]). -# For example, you can specify a network-mounted shared store -# of all debuginfos this way. -EVENT=analyze analyzer=CCpp backtrace= abrt-action-install-debuginfo.py "--core=$DUMP_DIR/coredump" "--tmpdir=/var/run/abrt/$$-$RANDOM" --cache=/var/cache/abrt-di -EVENT=analyze analyzer=CCpp backtrace= abrt-action-generate-backtrace - -# Same as "analyze", but executed when user requests "refresh" in GUI -#EVENT=reanalyze analyzer=CCpp trim-debuginfo-cache /var/cache/abrt-di 4096m -EVENT=reanalyze analyzer=CCpp abrt-action-install-debuginfo.py "--core=$DUMP_DIR/coredump" "--tmpdir=/var/run/abrt/$$-$RANDOM" --cache=/var/cache/abrt-di -EVENT=reanalyze analyzer=CCpp abrt-action-generate-backtrace +#EVENT=post-create + nice sosreport --tmp-dir "$DUMP_DIR" --batch \ + --only=anaconda --only=bootloader --only=devicemapper \ + --only=filesys --only=hardware --only=kernel --only=libraries \ + --only=memory --only=networking --only=nfsserver --only=pam \ + --only=process --only=rpm -k rpm.rpmva=off --only=ssh \ + --only=startup --only=yum --only=general --only=x11 \ + && { + rm sosreport*.md5 + mv sosreport*.tar.bz2 sosreport.tar.bz2 + mv sosreport*.tar.xz sosreport.tar.xz + true + } 2>/dev/null EVENT=report analyzer=Kerneloops abrt-action-kerneloops -EVENT=report_Bugzilla analyzer=CCpp abrt-action-bugzilla -c /etc/abrt/plugins/Bugzilla.conf -EVENT=report_Logger analyzer=CCpp abrt-action-print -o /var/log/abrt.log EVENT=report_Bugzilla analyzer=Python abrt-action-bugzilla -c /etc/abrt/plugins/Bugzilla.conf EVENT=report_Logger analyzer=Python abrt-action-print -o /var/log/abrt.log diff --git a/src/lib/CommLayerInner.cpp b/src/daemon/comm_layer_inner.cpp index 3c102d6e..9d5ddfc2 100644 --- a/src/lib/CommLayerInner.cpp +++ b/src/daemon/comm_layer_inner.cpp @@ -19,10 +19,9 @@ #include <pthread.h> #include <map> #include "abrtlib.h" +#include "CommLayerServerDBus.h" #include "comm_layer_inner.h" -static CObserver *s_pObs; - typedef std::map<uint64_t, std::string> map_uint_str_t; static map_uint_str_t s_mapClientID; static pthread_mutex_t s_map_mutex; @@ -31,9 +30,6 @@ static bool s_map_mutex_inited; /* called via [p]error_msg() */ static void warn_client(const char *msg) { - if (!s_pObs) - return; - uint64_t key = uint64_t(pthread_self()); pthread_mutex_lock(&s_map_mutex); @@ -42,12 +38,13 @@ static void warn_client(const char *msg) pthread_mutex_unlock(&s_map_mutex); if (peer) - s_pObs->Warning(msg, peer); + { + send_dbus_sig_Warning(msg, peer); + } } -void init_daemon_logging(CObserver *pObs) +void init_daemon_logging(void) { - s_pObs = pObs; if (!s_map_mutex_inited) { s_map_mutex_inited = true; @@ -71,9 +68,6 @@ void set_client_name(const char *name) void update_client(const char *fmt, ...) { - if (!s_pObs) - return; - uint64_t key = uint64_t(pthread_self()); pthread_mutex_lock(&s_map_mutex); @@ -89,6 +83,8 @@ void update_client(const char *fmt, ...) char *msg = xvasprintf(fmt, p); va_end(p); - s_pObs->Status(msg, peer); + VERB1 log("Update('%s'): %s", peer, msg); + send_dbus_sig_Update(msg, peer); + free(msg); } diff --git a/src/include/comm_layer_inner.h b/src/daemon/comm_layer_inner.h index 2cca9add..48eb2010 100644 --- a/src/include/comm_layer_inner.h +++ b/src/daemon/comm_layer_inner.h @@ -20,18 +20,11 @@ #define COMMLAYERINNER_H_ #ifdef __cplusplus - -#include "observer.h" - -void init_daemon_logging(CObserver *pObs); - -#endif - - -#ifdef __cplusplus extern "C" { #endif +void init_daemon_logging(void); + /* * Set client's name (dbus ID). NULL unsets it. */ diff --git a/src/daemon/rpm.c b/src/daemon/rpm.c index a726d357..1295211e 100644 --- a/src/daemon/rpm.c +++ b/src/daemon/rpm.c @@ -16,7 +16,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include <glib.h> #include "abrtlib.h" #include "rpm.h" @@ -150,7 +149,6 @@ bool CheckHash(const char* pPackage, const char* pPath) goto error; rpmfi fi = rpmfiNew(ts, header, RPMTAG_BASENAMES, RPMFI_NOHEADER); - pgpHashAlgo hashAlgo; std::string headerHash; char computedHash[1024] = ""; @@ -158,8 +156,8 @@ bool CheckHash(const char* pPackage, const char* pPath) { if (strcmp(pPath, rpmfiFN(fi)) == 0) { - headerHash = rpmfiFDigestHex(fi, &hashAlgo); - rpmDoDigest(hashAlgo, pPath, 1, (unsigned char*) computedHash, NULL); + headerHash = rpmfiFDigestHex(fi, NULL); + rpmDoDigest(rpmfiDigestAlgo(fi), pPath, 1, (unsigned char*) computedHash, NULL); ret = (headerHash != "" && headerHash == computedHash); break; } @@ -172,27 +170,6 @@ error: } */ -char* rpm_get_description(const char* pkg) -{ - char *dsc = NULL; - const char *errmsg = NULL; - rpmts ts = rpmtsCreate(); - - rpmdbMatchIterator iter = rpmtsInitIterator(ts, RPMTAG_NAME, pkg, 0); - Header header = rpmdbNextIterator(iter); - if (!header) - goto error; - - dsc = headerFormat(header, "%{SUMMARY}\n\n%{DESCRIPTION}", &errmsg); - if (!dsc && errmsg) - error_msg("cannot get summary and description. reason: %s", errmsg); - -error: - rpmdbFreeIterator(iter); - rpmtsFree(ts); - return dsc; -} - char* rpm_get_component(const char* filename) { char *ret = NULL; diff --git a/src/daemon/rpm.h b/src/daemon/rpm.h index 12b11ca8..11f02809 100644 --- a/src/daemon/rpm.h +++ b/src/daemon/rpm.h @@ -58,12 +58,6 @@ void rpm_load_gpgkey(const char* filename); int rpm_chk_fingerprint(const char* pkg); /** - * Gets a package description. - * @param pkg A package name. - * @return A package description. - */ -char* rpm_get_description(const char* pkg); -/** * Gets a package name. This package contains particular * file. If the file doesn't belong to any package, empty string is * returned. diff --git a/src/gui-gtk/Makefile.am b/src/gui-gtk/Makefile.am new file mode 100644 index 00000000..a0d0de40 --- /dev/null +++ b/src/gui-gtk/Makefile.am @@ -0,0 +1,45 @@ +bin_PROGRAMS = abrt-gtk + +abrt_gtk_SOURCES = \ + abrt-gtk.h abrt-gtk.c \ + main.c +abrt_gtk_CFLAGS = \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ + -I$(srcdir)/../lib \ + -DBIN_DIR=\"$(bindir)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -DICON_DIR=\"${datadir}/abrt/icons/hicolor/48x48/status\" \ + $(GTK_CFLAGS) \ + $(DBUS_CFLAGS) \ + -D_GNU_SOURCE \ + -Wall -Werror +# -I/usr/include/glib-2.0 +# -I/usr/lib/glib-2.0/include +# $(LIBNOTIFY_CFLAGS) +# $(DBUS_GLIB_CFLAGS) +abrt_gtk_LDADD = \ + ../lib/libreport.la \ + ../lib/libabrt_dbus.la \ + -lglib-2.0 \ + -lgthread-2.0 \ + $(GTK_LIBS) \ + $(DBUS_LIBS) +# $(LIBNOTIFY_LIBS) + +#test_report_SOURCES = \ +# test_report.c +#test_report_CPPFLAGS = \ +# -I$(srcdir)/../include/report -I$(srcdir)/../include \ +# $(GLIB_CFLAGS) \ +# -D_GNU_SOURCE \ +# -Wall -Werror +#test_report_LDADD = \ +# ../lib/libreport.la + +DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ + +@INTLTOOL_DESKTOP_RULE@ diff --git a/src/gui-gtk/abrt-gtk.c b/src/gui-gtk/abrt-gtk.c new file mode 100644 index 00000000..fc945d76 --- /dev/null +++ b/src/gui-gtk/abrt-gtk.c @@ -0,0 +1,307 @@ +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include "abrtlib.h" +#include "abrt_dbus.h" +#include "abrt-gtk.h" + +static GtkListStore *s_dumps_list_store; +static GtkWidget *s_treeview; + +enum +{ + COLUMN_REPORTED, + COLUMN_REASON, + COLUMN_DIRNAME, + COLUMN_LATEST_CRASH_STR, + COLUMN_LATEST_CRASH, + COLUMN_DUMP_DIR, + NUM_COLUMNS +}; + +void add_directory_to_dirlist(const char *dirname) +{ + struct dump_dir *dd = dd_opendir(dirname, DD_OPEN_READONLY); + if (!dd) + return; + + time_t time = atoi(dd_load_text(dd, FILENAME_TIME)); + struct tm *ptm = localtime(&time); + char time_buf[60]; + size_t time_len = strftime(time_buf, sizeof(time_buf)-1, "%c", ptm); + time_buf[time_len] = '\0'; + + char *msg = dd_load_text_ext(dd, FILENAME_MESSAGE, 0 + | DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE + | DD_FAIL_QUIETLY + ); + const char *reported = (msg ? GTK_STOCK_YES : GTK_STOCK_NO); + free(msg); + char *reason = dd_load_text(dd, FILENAME_REASON); + + GtkTreeIter iter; + gtk_list_store_append(s_dumps_list_store, &iter); + gtk_list_store_set(s_dumps_list_store, &iter, + COLUMN_REPORTED, reported, + COLUMN_REASON, reason, + COLUMN_DIRNAME, dd->dd_dirname, + //OPTION: time format + COLUMN_LATEST_CRASH_STR, time_buf, + COLUMN_LATEST_CRASH, (int)time, + COLUMN_DUMP_DIR, dirname, + -1); + + free(reason); + + dd_close(dd); + VERB1 log("added: %s", dirname); +} + + +/* create_main_window and helpers */ + +static void on_row_activated_cb(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data) +{ + GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview); + if (selection) + { + GtkTreeIter iter; + GtkTreeModel *store = gtk_tree_view_get_model(treeview); + if (gtk_tree_selection_get_selected(selection, &store, &iter) == TRUE) + { + GValue d_dir = { 0 }; + gtk_tree_model_get_value(store, &iter, COLUMN_DUMP_DIR, &d_dir); + + pid_t pid = vfork(); + if (pid == 0) + { + /* Undo signal(SIGCHLD, SIG_IGN), or child inherits it and gets terribly confused */ + signal(SIGCHLD, SIG_DFL); + + const char *dirname= g_value_get_string(&d_dir); + VERB1 log("Executing: %s %s", "bug-reporting-wizard", dirname); + execlp("bug-reporting-wizard", "bug-reporting-wizard", dirname, NULL); + perror_msg_and_die("Can't execute %s", "bug-reporting-wizard"); + } + } + } +} + +static gint on_key_press_event_cb(GtkTreeView *treeview, GdkEventKey *key, gpointer unused) +{ + int k = key->keyval; + + if (k == GDK_Delete || k == GDK_KP_Delete) + { + GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview); + if (selection) + { + GtkTreeIter iter; + GtkTreeModel *store = gtk_tree_view_get_model(treeview); + if (gtk_tree_selection_get_selected(selection, &store, &iter) == TRUE) + { + GtkTreePath *old_path = gtk_tree_model_get_path(store, &iter); + + GValue d_dir = { 0 }; + gtk_tree_model_get_value(store, &iter, COLUMN_DUMP_DIR, &d_dir); + const char *dump_dir_name = g_value_get_string(&d_dir); + + VERB1 log("Deleting '%s'", dump_dir_name); + if (delete_dump_dir_possibly_using_abrtd(dump_dir_name) == 0) + { + gtk_list_store_remove(s_dumps_list_store, &iter); + } + else + { + /* Strange. Deletion did not succeed. Someone else deleted it? + * Rescan the whole list */ + gtk_list_store_clear(s_dumps_list_store); + scan_dirs_and_add_to_dirlist(); + } + + /* Try to retain the same cursor position */ + sanitize_cursor(old_path); + gtk_tree_path_free(old_path); + } + } + + return TRUE; + } + return FALSE; +} + +static void add_columns(GtkTreeView *treeview) +{ + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + renderer = gtk_cell_renderer_pixbuf_new(); + column = gtk_tree_view_column_new_with_attributes(_("Reported"), + renderer, + "stock_id", + COLUMN_REPORTED, + NULL); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_column_set_sort_column_id(column, COLUMN_REPORTED); + gtk_tree_view_append_column(treeview, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("Problem"), + renderer, + "text", + COLUMN_REASON, + NULL); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_column_set_sort_column_id(column, COLUMN_REASON); + gtk_tree_view_append_column(treeview, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("Stored in"), + renderer, + "text", + COLUMN_DIRNAME, + NULL); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_column_set_sort_column_id(column, COLUMN_DIRNAME); + gtk_tree_view_append_column(treeview, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("Last occurrence"), + renderer, + "text", + COLUMN_LATEST_CRASH_STR, + NULL); + gtk_tree_view_column_set_sort_column_id(column, COLUMN_LATEST_CRASH); + gtk_tree_view_append_column(treeview, column); +} + +GtkWidget *create_menu(void) +{ + /* main bar */ + GtkWidget *menu = gtk_menu_bar_new(); + GtkWidget *file_item = gtk_menu_item_new_with_mnemonic(_("_File")); + GtkWidget *edit_item = gtk_menu_item_new_with_mnemonic(_("_Edit")); + GtkWidget *help_item = gtk_menu_item_new_with_mnemonic(_("_Help")); + + gtk_menu_shell_append(GTK_MENU_SHELL(menu), file_item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), edit_item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), help_item); + + /* file submenu */ + GtkWidget *file_submenu = gtk_menu_new(); + GtkWidget *quit_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(file_submenu), quit_item); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(file_item), file_submenu); + + /* edit submenu */ + GtkWidget *edit_submenu = gtk_menu_new(); + GtkWidget *plugins_item = gtk_menu_item_new_with_mnemonic(_("_Plugins")); + GtkWidget *preferences_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(edit_submenu), plugins_item); + gtk_menu_shell_append(GTK_MENU_SHELL(edit_submenu), preferences_item); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(edit_item), edit_submenu); + + /* help submenu */ + GtkWidget *help_submenu = gtk_menu_new(); + GtkWidget *log_item = gtk_menu_item_new_with_mnemonic(_("View _log")); + GtkWidget *online_help_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_HELP, NULL); + GtkWidget *about_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(help_submenu), log_item); + gtk_menu_shell_append(GTK_MENU_SHELL(help_submenu), online_help_item); + gtk_menu_shell_append(GTK_MENU_SHELL(help_submenu), about_item); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(help_item), help_submenu); + + return menu; +} + +GtkWidget *create_main_window(void) +{ + /* main window */ + GtkWidget *main_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size(GTK_WINDOW(main_window), 600, 700); + gtk_window_set_title(GTK_WINDOW(main_window), _("Automatic Bug Reporting Tool")); + gtk_window_set_icon_name(GTK_WINDOW(main_window), "abrt"); + + GtkWidget *main_vbox = gtk_vbox_new(false, 0); + + /* scrolled region inside main window */ + GtkWidget *scroll_win = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll_win), + GTK_SHADOW_ETCHED_IN); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_win), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + gtk_box_pack_start(GTK_BOX(main_vbox), create_menu(), false, false, 0); + gtk_box_pack_start(GTK_BOX(main_vbox), scroll_win, true, true, 0); + gtk_container_add(GTK_CONTAINER(main_window), main_vbox); + + /* tree view inside scrolled region */ + s_treeview = gtk_tree_view_new(); + add_columns(GTK_TREE_VIEW(s_treeview)); + gtk_container_add(GTK_CONTAINER(scroll_win), s_treeview); + + /* Create data store for the list and attach it */ + s_dumps_list_store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_STRING, /* reported */ + G_TYPE_STRING, /* executable */ + G_TYPE_STRING, /* hostname */ + G_TYPE_STRING, /* time */ + G_TYPE_INT, /* unix time - used for sort */ + G_TYPE_STRING);/* dump dir path */ + gtk_tree_view_set_model(GTK_TREE_VIEW(s_treeview), GTK_TREE_MODEL(s_dumps_list_store)); + + /* Double click/Enter handler */ + g_signal_connect(s_treeview, "row-activated", G_CALLBACK(on_row_activated_cb), NULL); + /* Delete handler */ + g_signal_connect(s_treeview, "key-press-event", G_CALLBACK(on_key_press_event_cb), NULL); + /* Quit when user closes the main window */ + g_signal_connect(main_window, "destroy", gtk_main_quit, NULL); + + return main_window; +} + +void sanitize_cursor(GtkTreePath *preferred_path) +{ + GtkTreePath *path; + + gtk_tree_view_get_cursor(GTK_TREE_VIEW(s_treeview), &path, /* GtkTreeViewColumn** */ NULL); + if (path) + { + /* Cursor exists already */ + goto ret; + } + + if (preferred_path) + { + /* Try to position cursor on preferred_path */ + gtk_tree_view_set_cursor(GTK_TREE_VIEW(s_treeview), preferred_path, + /* GtkTreeViewColumn *focus_column */ NULL, /* start_editing */ false); + + /* Did it work? */ + gtk_tree_view_get_cursor(GTK_TREE_VIEW(s_treeview), &path, /* GtkTreeViewColumn** */ NULL); + if (path) /* yes */ + { + goto ret; + } + } + + /* Try to position cursor on 1st element */ + GtkTreeIter iter; + if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(s_dumps_list_store), &iter)) + { + /* We have at least one element, put cursor on it */ + + /* Get path from iter pointing to 1st element */ + path = gtk_tree_model_get_path(GTK_TREE_MODEL(s_dumps_list_store), &iter); + + /* Use it to set cursor */ + gtk_tree_view_set_cursor(GTK_TREE_VIEW(s_treeview), path, + /* GtkTreeViewColumn *focus_column */ NULL, /* start_editing */ false); + } + /* else we have no elements */ + + ret: + gtk_tree_path_free(path); + + /* Without this, the *header* of the list gets the focus. Ugly. */ + gtk_widget_grab_focus(s_treeview); +} diff --git a/src/gui-gtk/abrt-gtk.h b/src/gui-gtk/abrt-gtk.h new file mode 100644 index 00000000..d67ca6ce --- /dev/null +++ b/src/gui-gtk/abrt-gtk.h @@ -0,0 +1,5 @@ +GtkWidget *create_main_window(void); +void add_directory_to_dirlist(const char *dirname); +void sanitize_cursor(GtkTreePath *preferred_path); + +void scan_dirs_and_add_to_dirlist(); diff --git a/src/gui-gtk/main.c b/src/gui-gtk/main.c new file mode 100644 index 00000000..d80256e9 --- /dev/null +++ b/src/gui-gtk/main.c @@ -0,0 +1,101 @@ +#include <gtk/gtk.h> +#include "abrtlib.h" +#include "parse_options.h" +#include "abrt-gtk.h" + +#define PROGNAME "abrt-gtk" + +static char **s_dirs; + +static void scan_directory_and_add_to_dirlist(const char *path) +{ + DIR *dp = opendir(path); + if (!dp) + { + /* We don't want to yell if, say, $HOME/.abrt/spool doesn't exist */ + //perror_msg("Can't open directory '%s'", path); + return; + } + + struct dirent *dent; + while ((dent = readdir(dp)) != NULL) + { + if (dot_or_dotdot(dent->d_name)) + continue; /* skip "." and ".." */ + + char *full_name = concat_path_file(path, dent->d_name); + struct stat statbuf; + if (stat(full_name, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) + add_directory_to_dirlist(full_name); + free(full_name); + } + closedir(dp); +} + +void scan_dirs_and_add_to_dirlist(void) +{ + char **argv = s_dirs; + while (*argv) + scan_directory_and_add_to_dirlist(*argv++); +} + +int main(int argc, char **argv) +{ + /* I18n */ + setlocale(LC_ALL, ""); + + gtk_init(&argc, &argv); + + char *env_verbose = getenv("ABRT_VERBOSE"); + if (env_verbose) + g_verbose = atoi(env_verbose); + + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( + PROGNAME" [-v] [DIR]...\n\n" + "Shows list of ABRT dump directories in specified DIR(s)\n" + "(default DIRs: "DEBUG_DUMPS_DIR" $HOME/.abrt/spool)" + ); + enum { + OPT_v = 1 << 0, + }; + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_END() + }; + /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); + + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + + GtkWidget *main_window = create_main_window(); + + const char *default_dirs[] = { + "/var/spool/abrt", + NULL, + NULL, + }; + argv += optind; + if (!argv[0]) + { + char *home = getenv("HOME"); + if (home) + default_dirs[1] = concat_path_file(home, ".abrt/spool"); + argv = (char**)default_dirs; + } + s_dirs = argv; + + scan_dirs_and_add_to_dirlist(); + + gtk_widget_show_all(main_window); + + sanitize_cursor(NULL); + + /* Prevent zombies when we spawn wizard */ + signal(SIGCHLD, SIG_IGN); + + /* Enter main loop */ + gtk_main(); + + return 0; +} diff --git a/src/gui-wizard-gtk/Makefile.am b/src/gui-wizard-gtk/Makefile.am new file mode 100644 index 00000000..4281c470 --- /dev/null +++ b/src/gui-wizard-gtk/Makefile.am @@ -0,0 +1,60 @@ +# bug-reporting-wizard binary +bin_PROGRAMS = bug-reporting-wizard + +bug_reporting_wizard_SOURCES = \ + wizard.h wizard.c \ + main.c \ + wizard_glade.c + +# Required for gtk_builder_connect_signals() to work correctly: +# -g +# -Wl,--export-dynamic +bug_reporting_wizard_CFLAGS = \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ + -I$(srcdir)/../lib \ + -DBIN_DIR=\"$(bindir)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DDATA_DIR=\"$(datadir)\" \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -DICON_DIR=\"${datadir}/abrt/icons/hicolor/48x48/status\" \ + $(GLIB_CFLAGS) \ + $(GTK_CFLAGS) \ + $(DBUS_CFLAGS) \ + -D_GNU_SOURCE \ + -Wall -Werror + +# Required for gtk_builder_connect_signals() to work correctly: +# -lgmodule-2.0 +# -lgthread-2.0 +bug_reporting_wizard_LDADD = \ + ../lib/libreport.la \ + ../lib/libabrt_dbus.la \ + $(GLIB_LIBS) \ + $(GTK_LIBS) \ + $(DBUS_LIBS) + + +# we don't want to install it, just make it part of tarball +# created by make dist +GLADE_FILES = wizard.glade +#pkgdata_DATA = $(GLADE_FILES) +EXTRA_DIST = $(GLADE_FILES) + + +# For internal glade file storage in the binary: +wizard.c: wizard_glade.c + +wizard_glade.c: wizard.glade + { \ + echo '#define WIZARD_GLADE_CONTENTS "\'; \ + cat $(srcdir)/wizard.glade | sed -e 's/"/\\"/g' -e 's/?/\\?/g' -e 's/$$/\\/g'; \ + echo '"'; \ + } >wizard_glade.c + + +DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ + +@INTLTOOL_DESKTOP_RULE@ diff --git a/src/gui-wizard-gtk/main.c b/src/gui-wizard-gtk/main.c new file mode 100644 index 00000000..85f962cd --- /dev/null +++ b/src/gui-wizard-gtk/main.c @@ -0,0 +1,80 @@ +#include <gtk/gtk.h> +#include "abrtlib.h" +#include "parse_options.h" +#include "wizard.h" + +#define PROGNAME "bug-reporting-wizard" + +char *g_glade_file = NULL; +char *g_dump_dir_name = NULL; +char *g_analyze_label_selected = NULL; +char *g_analyze_events = NULL; +char *g_reanalyze_events = NULL; +char *g_report_events = NULL; +crash_data_t *g_cd; + + +void reload_crash_data_from_dump_dir(void) +{ + free_crash_data(g_cd); + free(g_analyze_events); + free(g_reanalyze_events); + free(g_report_events); + + struct dump_dir *dd = dd_opendir(g_dump_dir_name, DD_OPEN_READONLY); + if (!dd) + xfunc_die(); /* dd_opendir already logged error msg */ + g_cd = create_crash_data_from_dump_dir(dd); + g_analyze_events = list_possible_events(dd, NULL, "analyze"); + g_reanalyze_events = list_possible_events(dd, NULL, "reanalyze"); + g_report_events = list_possible_events(dd, NULL, "report"); + dd_close(dd); +} + +int main(int argc, char **argv) +{ + gtk_init(&argc, &argv); + + char *env_verbose = getenv("ABRT_VERBOSE"); + if (env_verbose) + g_verbose = atoi(env_verbose); + + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( + PROGNAME" [-v] [-g GUI_FILE] DIR\n\n" + "GUI tool to analyze and report ABRT crash in specified DIR" + ); + enum { + OPT_v = 1 << 0, + OPT_g = 1 << 1, + }; + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_STRING('g', NULL, &g_glade_file, "FILE" , _("Alternate GUI file")), + OPT_END() + }; + + /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); + + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + + argv += optind; + if (!argv[0] || argv[1]) /* zero or >1 arguments */ + show_usage_and_die(program_usage_string, program_options); + + g_dump_dir_name = xstrdup(argv[0]); + + create_assistant(); + + g_custom_logger = &show_error_as_msgbox; + + reload_crash_data_from_dump_dir(); + + update_gui_state_from_crash_data(); + + /* Enter main loop */ + gtk_main(); + + return 0; +} diff --git a/src/gui-wizard-gtk/wizard.c b/src/gui-wizard-gtk/wizard.c new file mode 100644 index 00000000..77699480 --- /dev/null +++ b/src/gui-wizard-gtk/wizard.c @@ -0,0 +1,968 @@ +#include <gtk/gtk.h> +#include "abrtlib.h" +#include "abrt_dbus.h" +#include "wizard.h" + +#define DEFAULT_WIDTH 800 +#define DEFAULT_HEIGHT 500 + +GtkAssistant *g_assistant; + +GtkBox *g_box_analyzers; +GtkLabel *g_lbl_analyze_log; +GtkTextView *g_tv_analyze_log; +GtkBox *g_box_reporters; +GtkLabel *g_lbl_report_log; +GtkTextView *g_tv_report_log; +GtkContainer *g_container_details1; +GtkContainer *g_container_details2; + +GtkLabel *g_lbl_cd_reason; +GtkTextView *g_tv_backtrace; +GtkTextView *g_tv_reproduce; +GtkTextView *g_tv_comment; +GtkTreeView *g_tv_details; +GtkListStore *g_ls_details; +GtkWidget *g_widget_warnings_area; +GtkBox *g_box_warning_labels; +GtkToggleButton *g_tb_approve_bt; +GtkButton *g_btn_refresh; + +/* required for search in bt */ +guint g_timeout = 0; +GtkEntry * g_search_entry_bt; + + +static GtkBuilder *builder; +static PangoFontDescription *monospace_font; + + +/* THE PAGE FLOW + * page_1: analyze action selection + * page_2: analyze progress + * page_3: reporter selection + * page_4: backtrace editor + * page_5: how to + user comments + * page_6: summary + * page_7: reporting progress + */ + +/* Use of arrays (instead of, say, #defines to C strings) + * allows cheaper page_obj_t->name == PAGE_FOO comparisons + * instead of strcmp. + */ +static const gchar PAGE_SUMMARY[] = "page_0"; +static const gchar PAGE_ANALYZE_SELECTOR[] = "page_1"; +static const gchar PAGE_ANALYZE_PROGRESS[] = "page_2"; +static const gchar PAGE_REPORTER_SELECTOR[] = "page_3"; +static const gchar PAGE_BACKTRACE_APPROVAL[] = "page_4"; +static const gchar PAGE_HOWTO[] = "page_5"; +static const gchar PAGE_REPORT[] = "page_6"; +static const gchar PAGE_REPORT_PROGRESS[] = "page_7"; + +static const gchar *const page_names[] = +{ + PAGE_SUMMARY, + PAGE_ANALYZE_SELECTOR, + PAGE_ANALYZE_PROGRESS, + PAGE_REPORTER_SELECTOR, + PAGE_BACKTRACE_APPROVAL, + PAGE_HOWTO, + PAGE_REPORT, + PAGE_REPORT_PROGRESS, + NULL +}; + +typedef struct +{ + const gchar *name; + const gchar *title; + GtkAssistantPageType type; + GtkWidget *page_widget; +} page_obj_t; + +static page_obj_t pages[] = +{ + /* Page types: + * INTRO: only [Fwd] button is shown + * CONTENT: normal page + * CONFIRM: has [Apply] instead of [Fwd] and emits "apply" signal + * PROGRESS: skipped on backward navigation + * SUMMARY: has only [Close] button + */ + /* glade element name , on-screen text , type */ + { PAGE_SUMMARY , "Problem description" , GTK_ASSISTANT_PAGE_CONTENT }, + { PAGE_ANALYZE_SELECTOR , "Select analyzer" , GTK_ASSISTANT_PAGE_CONFIRM }, + { PAGE_ANALYZE_PROGRESS , "Analyzing" , GTK_ASSISTANT_PAGE_CONTENT }, + /* Some reporters don't need backtrace, we can skip bt page for them. + * Therefore we want to know reporters _before_ we go to bt page + */ + { PAGE_REPORTER_SELECTOR , "Select reporter" , GTK_ASSISTANT_PAGE_CONTENT }, + { PAGE_BACKTRACE_APPROVAL , "Review the backtrace" , GTK_ASSISTANT_PAGE_CONTENT }, + { PAGE_HOWTO , "Provide additional information", GTK_ASSISTANT_PAGE_CONTENT }, + { PAGE_REPORT , "Confirm data to report", GTK_ASSISTANT_PAGE_CONFIRM }, + /* Was GTK_ASSISTANT_PAGE_PROGRESS */ + { PAGE_REPORT_PROGRESS , "Reporting" , GTK_ASSISTANT_PAGE_SUMMARY }, + { NULL } +}; + + +/* Utility functions */ + +static void remove_child_widget(GtkWidget *widget, gpointer container) +{ + /* Destroy will safely remove it and free the memory + * if there are no refs left + */ + gtk_widget_destroy(widget); +} + +static void save_dialog_response(GtkDialog *dialog, gint response_id, gpointer user_data) +{ + *(gint*)user_data = response_id; +} + +struct dump_dir *steal_if_needed(struct dump_dir *dd) +{ + if (!dd) + xfunc_die(); /* error msg was already logged */ + + if (dd->locked) + return dd; + + dd_close(dd); + + char *HOME = getenv("HOME"); + if (HOME && HOME[0]) + HOME = concat_path_file(HOME, ".abrt/spool"); + else + HOME = xstrdup("/tmp"); + + GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(g_assistant), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_OK_CANCEL, + _("Need writable directory, but '%s' is not writable." + " Move it to '%s' and operate on the moved copy?"), + g_dump_dir_name, HOME + ); + gint response = GTK_RESPONSE_CANCEL; + g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(save_dialog_response), &response); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + if (response != GTK_RESPONSE_OK) + return NULL; + + dd = steal_directory(HOME, g_dump_dir_name); + if (!dd) + return NULL; /* Stealing failed. Error msg was already logged */ + + /* Delete old dir and switch to new one. + * Don't want to keep new dd open across deletion, + * therefore it's a bit more complicated. + */ + char *old_name = g_dump_dir_name; + g_dump_dir_name = xstrdup(dd->dd_dirname); + dd_close(dd); + + gtk_window_set_title(GTK_WINDOW(g_assistant), g_dump_dir_name); + delete_dump_dir_possibly_using_abrtd(old_name); //TODO: if (deletion_failed) error_msg("BAD")? + free(old_name); + + dd = dd_opendir(g_dump_dir_name, 0); + if (!dd) + xfunc_die(); /* error msg was already logged */ + + return dd; +} + +void show_error_as_msgbox(const char *msg) +{ + GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(g_assistant), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_CLOSE, + "%s", msg + ); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + +static void load_text_to_text_view(GtkTextView *tv, const char *name) +{ + const char *str = g_cd ? get_crash_item_content_or_NULL(g_cd, name) : NULL; + gtk_text_buffer_set_text(gtk_text_view_get_buffer(tv), (str ? str : ""), -1); +} + +static gchar *get_malloced_string_from_text_view(GtkTextView *tv) +{ + GtkTextBuffer *buffer = gtk_text_view_get_buffer(tv); + GtkTextIter start; + GtkTextIter end; + gtk_text_buffer_get_start_iter(buffer, &start); + gtk_text_buffer_get_end_iter(buffer, &end); + return gtk_text_buffer_get_text(buffer, &start, &end, FALSE); +} + +static void save_text_if_changed(const char *name, const char *new_value) +{ + const char *old_value = g_cd ? get_crash_item_content_or_NULL(g_cd, name) : ""; + if (!old_value) + old_value = ""; + if (strcmp(new_value, old_value) != 0) + { + struct dump_dir *dd = dd_opendir(g_dump_dir_name, DD_OPEN_READONLY); + dd = steal_if_needed(dd); + if (dd && dd->locked) + { + dd_save_text(dd, name, new_value); + add_to_crash_data_ext(g_cd, name, new_value, CD_FLAG_TXT | CD_FLAG_ISEDITABLE); + } +//FIXME: else: what to do with still-unsaved data in the widget?? + dd_close(dd); + } +} + +static void save_text_from_text_view(GtkTextView *tv, const char *name) +{ + gchar *new_str = get_malloced_string_from_text_view(tv); + save_text_if_changed(name, new_str); + free(new_str); +} + + +/* update_gui_state_from_crash_data */ + +static void analyze_rb_was_toggled(GtkButton *button, gpointer user_data) +{ + const char *label = gtk_button_get_label(button); + if (label) + { + free(g_analyze_label_selected); + g_analyze_label_selected = xstrdup(label); + } +} + +static void report_tb_was_toggled(GtkButton *button, gpointer user_data) +{ + GList *reporters = gtk_container_get_children(GTK_CONTAINER(g_box_reporters)); + GList *li = reporters; + if (reporters) + { + for (; li; li = li->next) + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(li->data)) == TRUE) + break; + } + g_list_free(reporters); + gtk_assistant_set_page_complete(g_assistant, + pages[PAGENO_REPORTER_SELECTOR].page_widget, + li != NULL /* true if at least one checkbox is active */ + ); +} + +static GtkWidget *add_event_buttons(GtkBox *box, char *event_name, GCallback func, bool radio, const char *prev_selected) +{ +VERB2 log("removing all buttons from box %p", box); + gtk_container_foreach(GTK_CONTAINER(box), &remove_child_widget, box); + + bool have_activated_btn = false; + GtkWidget *first_button = NULL; + while (event_name[0]) + { + char *event_name_end = strchr(event_name, '\n'); + *event_name_end = '\0'; + +VERB2 log("adding button '%s' to box %p", event_name, box); + GtkWidget *button = radio + ? gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(first_button), event_name) + : gtk_check_button_new_with_label(event_name); + if (!first_button) + first_button = button; + + if (prev_selected && strcmp(prev_selected, event_name) == 0) + { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), true); + have_activated_btn = true; + prev_selected = NULL; + } + + *event_name_end = '\n'; + event_name = event_name_end + 1; + + gtk_box_pack_start(box, button, /*expand*/ false, /*fill*/ false, /*padding*/ 0); + + if (func) + g_signal_connect(G_OBJECT(button), "toggled", func, NULL); + } + return (have_activated_btn ? NULL : first_button); +} + +static void append_item_to_details_ls(gpointer name, gpointer value, gpointer data) +{ + crash_item *item = (crash_item*)value; + GtkTreeIter iter; + + gtk_list_store_append(g_ls_details, &iter); + + //FIXME: use the value representation here + /* If text and not multiline... */ + if ((item->flags & CD_FLAG_TXT) && !strchr(item->content, '\n')) + { + gtk_list_store_set(g_ls_details, &iter, + DETAIL_COLUMN_NAME, (char *)name, + DETAIL_COLUMN_VALUE, item->content, + //DETAIL_COLUMN_PATH, xasprintf("%s%s", g_dump_dir_name, name), + -1); + } + else + { + gtk_list_store_set(g_ls_details, &iter, + DETAIL_COLUMN_NAME, (char *)name, + DETAIL_COLUMN_VALUE, _("(click here to view/edit)"), + //DETAIL_COLUMN_PATH, xasprintf("%s%s", g_dump_dir_name, name), + -1); + //WARNING: will leak xasprintf results above if uncommented + } +} + +void update_gui_state_from_crash_data(void) +{ + const char *reason = get_crash_item_content_or_NULL(g_cd, FILENAME_REASON); + gtk_label_set_text(g_lbl_cd_reason, reason ? reason : _("(no description)")); + + gtk_list_store_clear(g_ls_details); + g_hash_table_foreach(g_cd, append_item_to_details_ls, NULL); + + load_text_to_text_view(g_tv_backtrace, FILENAME_BACKTRACE); + load_text_to_text_view(g_tv_reproduce, FILENAME_REPRODUCE); + load_text_to_text_view(g_tv_comment, FILENAME_COMMENT); + +//Doesn't work: shows empty page +// if (!g_analyze_events[0]) +// { +// /* No available analyze events, go to reporter selector page */ +// gtk_assistant_set_current_page(GTK_ASSISTANT(assistant), PAGENO_REPORTER_SELECTOR); +// } + + /* Update analyze radio buttons */ + GtkWidget *first_rb = add_event_buttons(g_box_analyzers, g_analyze_events, G_CALLBACK(analyze_rb_was_toggled), /*radio:*/ true, /*prev:*/ g_analyze_label_selected); + /* Update the value of currently selected analyzer */ + if (first_rb) + { + const char *label = gtk_button_get_label(GTK_BUTTON(first_rb)); + if (label) + { + free(g_analyze_label_selected); + g_analyze_label_selected = xstrdup(label); + } + } + + /* Update reporter checkboxes */ + /* Remember names of selected reporters */ + GList *old_reporters = gtk_container_get_children(GTK_CONTAINER(g_box_reporters)); + for (GList *li = old_reporters; li; li = li->next) + { + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(li->data)) == TRUE) + li->data = xstrdup(gtk_button_get_label(GTK_BUTTON(li->data))); + else + li->data = NULL; + } + old_reporters = g_list_remove_all(old_reporters, NULL); + /* Delete old checkboxes and create new ones */ + add_event_buttons(g_box_reporters, g_report_events, /*callback:*/ G_CALLBACK(report_tb_was_toggled), /*radio:*/ false, /*prev:*/ NULL); + /* Re-select new reporters which were selected before we deleted them */ + GList *new_reporters = gtk_container_get_children(GTK_CONTAINER(g_box_reporters)); + for (GList *li_new = new_reporters; li_new; li_new = li_new->next) + { + const char *new_name = gtk_button_get_label(GTK_BUTTON(li_new->data)); + for (GList *li_old = old_reporters; li_old; li_old = li_old->next) + { + if (strcmp(new_name, li_old->data) == 0) + { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(li_new->data), true); + break; + } + } + } + g_list_free(new_reporters); + list_free_with_free(old_reporters); + /* Update readiness state of reporter selector page */ + report_tb_was_toggled(NULL, NULL); + + /* We can't just do gtk_widget_show_all once in main: + * We created new widgets (buttons). Need to make them visible. + */ + gtk_widget_show_all(GTK_WIDGET(g_assistant)); + + if (g_reanalyze_events[0]) + gtk_widget_show(GTK_WIDGET(g_btn_refresh)); + else + gtk_widget_hide(GTK_WIDGET(g_btn_refresh)); +} + + +/* start_event_run */ + +struct analyze_event_data +{ + struct run_event_state *run_state; + const char *event_name; + GList *more_events; + GtkWidget *page_widget; + GtkLabel *status_label; + GtkTextView *tv_log; + const char *end_msg; + GIOChannel *channel; + int fd; + /*guint event_source_id;*/ +}; + +static void append_to_textview(GtkTextView *tv, const char *str, int len) +{ + GtkTextBuffer *tb = gtk_text_view_get_buffer(tv); + + /* Ensure we insert text at the end */ + GtkTextIter text_iter; + gtk_text_buffer_get_iter_at_offset(tb, &text_iter, -1); + gtk_text_buffer_place_cursor(tb, &text_iter); + + gtk_text_buffer_insert_at_cursor(tb, str, len >= 0 ? len : strlen(str)); + + /* Scroll so that the end of the log is visible */ + gtk_text_buffer_get_iter_at_offset(tb, &text_iter, -1); + gtk_text_view_scroll_to_iter(tv, &text_iter, + /*within_margin:*/ 0.0, /*use_align:*/ FALSE, /*xalign:*/ 0, /*yalign:*/ 0); +} + +static gboolean consume_cmd_output(GIOChannel *source, GIOCondition condition, gpointer data) +{ + struct analyze_event_data *evd = data; + + /* Read and insert the output into the log pane */ + char buf[256]; /* usually we get one line, no need to have big buf */ + int r; + while ((r = read(evd->fd, buf, sizeof(buf))) > 0) + { + append_to_textview(evd->tv_log, buf, r); + } + + if (r < 0 && errno == EAGAIN) + /* We got all data, but fd is still open. Done for now */ + return TRUE; /* "please don't remove this event (yet)" */ + + /* EOF/error. Wait for child to actually exit, collect status */ + int status; + waitpid(evd->run_state->command_pid, &status, 0); + int retval = WEXITSTATUS(status); + if (WIFSIGNALED(status)) + retval = WTERMSIG(status) + 128; + + /* Stop if exit code is not 0, or no more commands */ + if (retval != 0 + || spawn_next_command(evd->run_state, g_dump_dir_name, evd->event_name) < 0 + ) { + VERB1 log("done running event on '%s': %d", g_dump_dir_name, retval); +//append_to_textview(evd->tv_log, msg); + + for (;;) + { + if (!evd->more_events) + { + char *msg = xasprintf(evd->end_msg, retval); + gtk_label_set_text(evd->status_label, msg); + free(msg); + /* Unfreeze assistant */ + gtk_assistant_set_page_complete(g_assistant, evd->page_widget, true); + + /*g_source_remove(evd->event_source_id);*/ + close(evd->fd); + free_run_event_state(evd->run_state); + free(evd); + + reload_crash_data_from_dump_dir(); + update_gui_state_from_crash_data(); + + return FALSE; /* "please remove this event" */ + } + + evd->event_name = evd->more_events->data; + evd->more_events = g_list_remove(evd->more_events, evd->more_events->data); + + if (prepare_commands(evd->run_state, g_dump_dir_name, evd->event_name) != 0 + && spawn_next_command(evd->run_state, g_dump_dir_name, evd->event_name) >= 0 + ) { + VERB1 log("running event '%s' on '%s'", evd->event_name, g_dump_dir_name); + break; + } + /* No commands needed?! (This is untypical) */ +//TODO: msg? +//append_to_textview(evd->tv_log, msg); + } + } + + /* New command was started. Continue waiting for input */ + + /* Transplant cmd's output fd onto old one, so that main loop + * is none the wiser that fd it waits on has changed + */ + xmove_fd(evd->run_state->command_out_fd, evd->fd); + evd->run_state->command_out_fd = evd->fd; /* just to keep it consistent */ + ndelay_on(evd->fd); + + return TRUE; /* "please don't remove this event (yet)" */ +} + +static void start_event_run(const char *event_name, + GList *more_events, + GtkWidget *page, + GtkTextView *tv_log, + GtkLabel *status_label, + const char *start_msg, + const char *end_msg +) { + /* Start event asyncronously on the dump dir + * (syncronous run would freeze GUI until completion) + */ + struct run_event_state *state = new_run_event_state(); + + if (prepare_commands(state, g_dump_dir_name, event_name) == 0) + { + no_cmds: + /* No commands needed?! (This is untypical) */ + free_run_event_state(state); +//TODO: better msg? + char *msg = xasprintf(_("No processing for event '%s' is defined"), event_name); + gtk_label_set_text(status_label, msg); + free(msg); + return; + } + + struct dump_dir *dd = dd_opendir(g_dump_dir_name, DD_OPEN_READONLY); + dd = steal_if_needed(dd); + int locked = (dd && dd->locked); + dd_close(dd); + if (!locked) + return; /* user refused to steal, or write error, etc... */ + + if (spawn_next_command(state, g_dump_dir_name, event_name) < 0) + goto no_cmds; + + VERB1 log("running event '%s' on '%s'", event_name, g_dump_dir_name); + + /* At least one command is needed, and we started first one. + * Hook its output fd to the main loop. + */ + struct analyze_event_data *evd = xzalloc(sizeof(*evd)); + evd->run_state = state; + evd->event_name = event_name; + evd->more_events = more_events; + evd->page_widget = page; + evd->status_label = status_label; + evd->tv_log = tv_log; + evd->end_msg = end_msg; + evd->fd = state->command_out_fd; + ndelay_on(evd->fd); + evd->channel = g_io_channel_unix_new(evd->fd); + /*evd->event_source_id = */ g_io_add_watch(evd->channel, + G_IO_IN | G_IO_ERR | G_IO_HUP, /* need HUP to detect EOF w/o any data */ + consume_cmd_output, + evd + ); + + gtk_label_set_text(status_label, start_msg); + /* Freeze assistant so it can't move away from the page until analyzing is done */ + gtk_assistant_set_page_complete(g_assistant, page, false); +} + + +/* Backtrace checkbox handling */ + +static void add_warning(const char *warning) +{ + char *label_str = xasprintf("• %s", warning); + GtkWidget *warning_lbl = gtk_label_new(label_str); + /* should be safe to free it, gtk calls strdup() to copy it */ + free(label_str); + + gtk_misc_set_alignment(GTK_MISC(warning_lbl), 0.0, 0.0); + gtk_label_set_justify(GTK_LABEL(warning_lbl), GTK_JUSTIFY_LEFT); + gtk_box_pack_start(g_box_warning_labels, warning_lbl, false, false, 0); + gtk_widget_show(warning_lbl); +} + +static void check_backtrace_and_allow_send(void) //TODO: rename, this checks rating, not backtrace +{ + bool send = true; + bool warn = false; + + /* erase all warnings */ + gtk_widget_hide(g_widget_warnings_area); + gtk_container_foreach(GTK_CONTAINER(g_box_warning_labels), &remove_child_widget, NULL); + + /* + * FIXME: this should be bind to a reporter not to a compoment + * but so far only oopses doesn't have rating, so for now we + * skip the "kernel" manually + */ + const char *component = get_crash_item_content_or_NULL(g_cd, FILENAME_COMPONENT); + if (strcmp(component, "kernel") != 0) + { + const char *rating = get_crash_item_content_or_NULL(g_cd, FILENAME_RATING); + if (rating) switch (*rating) + { + case '4': //bt is ok - no warning here + break; + case '3': //bt is usable, but not complete, so show a warning + add_warning(_("The backtrace is incomplete, please make sure you provide the steps to reproduce.")); + warn = true; + break; + case '2': + case '1': + //FIXME: see CreporterAssistant: 394 for ideas + add_warning(_("Reporting disabled because the backtrace is unusable.")); + send = false; + warn = true; + break; + } + } + + if (!gtk_toggle_button_get_active(g_tb_approve_bt)) + { + add_warning(_("You should check the backtrace for sensitive data.")); + add_warning(_("You must agree with sending the backtrace.")); + send = false; + warn = true; + } + + gtk_assistant_set_page_complete(g_assistant, + pages[PAGENO_BACKTRACE_APPROVAL].page_widget, + send); + if (warn) + gtk_widget_show(g_widget_warnings_area); +} + +static void on_bt_approve_toggle(GtkToggleButton *togglebutton, gpointer user_data) +{ + check_backtrace_and_allow_send(); +} + + +/* Refresh button handling */ + +static void on_btn_refresh_clicked(GtkButton *button) +{ + if (g_reanalyze_events[0]) + { + g_analyze_events = append_to_malloced_string(g_analyze_events, g_reanalyze_events); + g_reanalyze_events[0] = '\0'; + /* Save backtrace text if changed */ + save_text_from_text_view(g_tv_backtrace, FILENAME_BACKTRACE); + /* Refresh GUI so that we see new analyze+reanalyze buttons */ + update_gui_state_from_crash_data(); + /* Change page to analyzer selector - let user play with them */ + gtk_assistant_set_current_page(g_assistant, PAGENO_ANALYZE_SELECTOR); + } +} + + +/* Page navigation handlers */ + +static void next_page(GtkAssistant *assistant, gpointer user_data) +{ + /* page_no is actually the previous page, because this + * function is called before assistant goes to the next_page + */ + int page_no = gtk_assistant_get_current_page(assistant); + VERB2 log("page_no:%d", page_no); + + if (page_no == PAGENO_ANALYZE_SELECTOR + && g_analyze_label_selected != NULL) + { + start_event_run(/*event_name:*/ g_analyze_label_selected, + NULL, + pages[PAGENO_ANALYZE_PROGRESS].page_widget, + g_tv_analyze_log, + g_lbl_analyze_log, + _("Analyzing..."), + _("Analyzing finished with exit code %d") + ); + } + + if (page_no == PAGENO_REPORT) + { + GList *reporters = gtk_container_get_children(GTK_CONTAINER(g_box_reporters)); + if (reporters) + { + for (GList *li = reporters; li; li = li->next) + { + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(li->data)) == TRUE) + li->data = (gpointer)gtk_button_get_label(GTK_BUTTON(li->data)); + else + li->data = NULL; + } + reporters = g_list_remove_all(reporters, NULL); + if (reporters) + { + char *first_event_name = reporters->data; + reporters = g_list_remove(reporters, reporters->data); + start_event_run(first_event_name, + reporters, + pages[PAGENO_REPORT_PROGRESS].page_widget, + g_tv_report_log, + g_lbl_report_log, + _("Reporting..."), + _("Reporting finished with exit code %d") + ); + } + } + } +} + +static void on_page_prepare(GtkAssistant *assistant, GtkWidget *page, gpointer user_data) +{ + if (pages[PAGENO_BACKTRACE_APPROVAL].page_widget == page) + { + check_backtrace_and_allow_send(); + } + + if (pages[PAGENO_HOWTO].page_widget == page) + { + /* User just pressed [Fwd] on backtrace page. Save backtrace text if changed */ + save_text_from_text_view(g_tv_backtrace, FILENAME_BACKTRACE); + } + + if (pages[PAGENO_REPORT].page_widget == page) + { + /* User just pressed [Fwd] on comment page. Same as above */ + save_text_from_text_view(g_tv_reproduce, FILENAME_REPRODUCE); + save_text_from_text_view(g_tv_comment, FILENAME_COMMENT); + } + + if (pages[PAGENO_SUMMARY].page_widget == page + || pages[PAGENO_REPORT].page_widget == page + ) { + GtkWidget *w = GTK_WIDGET(g_tv_details); + GtkContainer *c = GTK_CONTAINER(gtk_widget_get_parent(w)); + if (c) + gtk_container_remove(c, w); + gtk_container_add(pages[PAGENO_SUMMARY].page_widget == page ? + g_container_details1 : g_container_details2, + w + ); + } +} + +static gint next_page_no(gint current_page_no, gpointer data) +{ + switch (current_page_no) + { + case PAGENO_SUMMARY: + if (!g_analyze_events[0]) + { + //TODO: if (!g_reporter_events[0]) /* no reporters available */ then what? + return PAGENO_REPORTER_SELECTOR; /* skip analyze pages */ + } + break; + + case PAGENO_REPORTER_SELECTOR: + if (get_crash_item_content_or_NULL(g_cd, FILENAME_BACKTRACE)) + break; + current_page_no++; /* no backtrace, skip next page */ + /* fall through */ + +#if 0 + case PAGENO_BACKTRACE_APPROVAL: + if (get_crash_item_content_or_NULL(g_cd, FILENAME_COMMENT) + || get_crash_item_content_or_NULL(g_cd, FILENAME_REPRODUCE) + ) { + break; + } + current_page_no++; /* no comment, skip next page */ + /* fall through */ +#endif + + } + + return current_page_no + 1; +} + + +static gboolean highlight_search(gpointer user_data) +{ + GtkEntry *entry = GTK_ENTRY(user_data); + g_print("searching: %s\n", gtk_entry_get_text(entry)); + //returning will make gtk to remove this event + return false; +} + +static void search_timeout(GtkEntry *entry) +{ + /* this little hack makes the search start after 500 milisec after + * user stops writing into entry box + * if this part is removed, then the search will be started on every + * char written into the entry + */ + if(g_timeout != 0) + g_source_remove(g_timeout); + g_timeout = g_timeout_add(500, &highlight_search, (gpointer)entry); +} + + +/* Initialization */ + +static void create_details_treeview() +{ + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("Name"), + renderer, + "text", + DETAIL_COLUMN_NAME, + NULL); + gtk_tree_view_column_set_sort_column_id(column, DETAIL_COLUMN_NAME); + gtk_tree_view_append_column(g_tv_details, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("Value"), + renderer, + "text", + DETAIL_COLUMN_VALUE, + NULL); + gtk_tree_view_append_column(g_tv_details, column); + + /* + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("Path"), + renderer, + "text", + DETAIL_COLUMN_PATH, + NULL); + gtk_tree_view_append_column(g_tv_details, column); + */ +} + +/* wizard.glade file as a string WIZARD_GLADE_CONTENTS: */ +#include "wizard_glade.c" + +static void add_pages(void) +{ + GError *error = NULL; + if (!g_glade_file) + { + /* Load UI from internal string */ + gtk_builder_add_objects_from_string(builder, + WIZARD_GLADE_CONTENTS, sizeof(WIZARD_GLADE_CONTENTS) - 1, + (gchar**)page_names, + &error); + if (error != NULL) + error_msg_and_die("Error loading glade data: %s", error->message); + } + else + { + /* -g FILE: load IU from it */ + gtk_builder_add_objects_from_file(builder, g_glade_file, (gchar**)page_names, &error); + if (error != NULL) + error_msg_and_die("Can't load %s: %s", g_glade_file, error->message); + } + + for (int i = 0; page_names[i] != NULL; i++) + { + GtkWidget *page = GTK_WIDGET(gtk_builder_get_object(builder, page_names[i])); + if (page == NULL) + continue; + + pages[i].page_widget = page; + + gtk_assistant_append_page(g_assistant, page); + /* If we set all pages to complete the wizard thinks there is nothing + * to do and shows the button "Last" which allows user to skip all pages + * so we need to set them all as incomplete and complete them one by one + * on proper place - on_page_prepare() ? + */ + gtk_assistant_set_page_complete(g_assistant, page, true); + + gtk_assistant_set_page_title(g_assistant, page, pages[i].title); + gtk_assistant_set_page_type(g_assistant, page, pages[i].type); + + VERB1 log("added page: %s", page_names[i]); + } + /* Set pointers to objects we might need to work with */ + g_lbl_cd_reason = GTK_LABEL( gtk_builder_get_object(builder, "lbl_cd_reason")); + g_box_analyzers = GTK_BOX( gtk_builder_get_object(builder, "vb_analyzers")); + g_lbl_analyze_log = GTK_LABEL( gtk_builder_get_object(builder, "lbl_analyze_log")); + g_tv_analyze_log = GTK_TEXT_VIEW( gtk_builder_get_object(builder, "tv_analyze_log")); + g_box_reporters = GTK_BOX( gtk_builder_get_object(builder, "vb_reporters")); + g_lbl_report_log = GTK_LABEL( gtk_builder_get_object(builder, "lbl_report_log")); + g_tv_report_log = GTK_TEXT_VIEW( gtk_builder_get_object(builder, "tv_report_log")); + g_tv_backtrace = GTK_TEXT_VIEW( gtk_builder_get_object(builder, "tv_backtrace")); + g_tv_reproduce = GTK_TEXT_VIEW( gtk_builder_get_object(builder, "tv_reproduce")); + g_tv_comment = GTK_TEXT_VIEW( gtk_builder_get_object(builder, "tv_comment")); + g_tv_details = GTK_TREE_VIEW( gtk_builder_get_object(builder, "tv_details")); + g_box_warning_labels = GTK_BOX( gtk_builder_get_object(builder, "box_warning_labels")); + g_tb_approve_bt = GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "cb_approve_bt")); + g_widget_warnings_area = GTK_WIDGET( gtk_builder_get_object(builder, "box_warning_area")); + g_btn_refresh = GTK_BUTTON( gtk_builder_get_object(builder, "btn_refresh")); + g_search_entry_bt = GTK_ENTRY( gtk_builder_get_object(builder, "entry_search_bt")); + g_container_details1 = GTK_CONTAINER( gtk_builder_get_object(builder, "container_details1")); + g_container_details2 = GTK_CONTAINER( gtk_builder_get_object(builder, "container_details2")); + + gtk_widget_modify_font(GTK_WIDGET(g_tv_analyze_log), monospace_font); + gtk_widget_modify_font(GTK_WIDGET(g_tv_report_log), monospace_font); + gtk_widget_modify_font(GTK_WIDGET(g_tv_backtrace), monospace_font); + + ///* hide the warnings by default */ + //gtk_widget_hide(g_widget_warnings_area); + + //gtk_assistant_set_page_complete(g_assistant, pages[PAGENO_REPORTER_SELECTOR].page_widget, false); + gtk_assistant_set_page_complete(g_assistant, pages[PAGENO_BACKTRACE_APPROVAL].page_widget, + gtk_toggle_button_get_active(g_tb_approve_bt)); +} + +void create_assistant() +{ + monospace_font = pango_font_description_from_string("monospace"); + + builder = gtk_builder_new(); + + g_assistant = GTK_ASSISTANT(gtk_assistant_new()); + + gtk_assistant_set_forward_page_func(g_assistant, next_page_no, NULL, NULL); + + GtkWindow *wnd_assistant = GTK_WINDOW(g_assistant); + gtk_window_set_default_size(wnd_assistant, DEFAULT_WIDTH, DEFAULT_HEIGHT); + gtk_window_set_title(wnd_assistant, g_dump_dir_name); + gtk_window_set_icon_name(wnd_assistant, "abrt"); + + GObject *obj_assistant = G_OBJECT(g_assistant); + g_signal_connect(obj_assistant, "cancel", G_CALLBACK(gtk_main_quit), NULL); + g_signal_connect(obj_assistant, "close", G_CALLBACK(gtk_main_quit), NULL); + g_signal_connect(obj_assistant, "apply", G_CALLBACK(next_page), NULL); + g_signal_connect(obj_assistant, "prepare", G_CALLBACK(on_page_prepare), NULL); + + add_pages(); + + create_details_treeview(); + g_ls_details = gtk_list_store_new(DETAIL_NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + gtk_tree_view_set_model(g_tv_details, GTK_TREE_MODEL(g_ls_details)); + +// gtk_builder_connect_signals(builder, NULL); + + g_signal_connect(g_tb_approve_bt, "toggled", G_CALLBACK(on_bt_approve_toggle), NULL); + g_signal_connect(g_btn_refresh, "clicked", G_CALLBACK(on_btn_refresh_clicked), NULL); + + /* init search */ + GtkTextBuffer *backtrace_buf = gtk_text_view_get_buffer(g_tv_backtrace); + /* found items background */ + gtk_text_buffer_create_tag(backtrace_buf, "search_result_bg", "background", "red", NULL); + /* current position */ + gtk_text_buffer_create_tag(backtrace_buf, "current_pos_bg", "background", "yellow", NULL); + g_signal_connect(g_search_entry_bt, "changed", G_CALLBACK(search_timeout), NULL); + + gtk_assistant_set_page_complete(g_assistant, + pages[PAGENO_BACKTRACE_APPROVAL].page_widget, + gtk_toggle_button_get_active(g_tb_approve_bt) + ); +} diff --git a/src/gui-wizard-gtk/wizard.glade b/src/gui-wizard-gtk/wizard.glade new file mode 100644 index 00000000..9dde4316 --- /dev/null +++ b/src/gui-wizard-gtk/wizard.glade @@ -0,0 +1,746 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="2.16"/> + <!-- interface-naming-policy project-wide --> + <object class="GtkWindow" id="window0"> + <property name="width_request">800</property> + <property name="height_request">500</property> + <child> + <object class="GtkVBox" id="page_0"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="lbl_cd_reason"> + <property name="width_request">750</property> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="ypad">5</property> + <property name="wrap">True</property> + <attributes> + <attribute name="style" value="normal"/> + <attribute name="weight" value="bold"/> + <attribute name="foreground" value="#ffff00000000"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label7"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="ypad">5</property> + <property name="label" translatable="yes">Press 'Forward' to proceed with analyzing and reporting this problem.</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkExpander" id="expander1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child> + <object class="GtkScrolledWindow" id="container_details1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">automatic</property> + <property name="vscrollbar_policy">automatic</property> + <child> + <object class="GtkTreeView" id="tv_details"> + <property name="visible">True</property> + <property name="can_focus">True</property> + </object> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="dump_elements"> + <property name="visible">True</property> + <property name="label" translatable="yes">Details</property> + </object> + </child> + </object> + <packing> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + </object> + <object class="GtkWindow" id="window1"> + <property name="width_request">800</property> + <property name="height_request">500</property> + <child> + <object class="GtkVBox" id="page_1"> + <property name="width_request">750</property> + <property name="height_request">400</property> + <property name="visible">True</property> + <property name="border_width">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="lbl_page5"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Select how you would like to analyze the problem:</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vb_analyzers"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + <object class="GtkWindow" id="window2"> + <property name="width_request">800</property> + <property name="height_request">500</property> + <child> + <object class="GtkVBox" id="page_2"> + <property name="visible">True</property> + <property name="border_width">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="lbl_analyze_log"> + <property name="width_request">750</property> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="ypad">5</property> + <property name="label" translatable="yes">Analyzing did not start yet</property> + <property name="use_markup">True</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">automatic</property> + <child> + <object class="GtkTextView" id="tv_analyze_log"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">False</property> + <property name="accepts_tab">False</property> + </object> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + <object class="GtkWindow" id="window3"> + <property name="width_request">800</property> + <property name="height_request">500</property> + <child> + <object class="GtkVBox" id="page_3"> + <property name="visible">True</property> + <property name="border_width">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="lbl_page3"> + <property name="width_request">750</property> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Select how you would like to report the problem:</property> + <property name="use_markup">True</property> + <property name="justify">fill</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vb_reporters"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + <object class="GtkWindow" id="window4"> + <property name="width_request">800</property> + <property name="height_request">500</property> + <property name="tooltip_text" translatable="yes">Use this button to generate more informative backtrace after you installed additional debug packages</property> + <child> + <object class="GtkVBox" id="page_4"> + <property name="visible">True</property> + <property name="border_width">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="lbl_page4"> + <property name="width_request">750</property> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="ypad">3</property> + <property name="label" translatable="yes">Backtrace provides developers with details of the crash, helping them track down the source of the problem. Please review it and remove any sensitive data you would rather not share:</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow2"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">automatic</property> + <property name="vscrollbar_policy">automatic</property> + <child> + <object class="GtkTextView" id="tv_backtrace"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="wrap_mode">word</property> + </object> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="border_width">5</property> + <child> + <object class="GtkHBox" id="box_warning_area"> + <property name="visible">True</property> + <property name="no_show_all">True</property> + <child> + <object class="GtkImage" id="image1"> + <property name="visible">True</property> + <property name="stock">gtk-dialog-warning</property> + <property name="icon-size">6</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox6"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="box_warning_labels"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkAlignment" id="alignment2"> + <property name="visible">True</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox5"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkHBox" id="hbox2"> + <property name="visible">True</property> + <child> + <object class="GtkEntry" id="entry_search_bt"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + <property name="secondary_icon_stock">gtk-find</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox2"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkArrow" id="arrow1"> + <property name="visible">True</property> + <property name="arrow_type">up</property> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkArrow" id="arrow2"> + <property name="visible">True</property> + <property name="arrow_type">down</property> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="btn_refresh"> + <property name="label" translatable="yes">Regenerate backtrace</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes">Can create more informative backtrace if you installed additional debug packages</property> + <signal name="clicked" handler="on_b_refresh_clicked"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="pack_type">end</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="cb_approve_bt"> + <property name="label" translatable="yes">I agree with submitting the backtrace</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">3</property> + </packing> + </child> + </object> + </child> + </object> + <object class="GtkWindow" id="window5"> + <property name="width_request">800</property> + <property name="height_request">500</property> + <child> + <object class="GtkVBox" id="page_5"> + <property name="visible">True</property> + <property name="border_width">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkVBox" id="vbox1"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="spacing">5</property> + <child> + <object class="GtkVBox" id="vbox3"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="xalign">1.1175871339474952e-09</property> + <property name="yalign">0</property> + <property name="ypad">5</property> + <property name="label" translatable="yes">How did this crash happen (step-by-step)? How can it be reproduced?</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow3"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">automatic</property> + <property name="vscrollbar_policy">automatic</property> + <child> + <object class="GtkTextView" id="tv_reproduce"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="wrap_mode">word</property> + </object> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox4"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="label" translatable="yes">Are there any comments you would like to share with the software maintainers?</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow4"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">automatic</property> + <property name="vscrollbar_policy">automatic</property> + <child> + <object class="GtkTextView" id="tv_comment"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="wrap_mode">word</property> + </object> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="ypad">5</property> + <property name="label" translatable="yes"><b>Your comments are not private.</b> They may be included into publicly visible problem reports.</property> + <property name="use_markup">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + <object class="GtkWindow" id="window6"> + <property name="width_request">800</property> + <property name="height_request">500</property> + <child> + <object class="GtkVBox" id="page_6"> + <property name="visible">True</property> + <property name="border_width">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="lbl_page6"> + <property name="width_request">750</property> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Below is the information to be reported, please review it.</property> + <property name="justify">fill</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="border_width">10</property> + <property name="n_rows">4</property> + <property name="n_columns">2</property> + <property name="column_spacing">10</property> + <child> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Size:</property> + <property name="justify">right</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label5"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes"># files:</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label6"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">ETA to upload:</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label8"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Reporter(s):</property> + <property name="justify">right</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label9"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Bugzilla, Logger</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label10"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">328kB</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label11"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">27</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label12"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">2 minutes</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="container_details2"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">automatic</property> + <property name="vscrollbar_policy">automatic</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + </object> + <object class="GtkWindow" id="window7"> + <property name="width_request">800</property> + <property name="height_request">500</property> + <child> + <object class="GtkVBox" id="page_7"> + <property name="visible">True</property> + <property name="border_width">10</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="lbl_report_log"> + <property name="width_request">750</property> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="ypad">5</property> + <property name="label" translatable="yes">Reporting did not start yet</property> + <property name="use_markup">True</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow6"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">automatic</property> + <child> + <object class="GtkTextView" id="tv_report_log"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">False</property> + <property name="accepts_tab">False</property> + </object> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> +</interface> diff --git a/src/gui-wizard-gtk/wizard.h b/src/gui-wizard-gtk/wizard.h new file mode 100644 index 00000000..f27fb777 --- /dev/null +++ b/src/gui-wizard-gtk/wizard.h @@ -0,0 +1,38 @@ +enum { + PAGENO_SUMMARY, + PAGENO_ANALYZE_SELECTOR, + PAGENO_ANALYZE_PROGRESS, + PAGENO_REPORTER_SELECTOR, + PAGENO_BACKTRACE_APPROVAL, + PAGENO_HOWTO, + PAGENO_REPORT, + PAGENO_REPORT_PROGRESS, +}; +extern GtkAssistant *g_assistant; +extern GtkLabel *g_lbl_cd_reason; +extern GtkLabel *g_lbl_analyze_log; +extern GtkBox *g_box_analyzers; +extern GtkBox *g_box_reporters; +extern GtkTextView *g_tv_backtrace; +enum +{ + DETAIL_COLUMN_NAME, + DETAIL_COLUMN_VALUE, + //COLUMN_PATH, + DETAIL_NUM_COLUMNS, +}; +extern GtkTreeView *g_tv_details; +extern GtkListStore *g_ls_details; +void create_assistant(void); +void update_gui_state_from_crash_data(void); +void show_error_as_msgbox(const char *msg); + + +extern char *g_glade_file; +extern char *g_dump_dir_name; +extern char *g_analyze_label_selected; +extern char *g_analyze_events; +extern char *g_reanalyze_events; +extern char *g_report_events; +extern crash_data_t *g_cd; +void reload_crash_data_from_dump_dir(void); diff --git a/src/gui/CCDBusBackend.py b/src/gui/CCDBusBackend.py index 3fa95eb9..d2b867c7 100644 --- a/src/gui/CCDBusBackend.py +++ b/src/gui/CCDBusBackend.py @@ -21,6 +21,7 @@ class DBusManager(gobject.GObject): # and later with policyKit bus = None def __init__(self): + session = None # binds the dbus to glib mainloop DBusGMainLoop(set_as_default=True) @@ -60,7 +61,7 @@ class DBusManager(gobject.GObject): # signal emited to update gui with current status gobject.signal_new("update", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) # signal emited to show gui if user try to run it again - gobject.signal_new("show", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()) + gobject.signal_new("show_gui", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()) gobject.signal_new("daemon-state-changed", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) gobject.signal_new("report-done", self, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) @@ -211,11 +212,13 @@ class DBusManager(gobject.GObject): def getSettings(self): return self.daemon().GetSettings() - def setSettings(self, settings): - # FIXME: STUB!!!! - log1("setSettings stub") - retval = self.daemon().SetSettings(self.daemon().GetSettings()) - print ">>>", retval + ### looks unused to me. + ### Ok to grep for setSettings and delete after 2011-04-01. + ### def setSettings(self, settings): + ### # FIXME: STUB!!!! + ### log1("setSettings stub") + ### retval = self.daemon().SetSettings(self.daemon().GetSettings()) + ### print ">>>", retval def __del__(self): log1("CCDBusBackend is about to be deleted") diff --git a/src/gui/CCDump.py b/src/gui/CCDump.py index df5c18aa..10d7c2a3 100644 --- a/src/gui/CCDump.py +++ b/src/gui/CCDump.py @@ -3,7 +3,7 @@ from datetime import datetime from abrt_utils import _, init_logging, log, log1, log2 -# Keep in sync with [abrt_]crash_dump.h! +# Keep in sync with [abrt_]crash_data.h! CD_TYPE = 0 CD_EDITABLE = 1 CD_CONTENT = 2 @@ -25,10 +25,9 @@ FILENAME_CRASH_FUNCTION = "crash_function" FILENAME_ARCHITECTURE = "architecture" FILENAME_KERNEL = "kernel" FILENAME_TIME = "time" -FILENAME_RELEASE = "release" +FILENAME_OS_RELEASE = "os_release" FILENAME_PACKAGE = "package" FILENAME_COMPONENT = "component" -FILENAME_DESCRIPTION = "description" FILENAME_COMMENT = "comment" FILENAME_REPRODUCE = "reproduce" FILENAME_RATING = "rating" @@ -129,7 +128,11 @@ class Dump(): return self.analyzer def get_release(self): - return self.release + # Old dump dir format compat. Delete in abrt-2.1: + try: + return self.os_release + except AttributeError: + return self.release # old name def get_reason(self): return self.reason diff --git a/src/gui/CCMainWindow.py b/src/gui/CCMainWindow.py index 651c8e54..c6642f78 100644 --- a/src/gui/CCMainWindow.py +++ b/src/gui/CCMainWindow.py @@ -21,7 +21,6 @@ import CCDBusBackend from CC_gui_functions import * from CCDumpList import getDumpList from CCDump import * # FILENAME_xxx, CD_xxx -from CCReporterDialog import ReporterDialog, ReporterSelector from CReporterAssistant import ReporterAssistant from PluginsSettingsDialog import PluginsSettingsDialog from SettingsDialog import SettingsDialog @@ -39,6 +38,7 @@ class MainWindow(): self.gladefile = "%s/ccgui.glade" % sys.path[0] self.wTree = gtk.glade.XML(self.gladefile) + #Get the Main Window, and connect the "destroy" event self.window = self.wTree.get_widget("main_window") if self.window: @@ -89,9 +89,10 @@ class MainWindow(): self.wTree.get_widget("bReport").connect("clicked", self.on_bReport_clicked) self.wTree.get_widget("b_close").connect("clicked", self.on_bQuit_clicked) self.wTree.get_widget("b_copy").connect("clicked", self.on_b_copy_clicked) - self.wTree.get_widget("b_help").connect("clicked", self.on_miAbout_clicked) + self.wTree.get_widget("b_help").connect("clicked", self.on_miOnlineHelp_clicked) self.wTree.get_widget("miQuit").connect("activate", self.on_bQuit_clicked) self.wTree.get_widget("miAbout").connect("activate", self.on_miAbout_clicked) + self.wTree.get_widget("miOnlineHelp").connect("activate", self.on_miOnlineHelp_clicked) self.wTree.get_widget("miPlugins").connect("activate", self.on_miPreferences_clicked) self.wTree.get_widget("miPreferences").connect("activate", self.on_miSettings_clicked) self.wTree.get_widget("miReport").connect("activate", self.on_bReport_clicked) @@ -102,7 +103,7 @@ class MainWindow(): #self.ccdaemon.connect("update", self.update_cb) # for now, just treat them the same (w/o this, we don't even see daemon warnings in logs!): #self.ccdaemon.connect("warning", self.update_cb) - self.ccdaemon.connect("show", self.show_cb) + self.ccdaemon.connect("show_gui", self.show_cb) self.ccdaemon.connect("daemon-state-changed", self.on_daemon_state_changed_cb) self.ccdaemon.connect("report-done", self.on_report_done_cb) @@ -130,6 +131,10 @@ class MainWindow(): result = dialog.run() dialog.hide() + def on_miOnlineHelp_clicked(self, widget): + # opens default browser and shows ABRT chapter from deployment guide + gtk.show_uri(None, "http://docs.fedoraproject.org/en-US/Fedora/14/html/Deployment_Guide/ch-abrt.html", gtk.gdk.CURRENT_TIME) + def on_miPreferences_clicked(self, widget): dialog = PluginsSettingsDialog(self.window,self.ccdaemon) dialog.hydrate() @@ -243,6 +248,7 @@ class MainWindow(): # process the labels in sw_details # hide the fields that are not filled by daemon - e.g. comments # and how to reproduce + for field in dump.not_required_fields: self.wTree.get_widget("l_%s" % field.lower()).hide() self.wTree.get_widget("l_%s_heading" % field.lower()).hide() @@ -342,7 +348,7 @@ class MainWindow(): ("Command:", dump.cmdline), ("Reason:", dump.reason), ("Comment:", dump.comment), - ("Bug Reports:", dump.Message), + ("Bug Reports:", dump.message), ] dumpinfo_text = "" for line in dumpinfo: @@ -394,7 +400,7 @@ class MainWindow(): sys.exit() def show(self): - self.window.show() + self.window.show_all() def show_cb(self, daemon): if self.window: diff --git a/src/gui/CCReporterDialog.py b/src/gui/CCReporterDialog.py deleted file mode 100644 index b34baf87..00000000 --- a/src/gui/CCReporterDialog.py +++ /dev/null @@ -1,578 +0,0 @@ -# -*- coding: utf-8 -*- -import pygtk -pygtk.require("2.0") -import gtk -import gobject -import sys -from CC_gui_functions import * -import CellRenderers -from ABRTPlugin import PluginInfo -from PluginSettingsUI import PluginSettingsUI -from PluginList import getPluginInfoList -from CCDump import * # FILENAME_xxx, CD_xxx -from abrt_utils import _, log, log1, log2, get_verbose_level, g_verbose - -# FIXME - create method or smth that returns type|editable|content - -# response -REFRESH = -50 -SHOW_LOG = -60 - -# default texts -COMMENT_HINT_TEXT = _("Brief description of how to reproduce this or what you did...") -HOW_TO_HINT_TEXT = "1.\n2.\n3.\n" - -class ReporterDialog(): - """Reporter window""" - def __init__(self, report, daemon, log=None, parent=None): - self.editable = [] - self.row_dict = {} - self.report = report - #Set the Glade file - # FIXME add to path - builderfile = "%s/report.glade" % sys.path[0] - self.builder = gtk.Builder() - self.builder.add_from_file(builderfile) - #Get the Main Window, and connect the "destroy" event - self.window = self.builder.get_object("reporter_dialog") - self.window.set_default_size(-1, 800) - self.window.connect("response", self.on_response, daemon) - if parent: - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.window.set_transient_for(parent) - self.window.set_modal(True) - else: - self.window.set_position(gtk.WIN_POS_CENTER) - - # comment textview - self.tvComment = self.builder.get_object("tvComment") - self.tvComment.connect("focus-in-event", self.on_comment_focus_cb) - self.show_hint_comment = 1 - - # "how to reproduce" textview - self.tevHowToReproduce = self.builder.get_object("tevHowToReproduce") - - self.builder.get_object("fErrors").hide() - bLog = self.builder.get_object("bLog") - #if g_verbose > 0: - doesn't work! why?! - if get_verbose_level() > 0: - bLog.connect("clicked", self.show_log_cb, log) - else: - bLog.unset_flags(gtk.VISIBLE) - tb_send_bt = self.builder.get_object("cbSendBacktrace") - tb_send_bt.connect("toggled", self.on_send_backtrace_toggled) - try: - tb_send_bt.get_child().modify_fg(gtk.STATE_NORMAL,gtk.gdk.color_parse("red")) - except Exception, ex: - # we don't want gui to die if it fails to set the button color - log(ex) - self.allow_send() - self.hydrate() - - def check_backtrace(self): - print "checking backtrace" - - def warn_user(self, warnings): - # FIXME: show in lError - fErrors = self.builder.get_object("fErrors") - lErrors = self.builder.get_object("lErrors") - warning_lbl = None - for warning in warnings: - if warning_lbl: - warning_lbl += "\n* %s" % warning - else: - warning_lbl = "* %s" % warning - lErrors.set_label(warning_lbl) - fErrors.show_all() - - def hide_warning(self): - fErrors = self.builder.get_object("fErrors") - lErrors = self.builder.get_object("lErrors") - fErrors.hide() - - def allow_send(self): - self.hide_warning() - bSend = self.builder.get_object("bSend") - SendBacktrace = self.builder.get_object("cbSendBacktrace").get_active() - send = True - error_msgs = [] - try: - rating = int(self.report[FILENAME_RATING][CD_CONTENT]) - except: - rating = None - # active buttons acording to required fields - # if an backtrace has rating use it - if not SendBacktrace: - send = False - error_msgs.append(_("You must check the backtrace for sensitive data.")) - # we have both SendBacktrace and rating - if rating != None: - try: - package = self.report[FILENAME_PACKAGE][CD_CONTENT] - # if we don't have package for some reason - except: - package = None - # not usable report - if int(self.report[FILENAME_RATING][CD_CONTENT]) < 3: - if package: - error_msgs.append(_("Reporting disabled because the backtrace is unusable.\nPlease try to install debuginfo manually using the command: <b>debuginfo-install %s</b> \nthen use the Refresh button to regenerate the backtrace." % package[0:package.rfind('-',0,package.rfind('-'))])) - else: - error_msgs.append(_("The backtrace is unusable, you cannot report this!")) - send = False - # probably usable 3 - elif int(self.report[FILENAME_RATING][CD_CONTENT]) < 4: - error_msgs.append(_("The backtrace is incomplete, please make sure you provide the steps to reproduce.")) - - if error_msgs: - self.warn_user(error_msgs) - bSend.set_sensitive(send) - if not send: - bSend.set_tooltip_text(_("Reporting disabled, please fix the problems shown above.")) - else: - bSend.set_tooltip_text(_("Sends the report using the selected plugin.")) - - def on_send_backtrace_toggled(self, toggle_button): - self.allow_send() - - def show_log_cb(self, widget, log): - show_log(log, parent=self.window) - - # this callback is called when user press Cancel or Report button in Report dialog - def on_response(self, dialog, response_id, daemon): - # the button has been pressed (probably) - if response_id == gtk.RESPONSE_APPLY: - if not (self.check_report()): - dialog.stop_emission("response") - self.builder.get_object("bSend").stop_emission("clicked") - if response_id == SHOW_LOG: - # prevent the report dialog from quitting the run() and closing itself - dialog.stop_emission("response") - - def on_send_toggled(self, cell, path, model): - model[path][3] = not model[path][3] - - def on_comment_focus_cb(self, widget, event): - if self.show_hint_comment: - # clear "hint" text by supplying a fresh, empty TextBuffer - widget.set_buffer(gtk.TextBuffer()) - self.show_hint_comment = 0 - - def set_label(self, label_widget, text): - if len(text) > label_widget.get_max_width_chars(): - label_widget.set_tooltip_text(text) - label_widget.set_text(text) - - def hydrate(self): - self.editable = [] - self.old_comment = COMMENT_HINT_TEXT - self.old_how_to_reproduce = HOW_TO_HINT_TEXT - for item in self.report: - try: - log2("report[%s]:%s/%s/%s", item, self.report[item][0], self.report[item][1], self.report[item][2][0:20]) - except: - pass - - if item == FILENAME_BACKTRACE: - buff = gtk.TextBuffer() - tvBacktrace = self.builder.get_object("tvBacktrace") - buff.set_text(self.report[item][CD_CONTENT]) - tvBacktrace.set_buffer(buff) - continue - - if item == FILENAME_COMMENT: - try: - if self.report[item][CD_CONTENT]: - self.old_comment = self.report[item][CD_CONTENT] - except Exception, e: - pass - continue - - if item == FILENAME_REPRODUCE: - try: - if self.report[item][CD_CONTENT]: - self.old_how_to_reproduce = self.report[item][CD_CONTENT] - except Exception, e: - pass - continue - - if self.report[item][CD_TYPE] == CD_SYS: - continue - - # item name 0| value 1| editable? 2| toggled? 3| visible?(attachment)4 - # FIXME: handle editable fields - if self.report[item][CD_TYPE] == CD_BIN: - self.builder.get_object("fAttachment").show() - vbAttachments = self.builder.get_object("vbAttachments") - toggle = gtk.CheckButton(self.report[item][CD_CONTENT]) - vbAttachments.pack_start(toggle) - # bind item to checkbox - toggle.item = item - #FIXME: temporary workaround, in 1.0.4 reporters don't care - # about this, they just send what they want to - # TicketUploader even sends coredump!! - #toggle.show() - continue - - # It must be CD_TXT field - item_label = self.builder.get_object("l%s" % item) - if item_label: - self.set_label(item_label, self.report[item][CD_CONTENT]) - else: - # no widget to show this item - # probably some new item need to adjust the GUI! - # FIXME: add some window+button to show all the info - # in raw form (smth like the old report dialog) - pass - #end for - - buff = gtk.TextBuffer() - self.show_hint_comment = (self.old_comment == COMMENT_HINT_TEXT) - if self.show_hint_comment: - buff.set_text(COMMENT_HINT_TEXT) - else: - buff.set_text(self.old_comment) - self.tvComment.set_buffer(buff) - - buff = gtk.TextBuffer() - if self.old_how_to_reproduce == "": - buff.set_text(HOW_TO_HINT_TEXT) - else: - buff.set_text(self.old_how_to_reproduce) - self.tevHowToReproduce.set_buffer(buff) - - def dehydrate(self): - ## # handle attachments - ## vbAttachments = self.builder.get_object("vbAttachments") - ## for attachment in vbAttachments.get_children(): - ## #print "%s file %s" % (["not sending","sending"][attachment.get_active()], attachment.get_label()) - ## del self.report[attachment.item] - - # handle comment - buff = self.tvComment.get_buffer() - text = buff.get_text(buff.get_start_iter(), buff.get_end_iter()) - if self.old_comment != text: - self.report[FILENAME_COMMENT] = [CD_TXT, 'y', text] - # handle how to reproduce - buff = self.tevHowToReproduce.get_buffer() - text = buff.get_text(buff.get_start_iter(), buff.get_end_iter()) - if self.old_how_to_reproduce != text: - self.report[FILENAME_REPRODUCE] = [CD_TXT, 'y', text] - # handle backtrace - tev_backtrace = self.builder.get_object("tvBacktrace") - buff = tev_backtrace.get_buffer() - text = buff.get_text(buff.get_start_iter(), buff.get_end_iter()) - self.report[FILENAME_BACKTRACE] = [CD_TXT, 'y', text] - - def check_report(self): - # FIXME: check the report for passwords and some other potentially - # sensitive info - self.dehydrate() - return True - - def run(self): - result = self.window.run() - self.window.destroy() - return (result, self.report) - -class ReporterSelector(): - def __init__(self, crashdump, daemon, log=None, parent=None): - self.connected_signals = [] - self.updates = "" - self.daemon = daemon - self.dump = crashdump - self.selected_reporters = [] - #FIXME: cache settings! Create some class to represent it like PluginList - self.settings = daemon.getSettings() - pluginlist = getPluginInfoList(daemon) - self.reporters = [] - AnalyzerActionsAndReporters = self.settings["AnalyzerActionsAndReporters"] - try: - reporters = None - try: - reporters = AnalyzerActionsAndReporters[self.dump.getAnalyzerName()+":"+self.dump.getPackageName()] - except KeyError: - pass - if not reporters: - reporters = AnalyzerActionsAndReporters[crashdump.getAnalyzerName()] - for reporter_name in reporters.split(','): - reporter = pluginlist.getReporterByName(reporter_name) - if reporter: - self.reporters.append(reporter) - except KeyError: - # Analyzer has no associated reporters. - pass - - builderfile = "%s/report.glade" % sys.path[0] - self.builder = gtk.Builder() - self.builder.add_from_file(builderfile) - self.window = self.builder.get_object("w_reporters") - b_cancel = self.builder.get_object("b_close") - - if parent: - self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - self.window.set_transient_for(parent) - self.window.set_modal(True) - self.connect_signal(self.window, "delete-event", self.on_window_delete) - self.connect_signal(self.window, "destroy-event", self.on_window_delete) - self.connect_signal(b_cancel, "clicked", self.on_close_clicked) - else: - # if we don't have parent we want to quit the mainloop on close - self.window.set_position(gtk.WIN_POS_CENTER) - self.connect_signal(self.window, "delete-event", gtk.main_quit) - self.connect_signal(self.window, "destroy-event", gtk.main_quit) - self.connect_signal(b_cancel, "clicked", gtk.main_quit) - - - self.pBarWindow = self.builder.get_object("pBarWindow") - self.pBarWindow.set_transient_for(self.window) - - reporters_vbox = self.builder.get_object("vb_reporters") - for reporter in self.reporters: - button = gtk.Button(str(reporter)) - self.connect_signal(button, "clicked", self.on_reporter_clicked, data=reporter) - reporters_vbox.pack_start(button) - - # progress bar window to show while bt is being extracted - self.pBarWindow = self.builder.get_object("pBarWindow") - if self.pBarWindow: - self.connect_signal(self.pBarWindow, "delete_event", self.sw_delete_event_cb) - if parent: - self.pBarWindow.set_transient_for(parent) - else: - self.pBarWindow.set_transient_for(self.window) - self.pBar = self.builder.get_object("pBar") - - # connect handlers for daemon signals - #self.ccdaemon.connect("abrt-error", self.error_cb) - self.connect_signal(daemon, "update", self.update_cb) - # for now, just treat them the same (w/o this, we don't even see daemon warnings in logs!): - #self.ccdaemon.connect("warning", self.update_cb) - #self.ccdaemon.connect("show", self.show_cb) - #self.ccdaemon.connect("daemon-state-changed", self.on_daemon_state_changed_cb) - self.connect_signal(daemon, "report-done", self.on_report_done_cb) - self.connect_signal(daemon, "analyze-complete", self.on_analyze_complete_cb, self.pBarWindow) - - def connect_signal(self, obj, signal, callback, data=None): - if data: - signal_id = obj.connect(signal, callback, data) - else: - signal_id = obj.connect(signal, callback) - self.connected_signals.append((obj, signal_id)) - - def disconnect_signals(self): - # we need to disconnect all signals in order to break all references - # to this object, otherwise python won't destroy this object and the - # signals emmited by daemon will get caught by multiple instances of - # this class - for obj, signal_id in self.connected_signals: - obj.disconnect(signal_id) - - def cleanup_and_exit(self): - if not self.window.get_property("visible"): - self.disconnect_signals() - # if the reporter selector doesn't have a parent - if not self.window.get_transient_for(): - gtk.main_quit() - - def update_cb(self, daemon, message): - self.updates += message - if self.updates[-1] != '\n': - self.updates += '\n' - message = message.replace('\n',' ') - self.builder.get_object("lStatus").set_text(message) - buff = gtk.TextBuffer() - buff.set_text(self.updates) - end = buff.get_insert() - tvUpdates = self.builder.get_object("tvUpdates") - tvUpdates.set_buffer(buff) - tvUpdates.scroll_mark_onscreen(end) - - def sw_delete_event_cb(self, widget, event, data=None): - if self.timer: - gobject.source_remove(self.timer) - widget.hide() - return True - - def show(self): - if not self.reporters: - gui_error_message(_("No reporter plugin available for this type of crash.\n" - "Please check abrt.conf.")) - elif len(self.reporters) > 1: - self.builder.get_object("vb_reporters").show_all() - self.window.show() - else: - # we have only one reporter in the list - self.selected_reporters = [str(self.reporters[0])] - self.show_report() - - def on_config_plugin_clicked(self, button, plugin, image): - ui = PluginSettingsUI(plugin, parent=self.window) - ui.hydrate() - response = ui.run() - if response == gtk.RESPONSE_APPLY: - ui.dehydrate() - if plugin.Settings.check(): - try: - plugin.save_settings_on_client_side() - except Exception, e: - gui_error_message(_("Cannot save plugin settings:\n %s" % e)) - box = image.get_parent() - im = gtk.Image() - im.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_MENU) - box.remove(image) - box.pack_start(im, expand = False, fill = False) - im.show() - image.destroy() - button.set_sensitive(False) - elif response == gtk.RESPONSE_CANCEL: - log1("cancel") - ui.destroy() - - def check_settings(self, reporters): - wrong_conf_plugs = [] - for reporter in reporters: - if reporter.Settings.check() == False: - wrong_conf_plugs.append(reporter) - - if wrong_conf_plugs: - gladefile = "%s%ssettings_wizard.glade" % (sys.path[0],"/") - builder = gtk.Builder() - builder.add_from_file(gladefile) - dialog = builder.get_object("WrongSettings") - vbWrongSettings = builder.get_object("vbWrongSettings") - for plugin in wrong_conf_plugs: - hbox = gtk.HBox() - hbox.set_spacing(6) - image = gtk.Image() - image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_MENU) - button = gtk.Button(_("Configure %s options" % plugin.getName())) - button.connect("clicked", self.on_config_plugin_clicked, plugin, image) - hbox.pack_start(button) - hbox.pack_start(image, expand = False, fill = False) - vbWrongSettings.pack_start(hbox) - vbWrongSettings.show_all() - dialog.set_transient_for(self.window) - dialog.set_modal(True) - response = dialog.run() - dialog.destroy() - if response != gtk.RESPONSE_YES: - # user cancelled reporting - return False - return True - - def on_reporter_clicked(self, widget, reporter): - self.selected_reporters = [reporter] - if self.check_settings(self.selected_reporters): - self.show_report() - - def on_close_clicked(self, widget): - self.disconnect_signals() - self.window.destroy() - - def on_window_delete(self, window, event): - self.disconnect_signals() - return False - - def on_report_done_cb(self, daemon, result): - try: - gobject.source_remove(self.timer) - except: - pass - self.pBarWindow.hide() - gui_report_dialog(result, self.window) - - self.cleanup_and_exit() - - def on_analyze_complete_cb(self, daemon, report, pBarWindow): - try: - gobject.source_remove(self.timer) - except: - pass - self.pBarWindow.hide() -#FIXME - why we need this?? -> timeout warnings -# try: -# dumplist = getDumpList(self.daemon) -# except Exception, e: -# print e - if not report: - gui_error_message(_("Unable to get report!\nIs debuginfo missing?")) - return - - # if we have only one reporter enabled, the window with - # the selection is not shown, so we can't use it as a parent - # and we use the mainwindow instead - if self.window.get_property("visible"): - parent_window = self.window - else: - parent_window = self.window.get_transient_for() - - report_dialog = ReporterDialog(report, self.daemon, log=self.updates, parent=parent_window) - # (response, report) - response, result = report_dialog.run() - - if response == gtk.RESPONSE_APPLY: - try: - self.pBarWindow.show_all() - self.timer = gobject.timeout_add(100, self.progress_update_cb) - pluginlist = getPluginInfoList(self.daemon) - reporters_settings = pluginlist.getReporterPluginsSettings() - log2("Report(result,reporters,settings):") - log2(" result:%s", str(result)) - # Careful, this will print reporters_settings["Password"] too - log2(" settings:%s", str(reporters_settings)) - self.daemon.Report(result, self.selected_reporters, reporters_settings) - log2("Report() returned") - #self.hydrate() - except Exception, ex: - gui_error_message(_("Reporting failed!\n%s" % ex)) - # -50 == REFRESH - elif response == -50: - self.refresh_report(report) - else: - self.cleanup_and_exit() - - # call to update the progressbar - def progress_update_cb(self, *args): - self.pBar.pulse() - return True - - def refresh_report(self, report): - self.updates = "" - self.pBarWindow.show_all() - self.timer = gobject.timeout_add(100, self.progress_update_cb) - - # show the report window with selected report - try: - self.daemon.start_job(report[CD_DUMPDIR][CD_CONTENT], force=1) - except Exception, ex: - # FIXME #3 dbus.exceptions.DBusException: org.freedesktop.DBus.Error.NoReply: Did not receive a reply - # do this async and wait for yum to end with debuginfoinstal - if self.timer: - gobject.source_remove(self.timer) - self.pBarWindow.hide() - gui_error_message(_("Error acquiring the report: %s" % ex)) - return - - def show_report(self): - self.updates = "" - # FIXME don't duplicate the code, move to function - #self.pBar.show() - self.pBarWindow.show_all() - self.timer = gobject.timeout_add(100, self.progress_update_cb) - - # show the report window with selected dump - # when getReport is done it emits "analyze-complete" and on_analyze_complete_cb is called - # FIXME: does it make sense to change it to use callback rather then signal emitting? - try: - self.daemon.start_job(self.dump.getDumpDir()) - except Exception, ex: - # FIXME #3 dbus.exceptions.DBusException: org.freedesktop.DBus.Error.NoReply: Did not receive a reply - # do this async and wait for yum to end with debuginfoinstal - if self.timer: - gobject.source_remove(self.timer) - self.pBarWindow.hide() - gui_error_message(_("Error acquiring the report: %s" % ex)) - return - - def __del__(self): - log1("ReporterSelector: instance is about to be garbage-collected") diff --git a/src/gui/CReporterAssistant.py b/src/gui/CReporterAssistant.py index d998b870..300ddc3f 100644 --- a/src/gui/CReporterAssistant.py +++ b/src/gui/CReporterAssistant.py @@ -23,12 +23,48 @@ MISSING_BACKTRACE_TEXT = _("Crash info doesn't contain a backtrace") DEFAULT_WIDTH = 800 DEFAULT_HEIGHT = 500 + +class Log(gtk.ScrolledWindow): + def __init__(self, log=""): + gtk.ScrolledWindow.__init__(self) + self.tv = gtk.TextView() + self.tv.set_editable(False) + self.add(self.tv) + # FIXME: log shouldn't be None + self.buff = gtk.TextBuffer() + self.buff.set_text(log) + self.tv.set_buffer(self.buff) + self.scroll = False + + def append_line(self, text): + end_iter = self.buff.get_end_iter() + self.buff.insert(end_iter, "%s\n" % text) + self.scroll_to_end() + + + def append_warning(self, text): + """ stub """ + pass + + def append_error(self, text): + """ do we need this? shouldn't error be an exception? """ + pass + + def set_scroll(self, scroll): + """ enables/disables scrolling to end, when new text is added """ + self.scroll = scroll + + def scroll_to_end(self): + if self.scroll: + mark = self.buff.get_insert() + self.tv.scroll_mark_onscreen(mark) + class ReporterAssistant(): def __init__(self, report, daemon, log=None, parent=None): self.connected_signals = [] self.plugins_cb = [] self.daemon = daemon - self.updates = "" + self.updates = [] self.pdict = {} self.report = report self.parent = parent @@ -37,6 +73,8 @@ class ReporterAssistant(): self.report_has_bt = False self.selected_report_events = [] self.ev_warning = None + self.log = Log() + self.log.set_scroll(True) """ create the assistant """ self.assistant = gtk.Assistant() self.assistant.set_icon_name("abrt") @@ -61,6 +99,8 @@ class ReporterAssistant(): self.pBarWindow = self.builder.get_object("pBarWindow") if self.pBarWindow: self.connect_signal(self.pBarWindow, "delete_event", self.sw_delete_event_cb) + pb_log_expander = self.builder.get_object("pb_log_expander") + pb_log_expander.add(self.log) self.connect_signal(daemon, "analyze-complete", self.on_analyze_complete_cb, self.pBarWindow) self.connect_signal(daemon, "report-done", self.on_report_done_cb) @@ -82,15 +122,10 @@ class ReporterAssistant(): viewer.set_transient_for(self.assistant) vbox = gtk.VBox() viewer.add(vbox) - log_tev = gtk.TextView() - log_tev.set_editable(False) - log_buff = gtk.TextBuffer() - log_buff.set_text(self.updates) - log_sw = gtk.ScrolledWindow() - log_sw.add(log_tev) - log_sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - log_tev.set_buffer(log_buff) - vbox.pack_start(log_sw) + log = Log() + for line in self.updates: + log.append_line(line) + vbox.pack_start(log) b_close = gtk.Button(stock=gtk.STOCK_CLOSE) b_close.connect("clicked",lambda *w: viewer.destroy()) vbox.pack_start(b_close, False) @@ -159,17 +194,10 @@ class ReporterAssistant(): gtk.main_quit() def update_cb(self, daemon, message): - self.updates += message - if self.updates[-1] != '\n': - self.updates += '\n' + self.updates.append(message) message = message.replace('\n',' ') self.builder.get_object("lStatus").set_text(message) - buff = gtk.TextBuffer() - buff.set_text(self.updates) - end = buff.get_insert() - tvUpdates = self.builder.get_object("tvUpdates") - tvUpdates.set_buffer(buff) - tvUpdates.scroll_mark_onscreen(end) + self.log.append_line(message) def sw_delete_event_cb(self, widget, event, data=None): if self.timer: @@ -1093,9 +1121,7 @@ class ReporterAssistant(): self.prepare_page_3() self.prepare_page_4() self.prepare_page_5() - self.updates = "" # FIXME don't duplicate the code, move to function - #self.pBar.show() self.show_progress() self.timer = gobject.timeout_add(100, self.progress_update_cb) diff --git a/src/gui/CellRenderers.py b/src/gui/CellRenderers.py deleted file mode 100644 index 155d8ce3..00000000 --- a/src/gui/CellRenderers.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -import gtk - -class CellTextView(gtk.TextView, gtk.CellEditable): - - __gtype_name__ = "CellTextView" - - def do_editing_done(self, *args): - self.remove_widget() - - def do_remove_widget(self, *args): - pass - - def do_start_editing(self, *args): - pass - - def get_text(self): - text_buffer = self.get_buffer() - bounds = text_buffer.get_bounds() - return text_buffer.get_text(*bounds) - - def set_text(self, text): - self.get_buffer().set_text(text) - - -class MultilineCellRenderer(gtk.CellRendererText): - - __gtype_name__ = "MultilineCellRenderer" - - def __init__(self): - gtk.CellRendererText.__init__(self) - self._in_editor_menu = False - self.old_text = "" - - def _on_editor_focus_out_event(self, editor, event): - if self._in_editor_menu: return - editor.remove_widget() - self.emit("edited", editor.get_data("path"), editor.get_text()) - - def _on_editor_key_press_event(self, editor, event): - if event.state & (gtk.gdk.SHIFT_MASK | gtk.gdk.CONTROL_MASK): return - if event.keyval == gtk.keysyms.Escape: - editor.set_text(self.old_text) - editor.remove_widget() - self.emit("editing-canceled") - - def _on_editor_populate_popup(self, editor, menu): - self._in_editor_menu = True - def on_menu_unmap(menu, self): - self._in_editor_menu = False - menu.connect("unmap", on_menu_unmap, self) - - def do_start_editing(self, event, widget, path, bg_area, cell_area, flags): - editor = CellTextView() - editor.modify_font(self.props.font_desc) - self.old_text = self.props.text - editor.set_text(self.props.text) - editor.set_size_request(cell_area.width, cell_area.height) - editor.set_border_width(min(self.props.xpad, self.props.ypad)) - editor.set_data("path", path) - editor.connect("focus-out-event", self._on_editor_focus_out_event) - editor.connect("key-press-event", self._on_editor_key_press_event) - editor.connect("populate-popup", self._on_editor_populate_popup) - editor.show() - return editor - diff --git a/src/gui/Makefile.am b/src/gui/Makefile.am index 002b3bc7..61725d6f 100644 --- a/src/gui/Makefile.am +++ b/src/gui/Makefile.am @@ -1,8 +1,8 @@ bin_SCRIPTS = abrt-gui PYTHON_FILES = CCDBusBackend.py CCDumpList.py CCDump.py CC_gui_functions.py \ - CCReporterDialog.py abrt_utils.py \ - CCMainWindow.py CellRenderers.py ABRTExceptions.py \ + abrt_utils.py \ + CCMainWindow.py ABRTExceptions.py \ SettingsDialog.py ABRTPlugin.py PluginList.py PluginSettingsUI.py \ PluginsSettingsDialog.py ConfBackend.py CReporterAssistant.py diff --git a/src/gui/SettingsDialog.py b/src/gui/SettingsDialog.py index e6d30e99..41fb9829 100644 --- a/src/gui/SettingsDialog.py +++ b/src/gui/SettingsDialog.py @@ -27,28 +27,10 @@ class SettingsDialog: self.window = self.builder.get_object("wGlobalSettings") self.builder.get_object("bSaveSettings").connect("clicked", self.on_ok_clicked) self.builder.get_object("bCancelSettings").connect("clicked", self.on_cancel_clicked) - self.builder.get_object("bAddCronJob").connect("clicked", self.on_bAddCronJob_clicked) - - # action plugin list for Cron tab - self.actionPluginsListStore = gtk.ListStore(str, object) - self.actionPluginsListStore.append([_("<b>Select plugin</b>"), None]) - # database plugin list - self.databasePluginsListStore = gtk.ListStore(str, object) - self.databasePluginsListStore.append([_("<b>Select database backend</b>"), None]) - - self.dbcombo = self.builder.get_object("cbDatabase") - self.dbcombo.set_model(self.databasePluginsListStore) - cell = gtk.CellRendererText() - self.dbcombo.pack_start(cell) - self.dbcombo.add_attribute(cell, "markup", 0) # blacklist edit self.builder.get_object("bEditBlackList").connect("clicked", self.on_blacklistEdit_clicked) self.builder.get_object("bOpenGPGPublicKeys").connect("clicked", self.on_GPGKeysEdit_clicked) - self.builder.get_object("bAddAction").connect("clicked", self.on_bAddAction_clicked) - # AnalyzerActionsAndReporters - self.analyzerPluginsListStore = gtk.ListStore(str, object) - self.analyzerPluginsListStore.append([_("<b>Select plugin</b>"), None]) # GPG keys self.wGPGKeys = self.builder.get_object("wGPGKeys") self.GPGKeysListStore = gtk.ListStore(str) @@ -77,18 +59,6 @@ class SettingsDialog: except Exception, e: raise Exception("Comunication with daemon has failed, have you restarted the daemon after update?") - ## hydrate cron jobs: - for key,val in self.settings["Cron"].iteritems(): - # actions are separated by ',' - actions = val.split(',') - self.settings["Cron"][key] = actions - for plugin in self.pluginlist.getActionPlugins(): - it = self.actionPluginsListStore.append([plugin.getName(), plugin]) - for key,val in self.settings["Cron"].iteritems(): - if plugin.getName() in val: - cron_job = (key,it) - self.add_CronJob(cron_job) - self.settings["Cron"][key].remove(plugin.getName()) # hydrate common common = self.settings["Common"] # ensure that all expected keys exist: @@ -96,11 +66,6 @@ class SettingsDialog: common["OpenGPGCheck"] = "no" # check unsigned pkgs too ## gpgcheck self.builder.get_object("cbOpenGPGCheck").set_active(common["OpenGPGCheck"] == 'yes') - ## database - for dbplugin in self.pluginlist.getDatabasePlugins(): - it = self.databasePluginsListStore.append([dbplugin.getName(), dbplugin]) - if common["Database"] == dbplugin.getName(): - self.dbcombo.set_active_iter(it) ## MaxCrashSize self.builder.get_object("sbMaxCrashReportsSize").set_value(float(common["MaxCrashReportsSize"])) ## GPG keys @@ -114,13 +79,6 @@ class SettingsDialog: ## blacklist self.builder.get_object("eBlacklist").set_text(common["BlackList"]) - # hydrate AnalyzerActionsAndReporters - AnalyzerActionsAndReporters = self.settings["AnalyzerActionsAndReporters"] - for analplugin in self.pluginlist.getAnalyzerPlugins(): - it = self.analyzerPluginsListStore.append([analplugin.getName(), analplugin]) - if analplugin.getName() in AnalyzerActionsAndReporters: - action = (AnalyzerActionsAndReporters[analplugin.getName()], it) - self.add_AnalyzerAction(action) def on_bCancelGPGKeys_clicked(self, button): self.wGPGKeys.hide() @@ -147,12 +105,6 @@ class SettingsDialog: def on_cancel_clicked(self, button): self.window.hide() - def on_remove_CronJob_clicked(self, button, job_hbox): - self.removeHBoxWihtChildren(job_hbox) - - def on_remove_Action_clicked(self, button, binding_hbox): - self.removeHBoxWihtChildren(binding_hbox) - def removeHBoxWihtChildren(self, job_hbox): job_hbox.get_parent().remove(job_hbox) for child in job_hbox.get_children(): @@ -237,7 +189,10 @@ class SettingsDialog: self.add_AnalyzerAction() def dehydrate(self): - self.ccdaemon.setSettings(self.settings) + pass + ### looks unused to me. + ### Ok to grep for setSettings and delete after 2011-04-01. + ### self.ccdaemon.setSettings(self.settings) def show(self): self.window.show() diff --git a/src/gui/ccgui.glade b/src/gui/ccgui.glade index 4f8c4f02..c2efa148 100644 --- a/src/gui/ccgui.glade +++ b/src/gui/ccgui.glade @@ -9,7 +9,6 @@ <property name="window_position">center-on-parent</property> <property name="icon_name">abrt</property> <property name="type_hint">dialog</property> - <property name="has_separator">False</property> <property name="program_name">ABRT</property> <property name="version">@VER@</property> <property name="copyright" translatable="yes">(C) 2009, 2010 Red Hat, Inc.</property> @@ -128,6 +127,20 @@ Máirín Duffy <duffy@redhat.com></property> </widget> </child> <child> + <widget class="GtkImageMenuItem" id="miOnlineHelp"> + <property name="label" translatable="yes">Online _Help</property> + <property name="visible">True</property> + <property name="use_underline">True</property> + <property name="use_stock">False</property> + <child internal-child="image"> + <widget class="GtkImage" id="image2"> + <property name="visible">True</property> + <property name="stock">gtk-help</property> + </widget> + </child> + </widget> + </child> + <child> <widget class="GtkImageMenuItem" id="miAbout"> <property name="label">gtk-about</property> <property name="visible">True</property> @@ -589,11 +602,34 @@ Máirín Duffy <duffy@redhat.com></property> <property name="homogeneous">True</property> <child> <widget class="GtkButton" id="b_help"> - <property name="label">gtk-help</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> - <property name="use_stock">True</property> + <child> + <widget class="GtkHBox" id="hbox5"> + <property name="visible">True</property> + <child> + <widget class="GtkImage" id="image1"> + <property name="visible">True</property> + <property name="yalign">0</property> + <property name="stock">gtk-help</property> + </widget> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label6"> + <property name="visible">True</property> + <property name="label" translatable="yes" context="yes">Online _Help</property> + <property name="use_underline">True</property> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> </widget> <packing> <property name="padding">10</property> diff --git a/src/gui/progress_window.glade b/src/gui/progress_window.glade index af48ee55..6fae1486 100644 --- a/src/gui/progress_window.glade +++ b/src/gui/progress_window.glade @@ -1,4 +1,4 @@ -<?xml version="1.0"?> +<?xml version="1.0" encoding="UTF-8"?> <interface> <requires lib="gtk+" version="2.16"/> <!-- interface-naming-policy project-wide --> @@ -12,7 +12,6 @@ <child> <object class="GtkVBox" id="vbox9"> <property name="visible">True</property> - <property name="orientation">vertical</property> <property name="spacing">12</property> <child> <object class="GtkLabel" id="lStatus"> @@ -35,23 +34,11 @@ </packing> </child> <child> - <object class="GtkExpander" id="expander1"> + <object class="GtkExpander" id="pb_log_expander"> <property name="visible">True</property> <property name="can_focus">True</property> <child> - <object class="GtkScrolledWindow" id="scrolledwindow4"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">automatic</property> - <property name="vscrollbar_policy">automatic</property> - <property name="shadow_type">etched-in</property> - <child> - <object class="GtkTextView" id="tvUpdates"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - </child> - </object> + <placeholder/> </child> <child type="label"> <object class="GtkLabel" id="lUpdates"> diff --git a/src/gui/settings.glade b/src/gui/settings.glade index b83e6617..9dc1b96f 100644 --- a/src/gui/settings.glade +++ b/src/gui/settings.glade @@ -276,7 +276,7 @@ <object class="GtkTable" id="common_table"> <property name="visible">True</property> <property name="border_width">6</property> - <property name="n_rows">5</property> + <property name="n_rows">4</property> <property name="n_columns">2</property> <property name="column_spacing">12</property> <property name="row_spacing">6</property> @@ -294,32 +294,6 @@ </packing> </child> <child> - <object class="GtkLabel" id="lDatabasePlugin"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="xpad">5</property> - <property name="label" translatable="yes">Database backend: </property> - </object> - <packing> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> - </packing> - </child> - <child> - <object class="GtkComboBox" id="cbDatabase"> - <property name="visible">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="y_options">GTK_FILL</property> - </packing> - </child> - <child> <object class="GtkLabel" id="lBlacklist"> <property name="visible">True</property> <property name="xalign">0</property> @@ -348,20 +322,6 @@ </packing> </child> <child> - <object class="GtkLabel" id="lOpenGPGPublicKeys"> - <property name="visible">True</property> - <property name="xalign">0</property> - <property name="xpad">5</property> - <property name="label" translatable="yes">GPG keys: </property> - </object> - <packing> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> - <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_FILL</property> - </packing> - </child> - <child> <object class="GtkSpinButton" id="sbMaxCrashReportsSize"> <property name="visible">True</property> <property name="can_focus">True</property> @@ -414,6 +374,20 @@ </packing> </child> <child> + <object class="GtkLabel" id="lOpenGPGPublicKeys"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="xpad">5</property> + <property name="label" translatable="yes">GPG keys: </property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> <object class="GtkHBox" id="gpghbox"> <property name="visible">True</property> <property name="spacing">6</property> @@ -445,8 +419,8 @@ <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="y_options">GTK_FILL</property> </packing> </child> @@ -461,241 +435,6 @@ <property name="tab_fill">False</property> </packing> </child> - <child> - <object class="GtkVBox" id="cron_vbox"> - <property name="visible">True</property> - <property name="border_width">6</property> - <property name="orientation">vertical</property> - <property name="spacing">6</property> - <child> - <object class="GtkScrolledWindow" id="swCronJobs"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">automatic</property> - <property name="vscrollbar_policy">automatic</property> - <child> - <object class="GtkViewport" id="vpCronJobs"> - <property name="visible">True</property> - <property name="resize_mode">queue</property> - <property name="shadow_type">none</property> - <child> - <object class="GtkVBox" id="cjvbox1"> - <property name="visible">True</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkHBox" id="cjhbox1"> - <property name="visible">True</property> - <child> - <object class="GtkLabel" id="lPlugin"> - <property name="visible">True</property> - <property name="label" translatable="yes"><b>Plugin</b></property> - <property name="use_markup">True</property> - </object> - <packing> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="lTime"> - <property name="visible">True</property> - <property name="label" translatable="yes"><b>Time (or period)</b></property> - <property name="use_markup">True</property> - </object> - <packing> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkVBox" id="vbCronJobs"> - <property name="visible">True</property> - <property name="orientation">vertical</property> - <child> - <placeholder/> - </child> - </object> - <packing> - <property name="position">1</property> - </packing> - </child> - </object> - </child> - </object> - </child> - </object> - <packing> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkHButtonBox" id="hbuttonboxy1"> - <property name="visible">True</property> - <property name="spacing">12</property> - <property name="layout_style">end</property> - <child> - <object class="GtkButton" id="bAddCronJob"> - <property name="label">gtk-add</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="use_stock">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="position">1</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel" id="pCron"> - <property name="visible">True</property> - <property name="label" translatable="yes">Cron</property> - </object> - <packing> - <property name="position">1</property> - <property name="tab_fill">False</property> - </packing> - </child> - <child> - <object class="GtkVBox" id="actions_vbox"> - <property name="visible">True</property> - <property name="border_width">6</property> - <property name="orientation">vertical</property> - <property name="spacing">6</property> - <child> - <object class="GtkScrolledWindow" id="scrolledwindow1"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">automatic</property> - <property name="vscrollbar_policy">automatic</property> - <child> - <object class="GtkViewport" id="viewportA1"> - <property name="visible">True</property> - <property name="resize_mode">queue</property> - <property name="shadow_type">none</property> - <child> - <object class="GtkVBox" id="vbox1"> - <property name="visible">True</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkHBox" id="ahbox1"> - <property name="visible">True</property> - <child> - <object class="GtkLabel" id="lReporter"> - <property name="visible">True</property> - <property name="label" translatable="yes"><b>Analyzer plugin</b></property> - <property name="use_markup">True</property> - </object> - <packing> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="lAssociatedActions"> - <property name="visible">True</property> - <property name="label" translatable="yes"><b>Associated action</b></property> - <property name="use_markup">True</property> - </object> - <packing> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkAlignment" id="action_lbl_align"> - <property name="visible">True</property> - <child> - <placeholder/> - </child> - </object> - <packing> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkVBox" id="vbActions"> - <property name="visible">True</property> - <property name="orientation">vertical</property> - <child> - <placeholder/> - </child> - </object> - <packing> - <property name="position">1</property> - </packing> - </child> - </object> - </child> - </object> - </child> - </object> - <packing> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkHButtonBox" id="hbuttonbox2"> - <property name="visible">True</property> - <property name="spacing">12</property> - <property name="layout_style">end</property> - <child> - <object class="GtkButton" id="bAddAction"> - <property name="label">gtk-add</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="use_stock">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="position">2</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel" id="pAnacre"> - <property name="visible">True</property> - <property name="label" translatable="yes">Analyzers, Actions, Reporters</property> - </object> - <packing> - <property name="position">2</property> - <property name="tab_fill">False</property> - </packing> - </child> </object> <packing> <property name="position">0</property> diff --git a/src/hooks/Makefile.am b/src/hooks/Makefile.am index 2f57d2e7..6ebf3628 100644 --- a/src/hooks/Makefile.am +++ b/src/hooks/Makefile.am @@ -1,38 +1,19 @@ libexec_PROGRAMS = abrt-hook-ccpp -bin_PROGRAMS = dumpoops # abrt-hook-ccpp abrt_hook_ccpp_SOURCES = \ abrt-hook-ccpp.c abrt_hook_ccpp_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ -DCONF_DIR=\"$(CONF_DIR)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ - -D_GNU_SOURCE -abrt_hook_ccpp_LDADD = \ - ../lib/libabrt.la - -# dumpoops -dumpoops_SOURCES = \ - dumpoops.cpp -dumpoops_CPPFLAGS = \ - -I$(srcdir)/../include \ - -I$(srcdir)/../lib \ - -I$(srcdir)/../plugins \ - -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ - -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ - -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ - -DCONF_DIR=\"$(CONF_DIR)\" \ - -DVAR_RUN=\"$(VAR_RUN)\" \ $(GLIB_CFLAGS) \ + -Wall \ -D_GNU_SOURCE -# build will succeed, but at runtime plugins do need ABRT*d*Utils -dumpoops_LDADD = \ - ../lib/libabrt_daemon.la \ - ../lib/libabrt.la \ - $(GLIB_FLAGS) +abrt_hook_ccpp_LDADD = \ + ../lib/libreport.la python_PYTHON = abrt.pth abrt_exception_handler.py EXTRA_DIST = abrt_exception_handler.py.in $(man_MANS) diff --git a/src/hooks/abrt-hook-ccpp.c b/src/hooks/abrt-hook-ccpp.c index bcdc8547..46d96c91 100644 --- a/src/hooks/abrt-hook-ccpp.c +++ b/src/hooks/abrt-hook-ccpp.c @@ -161,9 +161,22 @@ static char* get_cwd(pid_t pid) return malloc_readlink(buf); } -static char core_basename[sizeof("core.%lu") + sizeof(long)*3] = "core"; +/* + * %s - signal number + * %c - ulimit -c value + * %p - pid + * %u - uid + * %g - gid + * %t - UNIX time of dump + * %h - hostname + * %e - executable filename + * %% - output one "%" + */ +/* Must match CORE_PATTERN order in daemon! */ +static const char percent_specifiers[] = "%scpugthe"; +static char *core_basename = "core"; -static int open_user_core(const char *user_pwd, uid_t uid, pid_t pid) +static int open_user_core(const char *user_pwd, uid_t uid, pid_t pid, char **percent_values) { struct passwd* pw = getpwuid(uid); gid_t gid = pw ? pw->pw_gid : uid; @@ -178,20 +191,63 @@ static int open_user_core(const char *user_pwd, uid_t uid, pid_t pid) return -1; } - /* Mimic "core.PID" if requested */ - char buf[] = "0\n"; - int fd = open("/proc/sys/kernel/core_uses_pid", O_RDONLY); - if (fd >= 0) + if (strcmp(core_basename, "core") == 0) { - read(fd, buf, sizeof(buf)); - close(fd); + /* Mimic "core.PID" if requested */ + char buf[] = "0\n"; + int fd = open("/proc/sys/kernel/core_uses_pid", O_RDONLY); + if (fd >= 0) + { + read(fd, buf, sizeof(buf)); + close(fd); + } + if (strcmp(buf, "1\n") == 0) + { + core_basename = xasprintf("%s.%lu", core_basename, (long)pid); + } } - if (strcmp(buf, "1\n") == 0) + else { - sprintf(core_basename, "core.%lu", (long)pid); + /* Expand old core pattern, put expanded name in core_basename */ + core_basename = xstrdup(core_basename); + unsigned idx = 0; + while (1) + { + char c = core_basename[idx]; + if (!c) + break; + idx++; + if (c != '%') + continue; + + /* We just copied %, look at following char and expand %c */ + c = core_basename[idx]; + unsigned specifier_num = strchrnul(percent_specifiers, c) - percent_specifiers; + if (percent_specifiers[specifier_num] != '\0') /* valid %c (might be %% too) */ + { + const char *val = "%"; + if (specifier_num > 0) /* not %% */ + val = percent_values[specifier_num - 1]; + //log("c:'%c'", c); + //log("val:'%s'", val); + + /* Replace %c at core_basename[idx] by its value */ + idx--; + char *old = core_basename; + core_basename = xasprintf("%.*s%s%s", idx, core_basename, val, core_basename + idx + 2); + //log("pos:'%*s|'", idx, ""); + //log("new:'%s'", core_basename); + //log("old:'%s'", old); + free(old); + idx += strlen(val); + } + /* else: invalid %c, % is already copied verbatim, + * next loop iteration will copy c */ + } } - /* man core: + /* Open (create) compat core file. + * man core: * There are various circumstances in which a core dump file * is not produced: * @@ -209,8 +265,7 @@ static int open_user_core(const char *user_pwd, uid_t uid, pid_t pid) * * The RLIMIT_CORE or RLIMIT_FSIZE resource limits for the process * are set to zero. - * [shouldn't it be checked by kernel? 2.6.30.9-96 doesn't, still - * calls us even if "ulimit -c 0"] + * [we check RLIMIT_CORE, but how can we check RLIMIT_FSIZE?] * * The binary being executed by the process does not have * read permission enabled. [how we can check it here?] @@ -221,7 +276,6 @@ static int open_user_core(const char *user_pwd, uid_t uid, pid_t pid) * (However, see the description of the prctl(2) PR_SET_DUMPABLE operation, * and the description of the /proc/sys/fs/suid_dumpable file in proc(5).) */ - /* Do not O_TRUNC: if later checks fail, we do not want to have file already modified here */ struct stat sb; errno = 0; @@ -240,6 +294,7 @@ static int open_user_core(const char *user_pwd, uid_t uid, pid_t pid) if (ftruncate(user_core_fd, 0) != 0) { /* perror first, otherwise unlink may trash errno */ perror_msg("truncate %s/%s", user_pwd, core_basename); + unlink(core_basename); return -1; } @@ -248,23 +303,28 @@ static int open_user_core(const char *user_pwd, uid_t uid, pid_t pid) int main(int argc, char** argv) { - int i; struct stat sb; - if (argc < 5) + if (argc < 10) /* no argv[9]? */ { - error_msg_and_die("Usage: %s: DUMPDIR PID SIGNO UID CORE_SIZE_LIMIT", argv[0]); + /* percent specifier: %s %c %p %u %g %t %h %e */ + /* argv: [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] */ + error_msg_and_die("Usage: %s DUMPDIR SIGNO CORE_SIZE_LIMIT PID UID GID TIME HOSTNAME BINARY_NAME [OLD_PATTERN]", argv[0]); } /* Not needed on 2.6.30. * At least 2.6.18 has a bug where - * argv[1] = "DUMPDIR PID SIGNO UID CORE_SIZE_LIMIT" - * argv[2] = "PID SIGNO UID CORE_SIZE_LIMIT" + * argv[1] = "DUMPDIR SIGNO CORE_SIZE_LIMIT ..." + * argv[2] = "SIGNO CORE_SIZE_LIMIT ..." * and so on. Fixing it: */ - for (i = 1; argv[i]; i++) + if (strchr(argv[1], ' ')) { - strchrnul(argv[i], ' ')[0] = '\0'; + int i; + for (i = 1; argv[i]; i++) + { + strchrnul(argv[i], ' ')[0] = '\0'; + } } openlog("abrt", LOG_PID, LOG_DAEMON); @@ -272,19 +332,38 @@ int main(int argc, char** argv) errno = 0; const char* dddir = argv[1]; - pid_t pid = xatoi_u(argv[2]); - const char* signal_str = argv[3]; - int signal_no = xatoi_u(argv[3]); - uid_t uid = xatoi_u(argv[4]); - off_t ulimit_c = strtoull(argv[5], NULL, 10); + const char* signal_str = argv[2]; + int signal_no = xatoi_positive(signal_str); + off_t ulimit_c = strtoull(argv[3], NULL, 10); if (ulimit_c < 0) /* unlimited? */ { /* set to max possible >0 value */ ulimit_c = ~((off_t)1 << (sizeof(off_t)*8-1)); } + pid_t pid = xatoi_positive(argv[4]); + uid_t uid = xatoi_positive(argv[5]); if (errno || pid <= 0) { - error_msg_and_die("pid '%s' or limit '%s' is bogus", argv[2], argv[5]); + perror_msg_and_die("pid '%s' or limit '%s' is bogus", argv[4], argv[3]); + } + if (argv[10]) /* OLD_PATTERN */ + { + char *buf = (char*) xzalloc(strlen(argv[10]) / 2 + 2); + char *end = hex2bin(buf, argv[10], strlen(argv[10])); + if (end && end > buf && end[-1] == '\0') + { + core_basename = buf; + //log("core_basename:'%s'", core_basename); + } + else + { + /* Until recently, kernels were truncating expanded core pattern. + * In this case, we end up here... + */ + error_msg("bad old pattern '%s', ignoring and using 'core'", argv[10]); + /* core_basename = "core"; - already is */ + free(buf); + } } int src_fd_binary; @@ -311,8 +390,8 @@ int main(int argc, char** argv) /* Open a fd to compat coredump, if requested and is possible */ int user_core_fd = -1; if (setting_MakeCompatCore && ulimit_c != 0) - /* note: checks "user_pwd == NULL" inside */ - user_core_fd = open_user_core(user_pwd, uid, pid); + /* note: checks "user_pwd == NULL" inside, updates core_basename */ + user_core_fd = open_user_core(user_pwd, uid, pid, &argv[2]); if (executable == NULL) { @@ -340,7 +419,7 @@ int main(int argc, char** argv) if (!daemon_is_ok()) { - /* not an error, exit with exitcode 0 */ + /* not an error, exit with exit code 0 */ log("abrt daemon is not running. If it crashed, " "/proc/sys/kernel/core_pattern contains a stale value, " "consider resetting it to 'core'" @@ -410,16 +489,32 @@ int main(int argc, char** argv) return 0; } - unsigned path_len = snprintf(path, sizeof(path), "%s/ccpp-%ld-%lu.new", - dddir, (long)time(NULL), (long)pid); + unsigned path_len = snprintf(path, sizeof(path), "%s/ccpp-%s-%lu.new", + dddir, iso_date_string(NULL), (long)pid); if (path_len >= (sizeof(path) - sizeof("/"FILENAME_COREDUMP))) return 1; struct dump_dir *dd = dd_create(path, uid); if (dd) { + dd_create_basic_files(dd, uid); + + char source_filename[sizeof("/proc/%lu/smaps") + sizeof(long)*3]; + int base_name = sprintf(source_filename, "/proc/%lu/smaps", (long)pid); + base_name -= strlen("smaps"); + char *dest_filename = concat_path_file(dd->dd_dirname, FILENAME_SMAPS); + copy_file(source_filename, dest_filename, S_IRUSR | S_IRGRP | S_IWUSR); + chown(dest_filename, dd->dd_uid, dd->dd_gid); + strcpy(source_filename + base_name, "maps"); + strcpy(strrchr(dest_filename, '/') + 1, FILENAME_MAPS); + copy_file(source_filename, dest_filename, S_IRUSR | S_IRGRP | S_IWUSR); + chown(dest_filename, dd->dd_uid, dd->dd_gid); + free(dest_filename); + char *cmdline = get_cmdline(pid); /* never NULL */ - char *reason = xasprintf("Process %s was killed by signal %s (SIG%s)", executable, signal_str, signame ? signame : signal_str); + char *reason = xasprintf("Process %s was killed by signal %s (SIG%s)", + executable, signal_str, signame ? signame : signal_str); + dd_save_text(dd, FILENAME_ANALYZER, "CCpp"); dd_save_text(dd, FILENAME_EXECUTABLE, executable); dd_save_text(dd, FILENAME_CMDLINE, cmdline); @@ -498,8 +593,8 @@ int main(int argc, char** argv) } /* We close dumpdir before we start catering for crash storm case. - * Otherwise, delete_crash_dump_dir's from other concurrent - * CCpp's won't be able to delete our dump (their delete_crash_dump_dir + * Otherwise, delete_dump_dir's from other concurrent + * CCpp's won't be able to delete our dump (their delete_dump_dir * will wait for us), and we won't be able to delete their dumps. * Classic deadlock. */ diff --git a/src/hooks/abrt_exception_handler.py.in b/src/hooks/abrt_exception_handler.py.in index dd6fbaed..d12968e1 100644 --- a/src/hooks/abrt_exception_handler.py.in +++ b/src/hooks/abrt_exception_handler.py.in @@ -25,7 +25,6 @@ Module for the ABRT exception handling hook import sys import os import syslog -import subprocess import socket def write_dump(pid, tb): @@ -61,8 +60,10 @@ def handleMyException((etype, value, tb)): # restore original exception handler sys.excepthook = sys.__excepthook__ # pylint: disable-msg=E1101 - # ignore uncaught ctrl-c - if etype == KeyboardInterrupt: + # ignore + # - uncaught ctrl-c + # - SystemExit rhbz#636913 -> this exception is not an error + if etype in [KeyboardInterrupt, SystemExit]: return sys.__excepthook__(etype, value, tb) try: diff --git a/src/hooks/dumpoops.cpp b/src/hooks/dumpoops.cpp deleted file mode 100644 index c67f8cda..00000000 --- a/src/hooks/dumpoops.cpp +++ /dev/null @@ -1,131 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 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. - - Authors: - Denys Vlasenko <dvlasenk@redhat.com> - Zdenek Prikryl <zprikryl@redhat.com> -*/ - -#include "abrtlib.h" -#include "abrt_exception.h" -#include "KerneloopsScanner.h" -#include <dlfcn.h> -#include <glib.h> - -#define LOADSYM(fp, name) \ -do { \ - fp = (typeof(fp)) (dlsym(handle, name)); \ - if (!fp) \ - perror_msg_and_die(PLUGINS_LIB_DIR"/libKerneloopsScanner.so has no %s", name); \ -} while (0) - - -int main(int argc, char **argv) -{ - char *program_name = strrchr(argv[0], '/'); - program_name = program_name ? program_name + 1 : argv[0]; - - /* Parse options */ - bool opt_d = 0, opt_s = 0; - int opt; - while ((opt = getopt(argc, argv, "dsv")) != -1) { - switch (opt) { - case 'd': - opt_d = 1; - break; - case 's': - opt_s = 1; - break; - case 'v': - /* Kerneloops code uses VERB3, thus: */ - g_verbose = 3; - break; - default: -usage: - error_msg_and_die( - "Usage: %s [-dsv] FILE\n\n" - "Options:\n" - "\t-d\tCreate ABRT dump for every oops found\n" - "\t-s\tPrint found oopses on standard output\n" - "\t-v\tVerbose\n" - , program_name - ); - } - } - argv += optind; - if (!argv[0]) - goto usage; - - msg_prefix = program_name; - - /* Load KerneloopsScanner plugin */ - // const plugin_info_t *plugin_info; - CPlugin* (*plugin_newf)(void); - int (*scan_syslog_file)(GList **oopsList, const char *filename, time_t *last_changed_p); - int (*save_oops_to_debug_dump)(GList **oopsList); - void *handle; - - errno = 0; - //TODO: use it directly, not via dlopen? - handle = dlopen(PLUGINS_LIB_DIR"/libKerneloopsScanner.so", RTLD_NOW); - if (!handle) - perror_msg_and_die("can't load %s", PLUGINS_LIB_DIR"/libKerneloopsScanner.so"); - - // LOADSYM(plugin_info, "plugin_info"); - LOADSYM(plugin_newf, "plugin_new"); - LOADSYM(scan_syslog_file, "scan_syslog_file"); - LOADSYM(save_oops_to_debug_dump, "save_oops_to_debug_dump"); - - // CKerneloopsScanner* scanner = (CKerneloopsScanner*) plugin_newf(); - // scanner->Init(); - // scanner->LoadSettings(path); - - /* Use it: parse and dump the oops */ - GList *oopsList = NULL; - int cnt = scan_syslog_file(&oopsList, argv[0], NULL); - log("found oopses: %d", cnt); - - if (cnt > 0) { - if (opt_s) { - int i = 0; - int length = g_list_length(oopsList); - while (i < length) { - printf("\nVersion: %s", (char*)g_list_nth_data(oopsList, i)); - i++; - } - } - if (opt_d) { - log("dumping oopses"); - int errors = save_oops_to_debug_dump(&oopsList); - if (errors > 0) - { - log("%d errors while dumping oopses", errors); - return 1; - } - } - } - - for (GList *li = oopsList; li != NULL; li = g_list_next(li)) - free((char*)li->data); - - g_list_free(oopsList); - /*dlclose(handle); - why bother? - * cos we are handsome and good lookin' guys :D - */ - return 0; -} diff --git a/src/include/Makefile.am b/src/include/Makefile.am index 3716d19e..477963c3 100644 --- a/src/include/Makefile.am +++ b/src/include/Makefile.am @@ -1,16 +1,12 @@ -HEADER_FILES = \ - abrt_exception.h \ +libreport_includedir = $(includedir)/report +libreport_include_HEADERS = \ + report/crash_data.h \ + report/dump_dir.h \ + report/run_event.h + +libabrt_includedir = $(includedir)/abrt +libabrt_include_HEADERS = \ abrtlib.h \ abrt_types.h \ - comm_layer_inner.h \ - abrt_crash_dump.h \ - dbus_common.h \ - dump_dir.h \ - observer.h \ - plugin.h \ - action.h \ - analyzer.h \ + abrt_crash_data.h \ xfuncs.h - -lib_includedir=$(includedir)/abrt/ -lib_include_HEADERS = $(HEADER_FILES) diff --git a/src/include/abrt_crash_dump.h b/src/include/abrt_crash_data.h index ed7ffe95..b71b046f 100644 --- a/src/include/abrt_crash_dump.h +++ b/src/include/abrt_crash_data.h @@ -19,8 +19,12 @@ #ifndef ABRT_CRASH_DUMP_H_ #define ABRT_CRASH_DUMP_H_ +#include "crash_data.h" #include "abrt_types.h" +// Text bigger than this usually is attached, not added inline +#define CD_TEXT_ATT_SIZE (2*1024) + // Keep in sync with CCDump.py: // Filenames in dump directory: @@ -32,7 +36,8 @@ #define FILENAME_REASON "reason" #define FILENAME_COREDUMP "coredump" #define FILENAME_BACKTRACE "backtrace" -#define FILENAME_MEMORYMAP "memorymap" +#define FILENAME_MAPS "maps" +#define FILENAME_SMAPS "smaps" #define FILENAME_DUPHASH "global_uuid" /* name is compat, to be renamed to "duphash" */ // Name of the function where the application crashed. // Optional. @@ -41,11 +46,11 @@ #define FILENAME_ARCHITECTURE "architecture" #define FILENAME_KERNEL "kernel" #define FILENAME_TIME "time" -#define FILENAME_RELEASE "release" /* from /etc/redhat-release */ -// filled by <what?> +// From /etc/syste-release or /etc/redhat-release +#define FILENAME_OS_RELEASE "os_release" +// Filled by <what?> #define FILENAME_PACKAGE "package" #define FILENAME_COMPONENT "component" -#define FILENAME_DESCRIPTION "description" /* package descr (not crash descr) */ #define FILENAME_COMMENT "comment" #define FILENAME_REPRODUCE "reproduce" #define FILENAME_RATING "rating" @@ -67,62 +72,18 @@ #define CD_EVENTS "Events" -// Crash data is a map of 3-element vectors of strings: type, editable, content -#define CD_TYPE 0 -#define CD_EDITABLE 1 -#define CD_CONTENT 2 - -// SYS - system value, should not be displayed -// BIN - binary data -// TXT - text data, can be displayed -#define CD_SYS "s" -#define CD_BIN "b" -#define CD_TXT "t" -// Text bigger than this usually is attached, not added inline -#define CD_TEXT_ATT_SIZE (2*1024) - -#define CD_ISEDITABLE "y" -#define CD_ISNOTEDITABLE "n" - - #ifdef __cplusplus extern "C" { #endif -extern const char *const must_have_files[]; - +#define is_editable_file abrt_is_editable_file bool is_editable_file(const char *file_name); +#define log_crash_data abrt_log_crash_data +void log_crash_data(crash_data_t *crash_data, const char *pfx); + #ifdef __cplusplus } #endif - -#ifdef __cplusplus - -// <key, data> -typedef map_vector_string_t map_crash_data_t; -typedef std::vector<map_crash_data_t> vector_map_crash_data_t; - -void add_to_crash_data_ext(map_crash_data_t& pCrashData, - const char *pItem, - const char *pType, - const char *pEditable, - const char *pContent); -// Uses type:CD_TXT, editable:CD_ISNOTEDITABLE -void add_to_crash_data(map_crash_data_t& pCrashData, - const char *pItem, - const char *pContent); - -void load_crash_data_from_crash_dump_dir(struct dump_dir *dd, map_crash_data_t& data); - -const char *get_crash_data_item_content_or_NULL(const map_crash_data_t& crash_data, const char *key); -// Aborts if key is not found: -const std::string& get_crash_data_item_content(const map_crash_data_t& crash_data, const char *key); - -void log_map_crash_data(const map_crash_data_t& data, const char *name); - -#endif /* __cplusplus */ - - #endif diff --git a/src/include/abrt_exception.h b/src/include/abrt_exception.h deleted file mode 100644 index b826bfa8..00000000 --- a/src/include/abrt_exception.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 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 ABRTEXCEPTION_H_ -#define ABRTEXCEPTION_H_ - -#include "abrtlib.h" - -typedef enum { - EXCEP_UNKNOW, - EXCEP_DD_OPEN, - EXCEP_DD_LOAD, - EXCEP_DD_SAVE, - EXCEP_DD_DELETE, - EXCEP_DL, - EXCEP_PLUGIN, - EXCEP_ERROR, -} abrt_exception_t; - -/* std::exception is a class with virtual members. - * deriving from it makes our ctor/dtor much more heavy, - * and those are inlined in every throw and catch site! - */ -class CABRTException /*: public std::exception*/ -{ - private: - abrt_exception_t m_type; - char *m_what; - - /* Not defined. You can't use it */ - CABRTException& operator= (const CABRTException&); - - public: - ~CABRTException() { free(m_what); } - CABRTException(abrt_exception_t type, const char* fmt, ...); - CABRTException(const CABRTException& rhs); - - abrt_exception_t type() { return m_type; } - const char* what() const { return m_what; } -}; - -#endif diff --git a/src/include/abrt_types.h b/src/include/abrt_types.h index 38804895..200946e1 100644 --- a/src/include/abrt_types.h +++ b/src/include/abrt_types.h @@ -20,6 +20,34 @@ #define ABRT_TYPES_H_ #ifdef __cplusplus +extern "C" { +#endif + +/* We can't typedef it to map_string_t, since other parts of ABRT + * (daemon, cli) still use that name for C++ container. For now, + * let's call it map_string_h: + */ +typedef GHashTable map_string_h; + +#define new_map_string abrt_new_map_string +map_string_h *new_map_string(void); +#define free_map_string abrt_free_map_string +void free_map_string(map_string_h *ms); +#define get_map_string_item_or_empty abrt_get_map_string_item_or_empty +const char *get_map_string_item_or_empty(map_string_h *ms, const char *key); +static inline +const char *get_map_string_item_or_NULL(map_string_h *ms, const char *key) +{ + return (const char*)g_hash_table_lookup(ms, key); +} + + +#ifdef __cplusplus +} +#endif + + +#ifdef __cplusplus #include <map> #include <set> diff --git a/src/include/abrtlib.h b/src/include/abrtlib.h index 8cd0664a..61731587 100644 --- a/src/include/abrtlib.h +++ b/src/include/abrtlib.h @@ -42,6 +42,7 @@ #ifdef __cplusplus # include <string> #endif +#include <glib.h> #ifdef HAVE_CONFIG_H # include "config.h" @@ -77,74 +78,99 @@ int vdprintf(int d, const char *format, va_list ap); #include "hash_sha1.h" #include "hash_md5.h" -#include "abrt_crash_dump.h" -#include "dump_dir.h" +#include "abrt_crash_data.h" #include "abrt_types.h" +#include "dump_dir.h" +#include "run_event.h" #ifdef __cplusplus extern "C" { #endif +#define prefixcmp abrt_prefixcmp int prefixcmp(const char *str, const char *prefix); +#define suffixcmp abrt_suffixcmp int suffixcmp(const char *str, const char *suffix); +#define concat_path_file abrt_concat_path_file char *concat_path_file(const char *path, const char *filename); +#define append_to_malloced_string abrt_append_to_malloced_string char *append_to_malloced_string(char *mstr, const char *append); +#define skip_whitespace abrt_skip_whitespace char* skip_whitespace(const char *s); +#define skip_non_whitespace abrt_skip_non_whitespace char* skip_non_whitespace(const char *s); /* Like strcpy but can copy overlapping strings. */ +#define overlapping_strcpy abrt_overlapping_strcpy void overlapping_strcpy(char *dst, const char *src); /* A-la fgets, but malloced and of unlimited size */ +#define xmalloc_fgets abrt_xmalloc_fgets char *xmalloc_fgets(FILE *file); /* Similar, but removes trailing \n */ +#define xmalloc_fgetline abrt_xmalloc_fgetline char *xmalloc_fgetline(FILE *file); /* On error, copyfd_XX prints error messages and returns -1 */ enum { COPYFD_SPARSE = 1 << 0, }; +#define copyfd_eof abrt_copyfd_eof off_t copyfd_eof(int src_fd, int dst_fd, int flags); +#define copyfd_size abrt_copyfd_size off_t copyfd_size(int src_fd, int dst_fd, off_t size, int flags); +#define copyfd_exact_size abrt_copyfd_exact_size void copyfd_exact_size(int src_fd, int dst_fd, off_t size); +#define copy_file abrt_copy_file off_t copy_file(const char *src_name, const char *dst_name, int mode); +#define copy_file_recursive abrt_copy_file_recursive +int copy_file_recursive(const char *source, const char *dest); /* Returns malloc'ed block */ +#define encode_base64 abrt_encode_base64 char *encode_base64(const void *src, int length); +#define xatou abrt_xatou unsigned xatou(const char *numstr); +#define xatoi abrt_xatoi int xatoi(const char *numstr); /* Using xatoi() instead of naive atoi() is not always convenient - * in many places people want *non-negative* values, but store them * in signed int. Therefore we need this one: - * dies if input is not in [0, INT_MAX] range. Also will reject '-0' etc */ -int xatoi_u(const char *numstr); + * dies if input is not in [0, INT_MAX] range. Also will reject '-0' etc. + * It should really be named xatoi_nonnegative (since it allows 0), + * but that would be too long. + */ +#define xatoi_positive abrt_xatoi_positive +int xatoi_positive(const char *numstr); -unsigned long long monotonic_ns(void); -unsigned long long monotonic_us(void); -unsigned monotonic_sec(void); +//unused for now +//unsigned long long monotonic_ns(void); +//unsigned long long monotonic_us(void); +//unsigned monotonic_sec(void); enum { - /* on return, pipefds[1] is fd to which parent may write - * and deliver data to child's stdin: */ - EXECFLG_INPUT = 1 << 0, - /* on return, pipefds[0] is fd from which parent may read - * child's stdout: */ - EXECFLG_OUTPUT = 1 << 1, - /* open child's stdin to /dev/null: */ - EXECFLG_INPUT_NUL = 1 << 2, - /* open child's stdout to /dev/null: */ - EXECFLG_OUTPUT_NUL = 1 << 3, - /* redirect child's stderr to stdout: */ - EXECFLG_ERR2OUT = 1 << 4, - /* open child's stderr to /dev/null: */ - EXECFLG_ERR_NUL = 1 << 5, - /* suppress perror_msg("Can't execute 'foo'") if exec fails */ - EXECFLG_QUIET = 1 << 6, - EXECFLG_SETGUID = 1 << 7, - EXECFLG_SETSID = 1 << 8, + /* on return, pipefds[1] is fd to which parent may write + * and deliver data to child's stdin: */ + EXECFLG_INPUT = 1 << 0, + /* on return, pipefds[0] is fd from which parent may read + * child's stdout: */ + EXECFLG_OUTPUT = 1 << 1, + /* open child's stdin to /dev/null: */ + EXECFLG_INPUT_NUL = 1 << 2, + /* open child's stdout to /dev/null: */ + EXECFLG_OUTPUT_NUL = 1 << 3, + /* redirect child's stderr to stdout: */ + EXECFLG_ERR2OUT = 1 << 4, + /* open child's stderr to /dev/null: */ + EXECFLG_ERR_NUL = 1 << 5, + /* suppress perror_msg("Can't execute 'foo'") if exec fails */ + EXECFLG_QUIET = 1 << 6, + EXECFLG_SETGUID = 1 << 7, + EXECFLG_SETSID = 1 << 8, }; /* Returns pid */ +#define fork_execv_on_steroids abrt_fork_execv_on_steroids pid_t fork_execv_on_steroids(int flags, char **argv, int *pipefds, @@ -153,110 +179,67 @@ pid_t fork_execv_on_steroids(int flags, uid_t uid); /* Returns malloc'ed string. NULs are retained, and extra one is appended * after the last byte (this NUL is not accounted for in *size_p) */ +#define run_in_shell_and_save_output abrt_run_in_shell_and_save_output char *run_in_shell_and_save_output(int flags, - const char *cmd, - const char *dir, - size_t *size_p); - -/* Networking helpers */ -typedef struct len_and_sockaddr { - socklen_t len; - union { - struct sockaddr sa; - struct sockaddr_in sin; - struct sockaddr_in6 sin6; - } u; -} len_and_sockaddr; -enum { - LSA_LEN_SIZE = offsetof(len_and_sockaddr, u), - LSA_SIZEOF_SA = sizeof(struct sockaddr) > sizeof(struct sockaddr_in6) ? - sizeof(struct sockaddr) : sizeof(struct sockaddr_in6), -}; -void setsockopt_reuseaddr(int fd); -int setsockopt_broadcast(int fd); -int setsockopt_bindtodevice(int fd, const char *iface); -len_and_sockaddr* get_sock_lsa(int fd); -void xconnect(int s, const struct sockaddr *s_addr, socklen_t addrlen); -unsigned lookup_port(const char *port, const char *protocol, unsigned default_port); -int get_nport(const struct sockaddr *sa); -void set_nport(len_and_sockaddr *lsa, unsigned port); -len_and_sockaddr* host_and_af2sockaddr(const char *host, int port, sa_family_t af); -len_and_sockaddr* xhost_and_af2sockaddr(const char *host, int port, sa_family_t af); -len_and_sockaddr* host2sockaddr(const char *host, int port); -len_and_sockaddr* xhost2sockaddr(const char *host, int port); -len_and_sockaddr* xdotted2sockaddr(const char *host, int port); -int xsocket_type(len_and_sockaddr **lsap, int family, int sock_type); -int xsocket_stream(len_and_sockaddr **lsap); -int create_and_bind_stream_or_die(const char *bindaddr, int port); -int create_and_bind_dgram_or_die(const char *bindaddr, int port); -int create_and_connect_stream_or_die(const char *peer, int port); -int xconnect_stream(const len_and_sockaddr *lsa); -char* xmalloc_sockaddr2host(const struct sockaddr *sa); -char* xmalloc_sockaddr2host_noport(const struct sockaddr *sa); -char* xmalloc_sockaddr2hostonly_noport(const struct sockaddr *sa); -char* xmalloc_sockaddr2dotted(const struct sockaddr *sa); -char* xmalloc_sockaddr2dotted_noport(const struct sockaddr *sa); + const char *cmd, + const char *dir, + size_t *size_p); /* Random utility functions */ +/* Frees every element'd data using free(), + * then frees list itself using g_list_free(list): + */ +#define list_free_with_free abrt_list_free_with_free +void list_free_with_free(GList *list); + +#define get_dirsize abrt_get_dirsize double get_dirsize(const char *pPath); +#define get_dirsize_find_largest_dir abrt_get_dirsize_find_largest_dir double get_dirsize_find_largest_dir( const char *pPath, char **worst_dir, /* can be NULL */ const char *excluded /* can be NULL */ ); +/* Emit a string of hex representation of bytes */ +#define bin2hex abrt_bin2hex +char* bin2hex(char *dst, const char *str, int count); +/* Convert "xxxxxxxx" hex string to binary, no more than COUNT bytes */ +#define hex2bin abrt_hex2bin +char* hex2bin(char *dst, const char *str, int count); + /* Returns command line of running program. * Caller is responsible to free() the returned value. * If the pid is not valid or command line can not be obtained, * empty string is returned. */ +#define get_cmdline abrt_get_cmdline char* get_cmdline(pid_t pid); /* Returns 1 if abrtd daemon is running, 0 otherwise. */ +#define daemon_is_ok abrt_daemon_is_ok int daemon_is_ok(); -struct run_event_state { - int (*post_run_callback)(const char *dump_dir_name, void *param); - void *post_run_param; - char* (*logging_callback)(char *log_line, void *param); - void *logging_param; -}; -static inline struct run_event_state *new_run_event_state() - { return (struct run_event_state*)xzalloc(sizeof(struct run_event_state)); } -static inline void free_run_event_state(struct run_event_state *state) - { free(state); } -/* Returns exitcode of first failed action, or first nonzero return value - * of post_run_callback. If all actions are successful, returns 0. - * If no actions were run for the event, returns -1. +/* Takes ptr to time_t, or NULL if you want to use current time. + * Returns "YYYY-MM-DD-hh:mm:ss" string. */ -int run_event(struct run_event_state *state, const char *dump_dir_name, const char *event); -char *list_possible_events(struct dump_dir *dd, const char *dump_dir_name, const char *pfx); - -#ifdef __cplusplus -} -#endif - - -/* C++ style stuff */ -#ifdef __cplusplus -std::string unsigned_to_string(unsigned long long x); -std::string signed_to_string(long long x); -template <class T> inline -std::string to_string(T x) -{ - if ((T)~(T)0 < (T)0) /* T is a signed type */ - return signed_to_string(x); - return unsigned_to_string(x); -} - -void parse_args(const char *psArgs, vector_string_t& pArgs, int quote = -1); -void parse_release(const char *pRelease, char **product, char **version); - -char* make_description_bz(const map_crash_data_t& pCrashData); -char* make_description_reproduce_comment(const map_crash_data_t& pCrashData); -char* make_description_logger(const map_crash_data_t& pCrashData); -char* make_description_mailx(const map_crash_data_t& pCrashData); +#define iso_date_string abrt_iso_date_string +char *iso_date_string(time_t *pt); + +#define make_description_bz abrt_make_description_bz +char* make_description_bz(crash_data_t *crash_data); +#define make_description_reproduce_comment abrt_make_description_reproduce_comment +char* make_description_reproduce_comment(crash_data_t *crash_data); +#define make_description_logger abrt_make_description_logger +char* make_description_logger(crash_data_t *crash_data); +#define make_description_mailx abrt_make_description_mailx +char* make_description_mailx(crash_data_t *crash_data); + +#define parse_release_for_bz abrt_parse_release_for_bz +void parse_release_for_bz(const char *pRelease, char **product, char **version); +#define parse_release_for_rhts abrt_parse_release_for_rhts +void parse_release_for_rhts(const char *pRelease, char **product, char **version); /** * Loads settings and stores it in second parameter. On success it @@ -264,29 +247,21 @@ char* make_description_mailx(const map_crash_data_t& pCrashData); * * @param path A path of config file. * Config file consists of "key=value" lines. - * @param settings A readed plugin's settings. + * @param settings A read plugin's settings. * @param skipKeysWithoutValue * If true, lines in format "key=" (without value) are skipped. * Otherwise empty value "" is inserted into pSettings. * @return if it success it returns true, otherwise it returns false. */ -extern bool LoadPluginSettings(const char *pPath, - map_plugin_settings_t& pSettings, - bool skipKeysWithoutValue = true); +#define load_conf_file abrt_load_conf_file +bool load_conf_file(const char *pPath, map_string_h *settings, bool skipKeysWithoutValue); -// TODO: npajkovs: full rewrite ssprintf -> xasprintf -static inline std::string ssprintf(const char *format, ...) -{ - va_list p; - char *string_ptr; +/* Tries to create a copy of dump_dir_name in base_dir, with same or similar basename. + * Returns NULL if copying failed. In this case, logs a message before returning. */ +#define steal_directory abrt_steal_directory +struct dump_dir *steal_directory(const char *base_dir, const char *dump_dir_name); - va_start(p, format); - string_ptr = xvasprintf(format, p); - va_end(p); - - std::string res = string_ptr; - free(string_ptr); - return res; +#ifdef __cplusplus } #endif diff --git a/src/include/action.h b/src/include/action.h deleted file mode 100644 index 21183366..00000000 --- a/src/include/action.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - Action.h - header file for action plugin - - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) - 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 ACTION_H_ -#define ACTION_H_ - -#include "plugin.h" - -/** - * An abstract class. The class defines an action plugin interface. - */ -class CAction : public CPlugin -{ - public: - /** - * A Method which performs particular action. As the first parameter it - * takes an action directory. It could be either a directory of actual - * crash or it could be a directory contains all crashes. It depends on - * who call the plugin. The plugin can takes arguments, but the plugin - * has to parse them by itself. - * @param pActionDir An actual directory. - * @param pArgs Plugin's arguments. - */ - virtual void Run(const char *pActionDir, const char *pArgs, int force) = 0; -}; - -#endif diff --git a/src/include/analyzer.h b/src/include/analyzer.h deleted file mode 100644 index 1d78d576..00000000 --- a/src/include/analyzer.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - Analyzer.h - header file for analyzer plugin - - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) - 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 ANALYZER_H_ -#define ANALYZER_H_ - -#include <string> -#include "plugin.h" - -/** - * An abstract class. The class defines an analyzer plugin interface. - */ -class CAnalyzer : public CPlugin -{ -}; - -#endif /*ANALYZER_H_*/ diff --git a/src/include/dbus_common.h b/src/include/dbus_common.h deleted file mode 100644 index 6c739169..00000000 --- a/src/include/dbus_common.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - Copyright (C) 2009 Jiri Moskovcak (jmoskovc@redhat.com) - 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 DBUSCOMMON_H_ -#define DBUSCOMMON_H_ - -#include "abrt_crash_dump.h" - -#define ABRTD_DBUS_NAME "com.redhat.abrt" -#define ABRTD_DBUS_PATH "/com/redhat/abrt" -#define ABRTD_DBUS_IFACE "com.redhat.abrt" - -#endif diff --git a/src/include/observer.h b/src/include/observer.h deleted file mode 100644 index 1c8f2355..00000000 --- a/src/include/observer.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 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 OBSERVER_H_ -#define OBSERVER_H_ - -#include <string> -#include "dbus_common.h" - -class CObserver { - public: - virtual ~CObserver() {} - virtual void Status(const char *pMessage, const char* peer) = 0; - virtual void Warning(const char *pMessage, const char* peer) = 0; -}; - -#endif diff --git a/src/include/plugin.h b/src/include/plugin.h deleted file mode 100644 index 3f652e65..00000000 --- a/src/include/plugin.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - Plugin.h - header file for plugin. It contains mandatory macros - and common function for plugins - - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) - 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 PLUGIN_H_ -#define PLUGIN_H_ - -#include "abrt_types.h" -#include "abrt_crash_dump.h" - -#define PLUGINS_MAGIC_NUMBER 6 - -#define PLUGINS_CONF_EXTENSION "conf" -#define PLUGINS_LIB_EXTENSION "so" -#define PLUGINS_LIB_PREFIX "lib" - -/** - * An abstract class. The class defines a common plugin interface. If a plugin - * has some settings, then a *Settings(*) method has to be written. - */ -class CPlugin -{ - protected: - map_plugin_settings_t m_pSettings; - - public: - CPlugin(); - /** - * A destructor. - */ - virtual ~CPlugin(); - /** - * A method, which initializes a plugin. It is not mandatory method. - */ - virtual void Init(); - /** - * A method, which deinitializes a plugin. It is not mandatory method. - */ - virtual void DeInit(); - /** - * A method, which takes a settings and apply them. It is not a mandatory method. - * @param pSettings Plugin's settings - */ - virtual void SetSettings(const map_plugin_settings_t& pSettings); - /** - * A method, which return current settings. It is not mandatory method. - * @return Plugin's settings - */ -/// -// virtual const map_plugin_settings_t& GetSettings(); -}; - -/** - * An enum of plugin types. - */ -typedef enum { - ANALYZER, /**< An analyzer plugin*/ - ACTION, /**< An action plugin*/ - REPORTER, /**< A reporter plugin*/ - DATABASE, /**< A database plugin*/ - MAX_PLUGIN_TYPE = DATABASE, -} plugin_type_t; - -/** - * A struct contains all needed data about particular plugin. - */ -typedef struct SPluginInfo -{ - const plugin_type_t m_Type; /**< Plugin type.*/ - const char *const m_sName; /**< Plugin name.*/ - const char *const m_sVersion; /**< Plugin version.*/ - const char *const m_sDescription; /**< Plugin description.*/ - const char *const m_sEmail; /**< Plugin author's email.*/ - const char *const m_sWWW; /**< Plugin's home page.*/ - const char *const m_sGTKBuilder; /**< Plugin's gui description.*/ - const int m_nMagicNumber; /**< Plugin magical number.*/ -} plugin_info_t; - -#define PLUGIN_INFO(type, plugin_class, name, version, description, email, www, gtk_builder)\ - extern "C" CPlugin* plugin_new()\ - {\ - return new plugin_class();\ - }\ - extern "C" const plugin_info_t plugin_info =\ - {\ - type,\ - name,\ - version,\ - description,\ - email,\ - www,\ - gtk_builder,\ - PLUGINS_MAGIC_NUMBER,\ - }; - -#endif diff --git a/src/include/report/crash_data.h b/src/include/report/crash_data.h new file mode 100644 index 00000000..3854118a --- /dev/null +++ b/src/include/report/crash_data.h @@ -0,0 +1,101 @@ +/* + Copyright (C) 2009 Abrt team. + 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 CRASH_DATA_H_ +#define CRASH_DATA_H_ + +#include <glib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct dump_dir; + +enum { + CD_FLAG_BIN = (1 << 0), + CD_FLAG_TXT = (1 << 1), + CD_FLAG_ISEDITABLE = (1 << 2), + CD_FLAG_ISNOTEDITABLE = (1 << 3), +}; + +struct crash_item { + char *content; + unsigned flags; +}; +typedef struct crash_item crash_item; + +/* In-memory crash data structure and accessors */ + +typedef GHashTable crash_data_t; + +crash_data_t *new_crash_data(void); + +static inline void free_crash_data(crash_data_t *crash_data) +{ + if (crash_data) + g_hash_table_destroy(crash_data); +} + +void add_to_crash_data_ext(crash_data_t *crash_data, + const char *name, + const char *content, + unsigned flags); +/* Uses CD_FLAG_TXT + CD_FLAG_ISNOTEDITABLE flags */ +void add_to_crash_data(crash_data_t *crash_data, + const char *name, + const char *content); + +static inline struct crash_item *get_crash_data_item_or_NULL(crash_data_t *crash_data, const char *key) +{ + return (struct crash_item *)g_hash_table_lookup(crash_data, key); +} +const char *get_crash_item_content_or_NULL(crash_data_t *crash_data, const char *key); +/* Aborts if key is not found: */ +const char *get_crash_item_content_or_die(crash_data_t *crash_data, const char *key); + + +/* Vector of these structures */ + +typedef GPtrArray vector_of_crash_data_t; + +static inline crash_data_t *get_crash_data(vector_of_crash_data_t *vector, unsigned i) +{ + return (crash_data_t *)g_ptr_array_index(vector, i); +} + +vector_of_crash_data_t *new_vector_of_crash_data(void); +static inline void free_vector_of_crash_data(vector_of_crash_data_t *vector) +{ + if (vector) + g_ptr_array_free(vector, TRUE); +} + + +/* Conversions between in-memory and on-disk formats */ + +void load_crash_data_from_dump_dir(crash_data_t *crash_data, struct dump_dir *dd); +crash_data_t *create_crash_data_from_dump_dir(struct dump_dir *dd); + +struct dump_dir *create_dump_dir_from_crash_data(crash_data_t *crash_data, const char *base_dir_name); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/include/dump_dir.h b/src/include/report/dump_dir.h index 7a38f1e4..1376d7f3 100644 --- a/src/include/dump_dir.h +++ b/src/include/report/dump_dir.h @@ -21,16 +21,21 @@ #ifndef DUMP_DIR_H_ #define DUMP_DIR_H_ +/* For DIR */ +#include <sys/types.h> +#include <dirent.h> + #ifdef __cplusplus extern "C" { #endif enum { DD_FAIL_QUIETLY = (1 << 0), + DD_OPEN_READONLY = (1 << 1), }; struct dump_dir { - char *dd_dir; + char *dd_dirname; DIR *next_dir; int locked; uid_t dd_uid; @@ -40,22 +45,27 @@ struct dump_dir { void dd_close(struct dump_dir *dd); struct dump_dir *dd_opendir(const char *dir, int flags); +/* Pass uid = (uid_t)-1L to disable chown'ing of newly created files + * (IOW: if you aren't running under root): + */ struct dump_dir *dd_create(const char *dir, uid_t uid); +void dd_create_basic_files(struct dump_dir *dd, uid_t uid); int dd_exist(struct dump_dir *dd, const char *path); DIR *dd_init_next_file(struct dump_dir *dd); int dd_get_next_file(struct dump_dir *dd, char **short_name, char **full_name); enum { /* DD_FAIL_QUIETLY bit is valid for dd_load_text_ext too, */ - DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE = (1 << 1), + DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE = (1 << 2), }; char* dd_load_text_ext(const struct dump_dir *dd, const char *name, unsigned flags); char* dd_load_text(const struct dump_dir *dd, const char *name); void dd_save_text(struct dump_dir *dd, const char *name, const char *data); void dd_save_binary(struct dump_dir *dd, const char *name, const char *data, unsigned size); -void dd_delete(struct dump_dir *dd); +/* Returns 0 if directory is deleted or not found */ +int dd_delete(struct dump_dir *dd); -void delete_crash_dump_dir(const char *dd_dir); +void delete_dump_dir(const char *dirname); #ifdef __cplusplus } diff --git a/src/include/report/run_event.h b/src/include/report/run_event.h new file mode 100644 index 00000000..7fd8edea --- /dev/null +++ b/src/include/report/run_event.h @@ -0,0 +1,82 @@ +/* + Copyright (C) 2009 Abrt team. + 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 RUN_EVENT_H_ +#define RUN_EVENT_H_ + +#include "crash_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct dump_dir; + +struct run_event_state { + int children_count; + + /* Used only for post-create dup detection. TODO: document its API */ + int (*post_run_callback)(const char *dump_dir_name, void *param); + void *post_run_param; + + /* Can take ownership of log_line, which is malloced. In this case, return NULL. + * Otherwise should return log_line (it will be freed by caller) + */ + char* (*logging_callback)(char *log_line, void *param); + void *logging_param; + + /* Internal data for async command execution */ + GList *commands; + pid_t command_pid; + int command_out_fd; +}; +struct run_event_state *new_run_event_state(void); +void free_run_event_state(struct run_event_state *state); + +/* Asyncronous command execution */ + +/* Returns 0 if no commands found for this dump_dir_name+event, else >0 */ +int prepare_commands(struct run_event_state *state, const char *dump_dir_name, const char *event); +/* Returns -1 is no more commands needs to be executed, + * else sets state->command_pid and state->command_out_fd and returns >=0 + */ +int spawn_next_command(struct run_event_state *state, const char *dump_dir_name, const char *event); +/* Cleans up internal state created in prepare_commands */ +void free_commands(struct run_event_state *state); + +/* Syncronous command execution */ + +/* Returns exit code of first failed action, or first nonzero return value + * of post_run_callback. If all actions are successful, returns 0. + */ +int run_event_on_dir_name(struct run_event_state *state, const char *dump_dir_name, const char *event); +int run_event_on_crash_data(struct run_event_state *state, crash_data_t *data, const char *event); + +/* Querying for possible events */ + +/* Scans event.conf for events starting with pfx which are applicable + * to dd, or (if dd is NULL), to dump_dir. + * Returns a malloced string with '\n'-terminated event names. + */ +char *list_possible_events(struct dump_dir *dd, const char *dump_dir_name, const char *pfx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/include/xfuncs.h b/src/include/xfuncs.h index 20284564..61188c81 100644 --- a/src/include/xfuncs.h +++ b/src/include/xfuncs.h @@ -29,43 +29,69 @@ extern "C" { #endif +#define ndelay_on abrt_ndelay_on int ndelay_on(int fd); +#define ndelay_off abrt_ndelay_off int ndelay_off(int fd); +#define close_on_exec_on abrt_close_on_exec_on int close_on_exec_on(int fd); -void* xcalloc(size_t nmemb, size_t size); +#define xmalloc abrt_xmalloc void* xmalloc(size_t size); +#define xrealloc abrt_xrealloc void* xrealloc(void *ptr, size_t size); +#define xzalloc abrt_xzalloc void* xzalloc(size_t size); +#define xstrdup abrt_xstrdup char* xstrdup(const char *s); +#define xstrndup abrt_xstrndup char* xstrndup(const char *s, int n); +#define xpipe abrt_xpipe void xpipe(int filedes[2]); +#define xdup abrt_xdup void xdup(int from); +#define xdup2 abrt_xdup2 void xdup2(int from, int to); +#define xmove_fd abrt_xmove_fd void xmove_fd(int from, int to); +#define xwrite abrt_xwrite void xwrite(int fd, const void *buf, size_t count); +#define xwrite_str abrt_xwrite_str void xwrite_str(int fd, const char *str); +#define xlseek abrt_xlseek off_t xlseek(int fd, off_t offset, int whence); +#define xchdir abrt_xchdir void xchdir(const char *path); +#define xvasprintf abrt_xvasprintf char* xvasprintf(const char *format, va_list p); +#define xasprintf abrt_xasprintf char* xasprintf(const char *format, ...); +#define xsetenv abrt_xsetenv void xsetenv(const char *key, const char *value); +#define xsocket abrt_xsocket int xsocket(int domain, int type, int protocol); +#define xbind abrt_xbind void xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); +#define xlisten abrt_xlisten void xlisten(int s, int backlog); +#define xsendto abrt_xsendto ssize_t xsendto(int s, const void *buf, size_t len, const struct sockaddr *to, socklen_t tolen); +#define xstat abrt_xstat void xstat(const char *name, struct stat *stat_buf); +#define xopen3 abrt_xopen3 int xopen3(const char *pathname, int flags, int mode); +#define xopen abrt_xopen int xopen(const char *pathname, int flags); +#define xunlink abrt_xunlink void xunlink(const char *pathname); /* Just testing dent->d_type == DT_REG is wrong: some filesystems @@ -74,18 +100,28 @@ void xunlink(const char *pathname); * This function handles this case. Note: it returns 0 on symlinks * even if they point to regular files. */ +#define is_regular_file abrt_is_regular_file int is_regular_file(struct dirent *dent, const char *dirname); + +#define dot_or_dotdot abrt_dot_or_dotdot bool dot_or_dotdot(const char *filename); +#define last_char_is abrt_last_char_is char *last_char_is(const char *s, int c); +#define string_to_bool abrt_string_to_bool bool string_to_bool(const char *s); +#define xseteuid abrt_xseteuid void xseteuid(uid_t euid); +#define xsetegid abrt_xsetegid void xsetegid(gid_t egid); +#define xsetreuid abrt_xsetreuid void xsetreuid(uid_t ruid, uid_t euid); +#define xsetregid abrt_xsetregid void xsetregid(gid_t rgid, gid_t egid); /* Returns getpwuid(uid)->pw_dir or NULL */ +#define get_home_dir abrt_get_home_dir const char *get_home_dir(uid_t uid); #ifdef __cplusplus diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index ad2d2025..bad3e63a 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -1,69 +1,69 @@ -# libabrt - the stuff shared among most of abrt (like xmalloc, logging) -# libabrt_daemon - only daemon related things are here +# libreport - the stuff shared among most of abrt (like xmalloc, logging) # libabrt_dbus - daemon, cli and applet use this # libabrt_web - for abrt-action-foo where foo deals with network/web/ftp/... lib_LTLIBRARIES = \ - libabrt.la \ - libabrt_daemon.la \ + libreport.la \ libabrt_dbus.la \ libabrt_web.la -HEADER_DIR = $(srcdir)/../include -AM_CPPFLAGS = -I$(HEADER_DIR) - # Not used just yet: # time.cpp # xconnect.cpp -libabrt_la_SOURCES = \ +libreport_la_SOURCES = \ xfuncs.c \ encbase64.c \ + binhex.c \ stdio_helpers.c \ hash_md5.c hash_md5.h \ hash_sha1.c hash_sha1.h \ read_write.c read_write.h \ logging.c logging.h \ copyfd.c \ + copy_file_recursive.c \ concat_path_file.c \ append_to_malloced_string.c \ overlapping_strcpy.c \ skip_whitespace.c \ - stringops.cpp \ + glib_support.c \ + iso_date_string.c \ strbuf.c strbuf.h \ - xatonum.c numtoa.cpp \ + xatonum.c \ spawn.c \ dirsize.c \ dump_dir.c \ get_cmdline.c \ daemon_is_ok.c \ - load_plugin_settings.cpp \ - make_descr.cpp \ + load_plugin_settings.c \ + make_descr.c \ run_event.c \ - crash_dump.cpp \ - ABRTException.cpp \ + crash_data.c \ + create_dump_dir.c \ + abrt_types.c \ hooklib.c hooklib.h \ - parse_release.cpp \ - parse_options.c parse_options.h -libabrt_la_CPPFLAGS = \ + parse_release.c \ + parse_options.c parse_options.h \ + steal_directory.c +libreport_la_CPPFLAGS = \ -Wall -Werror \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' \ -DCONF_DIR=\"$(CONF_DIR)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ $(GLIB_CFLAGS) \ -D_GNU_SOURCE -libabrt_la_LDFLAGS = \ +libreport_la_LDFLAGS = \ -version-info 0:1:0 -libabrt_la_LIBADD = \ +libreport_la_LIBADD = \ $(GLIB_LIBS) libabrt_dbus_la_SOURCES = \ abrt_dbus.c abrt_dbus.h libabrt_dbus_la_CPPFLAGS = \ - -Wall -Werror \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ @@ -71,6 +71,7 @@ libabrt_dbus_la_CPPFLAGS = \ -DVAR_RUN=\"$(VAR_RUN)\" \ $(GLIB_CFLAGS) \ $(DBUS_CFLAGS) \ + -Wall -Werror \ -D_GNU_SOURCE libabrt_dbus_la_LDFLAGS = \ -version-info 0:1:0 @@ -78,34 +79,18 @@ libabrt_dbus_la_LIBADD = \ $(GLIB_LIBS) \ $(DBUS_LIBS) -libabrt_daemon_la_SOURCES = \ - $(HEADER_DIR)/comm_layer_inner.h CommLayerInner.cpp \ - $(HEADER_DIR)/plugin.h Plugin.cpp -libabrt_daemon_la_CPPFLAGS = \ - -Wall \ - -I$(srcdir)/../include \ - -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ - -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ - -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ - -DCONF_DIR=\"$(CONF_DIR)\" \ - -DVAR_RUN=\"$(VAR_RUN)\" \ - -D_GNU_SOURCE -libabrt_daemon_la_LDFLAGS = \ - -version-info 0:1:0 -libabrt_daemon_la_LIBADD = \ - -ldl - libabrt_web_la_SOURCES = \ abrt_curl.h abrt_curl.c \ abrt_xmlrpc.h abrt_xmlrpc.cpp libabrt_web_la_CPPFLAGS = \ -Wall -Werror \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ -DCONF_DIR=\"$(CONF_DIR)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ + $(GLIB_CFLAGS) \ $(CURL_CFLAGS) \ $(LIBXML_CFLAGS) \ $(XMLRPC_CFLAGS) $(XMLRPC_CLIENT_CFLAGS) \ @@ -113,6 +98,7 @@ libabrt_web_la_CPPFLAGS = \ libabrt_web_la_LDFLAGS = \ -version-info 0:1:0 libabrt_web_la_LIBADD = \ + $(GLIB_LIBS) \ $(CURL_LIBS) \ $(LIBXML_LIBS) \ $(XMLRPC_LIBS) $(XMLRPC_CLIENT_LIBS) diff --git a/src/lib/Plugin.cpp b/src/lib/Plugin.cpp deleted file mode 100644 index 0c2137f5..00000000 --- a/src/lib/Plugin.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) - 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 "plugin.h" -#include "abrtlib.h" - -CPlugin::CPlugin() {} - -/* class CPlugin's virtuals */ -CPlugin::~CPlugin() {} -void CPlugin::Init() {} -void CPlugin::DeInit() {} -void CPlugin::SetSettings(const map_plugin_settings_t& pSettings) -{ - m_pSettings = pSettings; - VERB3 - { - log("SetSettings:"); - map_plugin_settings_t::const_iterator it = m_pSettings.begin(); - while (it != m_pSettings.end()) - { - log(" settings[%s]='%s'", it->first.c_str(), it->second.c_str()); - it++; - } - } -} diff --git a/src/lib/abrt_curl.c b/src/lib/abrt_curl.c index f23d3949..a17f14d2 100644 --- a/src/lib/abrt_curl.c +++ b/src/lib/abrt_curl.c @@ -287,8 +287,10 @@ abrt_post(abrt_post_state_t *state, // truncates data_size on 32-bit arch. Need xcurl_easy_setopt_long_long()? // Also, I'm not sure CURLOPT_POSTFIELDSIZE_LARGE special-cases -1. } - // Override "Content-Type:" + struct curl_slist *httpheader_list = NULL; + + // Override "Content-Type:" if (data_size != ABRT_POST_DATA_FROMFILE_AS_FORM_DATA) { char *content_type_header = xasprintf("Content-Type: %s", content_type); @@ -300,6 +302,12 @@ abrt_post(abrt_post_state_t *state, xcurl_easy_setopt_ptr(handle, CURLOPT_HTTPHEADER, httpheader_list); } + // Override "Accept: text/plain": helps convince server to send plain-text + // error messages in the body of HTTP error responses [not verified to work] + httpheader_list = curl_slist_append(httpheader_list, "Accept: text/plain"); + if (!httpheader_list) + error_msg_and_die("out of memory"); + // Disabled: was observed to also handle "305 Use proxy" redirect, // apparently with POST->GET remapping - which server didn't like at all. // Attempted to suppress remapping on 305 using CURLOPT_POSTREDIR of -1, @@ -354,6 +362,11 @@ abrt_post(abrt_post_state_t *state, goto ret; } + // curl-7.20.1 doesn't do it, we get NULL body in the log message below + // unless we fflush the body memstream ourself + if (body_stream) + fflush(body_stream); + // Headers/body are already saved (if requested), extract more info curl_err = curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &response_code); die_if_curl_error(curl_err); diff --git a/src/lib/abrt_dbus.c b/src/lib/abrt_dbus.c index 6bc155e3..5d8d861d 100644 --- a/src/lib/abrt_dbus.c +++ b/src/lib/abrt_dbus.c @@ -17,7 +17,6 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <dbus/dbus.h> -#include <glib.h> #include "abrtlib.h" #include "abrt_dbus.h" @@ -156,6 +155,98 @@ void store_string(DBusMessageIter* iter, const char* val) free((char*)sanitized); } +/* Helper for storing map_string */ +void store_map_string(DBusMessageIter* dbus_iter, map_string_h *val) +{ + DBusMessageIter sub_iter; + /* map_string is a map. map in dbus is an array of two element structs "({...})": + * "s" (string) for key and "s" for value (in this case, also string) */ + if (!dbus_message_iter_open_container(dbus_iter, DBUS_TYPE_ARRAY, "{ss}", &sub_iter)) + die_out_of_memory(); + + GHashTableIter iter; + char *name; + char *value; + g_hash_table_iter_init(&iter, val); + while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value)) + { + DBusMessageIter sub_sub_iter; + if (!dbus_message_iter_open_container(&sub_iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub_sub_iter)) + die_out_of_memory(); + store_string(&sub_sub_iter, name); + store_string(&sub_sub_iter, value); + if (!dbus_message_iter_close_container(&sub_iter, &sub_sub_iter)) + die_out_of_memory(); + } + + if (!dbus_message_iter_close_container(dbus_iter, &sub_iter)) + die_out_of_memory(); +} + +/* Helpers for storing crash_data */ + +static void store_crash_item(DBusMessageIter* iter, struct crash_item *val) +{ + DBusMessageIter sub_iter; + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "s", &sub_iter)) + die_out_of_memory(); + + /* Compat with python/cli: + * Crash item is represented in dbus as 3-element vector of strings: + * type, editable, content. + * This doesn't match daemon-side representation: { content, flags } struct + */ + store_string(&sub_iter, (val->flags & CD_FLAG_BIN ? "b" : "t")); + store_string(&sub_iter, (val->flags & CD_FLAG_ISEDITABLE ? "y" : "n")); + store_string(&sub_iter, val->content); + + if (!dbus_message_iter_close_container(iter, &sub_iter)) + die_out_of_memory(); +} +void store_crash_data(DBusMessageIter* dbus_iter, crash_data_t *val) +{ + DBusMessageIter sub_iter; + /* crash_data is a map. map in dbus is an array of two element structs "({...})": + * "s" (string) for key and "as" for value (in this case, array of strings) */ + if (!dbus_message_iter_open_container(dbus_iter, DBUS_TYPE_ARRAY, "{sas}", &sub_iter)) + die_out_of_memory(); + + GHashTableIter iter; + char *name; + struct crash_item *value; + g_hash_table_iter_init(&iter, val); + while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value)) + { + DBusMessageIter sub_sub_iter; + if (!dbus_message_iter_open_container(&sub_iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub_sub_iter)) + die_out_of_memory(); + store_string(&sub_sub_iter, name); + store_crash_item(&sub_sub_iter, value); + if (!dbus_message_iter_close_container(&sub_iter, &sub_sub_iter)) + die_out_of_memory(); + } + + if (!dbus_message_iter_close_container(dbus_iter, &sub_iter)) + die_out_of_memory(); +} +void store_vector_of_crash_data(DBusMessageIter* iter, vector_of_crash_data_t *val) +{ + DBusMessageIter sub_iter; + /* "array of maps". map in dbus is an array ("a") of two element structs "({...})": + * "s" (string) for key and "as" for value (in this case, array of strings) */ + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "a{sas}", &sub_iter)) + die_out_of_memory(); + + for (unsigned i = 0; i < val->len; i++) + { + crash_data_t *crash_data = get_crash_data(val, i); + store_crash_data(&sub_iter, crash_data); + } + + if (!dbus_message_iter_close_container(iter, &sub_iter)) + die_out_of_memory(); +} + /* * Helpers for parsing DBus messages @@ -217,6 +308,8 @@ int load_uint64(DBusMessageIter* iter, uint64_t *val) } int load_charp(DBusMessageIter* iter, const char** val) { + *val = NULL; + int type = dbus_message_iter_get_arg_type(iter); if (type != DBUS_TYPE_STRING) { @@ -228,6 +321,157 @@ int load_charp(DBusMessageIter* iter, const char** val) return dbus_message_iter_next(iter); } +/* Helpers for loading crash_data */ + +static int load_crash_item(DBusMessageIter* iter, struct crash_item *item) +{ + int type = dbus_message_iter_get_arg_type(iter); + if (type != DBUS_TYPE_ARRAY) + { + error_msg("array expected in dbus message, but not found ('%c')", type); + return -1; + } + + /* Compat with python/cli: + * Crash item is represented in dbus as 3-element vector of strings: + * type, editable, content. + * This doesn't match daemon-side representation: { content, flags } struct + */ + + DBusMessageIter sub_iter; + dbus_message_iter_recurse(iter, &sub_iter); + + const char *typestr; + int r = load_charp(&sub_iter, &typestr); + if (r != ABRT_DBUS_MORE_FIELDS) + { + error_msg("malformed crash_item element in dbus message"); + return -1; + } + const char *editable; + r = load_charp(&sub_iter, &editable); + if (r != ABRT_DBUS_MORE_FIELDS) + { + error_msg("malformed crash_item element in dbus message"); + return -1; + } + const char *content; + r = load_charp(&sub_iter, &content); + if (r != ABRT_DBUS_LAST_FIELD) + { + error_msg("malformed crash_item element in dbus message"); + return -1; + } + item->flags = 0; + if (typestr[0] == 'b') item->flags |= CD_FLAG_BIN; + if (typestr[0] == 't') item->flags |= CD_FLAG_TXT; + if (editable[0] == 'y') item->flags |= CD_FLAG_ISEDITABLE; + if (editable[0] == 'n') item->flags |= CD_FLAG_ISNOTEDITABLE; + item->content = xstrdup(content); + return 0; +} +int load_crash_data(DBusMessageIter* iter, crash_data_t **val) +{ + *val = NULL; + + int type = dbus_message_iter_get_arg_type(iter); + if (type != DBUS_TYPE_ARRAY) + { + error_msg("array expected in dbus message, but not found ('%c')", type); + return -1; + } + + crash_data_t *result = new_crash_data(); + + DBusMessageIter sub_iter; + dbus_message_iter_recurse(iter, &sub_iter); + + bool next_exists; + int r; +//int cnt = 0; + do { + type = dbus_message_iter_get_arg_type(&sub_iter); + if (type != DBUS_TYPE_DICT_ENTRY) + { + /* When the map has 0 elements, we see DBUS_TYPE_INVALID (on the first iteration) */ + if (type == DBUS_TYPE_INVALID) + break; + error_msg("sub_iter type is not DBUS_TYPE_DICT_ENTRY (%c)!", type); + free_crash_data(result); + return -1; + } + + DBusMessageIter sub_sub_iter; + dbus_message_iter_recurse(&sub_iter, &sub_sub_iter); + + const char *key; + r = load_charp(&sub_sub_iter, &key); + if (r != ABRT_DBUS_MORE_FIELDS) + { + if (r == ABRT_DBUS_LAST_FIELD) + error_msg("malformed map element in dbus message"); + free_crash_data(result); + return -1; + } + struct crash_item *value = xzalloc(sizeof(*value)); + r = load_crash_item(&sub_sub_iter, value); + if (r != ABRT_DBUS_LAST_FIELD) + { + if (r == ABRT_DBUS_MORE_FIELDS) + error_msg("malformed map element in dbus message"); + free(value); + free_crash_data(result); + return -1; + } + g_hash_table_replace(result, xstrdup(key), value); +//cnt++; + next_exists = dbus_message_iter_next(&sub_iter); + } while (next_exists); +//log("%s: %d elems", __func__, cnt); + + *val = result; + return dbus_message_iter_next(iter); /* note: this can't fail (returns bool, thus never < 0) */ +} +int load_vector_of_crash_data(DBusMessageIter* iter, vector_of_crash_data_t **val) +{ + *val = NULL; + + int type = dbus_message_iter_get_arg_type(iter); + if (type != DBUS_TYPE_ARRAY) + { + error_msg("array expected in dbus message, but not found ('%c')", type); + return -1; + } + + DBusMessageIter sub_iter; + dbus_message_iter_recurse(iter, &sub_iter); + + vector_of_crash_data_t *result = new_vector_of_crash_data(); + + int r; +//int cnt = 0; + /* When the vector has 0 elements, we see DBUS_TYPE_INVALID here */ + type = dbus_message_iter_get_arg_type(&sub_iter); + if (type != DBUS_TYPE_INVALID) + { + do { + crash_data_t *cd = NULL; +//cnt++; + r = load_crash_data(&sub_iter, &cd); + if (r < 0) + { + free_vector_of_crash_data(result); + return r; + } + g_ptr_array_add(result, cd); + } while (r == ABRT_DBUS_MORE_FIELDS); + } +//log("%s: %d elems", __func__, cnt); + + *val = result; + return dbus_message_iter_next(iter); /* note: this can't fail (returns bool, thus never < 0) */ +} + /* * Glib integration machinery @@ -353,8 +597,29 @@ static void unregister_vtable(DBusConnection *conn, void* data) VERB3 log("%s()", __func__); } + /* - * Initialization works as follows: + * Simple logging handler for dbus errors. + */ +int log_dbus_error(const char *msg, DBusError *err) +{ + int ret = 0; + if (dbus_error_is_set(err)) + { + error_msg("dbus error: %s", err->message); + ret = 1; + } + if (msg) + { + error_msg(msg); + ret = 1; + } + return ret; +} + + +/* + * Initialization. Works as follows: * * we have a DBusConnection* (say, obtained with dbus_bus_get) * we call dbus_connection_set_watch_functions @@ -428,3 +693,172 @@ void attach_dbus_conn_to_glib_main_loop(DBusConnection* conn, } } } + + +/* + * Support functions for clients + */ + +/* 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(g_dbus_conn, msg, &serial)) + error_msg_and_die("Error sending DBus message"); + dbus_message_unref(msg); + + while (true) + { + DBusMessage *received = dbus_connection_pop_message(g_dbus_conn); + if (!received) + { + if (FALSE == dbus_connection_read_write(g_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); + } +} + +int32_t call_DeleteDebugDump(const char *dump_dir_name) +{ + DBusMessage* msg = new_call_msg(__func__ + 5); + dbus_message_append_args(msg, + DBUS_TYPE_STRING, &dump_dir_name, + 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_int32(&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; +} + +static int connect_to_abrtd_and_call_DeleteDebugDump(const char *dump_dir_name) +{ + DBusError err; + dbus_error_init(&err); + g_dbus_conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); + if (log_dbus_error( + g_dbus_conn ? NULL : + "error requesting system DBus, possible reasons: " + "dbus config is incorrect; dbus-daemon is not running, " + "or dbus daemon needs to be restarted to reload dbus config", + &err + ) + ) { + if (g_dbus_conn) + dbus_connection_unref(g_dbus_conn); + g_dbus_conn = NULL; + return 1; + } + + int ret = call_DeleteDebugDump(dump_dir_name); + if (ret == ENOENT) + error_msg("Dump directory '%s' is not found", dump_dir_name); + else if (ret != 0) + error_msg("Can't delete dump directory '%s'", dump_dir_name); + + dbus_connection_unref(g_dbus_conn); + g_dbus_conn = NULL; + + return ret; +} + +int delete_dump_dir_possibly_using_abrtd(const char *dump_dir_name) +{ + /* Try to delete it ourselves */ + struct dump_dir *dd = dd_opendir(dump_dir_name, DD_OPEN_READONLY); + if (dd) + { + if (dd->locked) /* it is not readonly */ + return dd_delete(dd); + dd_close(dd); + } + + VERB1 log("Deleting '%s' via abrtd dbus call", dump_dir_name); + return connect_to_abrtd_and_call_DeleteDebugDump(dump_dir_name); +} diff --git a/src/lib/abrt_dbus.h b/src/lib/abrt_dbus.h index cdc963ca..8f559980 100644 --- a/src/lib/abrt_dbus.h +++ b/src/lib/abrt_dbus.h @@ -23,6 +23,11 @@ #include "abrtlib.h" +#define ABRTD_DBUS_NAME "com.redhat.abrt" +#define ABRTD_DBUS_PATH "/com/redhat/abrt" +#define ABRTD_DBUS_IFACE "com.redhat.abrt" + + #ifdef __cplusplus extern "C" { #endif @@ -53,6 +58,7 @@ extern DBusConnection* g_dbus_conn; * // (note: "iface.sig.emitted.from" is not optional for signals!) * dbus_message_set_destination(msg, "peer"); // optional * dbus_connection_send(conn, msg, &serial); // &serial can be NULL + * dbus_connection_unref(conn); // if you don't want to *stay* connected * * - client which receives and processes signals: * conn = dbus_bus_get(DBUS_BUS_SYSTEM/SESSION, &err); @@ -73,6 +79,19 @@ void attach_dbus_conn_to_glib_main_loop(DBusConnection* conn, DBusHandlerResult (*message_received_func)(DBusConnection *conn, DBusMessage *msg, void* data) ); +/* Log dbus error if err has it set. Then log msg if it's !NULL. + * In both cases return 1. Otherwise return 0. + */ +int log_dbus_error(const char *msg, DBusError *err); + +/* Perform "DeleteDebugDump" call over g_dbus_conn */ +int32_t call_DeleteDebugDump(const char *dump_dir_name); + +/* Connect to system bus, find abrtd, perform "DeleteDebugDump" call, close g_dbus_conn */ +/* now static: int connect_to_abrtd_and_call_DeleteDebugDump(const char *dump_dir_name); */ +int delete_dump_dir_possibly_using_abrtd(const char *dump_dir_name); + + /* * Helpers for building DBus messages */ @@ -82,6 +101,9 @@ void store_uint32(DBusMessageIter* iter, uint32_t val); void store_int64(DBusMessageIter* iter, int64_t val); void store_uint64(DBusMessageIter* iter, uint64_t val); void store_string(DBusMessageIter* iter, const char* val); +void store_crash_data(DBusMessageIter* iter, crash_data_t *val); +void store_vector_of_crash_data(DBusMessageIter* iter, vector_of_crash_data_t *val); +void store_map_string(DBusMessageIter* iter, map_string_h *val); /* * Helpers for parsing DBus messages @@ -103,13 +125,18 @@ int load_uint32(DBusMessageIter* iter, uint32_t *val); int load_int64(DBusMessageIter* iter, int64_t *val); int load_uint64(DBusMessageIter* iter, uint64_t *val); int load_charp(DBusMessageIter* iter, const char **val); +int load_crash_data(DBusMessageIter* iter, crash_data_t **val); +int load_vector_of_crash_data(DBusMessageIter* iter, vector_of_crash_data_t **val); #ifdef __cplusplus } #endif -/* C++ style stuff */ +/* + * C++ style stuff + */ + #ifdef __cplusplus #include <map> @@ -119,6 +146,20 @@ int load_charp(DBusMessageIter* iter, const char **val); * Helpers for building DBus messages */ +static inline std::string ssprintf(const char *format, ...) +{ + va_list p; + char *string_ptr; + + va_start(p, format); + string_ptr = xvasprintf(format, p); + va_end(p); + + std::string res = string_ptr; + free(string_ptr); + return res; +} + //static inline void store_val(DBusMessageIter* iter, bool val) { store_bool(iter, val); } static inline void store_val(DBusMessageIter* iter, int32_t val) { store_int32(iter, val); } static inline void store_val(DBusMessageIter* iter, uint32_t val) { store_uint32(iter, val); } @@ -147,7 +188,7 @@ struct abrt_dbus_type< std::map<K,V> > { static std::string sig() { return ssprintf("a{%s%s}", ABRT_DBUS_SIG(K), ABRT_DBUS_SIG(V)); } }; -template<typename E> +template <typename E> static void store_vector(DBusMessageIter* iter, const std::vector<E>& val) { DBusMessageIter sub_iter; @@ -170,7 +211,7 @@ static void store_vector(DBus::MessageIter &iter, const std::vector<uint8_t>& va if we use such vector, MUST add specialized code here (see in dbus-c++ source) } */ -template<typename K, typename V> +template <typename K, typename V> static void store_map(DBusMessageIter* iter, const std::map<K,V>& val) { DBusMessageIter sub_iter; @@ -195,9 +236,9 @@ static void store_map(DBusMessageIter* iter, const std::map<K,V>& val) die_out_of_memory(); } -template<typename E> +template <typename E> static inline void store_val(DBusMessageIter* iter, const std::vector<E>& val) { store_vector(iter, val); } -template<typename K, typename V> +template <typename K, typename V> static inline void store_val(DBusMessageIter* iter, const std::map<K,V>& val) { store_map(iter, val); } @@ -220,7 +261,7 @@ static inline int load_val(DBusMessageIter* iter, std::string& val) } /* Templates for vector and map */ -template<typename E> +template <typename E> static int load_vector(DBusMessageIter* iter, std::vector<E>& val) { int type = dbus_message_iter_get_arg_type(iter); @@ -259,7 +300,7 @@ static int load_vector(DBusMessageIter* iter, std::vector<uint8_t>& val) if we use such vector, MUST add specialized code here (see in dbus-c++ source) } */ -template<typename K, typename V> +template <typename K, typename V> static int load_map(DBusMessageIter* iter, std::map<K,V>& val) { int type = dbus_message_iter_get_arg_type(iter); @@ -314,9 +355,9 @@ static int load_map(DBusMessageIter* iter, std::map<K,V>& val) return dbus_message_iter_next(iter); } -template<typename E> +template <typename E> static inline int load_val(DBusMessageIter* iter, std::vector<E>& val) { return load_vector(iter, val); } -template<typename K, typename V> +template <typename K, typename V> static inline int load_val(DBusMessageIter* iter, std::map<K,V>& val) { return load_map(iter, val); } #endif /* __cplusplus */ diff --git a/src/lib/ABRTException.cpp b/src/lib/abrt_types.c index 0ae5d452..42100075 100644 --- a/src/lib/ABRTException.cpp +++ b/src/lib/abrt_types.c @@ -1,6 +1,6 @@ /* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 RedHat Inc + Copyright (C) 2010 ABRT Team + Copyright (C) 2010 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 @@ -16,18 +16,22 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "abrt_exception.h" +#include "abrtlib.h" -CABRTException::CABRTException(abrt_exception_t type, const char* fmt, ...) +map_string_h *new_map_string(void) { - m_type = type; - va_list ap; - va_start(ap, fmt); - m_what = xvasprintf(fmt, ap); - va_end(ap); + return g_hash_table_new_full(g_str_hash, g_str_equal, free, free); } -CABRTException::CABRTException(const CABRTException& rhs): - m_type(rhs.m_type), - m_what(xstrdup(rhs.m_what)) -{} +void free_map_string(map_string_h *ms) +{ + if (ms) + g_hash_table_destroy(ms); +} + +const char *get_map_string_item_or_empty(map_string_h *ms, const char *key) +{ + const char *v = (const char*)g_hash_table_lookup(ms, key); + if (!v) v = ""; + return v; +} diff --git a/src/lib/abrt_xmlrpc.cpp b/src/lib/abrt_xmlrpc.cpp index bf74f05b..e2b8674c 100644 --- a/src/lib/abrt_xmlrpc.cpp +++ b/src/lib/abrt_xmlrpc.cpp @@ -18,15 +18,10 @@ */ #include "abrtlib.h" #include "abrt_xmlrpc.h" -#include "abrt_exception.h" void throw_xml_fault(xmlrpc_env *env) { - std::string errmsg = ssprintf("XML-RPC Fault(%d): %s", env->fault_code, env->fault_string); - xmlrpc_env_clean(env); // this is needed ONLY if fault_occurred - xmlrpc_env_init(env); // just in case user catches ex and _continues_ to use env - error_msg("%s", errmsg.c_str()); // show error in daemon log - throw CABRTException(EXCEP_PLUGIN, errmsg.c_str()); + error_msg_and_die("XML-RPC Fault(%d): %s", env->fault_code, env->fault_string); } void throw_if_xml_fault_occurred(xmlrpc_env *env) @@ -49,6 +44,17 @@ void abrt_xmlrpc_conn::new_xmlrpc_client(const char* url, bool ssl_verify) * We do it in abrtd's main */ /* xmlrpc_client_setup_global_const(&env); */ + /* URL - bugzilla.redhat.com/show_bug.cgi?id=666893 Unable to make sense of + * XML-RPC response from server + * + * By default, XML data from the network may be no larger than 512K. + * XMLRPC_XML_SIZE_LIMIT_DEFAULT is #defined to (512*1024) in xmlrpc-c/base.h + * + * Users reported trouble with 733402 byte long responses, hope raising the + * limit to 2*512k is enough + */ + xmlrpc_limit_set(XMLRPC_XML_SIZE_LIMIT_ID, 2 * XMLRPC_XML_SIZE_LIMIT_DEFAULT); + struct xmlrpc_curl_xportparms curlParms; memset(&curlParms, 0, sizeof(curlParms)); /* curlParms.network_interface = NULL; - done by memset */ diff --git a/src/lib/abrt_xmlrpc.h b/src/lib/abrt_xmlrpc.h index ad1a87d3..93c5a9d6 100644 --- a/src/lib/abrt_xmlrpc.h +++ b/src/lib/abrt_xmlrpc.h @@ -23,12 +23,12 @@ #include <xmlrpc-c/base.h> #include <xmlrpc-c/client.h> +#ifdef __cplusplus /* * Simple class holding XMLRPC connection data. * Used mainly to ensure we always destroy xmlrpc client and server_info * on return or throw. */ - struct abrt_xmlrpc_conn { xmlrpc_client* m_pClient; xmlrpc_server_info* m_pServer_info; @@ -40,9 +40,19 @@ struct abrt_xmlrpc_conn { void new_xmlrpc_client(const char* url, bool ssl_verify); void destroy_xmlrpc_client(); }; +#endif + + +#ifdef __cplusplus +extern "C" { +#endif /* Utility functions */ void throw_xml_fault(xmlrpc_env *env); void throw_if_xml_fault_occurred(xmlrpc_env *env); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/lib/binhex.c b/src/lib/binhex.c new file mode 100644 index 00000000..1fcb7445 --- /dev/null +++ b/src/lib/binhex.c @@ -0,0 +1,74 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 RedHat Inc + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + 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 "abrtlib.h" + +static const char hexdigits_locase[] = "0123456789abcdef"; + +/* Emit a string of hex representation of bytes */ +char *bin2hex(char *dst, const char *str, int count) +{ + while (count) { + unsigned char c = *str++; + /* put lowercase hex digits */ + *dst++ = hexdigits_locase[c >> 4]; + *dst++ = hexdigits_locase[c & 0xf]; + count--; + } + return dst; +} + +/* Convert "xxxxxxxx" hex string to binary, no more than COUNT bytes */ +char *hex2bin(char *dst, const char *str, int count) +{ + /* Parts commented out with // allow parsing + * of strings like "xx:x:x:xx:xx:xx:xxxxxx" + * (IPv6, ethernet addresses and the like). + */ + errno = EINVAL; + while (*str && count) { + uint8_t val; + uint8_t c; + + c = *str++; + if (isdigit(c)) + val = c - '0'; + else if ((c|0x20) >= 'a' && (c|0x20) <= 'f') + val = (c|0x20) - ('a' - 10); + else + return NULL; + val <<= 4; + c = *str; + if (isdigit(c)) + val |= c - '0'; + else if ((c|0x20) >= 'a' && (c|0x20) <= 'f') + val |= (c|0x20) - ('a' - 10); + //else if (c == ':' || c == '\0') + // val >>= 4; + else + return NULL; + + *dst++ = val; + //if (c != '\0') + str++; + //if (*str == ':') + // str++; + count--; + } + errno = (*str ? ERANGE : 0); + return dst; +} diff --git a/src/lib/copy_file_recursive.c b/src/lib/copy_file_recursive.c new file mode 100644 index 00000000..c3f021c7 --- /dev/null +++ b/src/lib/copy_file_recursive.c @@ -0,0 +1,139 @@ +/* + Copyright (C) 2011 ABRT team + Copyright (C) 2011 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 "abrtlib.h" + +int copy_file_recursive(const char *source, const char *dest) +{ + /* This is a recursive function, try to minimize stack usage */ + /* NB: each struct stat is ~100 bytes */ + struct stat source_stat; + struct stat dest_stat; + int retval = 0; + int dest_exists = 0; + + if (strcmp(source, ".lock") == 0) + goto skip; + + if (stat(source, &source_stat) < 0) { + perror_msg("Can't stat '%s'", source); + return -1; + } + + if (lstat(dest, &dest_stat) < 0) { + if (errno != ENOENT) { + perror_msg("Can't stat '%s'", dest); + return -1; + } + } else { + if (source_stat.st_dev == dest_stat.st_dev + && source_stat.st_ino == dest_stat.st_ino + ) { + error_msg("'%s' and '%s' are the same file", source, dest); + return -1; + } + dest_exists = 1; + } + + if (S_ISDIR(source_stat.st_mode)) { + DIR *dp; + struct dirent *d; + + if (dest_exists) { + if (!S_ISDIR(dest_stat.st_mode)) { + error_msg("Target '%s' is not a directory", dest); + return -1; + } + /* race here: user can substitute a symlink between + * this check and actual creation of files inside dest */ + } else { + /* Create DEST */ + mode_t mode = source_stat.st_mode; + /* Allow owner to access new dir (at least for now) */ + mode |= S_IRWXU; + if (mkdir(dest, mode) < 0) { + perror_msg("Can't create directory '%s'", dest); + return -1; + } + } + /* Recursively copy files in SOURCE */ + dp = opendir(source); + if (dp == NULL) { + retval = -1; + goto ret; + } + + while (retval == 0 && (d = readdir(dp)) != NULL) { + char *new_source, *new_dest; + + if (dot_or_dotdot(d->d_name)) + continue; + new_source = concat_path_file(source, d->d_name); + new_dest = concat_path_file(dest, d->d_name); + if (copy_file_recursive(new_source, new_dest) < 0) + retval = -1; + free(new_source); + free(new_dest); + } + closedir(dp); + + goto ret; + } + + if (S_ISREG(source_stat.st_mode)) { + int src_fd; + int dst_fd; + mode_t new_mode; + + src_fd = open(source, O_RDONLY); + if (src_fd < 0) { + perror_msg("Can't open '%s'", source); + return -1; + } + + /* Do not try to open with weird mode fields */ + new_mode = source_stat.st_mode; + + // security problem versus (sym)link attacks + // dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode); + /* safe way: */ + dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode); + if (dst_fd < 0) { + close(src_fd); + return -1; + } + + if (copyfd_eof(src_fd, dst_fd, COPYFD_SPARSE) == -1) + retval = -1; + close(src_fd); + /* Careful: do check that buffered writes succeeded... */ + if (close(dst_fd) < 0) { + perror_msg("Error writing to '%s'", dest); + retval = -1; + } + goto ret; + } + + /* Neither dir not regular file: skip */ + + skip: + log("Skipping '%s'", source); + ret: + return retval; +} diff --git a/src/lib/crash_dump.cpp b/src/lib/crash_data.c index 338724d5..1ae8f4f1 100644 --- a/src/lib/crash_dump.cpp +++ b/src/lib/crash_data.c @@ -17,20 +17,77 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "abrtlib.h" -#include "abrt_crash_dump.h" - -const char *const must_have_files[] = { - FILENAME_ARCHITECTURE, - FILENAME_KERNEL , - FILENAME_PACKAGE , - FILENAME_COMPONENT , - FILENAME_RELEASE , - FILENAME_EXECUTABLE , - NULL -}; + +static void free_crash_item(void *ptr) +{ + if (ptr) + { + struct crash_item *item = (struct crash_item *)ptr; + free(item->content); + free(item); + } +} + + +/* crash_data["name"] = { "content", CD_FLAG_foo_bits } */ + +crash_data_t *new_crash_data(void) +{ + return g_hash_table_new_full(g_str_hash, g_str_equal, + free, free_crash_item); +} + +void add_to_crash_data_ext(crash_data_t *crash_data, + const char *name, + const char *content, + unsigned flags) +{ + if (!(flags & (CD_FLAG_BIN|CD_FLAG_TXT))) + flags |= CD_FLAG_TXT; + if (!(flags & (CD_FLAG_ISEDITABLE|CD_FLAG_ISNOTEDITABLE))) + flags |= CD_FLAG_ISNOTEDITABLE; + + struct crash_item *item = (struct crash_item *)xzalloc(sizeof(*item)); + item->content = xstrdup(content); + item->flags = flags; + g_hash_table_replace(crash_data, xstrdup(name), item); +} + +void add_to_crash_data(crash_data_t *crash_data, + const char *name, + const char *content) +{ + add_to_crash_data_ext(crash_data, name, content, CD_FLAG_TXT + CD_FLAG_ISNOTEDITABLE); +} + +const char *get_crash_item_content_or_die(crash_data_t *crash_data, const char *key) +{ + struct crash_item *item = get_crash_data_item_or_NULL(crash_data, key); + if (!item) + error_msg_and_die("Error accessing crash data: no ['%s']", key); + return item->content; +} + +const char *get_crash_item_content_or_NULL(crash_data_t *crash_data, const char *key) +{ + struct crash_item *item = get_crash_data_item_or_NULL(crash_data, key); + if (!item) + return NULL; + return item->content; +} + + +/* crash_data_vector[i] = { "name" = { "content", CD_FLAG_foo_bits } } */ + +vector_of_crash_data_t *new_vector_of_crash_data(void) +{ + return g_ptr_array_new_with_free_func((void (*)(void*)) &free_crash_data); +} + + +/* Miscellaneous helpers */ static const char *const editable_files[] = { - FILENAME_DESCRIPTION, FILENAME_COMMENT , FILENAME_REPRODUCE , FILENAME_BACKTRACE , @@ -52,36 +109,6 @@ bool is_editable_file(const char *file_name) return is_editable(file_name, editable_files); } - -void add_to_crash_data_ext(map_crash_data_t& pCrashData, - const char *pItem, - const char *pType, - const char *pEditable, - const char *pContent) -{ - map_crash_data_t::iterator it = pCrashData.find(pItem); - if (it == pCrashData.end()) { - vector_string_t& v = pCrashData[pItem]; /* create empty vector */ - v.push_back(pType); - v.push_back(pEditable); - v.push_back(pContent); - return; - } - vector_string_t& v = it->second; - while (v.size() < 3) - v.push_back(""); - v[CD_TYPE] = pType; - v[CD_EDITABLE] = pEditable; - v[CD_CONTENT] = pContent; -} - -void add_to_crash_data(map_crash_data_t& pCrashData, - const char *pItem, - const char *pContent) -{ - add_to_crash_data_ext(pCrashData, pItem, CD_TXT, CD_ISNOTEDITABLE, pContent); -} - static char* is_text_file(const char *name, ssize_t *sz) { /* We were using magic.h API to check for file being text, but it thinks @@ -156,7 +183,7 @@ static char* is_text_file(const char *name, ssize_t *sz) return NULL; /* it's binary */ } -void load_crash_data_from_crash_dump_dir(struct dump_dir *dd, map_crash_data_t& data) +void load_crash_data_from_dump_dir(crash_data_t *crash_data, struct dump_dir *dd) { char *short_name; char *full_name; @@ -173,13 +200,11 @@ void load_crash_data_from_crash_dump_dir(struct dump_dir *dd, map_crash_data_t& text = is_text_file(full_name, &sz); if (!text) { - add_to_crash_data_ext(data, + add_to_crash_data_ext(crash_data, short_name, - CD_BIN, - CD_ISNOTEDITABLE, - full_name + full_name, + CD_FLAG_BIN + CD_FLAG_ISNOTEDITABLE ); - free(short_name); free(full_name); continue; @@ -193,11 +218,10 @@ void load_crash_data_from_crash_dump_dir(struct dump_dir *dd, map_crash_data_t& content = dd_load_text(dd, short_name); free(text); - add_to_crash_data_ext(data, + add_to_crash_data_ext(crash_data, short_name, - CD_TXT, - editable ? CD_ISEDITABLE : CD_ISNOTEDITABLE, - content + content, + (editable ? CD_FLAG_TXT + CD_FLAG_ISEDITABLE : CD_FLAG_TXT + CD_FLAG_ISNOTEDITABLE) ); free(short_name); free(full_name); @@ -205,50 +229,25 @@ void load_crash_data_from_crash_dump_dir(struct dump_dir *dd, map_crash_data_t& } } -static const std::string* helper_get_crash_data_item_content(const map_crash_data_t& crash_data, const char *key) +crash_data_t *create_crash_data_from_dump_dir(struct dump_dir *dd) { - map_crash_data_t::const_iterator it = crash_data.find(key); - if (it == crash_data.end()) { - return NULL; - } - if (it->second.size() <= CD_CONTENT) { - return NULL; - } - return &it->second[CD_CONTENT]; + crash_data_t *crash_data = new_crash_data(); + load_crash_data_from_dump_dir(crash_data, dd); + return crash_data; } -const std::string& get_crash_data_item_content(const map_crash_data_t& crash_data, const char *key) +void log_crash_data(crash_data_t *crash_data, const char *pfx) { - const std::string* sp = helper_get_crash_data_item_content(crash_data, key); - if (sp == NULL) { - if (crash_data.find(key) == crash_data.end()) - error_msg_and_die("Error accessing crash data: no ['%s']", key); - error_msg_and_die("Error accessing crash data: no ['%s'][%d]", key, CD_CONTENT); - } - return *sp; -} - -const char *get_crash_data_item_content_or_NULL(const map_crash_data_t& crash_data, const char *key) -{ - const std::string* sp = helper_get_crash_data_item_content(crash_data, key); - if (!sp) { - return NULL; - } - return sp->c_str(); -} - -void log_map_crash_data(const map_crash_data_t& data, const char *name) -{ - map_crash_data_t::const_iterator it = data.begin(); - while (it != data.end()) - { - ssize_t sz = it->second.size(); - log("%s[%s]:%s/%s/'%.20s'", - name, it->first.c_str(), - sz > 0 ? it->second[0].c_str() : "<NO [0]>", - sz > 1 ? it->second[1].c_str() : "<NO [1]>", - sz > 2 ? it->second[2].c_str() : "<NO [2]>" - ); - it++; - } + GHashTableIter iter; + char *name; + struct crash_item *value; + g_hash_table_iter_init(&iter, crash_data); + while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value)) + { + log("%s[%s]:'%s' 0x%x", + pfx, name, + value->content, + value->flags + ); + } } diff --git a/src/lib/create_dump_dir.c b/src/lib/create_dump_dir.c new file mode 100644 index 00000000..cbacdab6 --- /dev/null +++ b/src/lib/create_dump_dir.c @@ -0,0 +1,85 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 "abrtlib.h" + +static struct dump_dir *try_dd_create(const char *base_dir_name, const char *dir_name) +{ + char *path = concat_path_file(base_dir_name, dir_name); + struct dump_dir *dd = dd_create(path, (uid_t)-1L); + if (dd) + dd_create_basic_files(dd, (uid_t)-1L); + free(path); + return dd; +} + +struct dump_dir *create_dump_dir_from_crash_data(crash_data_t *crash_data, const char *base_dir_name) +{ + char dir_name[sizeof("abrt-tmp-YYYY-MM-DD-HH:MM:SS-%lu") + sizeof(long)*3]; + sprintf(dir_name, "abrt-tmp-%s-%lu", iso_date_string(NULL), (long)getpid()); + + struct dump_dir *dd; + if (base_dir_name) + dd = try_dd_create(base_dir_name, dir_name); + else + { + /* Try /var/run/abrt */ + dd = try_dd_create(LOCALSTATEDIR"/run/abrt", dir_name); + /* Try $HOME/tmp */ + if (!dd) + { + char *home = getenv("HOME"); + if (home && home[0]) + { + home = concat_path_file(home, "tmp"); + /*mkdir(home, 0777); - do we want this? */ + dd = try_dd_create(home, dir_name); + free(home); + } + } +//TODO: try user's home dir obtained by getpwuid(getuid())? + /* Try /tmp */ + if (!dd) + dd = try_dd_create("/tmp", dir_name); + } + if (!dd) + return NULL; + + GHashTableIter iter; + char *name; + struct crash_item *value; + g_hash_table_iter_init(&iter, crash_data); + while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value)) + { + if (name[0] == '.' || strchr(name, '/')) + { + error_msg("Crash data field name contains disallowed chars: '%s'", name); + goto next; + } + +//FIXME: what to do with CD_FLAG_BINs?? + if (value->flags & CD_FLAG_BIN) + goto next; + + dd_save_text(dd, name, value->content); + next: ; + } + + return dd; +} diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c index 19f86072..a84e2814 100644 --- a/src/lib/dump_dir.c +++ b/src/lib/dump_dir.c @@ -1,6 +1,4 @@ /* - DebugDump.cpp - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) Copyright (C) 2009 RedHat inc. @@ -22,11 +20,70 @@ #include "abrtlib.h" #include "strbuf.h" -// TODO: +// Locking logic: +// +// The directory is locked by creating a symlink named .lock inside it, +// whose value (where it "points to") is the pid of locking process. +// We use symlink, not an ordinary file, because symlink creation +// is an atomic operation. +// +// There are two cases where after .lock creation, we might discover +// that directory is not really free: +// * another process just created new directory, but didn't manage +// to lock it before us. +// * another process is deleting the directory, and we managed to sneak in +// and create .lock after it deleted all files (including .lock) +// but before it rmdir'ed the empty directory. +// +// Both these cases are detected by the fact that file named "time" +// is not present (it must be present in any valid dump dir). +// If after locking the dir we don't see time file, we remove the lock +// at once and back off. What happens in concurrent processes +// we interfered with? +// * "create new dump dir" process just re-tries locking. +// * "delete dump dir" process just retries rmdir. // -// Perhaps dd_opendir should do some sanity checking like -// "if there is no "uid" file in the directory, it's not a crash dump", -// and fail. +// There is another case when we don't find time file: +// when the directory is not really a *dump* dir - user gave us +// an ordinary directory name by mistake. +// We detect it by bailing out of "lock, check time file; sleep +// and retry if it doesn't exist" loop using a counter. +// +// To make locking work reliably, it's important to set timeouts +// correctly. For example, dd_create should retry locking +// its newly-created directory much faster than dd_opendir +// tries to lock the directory it tries to open. + + +// How long to sleep between "symlink fails with EEXIST, +// readlink fails with ENOENT" tries. Someone just unlocked the dir. +// We never bail out in this case, we retry forever. +// The value can be really small: +#define SYMLINK_RETRY_USLEEP (10*1000) + +// How long to sleep when lock file with valid pid is seen by dd_opendir +// (we are waiting for other process to unlock or die): +#define WAIT_FOR_OTHER_PROCESS_USLEEP (500*1000) + +// How long to sleep when lock file with valid pid is seen by dd_create +// (some idiot jumped the gun and locked the dir we just created). +// Must not be the same as WAIT_FOR_OTHER_PROCESS_USLEEP (we depend on this) +// and should be small (we have the priority in locking, this is OUR dir): +#define CREATE_LOCK_USLEEP (10*1000) + +// How long to sleep after we locked a dir, found no time file +// (either we are racing with someone, or it's not a dump dir) +// and unlocked it; +// and after how many tries to give up and declare it's not a dump dir: +#define NO_TIME_FILE_USLEEP (50*1000) +#define NO_TIME_FILE_COUNT 10 + +// How long to sleep after we unlocked an empty dir, but then rmdir failed +// (some idiot jumped the gun and locked the dir we are deleting); +// and after how many tries to give up: +#define RMDIR_FAIL_USLEEP (10*1000) +#define RMDIR_FAIL_COUNT 50 + static char *load_text_file(const char *path, unsigned flags); @@ -53,12 +110,21 @@ static bool exist_file_dir(const char *path) return false; } -static bool get_and_set_lock(const char* lock_file, const char* pid) +/* Return values: + * -1: error + * 0: failed to lock (someone else has it locked) + * 1: success + */ +static int get_and_set_lock(const char* lock_file, const char* pid) { while (symlink(pid, lock_file) != 0) { if (errno != EEXIST) - perror_msg_and_die("Can't create lock file '%s'", lock_file); + { + if (errno != ENOENT && errno != ENOTDIR) + perror_msg("Can't create lock file '%s'", lock_file); + return -1; + } char pid_buf[sizeof(pid_t)*3 + 4]; ssize_t r = readlink(lock_file, pid_buf, sizeof(pid_buf) - 1); @@ -67,54 +133,94 @@ static bool get_and_set_lock(const char* lock_file, const char* pid) if (errno == ENOENT) { /* Looks like lock_file was deleted */ - usleep(10 * 1000); /* avoid CPU eating loop */ + usleep(SYMLINK_RETRY_USLEEP); /* avoid CPU eating loop */ continue; } - perror_msg_and_die("Can't read lock file '%s'", lock_file); + perror_msg("Can't read lock file '%s'", lock_file); + return -1; } pid_buf[r] = '\0'; if (strcmp(pid_buf, pid) == 0) { log("Lock file '%s' is already locked by us", lock_file); - return false; + return 0; } if (isdigit_str(pid_buf)) { - char pid_str[sizeof("/proc/") + strlen(pid_buf)]; + char pid_str[sizeof("/proc/") + sizeof(pid_buf)]; sprintf(pid_str, "/proc/%s", pid_buf); if (access(pid_str, F_OK) == 0) { log("Lock file '%s' is locked by process %s", lock_file, pid_buf); - return false; + return 0; } log("Lock file '%s' was locked by process %s, but it crashed?", lock_file, pid_buf); } /* The file may be deleted by now by other process. Ignore ENOENT */ if (unlink(lock_file) != 0 && errno != ENOENT) { - perror_msg_and_die("Can't remove stale lock file '%s'", lock_file); + perror_msg("Can't remove stale lock file '%s'", lock_file); + return -1; } } VERB1 log("Locked '%s'", lock_file); - return true; + return 1; } -static void dd_lock(struct dump_dir *dd) +static int dd_lock(struct dump_dir *dd, unsigned sleep_usec) { if (dd->locked) - error_msg_and_die("Locking bug on '%s'", dd->dd_dir); - - char lock_buf[strlen(dd->dd_dir) + sizeof(".lock")]; - sprintf(lock_buf, "%s.lock", dd->dd_dir); + error_msg_and_die("Locking bug on '%s'", dd->dd_dirname); char pid_buf[sizeof(long)*3 + 2]; sprintf(pid_buf, "%lu", (long)getpid()); - while ((dd->locked = get_and_set_lock(lock_buf, pid_buf)) != true) + + unsigned dirname_len = strlen(dd->dd_dirname); + char lock_buf[dirname_len + sizeof("/.lock")]; + strcpy(lock_buf, dd->dd_dirname); + strcpy(lock_buf + dirname_len, "/.lock"); + + unsigned count = NO_TIME_FILE_COUNT; + retry: + while (1) { - sleep(1); /* was 0.5 seconds */ + int r = get_and_set_lock(lock_buf, pid_buf); + if (r < 0) + return r; /* error */ + if (r > 0) + break; /* locked successfully */ + /* Other process has the lock, wait for it to go away */ + usleep(sleep_usec); } + + /* Are we called by dd_opendir (as opposed to dd_create)? */ + if (sleep_usec == WAIT_FOR_OTHER_PROCESS_USLEEP) /* yes */ + { + strcpy(lock_buf + dirname_len, "/time"); + if (access(lock_buf, F_OK) != 0) + { + /* time file doesn't exist. We managed to lock the directory + * which was just created by somebody else, or is almost deleted + * by delete_file_dir. + * Unlock and back off. + */ + strcpy(lock_buf + dirname_len, "/.lock"); + xunlink(lock_buf); + VERB1 log("Unlocked '%s' (no time file)", lock_buf); + if (--count == 0) + { + errno = EISDIR; /* "this is an ordinary dir, not dump dir" */ + return -1; + } + usleep(NO_TIME_FILE_USLEEP); + goto retry; + } + } + + dd->locked = true; + return 0; } static void dd_unlock(struct dump_dir *dd) @@ -122,9 +228,13 @@ static void dd_unlock(struct dump_dir *dd) if (dd->locked) { dd->locked = 0; - char lock_buf[strlen(dd->dd_dir) + sizeof(".lock")]; - sprintf(lock_buf, "%s.lock", dd->dd_dir); + + unsigned dirname_len = strlen(dd->dd_dirname); + char lock_buf[dirname_len + sizeof("/.lock")]; + strcpy(lock_buf, dd->dd_dirname); + strcpy(lock_buf + dirname_len, "/.lock"); xunlink(lock_buf); + VERB1 log("Unlocked '%s'", lock_buf); } } @@ -136,7 +246,7 @@ static inline struct dump_dir *dd_init(void) int dd_exist(struct dump_dir *dd, const char *path) { - char *full_path = concat_path_file(dd->dd_dir, path); + char *full_path = concat_path_file(dd->dd_dirname, path); int ret = exist_file_dir(full_path); free(full_path); return ret; @@ -154,7 +264,7 @@ void dd_close(struct dump_dir *dd) /* free(dd->next_dir); - WRONG! */ } - free(dd->dd_dir); + free(dd->dd_dirname); free(dd); } @@ -170,52 +280,68 @@ struct dump_dir *dd_opendir(const char *dir, int flags) { struct dump_dir *dd = dd_init(); - /* Used to use rm_trailing_slashes(dir) here, but with dir = "." - * or "..", or if the last component is a symlink, - * then lock file is created in the wrong place. - * IOW: this breaks locking. - */ - dd->dd_dir = realpath(dir, NULL); - if (!dd->dd_dir) - { - if (!(flags & DD_FAIL_QUIETLY)) - error_msg("'%s' does not exist", dir); - dd_close(dd); - return NULL; - } - dir = dd->dd_dir; - - dd_lock(dd); + dir = dd->dd_dirname = rm_trailing_slashes(dir); - struct stat stat_buf; - if (stat(dir, &stat_buf) != 0 || !S_ISDIR(stat_buf.st_mode)) + errno = 0; + if (dd_lock(dd, WAIT_FOR_OTHER_PROCESS_USLEEP) < 0) { - if (!(flags & DD_FAIL_QUIETLY)) - error_msg("'%s' does not exist", dir); + if ((flags & DD_OPEN_READONLY) && errno == EACCES) + { + /* Directory is not writable. If it seems to be readable, + * return "read only" dd, not NULL */ + struct stat stat_buf; + if (stat(dir, &stat_buf) == 0 + && S_ISDIR(stat_buf.st_mode) + && access(dir, R_OK) == 0 + ) { + return dd; + } + } + if (errno == EISDIR) + { + /* EISDIR: dd_lock can lock the dir, but it sees no time file there, + * even after it retried many times. It must be an ordinary directory! + * + * Without this check, e.g. abrt-action-print happily prints any current + * directory when run without arguments, because its option -d DIR + * defaults to "."! + */ + /*if (!(flags & DD_FAIL_QUIETLY))... - no, DD_FAIL_QUIETLY only means + * "it's ok if it doesn exist", not "ok if contents is bogus"! + */ + error_msg("'%s' is not a crash dump directory", dir); + dd_close(dd); + return NULL; + } + + if (errno == ENOENT || errno == ENOTDIR) + { + if (!(flags & DD_FAIL_QUIETLY)) + error_msg("'%s' does not exist", dir); + } + else + { + perror_msg("Can't access '%s'", dir); + } dd_close(dd); return NULL; } - /* In case caller would want to create more files, he'll need uid:gid */ - dd->dd_uid = stat_buf.st_uid; - dd->dd_gid = stat_buf.st_gid; - - /* Without this check, e.g. abrt-action-print happily prints any current - * directory when run without arguments, because its option -d DIR - * defaults to "."! Let's require that at least some crash dump dir - * specific files exist before we declare open successful: - */ - char *name = concat_path_file(dir, FILENAME_ANALYZER); - int bad = (lstat(name, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)); - free(name); - if (bad) + dd->dd_uid = (uid_t)-1L; + dd->dd_gid = (gid_t)-1L; + if (geteuid() == 0) { - /*if (!(flags & DD_FAIL_QUIETLY))... - no, DD_FAIL_QUIETLY only means - * "it's ok if it doesn exist", not "ok if contents is bogus"! - */ - error_msg("'%s' is not a crash dump directory", dir); - dd_close(dd); - return NULL; + /* In case caller would want to create more files, he'll need uid:gid */ + struct stat stat_buf; + if (stat(dir, &stat_buf) != 0 || !S_ISDIR(stat_buf.st_mode)) + { + if (!(flags & DD_FAIL_QUIETLY)) + error_msg("'%s' does not exist", dir); + dd_close(dd); + return NULL; + } + dd->dd_uid = stat_buf.st_uid; + dd->dd_gid = stat_buf.st_gid; } return dd; @@ -248,7 +374,7 @@ struct dump_dir *dd_create(const char *dir, uid_t uid) * realpath will always return NULL. We don't really have to: * dd_opendir(".") makes sense, dd_create(".") does not. */ - dir = dd->dd_dir = rm_trailing_slashes(dir); + dir = dd->dd_dirname = rm_trailing_slashes(dir); const char *last_component = strrchr(dir, '/'); if (last_component) @@ -265,15 +391,38 @@ struct dump_dir *dd_create(const char *dir, uid_t uid) return NULL; } - dd_lock(dd); - + bool created_parents = false; + try_again: /* Was creating it with mode 0700 and user as the owner, but this allows * the user to replace any file in the directory, changing security-sensitive data * (e.g. "uid", "analyzer", "executable") */ if (mkdir(dir, 0750) == -1) { - perror_msg("Can't create dir '%s'", dir); + int err = errno; + if (!created_parents && errno == ENOENT) + { + char *p = dd->dd_dirname + 1; + while ((p = strchr(p, '/')) != NULL) + { + *p = '\0'; + int r = (mkdir(dd->dd_dirname, 0755) == 0 || errno == EEXIST); + *p++ = '/'; + if (!r) + goto report_err; + } + created_parents = true; + goto try_again; + } + report_err: + errno = err; + perror_msg("Can't create directory '%s'", dir); + dd_close(dd); + return NULL; + } + + if (dd_lock(dd, CREATE_LOCK_USLEEP) < 0) + { dd_close(dd); return NULL; } @@ -281,35 +430,51 @@ struct dump_dir *dd_create(const char *dir, uid_t uid) /* mkdir's mode (above) can be affected by umask, fix it */ if (chmod(dir, 0750) == -1) { - perror_msg("Can't change mode of '%s'", dir); + perror_msg("can't change mode of '%s'", dir); dd_close(dd); return NULL; } - /* Get ABRT's user id */ - /*dd->dd_uid = 0; - dd_init did this already */ - struct passwd *pw = getpwnam("abrt"); - if (pw) - dd->dd_uid = pw->pw_uid; - else - error_msg("User 'abrt' does not exist, using uid 0"); - - /* Get crashed application's group id */ - /*dd->dd_gid = 0; - dd_init did this already */ - pw = getpwuid(uid); - if (pw) - dd->dd_gid = pw->pw_gid; - else - error_msg("User %lu does not exist, using gid 0", (long)uid); - - if (chown(dir, dd->dd_uid, dd->dd_gid) == -1) + dd->dd_uid = (uid_t)-1L; + dd->dd_gid = (gid_t)-1L; + if (uid != (uid_t)-1L) { - perror_msg("Can't change '%s' ownership to %lu:%lu", dir, - (long)dd->dd_uid, (long)dd->dd_gid); + /* Get ABRT's user id */ + dd->dd_uid = 0; + struct passwd *pw = getpwnam("abrt"); + if (pw) + dd->dd_uid = pw->pw_uid; + else + error_msg("user 'abrt' does not exist, using uid 0"); + + /* Get crashed application's group id */ + /*dd->dd_gid = 0; - dd_init did this already */ + pw = getpwuid(uid); + if (pw) + dd->dd_gid = pw->pw_gid; + else + error_msg("User %lu does not exist, using gid 0", (long)uid); + + if (chown(dir, dd->dd_uid, dd->dd_gid) == -1) + { + perror_msg("can't change '%s' ownership to %lu:%lu", dir, + (long)dd->dd_uid, (long)dd->dd_gid); + } } + return dd; +} + +void dd_create_basic_files(struct dump_dir *dd, uid_t uid) +{ char long_str[sizeof(long) * 3 + 2]; + time_t t = time(NULL); + sprintf(long_str, "%lu", (long)t); + dd_save_text(dd, FILENAME_TIME, long_str); + + if (uid == (uid_t)-1) + uid = getuid(); sprintf(long_str, "%lu", (long)uid); dd_save_text(dd, FILENAME_UID, long_str); @@ -317,54 +482,102 @@ struct dump_dir *dd_create(const char *dir, uid_t uid) uname(&buf); /* never fails */ dd_save_text(dd, FILENAME_KERNEL, buf.release); dd_save_text(dd, FILENAME_ARCHITECTURE, buf.machine); - char *release = load_text_file("/etc/redhat-release", /*flags:*/ 0); - strchrnul(release, '\n')[0] = '\0'; - dd_save_text(dd, FILENAME_RELEASE, release); - free(release); + dd_save_text(dd, FILENAME_HOSTNAME, buf.nodename); - time_t t = time(NULL); - sprintf(long_str, "%lu", (long)t); - dd_save_text(dd, FILENAME_TIME, long_str); - - return dd; + char *release = load_text_file("/etc/system-release", + DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE); + if (!release) + release = load_text_file("/etc/redhat-release", /*flags:*/ 0); + dd_save_text(dd, FILENAME_OS_RELEASE, release); + free(release); } -static void delete_file_dir(const char *dir) +static int delete_file_dir(const char *dir, bool skip_lock_file) { DIR *d = opendir(dir); if (!d) - return; + { + /* The caller expects us to error out only if the directory + * still exists (not deleted). If directory + * *doesn't exist*, return 0 and clear errno. + */ + if (errno == ENOENT || errno == ENOTDIR) + { + errno = 0; + return 0; + } + return -1; + } + bool unlink_lock_file = false; struct dirent *dent; while ((dent = readdir(d)) != NULL) { if (dot_or_dotdot(dent->d_name)) continue; + if (skip_lock_file && strcmp(dent->d_name, ".lock") == 0) + { + unlink_lock_file = true; + continue; + } char *full_path = concat_path_file(dir, dent->d_name); if (unlink(full_path) == -1 && errno != ENOENT) { - if (errno != EISDIR) + int err = 0; + if (errno == EISDIR) + { + errno = 0; + err = delete_file_dir(full_path, /*skip_lock_file:*/ false); + } + if (errno || err) { - error_msg("Can't remove '%s'", full_path); + perror_msg("Can't remove '%s'", full_path); free(full_path); closedir(d); - return; + return -1; } - delete_file_dir(full_path); } free(full_path); } closedir(d); - if (rmdir(dir) == -1) + + /* Here we know for sure that all files/subdirs we found via readdir + * were deleted successfully. If rmdir below fails, we assume someone + * is racing with us and created a new file. + */ + + if (unlink_lock_file) { - error_msg("Can't remove dir '%s'", dir); + char *full_path = concat_path_file(dir, ".lock"); + xunlink(full_path); + free(full_path); + + unsigned cnt = RMDIR_FAIL_COUNT; + do { + if (rmdir(dir) == 0) + return 0; + /* Someone locked the dir after unlink, but before rmdir. + * This "someone" must be dd_lock(). + * It detects this (by seeing that there is no time file) + * and backs off at once. So we need to just retry rmdir, + * with minimal sleep. + */ + usleep(RMDIR_FAIL_USLEEP); + } while (--cnt != 0); } + + int r = rmdir(dir); + if (r) + perror_msg("Can't remove directory '%s'", dir); + return r; } -void dd_delete(struct dump_dir *dd) +int dd_delete(struct dump_dir *dd) { - delete_file_dir(dd->dd_dir); + int r = delete_file_dir(dd->dd_dirname, /*skip_lock_file:*/ true); + dd->locked = 0; /* delete_file_dir already removed .lock */ dd_close(dd); + return r; } static char *load_text_file(const char *path, unsigned flags) @@ -411,10 +624,15 @@ static bool save_binary_file(const char *path, const char* data, unsigned size, perror_msg("Can't open file '%s'", path); return false; } - if (fchown(fd, uid, gid) == -1) + + if (uid != (uid_t)-1L) { - perror_msg("can't change '%s' ownership to %lu:%lu", path, (long)uid, (long)gid); + if (fchown(fd, uid, gid) == -1) + { + perror_msg("can't change '%s' ownership to %lu:%lu", path, (long)uid, (long)gid); + } } + unsigned r = full_write(fd, data, size); close(fd); if (r != size) @@ -428,10 +646,14 @@ static bool save_binary_file(const char *path, const char* data, unsigned size, char* dd_load_text_ext(const struct dump_dir *dd, const char *name, unsigned flags) { - if (!dd->locked) - error_msg_and_die("dump_dir is not opened"); /* bug */ +// if (!dd->locked) +// error_msg_and_die("dump_dir is not opened"); /* bug */ - char *full_path = concat_path_file(dd->dd_dir, name); + /* Compat with old abrt dumps. Remove in abrt-2.1 */ + if (strcmp(name, "release") == 0) + name = FILENAME_OS_RELEASE; + + char *full_path = concat_path_file(dd->dd_dirname, name); char *ret = load_text_file(full_path, flags); free(full_path); @@ -448,7 +670,7 @@ void dd_save_text(struct dump_dir *dd, const char *name, const char *data) if (!dd->locked) error_msg_and_die("dump_dir is not opened"); /* bug */ - char *full_path = concat_path_file(dd->dd_dir, name); + char *full_path = concat_path_file(dd->dd_dirname, name); save_binary_file(full_path, data, strlen(data), dd->dd_uid, dd->dd_gid); free(full_path); } @@ -458,23 +680,23 @@ void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, uns if (!dd->locked) error_msg_and_die("dump_dir is not opened"); /* bug */ - char *full_path = concat_path_file(dd->dd_dir, name); + char *full_path = concat_path_file(dd->dd_dirname, name); save_binary_file(full_path, data, size, dd->dd_uid, dd->dd_gid); free(full_path); } DIR *dd_init_next_file(struct dump_dir *dd) { - if (!dd->locked) - error_msg_and_die("dump_dir is not opened"); /* bug */ +// if (!dd->locked) +// error_msg_and_die("dump_dir is not opened"); /* bug */ if (dd->next_dir) closedir(dd->next_dir); - dd->next_dir = opendir(dd->dd_dir); + dd->next_dir = opendir(dd->dd_dirname); if (!dd->next_dir) { - error_msg("Can't open dir '%s'", dd->dd_dir); + error_msg("Can't open directory '%s'", dd->dd_dirname); } return dd->next_dir; @@ -488,12 +710,12 @@ int dd_get_next_file(struct dump_dir *dd, char **short_name, char **full_name) struct dirent *dent; while ((dent = readdir(dd->next_dir)) != NULL) { - if (is_regular_file(dent, dd->dd_dir)) + if (is_regular_file(dent, dd->dd_dirname)) { if (short_name) *short_name = xstrdup(dent->d_name); if (full_name) - *full_name = concat_path_file(dd->dd_dir, dent->d_name); + *full_name = concat_path_file(dd->dd_dirname, dent->d_name); return 1; } } @@ -504,9 +726,9 @@ int dd_get_next_file(struct dump_dir *dd, char **short_name, char **full_name) } /* Utility function */ -void delete_crash_dump_dir(const char *dd_dir) +void delete_dump_dir(const char *dirname) { - struct dump_dir *dd = dd_opendir(dd_dir, /*flags:*/ 0); + struct dump_dir *dd = dd_opendir(dirname, /*flags:*/ 0); if (dd) { dd_delete(dd); diff --git a/src/daemon/CommLayerServer.cpp b/src/lib/glib_support.c index 5e250121..feb4c18b 100644 --- a/src/daemon/CommLayerServer.cpp +++ b/src/lib/glib_support.c @@ -1,6 +1,6 @@ /* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 RedHat Inc + Copyright (C) 2011 ABRT team + Copyright (C) 2011 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 @@ -16,14 +16,11 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "CommLayerServer.h" -#include "CrashWatcher.h" +#include "abrtlib.h" -CCommLayerServer::CCommLayerServer() -{ - m_init_error = 0; -} - -CCommLayerServer::~CCommLayerServer() +void list_free_with_free(GList *list) { + for (GList *li = list; li; li = g_list_next(li)) + free(li->data); + g_list_free(list); } diff --git a/src/lib/hash_md5.h b/src/lib/hash_md5.h index cc1d2c43..f7e9f398 100644 --- a/src/lib/hash_md5.h +++ b/src/lib/hash_md5.h @@ -24,6 +24,9 @@ typedef struct md5_ctx_t { uint32_t buflen; char buffer[128]; } md5_ctx_t; +#define md5_begin abrt_md5_begin void md5_begin(md5_ctx_t *ctx); +#define md5_hash abrt_md5_hash void md5_hash(const void *data, size_t length, md5_ctx_t *ctx); +#define md5_end abrt_md5_end void md5_end(void *resbuf, md5_ctx_t *ctx); diff --git a/src/lib/hash_sha1.h b/src/lib/hash_sha1.h index 02978ea4..09f50d12 100644 --- a/src/lib/hash_sha1.h +++ b/src/lib/hash_sha1.h @@ -31,8 +31,11 @@ typedef struct sha1_ctx_t { void (*process_block)(struct sha1_ctx_t*); } sha1_ctx_t; +#define sha1_begin abrt_sha1_begin void sha1_begin(sha1_ctx_t *ctx); +#define sha1_hash abrt_sha1_hash void sha1_hash(const void *buffer, size_t len, sha1_ctx_t *ctx); +#define sha1_end abrt_sha1_end void sha1_end(void *resbuf, sha1_ctx_t *ctx); #ifdef __cplusplus diff --git a/src/lib/hooklib.c b/src/lib/hooklib.c index 63c8a634..a89ab008 100644 --- a/src/lib/hooklib.c +++ b/src/lib/hooklib.c @@ -132,7 +132,7 @@ void trim_debug_dumps(unsigned setting_MaxCrashReportsSize, const char *exclude_ char *d = concat_path_file(DEBUG_DUMPS_DIR, worst_dir); free(worst_dir); worst_dir = NULL; - delete_crash_dump_dir(d); + delete_dump_dir(d); free(d); } } diff --git a/src/lib/hooklib.h b/src/lib/hooklib.h index ba76efbc..84b31a5f 100644 --- a/src/lib/hooklib.h +++ b/src/lib/hooklib.h @@ -20,8 +20,14 @@ extern "C" { #endif -void parse_conf(const char *additional_conf, unsigned *setting_MaxCrashReportsSize, bool *setting_MakeCompatCore, bool *setting_SaveBinaryImage); +#define parse_conf abrt_parse_conf +void parse_conf(const char *additional_conf, + unsigned *setting_MaxCrashReportsSize, + bool *setting_MakeCompatCore, + bool *setting_SaveBinaryImage); +#define check_free_space abrt_check_free_space void check_free_space(unsigned setting_MaxCrashReportsSize); +#define trim_debug_dumps abrt_trim_debug_dumps void trim_debug_dumps(unsigned setting_MaxCrashReportsSize, const char *exclude_path); #ifdef __cplusplus diff --git a/src/lib/numtoa.cpp b/src/lib/iso_date_string.c index 061da553..4600ff7f 100644 --- a/src/lib/numtoa.cpp +++ b/src/lib/iso_date_string.c @@ -1,8 +1,6 @@ /* - Number to string conversions - - Copyright (C) 2010 ABRT team - Copyright (C) 2010 RedHat inc. + Copyright (C) 2011 ABRT team + Copyright (C) 2011 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 @@ -18,17 +16,16 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ + #include "abrtlib.h" -std::string unsigned_to_string(unsigned long long x) -{ - char buf[sizeof(x)*3]; - sprintf(buf, "%llu", x); - return buf; -} -std::string signed_to_string(long long x) +char *iso_date_string(time_t *pt) { - char buf[sizeof(x)*3]; - sprintf(buf, "%lld", x); - return buf; + static char buf[sizeof("YYYY-MM-DD-HH:MM:SS") + 4]; + + time_t t; + struct tm *ptm = localtime(pt ? pt : (time(&t), &t)); + strftime(buf, sizeof(buf), "%Y-%m-%d-%H:%M:%S", ptm); + + return buf; } diff --git a/src/lib/load_plugin_settings.cpp b/src/lib/load_plugin_settings.c index 1052f19e..1e6b31e7 100644 --- a/src/lib/load_plugin_settings.cpp +++ b/src/lib/load_plugin_settings.c @@ -18,8 +18,10 @@ */ #include "abrtlib.h" -bool LoadPluginSettings(const char *pPath, map_plugin_settings_t& pSettings, - bool skipKeysWithoutValue /*= true*/) +/* Returns NULL if open failed. + * Returns empty hash if conf file is empty. + */ +bool load_conf_file(const char *pPath, map_string_h *settings, bool skipKeysWithoutValue) { FILE *fp = stdin; if (strcmp(pPath, "-") != 0) @@ -33,11 +35,15 @@ bool LoadPluginSettings(const char *pPath, map_plugin_settings_t& pSettings, while ((line = xmalloc_fgetline(fp)) != NULL) { unsigned ii; - bool is_value = false; bool valid = false; bool in_quote = false; - std::string key; - std::string value; + /* We are reusing line buffer to form temporary + * "key\0value\0..." in its beginning + */ + char *key = line; + char *value = line; + char *cur = line; + for (ii = 0; line[ii] != '\0'; ii++) { if (line[ii] == '"') @@ -48,47 +54,43 @@ bool LoadPluginSettings(const char *pPath, map_plugin_settings_t& pSettings, { continue; } - if (line[ii] == '#' && !in_quote && key == "") + if (line[ii] == '#' && !in_quote && cur == line) { break; } if (line[ii] == '=' && !in_quote) { - is_value = true; valid = true; + *cur++ = '\0'; /* terminate key */ + value = cur; /* remember where value starts */ continue; } - if (!is_value) - { - key += line[ii]; - } - else - { - value += line[ii]; - } + *cur++ = line[ii]; /* store next key or value char */ } + *cur++ = '\0'; /* terminate value */ /* Skip broken or empty lines. */ if (!valid) goto free_line; /* Skip lines with empty key. */ - if (key.length() == 0) + if (key[0] == '\0') goto free_line; - if (skipKeysWithoutValue && value.length() == 0) + if (skipKeysWithoutValue && value[0] == '\0') goto free_line; /* Skip lines with unclosed quotes. */ if (in_quote) goto free_line; - pSettings[key] = value; + g_hash_table_replace(settings, xstrdup(key), xstrdup(value)); free_line: free(line); } if (fp != stdin) fclose(fp); + return true; } diff --git a/src/lib/logging.h b/src/lib/logging.h index 8a038bc7..316c1a22 100644 --- a/src/lib/logging.h +++ b/src/lib/logging.h @@ -33,14 +33,6 @@ extern "C" { #define NORETURN __attribute__ ((noreturn)) -/* VERB1 log("what you sometimes want to see, even on a production box") */ -#define VERB1 if (g_verbose >= 1) -/* VERB2 log("debug message, not going into insanely small details") */ -#define VERB2 if (g_verbose >= 2) -/* VERB3 log("lots and lots of details") */ -#define VERB3 if (g_verbose >= 3) -/* there is no level > 3 */ - enum { LOGMODE_NONE = 0, LOGMODE_STDIO = (1 << 0), @@ -49,26 +41,47 @@ enum { LOGMODE_CUSTOM = (1 << 2), }; +#define g_custom_logger abrt_g_custom_logger extern void (*g_custom_logger)(const char*); +#define msg_prefix abrt_msg_prefix extern const char *msg_prefix; +#define msg_eol abrt_msg_eol extern const char *msg_eol; +#define logmode abrt_logmode extern int logmode; +#define xfunc_error_retval abrt_xfunc_error_retval extern int xfunc_error_retval; /* Verbosity level */ +#define g_verbose abrt_g_verbose extern int g_verbose; +/* VERB1 log("what you sometimes want to see, even on a production box") */ +#define VERB1 if (g_verbose >= 1) +/* VERB2 log("debug message, not going into insanely small details") */ +#define VERB2 if (g_verbose >= 2) +/* VERB3 log("lots and lots of details") */ +#define VERB3 if (g_verbose >= 3) +/* there is no level > 3 */ +#define abrt_ +#define xfunc_die abrt_xfunc_die void xfunc_die(void) NORETURN; +#define log_msg abrt_log_msg void log_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))); /* It's a macro, not function, since it collides with log() from math.h */ #undef log #define log(...) log_msg(__VA_ARGS__) /* error_msg family will use g_custom_logger. log_msg does not. */ +#define error_msg abrt_error_msg void error_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))); +#define error_msg_and_die abrt_error_msg_and_die void error_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2))); /* Reports error message with libc's errno error description attached. */ +#define perror_msg abrt_perror_msg void perror_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))); +#define perror_msg_and_die abrt_perror_msg_and_die void perror_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2))); +#define die_out_of_memory abrt_die_out_of_memory void die_out_of_memory(void) NORETURN; #ifdef __cplusplus diff --git a/src/lib/make_descr.cpp b/src/lib/make_descr.c index e11325c8..1ba15203 100644 --- a/src/lib/make_descr.cpp +++ b/src/lib/make_descr.c @@ -17,10 +17,6 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "abrtlib.h" -#include "abrt_crash_dump.h" - - -using namespace std; // caller is responsible for freeing **dsc static void add_content(bool *was_multiline, char **dsc, const char *header, const char *content) @@ -64,7 +60,6 @@ static void add_content(bool *was_multiline, char **dsc, const char *header, con static const char *const blacklisted_items[] = { FILENAME_ANALYZER , FILENAME_COREDUMP , - FILENAME_DESCRIPTION, /* package description - basically useless */ FILENAME_HOSTNAME , FILENAME_DUPHASH , FILENAME_UUID , @@ -75,30 +70,32 @@ static const char *const blacklisted_items[] = { NULL }; -char* make_description_mailx(const map_crash_data_t & crash_data) +char* make_description_mailx(crash_data_t *crash_data) { struct strbuf *buf_dsc = strbuf_new(); struct strbuf *buf_additional_files = strbuf_new(); struct strbuf *buf_duphash_file = strbuf_new(); struct strbuf *buf_common_files = strbuf_new(); - map_crash_data_t::const_iterator it; - for (it = crash_data.begin(); it != crash_data.end(); it++) + GHashTableIter iter; + char *name; + struct crash_item *value; + g_hash_table_iter_init(&iter, crash_data); + while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value)) { - if (it->second[CD_TYPE] == CD_TXT) + if (value->flags & CD_FLAG_TXT) { - const char *itemname = it->first.c_str(); - if ((strcmp(itemname, FILENAME_DUPHASH) != 0) - && (strcmp(itemname, FILENAME_ARCHITECTURE) != 0) - && (strcmp(itemname, FILENAME_KERNEL) != 0) - && (strcmp(itemname, FILENAME_PACKAGE) != 0) + if ((strcmp(name, FILENAME_DUPHASH) != 0) + && (strcmp(name, FILENAME_ARCHITECTURE) != 0) + && (strcmp(name, FILENAME_KERNEL) != 0) + && (strcmp(name, FILENAME_PACKAGE) != 0) ) { - strbuf_append_strf(buf_additional_files, "%s\n-----\n%s\n\n", itemname, it->second[CD_CONTENT].c_str()); + strbuf_append_strf(buf_additional_files, "%s\n-----\n%s\n\n", name, value->content); } - else if (strcmp(itemname, FILENAME_DUPHASH) == 0) - strbuf_append_strf(buf_duphash_file, "%s\n-----\n%s\n\n", itemname, it->second[CD_CONTENT].c_str()); + else if (strcmp(name, FILENAME_DUPHASH) == 0) + strbuf_append_strf(buf_duphash_file, "%s\n-----\n%s\n\n", name, value->content); else - strbuf_append_strf(buf_common_files, "%s\n-----\n%s\n\n", itemname, it->second[CD_CONTENT].c_str()); + strbuf_append_strf(buf_common_files, "%s\n-----\n%s\n\n", name, value->content); } } @@ -117,24 +114,27 @@ char* make_description_mailx(const map_crash_data_t & crash_data) return strbuf_free_nobuf(buf_dsc); } -char* make_description_bz(const map_crash_data_t& pCrashData) +char* make_description_bz(crash_data_t *crash_data) { struct strbuf *buf_dsc = strbuf_new(); struct strbuf *buf_long_dsc = strbuf_new(); - map_crash_data_t::const_iterator it = pCrashData.begin(); - for (; it != pCrashData.end(); it++) + GHashTableIter iter; + char *name; + struct crash_item *value; + g_hash_table_iter_init(&iter, crash_data); + while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value)) { - const char *itemname = it->first.c_str(); - const char *type = it->second[CD_TYPE].c_str(); - const char *content = it->second[CD_CONTENT].c_str(); - if (strcmp(type, CD_TXT) == 0) + struct stat statbuf; + unsigned flags = value->flags; + const char *content = value->content; + if (flags & CD_FLAG_TXT) { /* Skip items we are not interested in */ const char *const *bl = blacklisted_items; while (*bl) { - if (strcmp(itemname, *bl) == 0) + if (strcmp(name, *bl) == 0) break; bl++; } @@ -151,7 +151,7 @@ char* make_description_bz(const map_crash_data_t& pCrashData) add_content(&was_multiline, &tmp, /* "reproduce: blah" looks ugly, fixing: */ - (strcmp(itemname, FILENAME_REPRODUCE) == 0) ? "How to reproduce" : itemname, + (strcmp(name, FILENAME_REPRODUCE) == 0) ? "How to reproduce" : name, content ); @@ -167,13 +167,33 @@ char* make_description_bz(const map_crash_data_t& pCrashData) strbuf_append_str(buf_dsc, tmp); free(tmp); - } else { - bool was_multiline = 0; - char *dsc = NULL; - add_content(&was_multiline, &dsc, "Attached file", itemname); - strbuf_append_str(buf_dsc, dsc); - free(dsc); } + else + { + statbuf.st_size = strlen(content); + goto add_attachment_info; + } + } + if (flags & CD_FLAG_BIN) + { + /* In many cases, it is useful to know how big binary files are + * (for example, helps with diagnosing bug upload problems) + */ + if (stat(content, &statbuf) != 0) + statbuf.st_size = (off_t) -1; + + add_attachment_info: ; + char *descr; + if (statbuf.st_size >= 0) + descr = xasprintf("%s, %llu bytes", name, (long long)statbuf.st_size); + else + descr = xstrdup(name); + bool was_multiline = 0; + char *tmp = NULL; + add_content(&was_multiline, &tmp, "Attached file", descr); + free(descr); + strbuf_append_str(buf_dsc, tmp); + free(tmp); } } @@ -189,25 +209,25 @@ char* make_description_bz(const map_crash_data_t& pCrashData) return strbuf_free_nobuf(buf_dsc); } -char* make_description_logger(const map_crash_data_t& pCrashData) +char* make_description_logger(crash_data_t *crash_data) { struct strbuf *buf_dsc = strbuf_new(); struct strbuf *buf_long_dsc = strbuf_new(); - map_crash_data_t::const_iterator it = pCrashData.begin(); - for (; it != pCrashData.end(); it++) + GHashTableIter iter; + char *name; + struct crash_item *value; + g_hash_table_iter_init(&iter, crash_data); + while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value)) { - const char *filename = it->first.c_str(); - const char *type = it->second[CD_TYPE].c_str(); - const char *content = it->second[CD_CONTENT].c_str(); - if ((strcmp(type, CD_TXT) == 0) - || (strcmp(type, CD_BIN) == 0) - ) { + const char *content = value->content; + if (value->flags & (CD_FLAG_TXT|CD_FLAG_BIN)) + { /* Skip items we are not interested in */ const char *const *bl = blacklisted_items; while (*bl) { - if (filename == *bl) + if (name == *bl) break; bl++; } @@ -218,12 +238,12 @@ char* make_description_logger(const map_crash_data_t& pCrashData) bool was_multiline = 0; char *tmp = NULL; - add_content(&was_multiline, &tmp, filename, content); + add_content(&was_multiline, &tmp, name, content); if (was_multiline) { if (buf_long_dsc->len != 0) - strbuf_append_char(buf_long_dsc,'\n'); + strbuf_append_char(buf_long_dsc, '\n'); strbuf_append_str(buf_long_dsc, tmp); } @@ -242,29 +262,27 @@ char* make_description_logger(const map_crash_data_t& pCrashData) return strbuf_free_nobuf(buf_dsc); } -char* make_description_reproduce_comment(const map_crash_data_t& pCrashData) +char* make_description_reproduce_comment(crash_data_t *crash_data) { char *repro = NULL; char *comment = NULL; + struct crash_item *value; - map_crash_data_t::const_iterator end = pCrashData.end(); - map_crash_data_t::const_iterator it; - - it = pCrashData.find(FILENAME_REPRODUCE); - if (it != end) + value = get_crash_data_item_or_NULL(crash_data, FILENAME_REPRODUCE); + if (value) { - if ((it->second[CD_CONTENT].size() > 0) - && (it->second[CD_CONTENT] != "1.\n2.\n3.\n")) - { - repro = xasprintf("\n\nHow to reproduce\n-----\n%s", it->second[CD_CONTENT].c_str()); + if (value->content[0] + && strcmp(value->content, "1.\n2.\n3.\n") != 0 + ) { + repro = xasprintf("\n\nHow to reproduce\n-----\n%s", value->content); } } - it = pCrashData.find(FILENAME_COMMENT); - if (it != end) + value = get_crash_data_item_or_NULL(crash_data, FILENAME_COMMENT); + if (value) { - if (it->second[CD_CONTENT].size() > 0) - comment = xasprintf("\n\nComment\n-----\n%s", it->second[CD_CONTENT].c_str()); + if (value->content[0]) + comment = xasprintf("\n\nComment\n-----\n%s", value->content); } if (!repro && !comment) diff --git a/src/lib/parse_options.c b/src/lib/parse_options.c index c1a2c297..3d631461 100644 --- a/src/lib/parse_options.c +++ b/src/lib/parse_options.c @@ -1,3 +1,21 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 <getopt.h> #include "abrtlib.h" @@ -6,26 +24,17 @@ #define USAGE_OPTS_WIDTH 24 #define USAGE_GAP 2 -void parse_usage_and_die(const char *usage, const struct options *opt) +void show_usage_and_die(const char *usage, const struct options *opt) { fprintf(stderr, _("Usage: %s\n"), usage); - if (opt->type != OPTION_GROUP) - fputc('\n', stderr); + fputc('\n', stderr); for (; opt->type != OPTION_END; opt++) { size_t pos; int pad; - if (opt->type == OPTION_GROUP) - { - fputc('\n', stderr); - if (*opt->help) - fprintf(stderr, "%s\n", opt->help); - continue; - } - pos = fprintf(stderr, " "); if (opt->short_name) pos += fprintf(stderr, "-%c", opt->short_name); @@ -92,6 +101,7 @@ unsigned parse_opts(int argc, char **argv, const struct options *opt, break; case OPTION_INTEGER: case OPTION_STRING: + case OPTION_LIST: curopt->has_arg = required_argument; if (opt[ii].short_name) strbuf_append_strf(shortopts, "%c:", opt[ii].short_name); @@ -101,7 +111,6 @@ unsigned parse_opts(int argc, char **argv, const struct options *opt, if (opt[ii].short_name) strbuf_append_strf(shortopts, "%c::", opt[ii].short_name); break; - case OPTION_GROUP: case OPTION_END: break; } @@ -144,7 +153,7 @@ unsigned parse_opts(int argc, char **argv, const struct options *opt, { free(longopts); strbuf_free(shortopts); - parse_usage_and_die(usage, opt); + show_usage_and_die(usage, opt); } for (ii = 0; ii < size; ++ii) @@ -157,17 +166,19 @@ unsigned parse_opts(int argc, char **argv, const struct options *opt, if (opt[ii].value != NULL) switch (opt[ii].type) { case OPTION_BOOL: - *(int*)opt[ii].value += 1; + *(int*)(opt[ii].value) += 1; break; case OPTION_INTEGER: - *(int*)opt[ii].value = xatoi(optarg); + *(int*)(opt[ii].value) = xatoi(optarg); break; case OPTION_STRING: case OPTION_OPTSTRING: if (optarg) - *(char**)opt[ii].value = (char*)optarg; + *(char**)(opt[ii].value) = (char*)optarg; + break; + case OPTION_LIST: + *(GList**)(opt[ii].value) = g_list_append(*(GList**)(opt[ii].value), optarg); break; - case OPTION_GROUP: case OPTION_END: break; } diff --git a/src/lib/parse_options.h b/src/lib/parse_options.h index 105f081c..82f3c6b8 100644 --- a/src/lib/parse_options.h +++ b/src/lib/parse_options.h @@ -1,18 +1,34 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 PARSE_OPTIONS_H #define PARSE_OPTIONS_H - #ifdef __cplusplus extern "C" { #endif enum parse_opt_type { OPTION_BOOL, - OPTION_GROUP, OPTION_STRING, OPTION_INTEGER, OPTION_OPTSTRING, + OPTION_LIST, OPTION_END, }; @@ -29,22 +45,24 @@ struct options { * s - short_name * l - long_name * v - value - * a - argh argument help + * a - option parameter name (for help text) * h - help */ #define OPT_END() { OPTION_END } #define OPT_BOOL(s, l, v, h) { OPTION_BOOL, (s), (l), (v), NULL, (h) } -#define OPT_GROUP(h) { OPTION_GROUP, 0, NULL, NULL, NULL, (h) } -#define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), "n", (h) } +#define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), "NUM", (h) } #define OPT_STRING(s, l, v, a, h) { OPTION_STRING, (s), (l), (v), (a), (h) } #define OPT_OPTSTRING(s, l, v, a, h) { OPTION_OPTSTRING, (s), (l), (v), (a), (h) } +#define OPT_LIST(s, l, v, a, h) { OPTION_LIST, (s), (l), (v), (a), (h) } -#define OPT__VERBOSE(v) OPT_BOOL('v', "verbose", (v), "be verbose") +#define OPT__VERBOSE(v) OPT_BOOL('v', "verbose", (v), _("Be verbose")) +#define parse_opts abrt_parse_opts unsigned parse_opts(int argc, char **argv, const struct options *opt, const char *usage); -void parse_usage_and_die(const char *usage, const struct options *opt); +#define show_usage_and_die abrt_show_usage_and_die +void show_usage_and_die(const char *usage, const struct options *opt); #ifdef __cplusplus } diff --git a/src/lib/parse_release.cpp b/src/lib/parse_release.c index f9057bfe..b24f928c 100644 --- a/src/lib/parse_release.cpp +++ b/src/lib/parse_release.c @@ -19,7 +19,7 @@ #include "abrtlib.h" // caller is reposible for freeing *product* and *version* -void parse_release(const char *release, char** product, char** version) +static void parse_release(const char *release, char** product, char** version, bool append_rhel_version) { if (strstr(release, "Rawhide")) { @@ -33,21 +33,31 @@ void parse_release(const char *release, char** product, char** version) if (strstr(release, "Fedora")) strbuf_append_str(buf_product, "Fedora"); else if (strstr(release, "Red Hat Enterprise Linux")) - strbuf_append_str(buf_product, "Red Hat Enterprise Linux "); + strbuf_append_str(buf_product, "Red Hat Enterprise Linux"); + else + { + /* TODO: add logic for parsing other distros' names here */ + strbuf_append_str(buf_product, release); + } const char *r = strstr(release, "release"); const char *space = r ? strchr(r, ' ') : NULL; struct strbuf *buf_version = strbuf_new(); - if (space++) + if (space) { + space++; while (*space != '\0' && *space != ' ') { /* Eat string like "5.2" */ strbuf_append_char(buf_version, *space); - if ((strcmp(buf_product->buf, "Red Hat Enterprise Linux ") == 0)) + if (append_rhel_version + && strcmp(buf_product->buf, "Red Hat Enterprise Linux") == 0 + ) { + strbuf_append_char(buf_product, ' '); strbuf_append_char(buf_product, *space); - + } + append_rhel_version = false; space++; } } @@ -57,3 +67,15 @@ void parse_release(const char *release, char** product, char** version) VERB3 log("%s: version:'%s' product:'%s'", __func__, *version, *product); } + +void parse_release_for_bz(const char *release, char** product, char** version) +{ + /* Fedora/RH bugzilla uses "Red Hat Enterprise Linux N" product RHEL */ + parse_release(release, product, version, /*append_rhel_version:*/ true); +} + +void parse_release_for_rhts(const char *release, char** product, char** version) +{ + /* RHTS uses "Red Hat Enterprise Linux" product for RHEL */ + parse_release(release, product, version, /*append_rhel_version:*/ false); +} diff --git a/src/lib/read_write.h b/src/lib/read_write.h index 054a1a9a..dc85f33b 100644 --- a/src/lib/read_write.h +++ b/src/lib/read_write.h @@ -32,14 +32,20 @@ extern "C" { // NB: will return short read on error, not -1, // if some data was read before error occurred +#define xread abrt_xread void xread(int fd, void *buf, size_t count); +#define safe_read abrt_safe_read ssize_t safe_read(int fd, void *buf, size_t count); +#define safe_write abrt_safe_write ssize_t safe_write(int fd, const void *buf, size_t count); +#define full_read abrt_full_read ssize_t full_read(int fd, void *buf, size_t count); +#define full_write abrt_full_write ssize_t full_write(int fd, const void *buf, size_t count); +#define full_write_str abrt_full_write_str ssize_t full_write_str(int fd, const char *buf); #ifdef __cplusplus diff --git a/src/lib/run_event.c b/src/lib/run_event.c index 4ff1070d..e96e762f 100644 --- a/src/lib/run_event.c +++ b/src/lib/run_event.c @@ -16,36 +16,78 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include <glob.h> #include "abrtlib.h" -int run_event(struct run_event_state *state, +struct run_event_state *new_run_event_state() +{ + return xzalloc(sizeof(struct run_event_state)); +} + +void free_run_event_state(struct run_event_state *state) +{ + if (state) + { + free_commands(state); + free(state); + } +} + + +/* Asyncronous command execution */ + +/* It is not yet clear whether we need to re-parse event config file + * and re-check the elements in dump dir after each comamnd. + * + * Consider this config file: + * + * EVENT=e cmd1 + * EVENT=e foo=bar cmd2 + * EVENT=e foo=baz cmd3 + * + * Imagine that element foo existed and was equal to bar at the beginning. + * After cmd1, should we execute cmd2 if element foo disappeared? + * After cmd1/2, should we execute cmd3 if element foo changed value to baz? + * + * So far, we read entire config file and select a list of commands to execute, + * checking all conditions in the beginning. It is a bit more simple to code up. + * But we may want to change it later. Therefore list of commands machinery + * is encapsulated in struct run_event_state and public async API: + * prepare_commands(state, dir, event); + * spawn_next_command(state, dir, event); + * free_commands(state); + * does not expose it. + */ + +static GList *load_event_config(GList *list, const char *dump_dir_name, - const char *event + const char *event, + const char *conf_file_name ) { - FILE *conffile = fopen(CONF_DIR"/abrt_event.conf", "r"); + FILE *conffile = fopen(conf_file_name, "r"); if (!conffile) { - error_msg("Can't open '%s'", CONF_DIR"/abrt_event.conf"); - return 1; + error_msg("Can't open '%s'", conf_file_name); + return list; } - close_on_exec_on(fileno(conffile)); - - /* Export some useful environment variables for children */ - /* Just exporting dump_dir_name isn't always ok: it can be "." - * and some children want to cd to other directory but still - * be able to find dump directory by using $DUMP_DIR... - */ - char *full_name = realpath(dump_dir_name, NULL); - setenv("DUMP_DIR", (full_name ? full_name : dump_dir_name), 1); - free(full_name); - setenv("EVENT", event, 1); - /* Read, match, and execute lines from abrt_event.conf */ - int retval = -1; + /* Read, match, and remember commands to execute */ struct dump_dir *dd = NULL; - char *line; - while ((line = xmalloc_fgetline(conffile)) != NULL) + char *next_line = xmalloc_fgetline(conffile); + while (next_line) { + char *line = next_line; + while (1) + { + next_line = xmalloc_fgetline(conffile); + if (!next_line || !isblank(next_line[0])) + break; + char *old_line = line; + line = xasprintf("%s\n%s", line, next_line); + free(old_line); + free(next_line); + } + /* Line has form: [VAR=VAL]... PROG [ARGS] */ char *p = skip_whitespace(line); if (*p == '\0' || *p == '#') @@ -53,6 +95,43 @@ int run_event(struct run_event_state *state, VERB3 log("%s: line '%s'", __func__, p); + if (strncmp(p, "include", strlen("include")) == 0 && isblank(p[strlen("include")])) + { + /* include GLOB_PATTERN */ + p = skip_whitespace(p + strlen("include")); + + const char *last_slash; + char *name_to_glob; + if (*p != '/' + && (last_slash = strrchr(conf_file_name, '/')) != NULL + ) + /* GLOB_PATTERN is relative, and this include is in path/to/file.conf + * Construct path/to/GLOB_PATTERN: + */ + name_to_glob = xasprintf("%.*s%s", (int)(last_slash - conf_file_name + 1), conf_file_name, p); + else + /* Either GLOB_PATTERN is absolute, or this include is in file.conf + * (no slashes in its name). Use unchanged GLOB_PATTERN: + */ + name_to_glob = xstrdup(p); + + glob_t globbuf; + memset(&globbuf, 0, sizeof(globbuf)); + VERB3 log("%s: globbing '%s'", __func__, name_to_glob); + glob(name_to_glob, 0, NULL, &globbuf); + free(name_to_glob); + char **name = globbuf.gl_pathv; + if (name) while (*name) + { + VERB3 log("%s: recursing into '%s'", __func__, *name); + list = load_event_config(list, dump_dir_name, event, *name); + VERB3 log("%s: returned from '%s'", __func__, *name); + name++; + } + globfree(&globbuf); + goto next_line; + } + while (1) /* word loop */ { char *end_word = skip_non_whitespace(p); @@ -83,7 +162,11 @@ int run_event(struct run_event_state *state, { dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) + { + free(line); + free(next_line); goto stop; /* error (note: dd_opendir logged error msg) */ + } } real_val = malloced_val = dd_load_text_ext(dd, p, DD_FAIL_QUIETLY); } @@ -104,54 +187,133 @@ int run_event(struct run_event_state *state, p = next_word; } /* end of word loop */ - /* Don't keep dump dir locked across program runs */ - dd_close(dd); - dd = NULL; + /* We found matching line, remember its command */ + VERB1 log("Adding '%s'", p); + overlapping_strcpy(line, p); + list = g_list_append(list, line); + continue; + + next_line: + free(line); + } /* end of line loop */ + + stop: + dd_close(dd); + fclose(conffile); + + return list; +} + +int prepare_commands(struct run_event_state *state, + const char *dump_dir_name, + const char *event +) { + free_commands(state); - /* We found matching line, execute its command(s) in shell */ + state->children_count = 0; + + GList *commands = load_event_config(NULL, dump_dir_name, event, CONF_DIR"/abrt_event.conf"); + state->commands = commands; + return commands != NULL; +} + +void free_commands(struct run_event_state *state) +{ + list_free_with_free(state->commands); + state->commands = NULL; + state->command_out_fd = -1; + state->command_pid = 0; +} + +/* event parameter is unused for now, + * but may be needed if we change implementation later + */ +int spawn_next_command(struct run_event_state *state, + const char *dump_dir_name, + const char *event +) { + if (!state->commands) + return -1; + + /* We count it even if fork fails. The counter isn't meant + * to count *successful* forks, it is meant to let caller know + * whether the event we run has *any* handlers configured, or not. + */ + state->children_count++; + + char *cmd = state->commands->data; + VERB1 log("Executing '%s'", cmd); + + /* Export some useful environment variables for children */ + /* Just exporting dump_dir_name isn't always ok: it can be "." + * and some children want to cd to other directory but still + * be able to find dump directory by using $DUMP_DIR... + */ + char *full_name = realpath(dump_dir_name, NULL); + setenv("DUMP_DIR", (full_name ? full_name : dump_dir_name), 1); + free(full_name); + setenv("EVENT", event, 1); +//FIXME: set vars in the child, not here! Need to improve fork_execv_on_steroids... + + char *argv[4]; + argv[0] = (char*)"/bin/sh"; + argv[1] = (char*)"-c"; + argv[2] = cmd; + argv[3] = NULL; + + int pipefds[2]; + state->command_pid = fork_execv_on_steroids( + EXECFLG_INPUT_NUL + EXECFLG_OUTPUT + EXECFLG_ERR2OUT, + argv, + pipefds, + /* unsetenv_vec: */ NULL, + /* dir: */ dump_dir_name, + /* uid(unused): */ 0 + ); + state->command_out_fd = pipefds[0]; + + state->commands = g_list_remove(state->commands, cmd); + + return 0; +} + + +/* Syncronous command execution: + */ +int run_event_on_dir_name(struct run_event_state *state, + const char *dump_dir_name, + const char *event +) { + prepare_commands(state, dump_dir_name, event); + + /* Execute every command in shell */ + + int retval = 0; + while (spawn_next_command(state, dump_dir_name, event) >= 0) + { + /* Consume log from stdout */ + FILE *fp = fdopen(state->command_out_fd, "r"); + if (!fp) + die_out_of_memory(); + char *buf; + while ((buf = xmalloc_fgetline(fp)) != NULL) { - VERB1 log("Executing '%s'", p); - - /* /bin/sh -c 'cmd [args]' NULL */ - char *argv[4]; - char **pp = argv; - *pp++ = (char*)"/bin/sh"; - *pp++ = (char*)"-c"; - *pp++ = (char*)p; - *pp = NULL; - int pipefds[2]; - pid_t pid = fork_execv_on_steroids(EXECFLG_INPUT_NUL + EXECFLG_OUTPUT + EXECFLG_ERR2OUT, - argv, - pipefds, - /* unsetenv_vec: */ NULL, - /* dir: */ dump_dir_name, - /* uid(unused): */ 0 - ); - free(line); - line = NULL; - - /* Consume log from stdout */ - FILE *fp = fdopen(pipefds[0], "r"); - if (!fp) - die_out_of_memory(); - char *buf; - while ((buf = xmalloc_fgetline(fp)) != NULL) - { - if (state->logging_callback) - buf = state->logging_callback(buf, state->logging_param); - free(buf); - } - fclose(fp); /* Got EOF, close. This also closes pipefds[0] */ + if (state->logging_callback) + buf = state->logging_callback(buf, state->logging_param); + free(buf); + } + fclose(fp); /* Got EOF, close. This also closes state->command_out_fd */ - /* Wait for child to actually exit, collect status */ - int status; - waitpid(pid, &status, 0); + /* Wait for child to actually exit, collect status */ + int status; + waitpid(state->command_pid, &status, 0); - retval = WEXITSTATUS(status); - if (WIFSIGNALED(status)) - retval = WTERMSIG(status) + 128; - if (retval != 0) - break; + retval = WEXITSTATUS(status); + if (WIFSIGNALED(status)) + retval = WTERMSIG(status) + 128; + if (retval != 0) + { + break; } if (state->post_run_callback) @@ -160,26 +322,49 @@ int run_event(struct run_event_state *state, if (retval != 0) break; } + } - next_line: - free(line); - } /* end of line loop */ - - stop: - free(line); - dd_close(dd); - fclose(conffile); + free_commands(state); return retval; } -char *list_possible_events(struct dump_dir *dd, const char *dump_dir_name, const char *pfx) +int run_event_on_crash_data(struct run_event_state *state, crash_data_t *data, const char *event) { - FILE *conffile = fopen(CONF_DIR"/abrt_event.conf", "r"); + state->children_count = 0; + + struct dump_dir *dd = create_dump_dir_from_crash_data(data, NULL); + if (!dd) + return -1; + char *dir_name = xstrdup(dd->dd_dirname); + dd_close(dd); + + int r = run_event_on_dir_name(state, dir_name, event); + + g_hash_table_remove_all(data); + dd = dd_opendir(dir_name, 0); + free(dir_name); + if (dd) + { + load_crash_data_from_dump_dir(data, dd); + dd_delete(dd); + } + return r; +} + + +/* TODO: very similar to run_event_helper, try to combine into one fn? */ +static int list_possible_events_helper(struct strbuf *result, + struct dump_dir *dd, + const char *dump_dir_name, + const char *pfx, + const char *conf_file_name +) { + FILE *conffile = fopen(conf_file_name, "r"); if (!conffile) { - error_msg("Can't open '%s'", CONF_DIR"/abrt_event.conf"); - return NULL; + error_msg("Can't open '%s'", conf_file_name); + return 0; } /* We check "dump_dir_name == NULL" later. @@ -189,11 +374,23 @@ char *list_possible_events(struct dump_dir *dd, const char *dump_dir_name, const if (dd) dump_dir_name = NULL; + int error = 0; unsigned pfx_len = strlen(pfx); - struct strbuf *result = strbuf_new(); - char *line; - while ((line = xmalloc_fgetline(conffile)) != NULL) + char *next_line = xmalloc_fgetline(conffile); + while (next_line) { + char *line = next_line; + while (1) + { + next_line = xmalloc_fgetline(conffile); + if (!next_line || !isblank(next_line[0])) + break; + char *old_line = line; + line = xasprintf("%s\n%s", line, next_line); + free(old_line); + free(next_line); + } + /* Line has form: [VAR=VAL]... PROG [ARGS] */ char *p = skip_whitespace(line); if (*p == '\0' || *p == '#') @@ -201,6 +398,45 @@ char *list_possible_events(struct dump_dir *dd, const char *dump_dir_name, const VERB3 log("%s: line '%s'", __func__, p); + if (strncmp(p, "include", strlen("include")) == 0 && isblank(p[strlen("include")])) + { + /* include GLOB_PATTERN */ + p = skip_whitespace(p + strlen("include")); + + const char *last_slash; + char *name_to_glob; + if (*p != '/' + && (last_slash = strrchr(conf_file_name, '/')) != NULL + ) + /* GLOB_PATTERN is relative, and this include is in path/to/file.conf + * Construct path/to/GLOB_PATTERN: + */ + name_to_glob = xasprintf("%.*s%s", (int)(last_slash - conf_file_name + 1), conf_file_name, p); + else + /* Either GLOB_PATTERN is absolute, or this include is in file.conf + * (no slashes in its name). Use unchanged GLOB_PATTERN: + */ + name_to_glob = xstrdup(p); + + glob_t globbuf; + memset(&globbuf, 0, sizeof(globbuf)); + VERB3 log("%s: globbing '%s'", __func__, name_to_glob); + glob(name_to_glob, 0, NULL, &globbuf); + free(name_to_glob); + char **name = globbuf.gl_pathv; + if (name) while (*name) + { + VERB3 log("%s: recursing into '%s'", __func__, *name); + error = list_possible_events_helper(result, dd, dump_dir_name, pfx, *name); + VERB3 log("%s: returned from '%s'", __func__, *name); + if (error) + break; + name++; + } + globfree(&globbuf); + goto next_line; + } + while (1) /* word loop */ { char *end_word = skip_non_whitespace(p); @@ -233,13 +469,21 @@ char *list_possible_events(struct dump_dir *dd, const char *dump_dir_name, const goto next_word; dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) + { + error = -1; + free(line); + free(next_line); goto stop; /* error (note: dd_opendir logged error msg) */ + } } char *real_val = dd_load_text_ext(dd, p, DD_FAIL_QUIETLY); /* Does VAL match? */ if (strcmp(real_val, line_val) != 0) { - VERB3 log("var '%s': '%s'!='%s', skipping line", p, real_val, line_val); + VERB3 log("var '%s': '%.*s'!='%s', skipping line", + p, + (int)(strchrnul(real_val, '\n') - real_val), real_val, + line_val); free(real_val); goto next_line; /* no */ } @@ -265,10 +509,18 @@ char *list_possible_events(struct dump_dir *dd, const char *dump_dir_name, const } /* end of line loop */ stop: - free(line); if (dump_dir_name != NULL) dd_close(dd); fclose(conffile); + return error; +} + +char *list_possible_events(struct dump_dir *dd, const char *dump_dir_name, const char *pfx) +{ + struct strbuf *result = strbuf_new(); + int error = list_possible_events_helper(result, dd, dump_dir_name, pfx, CONF_DIR"/abrt_event.conf"); + if (error) + strbuf_clear(result); return strbuf_free_nobuf(result); } diff --git a/src/lib/spawn.c b/src/lib/spawn.c index 068f4ac7..f6b7263c 100644 --- a/src/lib/spawn.c +++ b/src/lib/spawn.c @@ -108,7 +108,7 @@ pid_t fork_execv_on_steroids(int flags, execvp(argv[0], argv); if (!(flags & EXECFLG_QUIET)) perror_msg("Can't execute '%s'", argv[0]); - exit(127); /* shell uses this exitcode in this case */ + exit(127); /* shell uses this exit code in this case */ } if (flags & EXECFLG_INPUT) { diff --git a/src/lib/steal_directory.c b/src/lib/steal_directory.c new file mode 100644 index 00000000..a77861ee --- /dev/null +++ b/src/lib/steal_directory.c @@ -0,0 +1,40 @@ +#include "abrtlib.h" + +struct dump_dir *steal_directory(const char *base_dir, const char *dump_dir_name) +{ + const char *base_name = strrchr(dump_dir_name, '/'); + if (base_name) + base_name++; + else + base_name = dump_dir_name; + + struct dump_dir *dd_dst; + unsigned count = 100; + char *dst_dir_name = concat_path_file(base_dir, base_name); + while (1) + { + dd_dst = dd_create(dst_dir_name, (uid_t)-1); + free(dst_dir_name); + if (dd_dst) + break; + if (--count == 0) + { + error_msg("Can't create new dump dir in '%s'", base_dir); + return NULL; + } + struct timeval tv; + gettimeofday(&tv, NULL); + dst_dir_name = xasprintf("%s/%s.%u", base_dir, base_name, (int)tv.tv_usec); + } + + VERB1 log("Creating copy in '%s'", dd_dst->dd_dirname); + if (copy_file_recursive(dump_dir_name, dd_dst->dd_dirname) < 0) + { + /* error. copy_file_recursive already emitted error message */ + /* Don't leave half-copied dir lying around */ + dd_delete(dd_dst); + return NULL; + } + + return dd_dst; +} diff --git a/src/lib/strbuf.c b/src/lib/strbuf.c index 04a35998..f56815a0 100644 --- a/src/lib/strbuf.c +++ b/src/lib/strbuf.c @@ -37,7 +37,7 @@ int suffixcmp(const char *str, const char *suffix) return strcmp(str + len_minus_suflen, suffix); } -struct strbuf *strbuf_new() +struct strbuf *strbuf_new(void) { struct strbuf *buf = xzalloc(sizeof(*buf)); /*buf->len = 0; - done by xzalloc */ diff --git a/src/lib/strbuf.h b/src/lib/strbuf.h index dc45a199..44c6599a 100644 --- a/src/lib/strbuf.h +++ b/src/lib/strbuf.h @@ -39,13 +39,15 @@ struct strbuf * It never returns NULL. The returned pointer must be released by * calling the function strbuf_free(). */ -struct strbuf *strbuf_new(); +#define strbuf_new abrt_strbuf_new +struct strbuf *strbuf_new(void); /** * Releases the memory held by the string buffer. * @param strbuf * If the strbuf is NULL, no operation is performed. */ +#define strbuf_free abrt_strbuf_free void strbuf_free(struct strbuf *strbuf); /** @@ -53,24 +55,28 @@ void strbuf_free(struct strbuf *strbuf); * string buffer is returned. Caller is responsible to release the * returned memory using free(). */ +#define strbuf_free_nobuf abrt_strbuf_free_nobuf char* strbuf_free_nobuf(struct strbuf *strbuf); /** * The string content is set to an empty string, erasing any previous * content and leaving its length at 0 characters. */ +#define strbuf_clear abrt_strbuf_clear void strbuf_clear(struct strbuf *strbuf); /** * The current content of the string buffer is extended by adding a * character c at its end. */ +#define strbuf_append_char abrt_strbuf_append_char struct strbuf *strbuf_append_char(struct strbuf *strbuf, char c); /** * The current content of the string buffer is extended by adding a * string str at its end. */ +#define strbuf_append_str abrt_strbuf_append_str struct strbuf *strbuf_append_str(struct strbuf *strbuf, const char *str); @@ -78,6 +84,7 @@ struct strbuf *strbuf_append_str(struct strbuf *strbuf, * The current content of the string buffer is extended by inserting a * string str at its beginning. */ +#define strbuf_prepend_str abrt_strbuf_prepend_str struct strbuf *strbuf_prepend_str(struct strbuf *strbuf, const char *str); @@ -85,6 +92,7 @@ struct strbuf *strbuf_prepend_str(struct strbuf *strbuf, * The current content of the string buffer is extended by adding a * sequence of data formatted as the format argument specifies. */ +#define strbuf_append_strf abrt_strbuf_append_strf struct strbuf *strbuf_append_strf(struct strbuf *strbuf, const char *format, ...); @@ -93,6 +101,7 @@ struct strbuf *strbuf_append_strf(struct strbuf *strbuf, * sequence of data formatted as the format argument specifies at the * buffer beginning. */ +#define strbuf_prepend_strf abrt_strbuf_prepend_strf struct strbuf *strbuf_prepend_strf(struct strbuf *strbuf, const char *format, ...); diff --git a/src/lib/stringops.cpp b/src/lib/stringops.cpp deleted file mode 100644 index 7bc5413f..00000000 --- a/src/lib/stringops.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 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 "abrtlib.h" - -void parse_args(const char *psArgs, vector_string_t& pArgs, int quote) -{ - unsigned ii; - bool inside_quotes = false; - std::string item; - - for (ii = 0; psArgs[ii]; ii++) - { - if (quote != -1) - { - if (psArgs[ii] == quote) - { - inside_quotes = !inside_quotes; - continue; - } - /* inside quotes we support escaping with \x */ - if (inside_quotes && psArgs[ii] == '\\' && psArgs[ii+1]) - { - ii++; - item += psArgs[ii]; - continue; - } - } - if (psArgs[ii] == ',' && !inside_quotes) - { - pArgs.push_back(item); - item.clear(); - continue; - } - item += psArgs[ii]; - } - - if (item.size() != 0) - { - pArgs.push_back(item); - } -} diff --git a/src/lib/xatonum.c b/src/lib/xatonum.c index 3b22071f..1a92db7f 100644 --- a/src/lib/xatonum.c +++ b/src/lib/xatonum.c @@ -28,7 +28,7 @@ inval: error_msg_and_die("invalid number '%s'", numstr); } -int xatoi_u(const char *numstr) +int xatoi_positive(const char *numstr) { unsigned r = xatou(numstr); if (r > (unsigned)INT_MAX) @@ -41,7 +41,7 @@ int xatoi(const char *numstr) unsigned r; if (*numstr != '-') - return xatoi_u(numstr); + return xatoi_positive(numstr); r = xatou(numstr + 1); if (r > (unsigned)INT_MAX + 1) diff --git a/src/lib/xfuncs.c b/src/lib/xfuncs.c index d5166037..f451693a 100644 --- a/src/lib/xfuncs.c +++ b/src/lib/xfuncs.c @@ -26,12 +26,18 @@ /* Turn on nonblocking I/O on a fd */ int ndelay_on(int fd) { - return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); + int flags = fcntl(fd, F_GETFL); + if (flags & O_NONBLOCK) + return 0; + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); } int ndelay_off(int fd) { - return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK); + int flags = fcntl(fd, F_GETFL); + if (!(flags & O_NONBLOCK)) + return 0; + return fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); } int close_on_exec_on(int fd) @@ -39,16 +45,6 @@ int close_on_exec_on(int fd) return fcntl(fd, F_SETFD, FD_CLOEXEC); } -#if 0 /* unused */ -void *xcalloc(size_t nmemb, size_t size) -{ - void *ptr = calloc(nmemb, size); - if (!ptr && nmemb && size) - die_out_of_memory(); - return ptr; -} -#endif - // Die if we can't allocate size bytes of memory. void* xmalloc(size_t size) { @@ -290,7 +286,7 @@ int xopen(const char *pathname, int flags) void xunlink(const char *pathname) { if (unlink(pathname)) - perror_msg_and_die("can't remove file '%s'", pathname); + perror_msg_and_die("Can't remove file '%s'", pathname); } #if 0 //UNUSED diff --git a/src/plugins/CCpp.cpp b/src/plugins/CCpp.cpp deleted file mode 100644 index e6807ea7..00000000 --- a/src/plugins/CCpp.cpp +++ /dev/null @@ -1,280 +0,0 @@ -/* - CCpp.cpp - - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) - 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 <set> -#include "abrtlib.h" -#include "CCpp.h" -#include "abrt_exception.h" -#include "comm_layer_inner.h" - -using namespace std; - -#define CORE_PATTERN_IFACE "/proc/sys/kernel/core_pattern" -#define CORE_PATTERN "|"CCPP_HOOK_PATH" "DEBUG_DUMPS_DIR" %p %s %u %c" -#define CORE_PIPE_LIMIT_IFACE "/proc/sys/kernel/core_pipe_limit" -/* core_pipe_limit specifies how many dump_helpers might run at the same time - * 0 - means unlimited, but it's not guaranteed that /proc/<pid> of crashing - * process will be available for dump_helper - * 4 - means that 4 dump_helpers can run at the same time (the rest will also - * run, but they will fail to read /proc/<pid>) - * This should be enough for ABRT, we can miss some crashes, but what are - * the odds that more processes crash at the same time? - * The value of 4 has been recommended by nhorman. - */ -#define CORE_PIPE_LIMIT "4" - -#define DEBUGINFO_CACHE_DIR LOCALSTATEDIR"/cache/abrt-di" - -CAnalyzerCCpp::CAnalyzerCCpp() : - m_bBacktrace(true), - m_bBacktraceRemotes(false), - m_bMemoryMap(false), - m_bInstallDebugInfo(true), - m_nDebugInfoCacheMB(4000), - m_nGdbTimeoutSec(60) -{} - -/* - this is just a workaround until kernel changes it's behavior - when handling pipes in core_pattern -*/ -#ifdef HOSTILE_KERNEL -#define CORE_SIZE_PATTERN "Max core file size=1:unlimited" -static int isdigit_str(char *str) -{ - do { - if (*str < '0' || *str > '9') - return 0; - } while (*++str); - return 1; -} - -static int set_limits() -{ - DIR *dir = opendir("/proc"); - if (!dir) { - /* this shouldn't fail, but to be safe.. */ - return 1; - } - - struct dirent *ent; - while ((ent = readdir(dir)) != NULL) { - if (!isdigit_str(ent->d_name)) - continue; - - char limits_name[sizeof("/proc/%s/limits") + sizeof(long)*3]; - snprintf(limits_name, sizeof(limits_name), "/proc/%s/limits", ent->d_name); - FILE *limits_fp = fopen(limits_name, "r"); - if (!limits_fp) { - break; - } - - char line[128]; - char *ulimit_c = NULL; - while (1) { - if (fgets(line, sizeof(line)-1, limits_fp) == NULL) - break; - if (strncmp(line, "Max core file size", sizeof("Max core file size")-1) == 0) { - ulimit_c = skip_whitespace(line + sizeof("Max core file size")-1); - skip_non_whitespace(ulimit_c)[0] = '\0'; - break; - } - } - fclose(limits_fp); - if (!ulimit_c || ulimit_c[0] != '0' || ulimit_c[1] != '\0') { - /*process has nonzero ulimit -c, so need to modify it*/ - continue; - } - /* echo -n 'Max core file size=1:unlimited' >/proc/PID/limits */ - int fd = open(limits_name, O_WRONLY); - if (fd >= 0) { - errno = 0; - /*full_*/ - ssize_t n = write(fd, CORE_SIZE_PATTERN, sizeof(CORE_SIZE_PATTERN)-1); - if (n < sizeof(CORE_SIZE_PATTERN)-1) - log("warning: can't write core_size limit to: %s", limits_name); - close(fd); - } - else - { - log("warning: can't open %s for writing", limits_name); - } - } - closedir(dir); - return 0; -} -#endif /* HOSTILE_KERNEL */ - -void CAnalyzerCCpp::Init() -{ - FILE *fp = fopen(CORE_PATTERN_IFACE, "r"); - if (fp) - { - char line[PATH_MAX]; - if (fgets(line, sizeof(line), fp)) - m_sOldCorePattern = line; - fclose(fp); - } - if (m_sOldCorePattern[0] == '|') - { - if (m_sOldCorePattern == CORE_PATTERN) - { - log("warning: %s already contains %s, " - "did abrt daemon crash recently?", - CORE_PATTERN_IFACE, CORE_PATTERN); - /* There is no point in "restoring" CORE_PATTERN_IFACE - * to CORE_PATTERN on exit. Will restore to a default value: - */ - m_sOldCorePattern = "core"; - } else { - log("warning: %s was already set to run a crash analyser (%s), " - "abrt may interfere with it", - CORE_PATTERN_IFACE, CORE_PATTERN); - } - } -#ifdef HOSTILE_KERNEL - if (set_limits() != 0) - log("warning: failed to set core_size limit, ABRT won't detect crashes in" - "compiled apps"); -#endif - - fp = fopen(CORE_PATTERN_IFACE, "w"); - if (fp) - { - fputs(CORE_PATTERN, fp); - fclose(fp); - } - - /* read the core_pipe_limit and change it if it's == 0 - otherwise the abrt-hook-ccpp won't be able to read /proc/<pid> - of the crashing process - */ - fp = fopen(CORE_PIPE_LIMIT_IFACE, "r"); - if (fp) - { - /* we care only about the first char, if it's - * not '0' then we don't have to change it, - * because it means that it's already != 0 - */ - char pipe_limit[2]; - if (!fgets(pipe_limit, sizeof(pipe_limit), fp)) - pipe_limit[0] = '1'; /* not 0 */ - fclose(fp); - if (pipe_limit[0] == '0') - { - fp = fopen(CORE_PIPE_LIMIT_IFACE, "w"); - if (fp) - { - fputs(CORE_PIPE_LIMIT, fp); - fclose(fp); - } - else - { - log("warning: failed to set core_pipe_limit, ABRT won't detect" - "crashes in compiled apps if kernel > 2.6.31"); - } - } - } -} - -void CAnalyzerCCpp::DeInit() -{ - /* no need to restore the core_pipe_limit, because it's only used - when there is s pipe in core_pattern - */ - FILE *fp = fopen(CORE_PATTERN_IFACE, "w"); - if (fp) - { - fputs(m_sOldCorePattern.c_str(), fp); - fclose(fp); - } -} - -void CAnalyzerCCpp::SetSettings(const map_plugin_settings_t& pSettings) -{ - m_pSettings = pSettings; - - map_plugin_settings_t::const_iterator end = pSettings.end(); - map_plugin_settings_t::const_iterator it; - it = pSettings.find("Backtrace"); - if (it != end) - { - m_bBacktrace = string_to_bool(it->second.c_str()); - } - it = pSettings.find("BacktraceRemotes"); - if (it != end) - { - m_bBacktraceRemotes = string_to_bool(it->second.c_str()); - } - it = pSettings.find("MemoryMap"); - if (it != end) - { - m_bMemoryMap = string_to_bool(it->second.c_str()); - } - it = pSettings.find("DebugInfo"); - if (it != end) - { - m_sDebugInfo = it->second; - } - it = pSettings.find("DebugInfoCacheMB"); - if (it != end) - { - m_nDebugInfoCacheMB = xatou(it->second.c_str()); - } - it = pSettings.find("GdbTimeoutSec"); - if (it != end) - { - m_nGdbTimeoutSec = xatoi_u(it->second.c_str()); - } - it = pSettings.find("InstallDebugInfo"); - if (it == end) //compat, remove after 0.0.11 - it = pSettings.find("InstallDebuginfo"); - if (it != end) - { - m_bInstallDebugInfo = string_to_bool(it->second.c_str()); - } - m_sDebugInfoDirs = DEBUGINFO_CACHE_DIR; - it = pSettings.find("ReadonlyLocalDebugInfoDirs"); - if (it != end) - { - m_sDebugInfoDirs += ':'; - m_sDebugInfoDirs += it->second; - } -} - -//ok to delete? -//const map_plugin_settings_t& CAnalyzerCCpp::GetSettings() -//{ -// m_pSettings["MemoryMap"] = m_bMemoryMap ? "yes" : "no"; -// m_pSettings["DebugInfo"] = m_sDebugInfo; -// m_pSettings["DebugInfoCacheMB"] = to_string(m_nDebugInfoCacheMB); -// m_pSettings["InstallDebugInfo"] = m_bInstallDebugInfo ? "yes" : "no"; -// -// return m_pSettings; -//} - -PLUGIN_INFO(ANALYZER, - CAnalyzerCCpp, - "CCpp", - "0.0.1", - _("Analyzes crashes in C/C++ programs"), - "zprikryl@redhat.com", - "https://fedorahosted.org/abrt/wiki", - ""); diff --git a/src/plugins/CCpp.h b/src/plugins/CCpp.h deleted file mode 100644 index e95b4d09..00000000 --- a/src/plugins/CCpp.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - CCpp.h - header file for C/C++ analyzer plugin - - it can get UUID and memory maps from core files - - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) - 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 CCPP_H_ -#define CCPP_H_ - -#include <string> -#include "plugin.h" -#include "analyzer.h" - -class CAnalyzerCCpp : public CAnalyzer -{ - private: - bool m_bBacktrace; - bool m_bBacktraceRemotes; - bool m_bMemoryMap; - bool m_bInstallDebugInfo; - unsigned m_nDebugInfoCacheMB; - unsigned m_nGdbTimeoutSec; - std::string m_sOldCorePattern; - std::string m_sDebugInfo; - std::string m_sDebugInfoDirs; - - public: - CAnalyzerCCpp(); - virtual void Init(); - virtual void DeInit(); - virtual void SetSettings(const map_plugin_settings_t& pSettings); -}; - -#endif /* CCPP */ diff --git a/src/plugins/Kerneloops.conf b/src/plugins/Kerneloops.conf index 67ad07b9..e65e176f 100644 --- a/src/plugins/Kerneloops.conf +++ b/src/plugins/Kerneloops.conf @@ -4,10 +4,4 @@ Enabled = yes # Set to "yes" for compatibility with kerneloops.org tool. InformAllUsers = yes -# Kerneloops Scanner configuration -################################## -SysLogFile = /var/log/messages - -# KerneloopsReporter configuration -################################## SubmitURL = http://submit.kerneloops.org/submitoops.php diff --git a/src/plugins/KerneloopsScanner.cpp b/src/plugins/KerneloopsScanner.cpp deleted file mode 100644 index 93f37e07..00000000 --- a/src/plugins/KerneloopsScanner.cpp +++ /dev/null @@ -1,230 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 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. - - Authors: - Anton Arapov <anton@redhat.com> - Arjan van de Ven <arjan@linux.intel.com> -*/ -#include <syslog.h> -#include <asm/unistd.h> /* __NR_syslog */ -#include <glib.h> -#include "abrtlib.h" -#include "abrt_exception.h" -#include "comm_layer_inner.h" -#include "KerneloopsSysLog.h" -#include "KerneloopsScanner.h" - -// TODO: https://fedorahosted.org/abrt/ticket/78 - -static int scan_dmesg(GList **oopsList) -{ - VERB1 log("Scanning dmesg"); - - /* syslog(3) - read the last len bytes from the log buffer - * (non-destructively), but dont read more than was written - * into the buffer since the last"clear ring buffer" cmd. - * Returns the number of bytes read. - */ - char *buffer = (char*)xzalloc(16*1024); - syscall(__NR_syslog, 3, buffer, 16*1024 - 1); /* always NUL terminated */ - int cnt_FoundOopses = extract_oopses(oopsList, buffer, strlen(buffer)); - free(buffer); - - return cnt_FoundOopses; -} - - -/* "dumpoops" tool uses these two functions too */ -extern "C" { - -int scan_syslog_file(GList **oopsList, const char *filename, time_t *last_changed_p) -{ - VERB1 log("Scanning syslog file '%s'", filename); - - char *buffer; - struct stat statb; - int fd; - int cnt_FoundOopses; - ssize_t sz; - fd = open(filename, O_RDONLY); - if (fd < 0) - return 0; - statb.st_size = 0; /* paranoia */ - if (fstat(fd, &statb) != 0 || statb.st_size < 1) - { - close(fd); - return 0; - } - - if (last_changed_p != NULL) - { - if (*last_changed_p == statb.st_mtime) - { - VERB1 log("Syslog file '%s' hasn't changed since last scan, skipping", filename); - close(fd); - return 0; - } - *last_changed_p = statb.st_mtime; - } - - /* - * In theory we have a race here, since someone could spew - * to /var/log/messages before we read it in... we try to - * deal with it by reading at most 10kbytes extra. If there's - * more than that.. any oops will be in dmesg anyway. - * Do not try to allocate an absurd amount of memory; ignore - * older log messages because they are unlikely to have - * sufficiently recent data to be useful. 32MB is more - * than enough; it's not worth looping through more log - * if the log is larger than that. - */ - sz = statb.st_size + 10*1024; - if (statb.st_size > (32*1024*1024 - 10*1024)) - { - xlseek(fd, statb.st_size - (32*1024*1024 - 10*1024), SEEK_SET); - sz = 32*1024*1024; - } - buffer = (char*)xzalloc(sz); - sz = full_read(fd, buffer, sz); - close(fd); - - cnt_FoundOopses = 0; - if (sz > 0) - cnt_FoundOopses = extract_oopses(oopsList, buffer, sz); - free(buffer); - - return cnt_FoundOopses; -} - -/* returns number of errors */ -int save_oops_to_debug_dump(GList **oopsList) -{ - unsigned countdown = 16; /* do not report hundreds of oopses */ - unsigned idx = g_list_length(*oopsList); - time_t t = time(NULL); - pid_t my_pid = getpid(); - - VERB1 log("Saving %u oopses as crash dump dirs", idx >= countdown ? countdown-1 : idx); - - char *tainted_str = NULL; - /* once tainted flag is set to 1, only restart can reset the flag to 0 */ - FILE *tainted_fd = fopen("/proc/sys/kernel/tainted", "r"); - if (tainted_fd) - { - tainted_str = xmalloc_fgetline(tainted_fd); - fclose(tainted_fd); - } - else - error_msg("/proc/sys/kernel/tainted does not exist"); - - int errors = 0; - - while (idx != 0 && --countdown != 0) - { - char path[sizeof(DEBUG_DUMPS_DIR"/kerneloops-%lu-%lu-%lu") + 3 * sizeof(long)*3]; - sprintf(path, DEBUG_DUMPS_DIR"/kerneloops-%lu-%lu-%lu", (long)t, (long)my_pid, (long)idx); - - char *first_line = (char*)g_list_nth_data(*oopsList,--idx); - char *second_line = (char*)strchr(first_line, '\n'); /* never NULL */ - *second_line++ = '\0'; - - struct dump_dir *dd = dd_create(path, /*uid:*/ 0); - if (dd) - { - dd_save_text(dd, FILENAME_ANALYZER, "Kerneloops"); - dd_save_text(dd, FILENAME_EXECUTABLE, "kernel"); - dd_save_text(dd, FILENAME_KERNEL, first_line); - dd_save_text(dd, FILENAME_CMDLINE, "not_applicable"); - dd_save_text(dd, FILENAME_BACKTRACE, second_line); - /* Optional, makes generated bz more informative */ - strchrnul(second_line, '\n')[0] = '\0'; - dd_save_text(dd, FILENAME_REASON, second_line); - - if (tainted_str && tainted_str[0] != '0') - dd_save_text(dd, FILENAME_TAINTED, tainted_str); - - free(tainted_str); - dd_close(dd); - } - else - errors++; - } - - return errors; -} - -} /* extern "C" */ - - -CKerneloopsScanner::CKerneloopsScanner() -{ - int cnt_FoundOopses; - m_syslog_last_change = 0; - - /* Scan dmesg, on first call only */ - GList *oopsList = NULL; - cnt_FoundOopses = scan_dmesg(&oopsList); - if (cnt_FoundOopses > 0) - { - int errors = save_oops_to_debug_dump(&oopsList); - if (errors > 0) - log("%d errors while dumping oopses", errors); - } -} - -void CKerneloopsScanner::Run(const char *pActionDir, const char *pArgs, int force) -{ - const char *syslog_file = "/var/log/messages"; - map_plugin_settings_t::const_iterator it = m_pSettings.find("SysLogFile"); - if (it != m_pSettings.end()) - syslog_file = it->second.c_str(); - - GList *oopsList = NULL; - int cnt_FoundOopses = scan_syslog_file(&oopsList, syslog_file, &m_syslog_last_change); - if (cnt_FoundOopses > 0) - { - int errors = save_oops_to_debug_dump(&oopsList); - if (errors > 0) - log("%d errors while dumping oopses", errors); - /* - * This marker in syslog file prevents us from - * re-parsing old oopses (any oops before it is - * ignored by scan_syslog_file()). The only problem - * is that we can't be sure here that syslog_file - * is the file where syslog(xxx) stuff ends up. - */ - openlog("abrt", 0, LOG_KERN); - syslog(LOG_WARNING, - "Kerneloops: Reported %u kernel oopses to Abrt", - cnt_FoundOopses); - closelog(); - } - - for (GList *li = oopsList; li != NULL; li = g_list_next(li)) - free((char*)li->data); - g_list_free(oopsList); -} - -PLUGIN_INFO(ACTION, - CKerneloopsScanner, - "KerneloopsScanner", - "0.0.1", - _("Periodically scans for and saves kernel oopses"), - "anton@redhat.com", - "http://people.redhat.com/aarapov", - ""); diff --git a/src/plugins/KerneloopsScanner.h b/src/plugins/KerneloopsScanner.h deleted file mode 100644 index 2bddb0f4..00000000 --- a/src/plugins/KerneloopsScanner.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2007, Intel Corporation - * Copyright 2009, Red Hat Inc. - * - * This file is part of Abrt. - * - * This program file 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; version 2 of the License. - * - * 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 in a file named COPYING; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA - * - * Authors: - * Anton Arapov <anton@redhat.com> - * Arjan van de Ven <arjan@linux.intel.com> - */ -#ifndef KERNELOOPSSCANNER_H_ -#define KERNELOOPSSCANNER_H_ - -#include "abrt_types.h" -#include "plugin.h" -#include "action.h" - -class CKerneloopsScanner : public CAction -{ - private: - time_t m_syslog_last_change; - public: - CKerneloopsScanner(); - virtual void Run(const char *pActionDir, const char *pArgs, int force); -}; - -#endif diff --git a/src/plugins/KerneloopsSysLog.cpp b/src/plugins/KerneloopsSysLog.cpp deleted file mode 100644 index 68f309bc..00000000 --- a/src/plugins/KerneloopsSysLog.cpp +++ /dev/null @@ -1,383 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - Copyright (C) 2010 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. - - Authors: - Anton Arapov <anton@redhat.com> - Arjan van de Ven <arjan@linux.intel.com> - */ -#include "abrtlib.h" -#include "KerneloopsSysLog.h" -#include <glib.h> - -static void queue_oops(GList **vec, const char *data, const char *version) -{ - char *ver_data = xasprintf("%s\n%s", version, data); - *vec = g_list_append(*vec, ver_data); -} - -/* - * extract_version tries to find the kernel version in given data - */ -static char *extract_version(const char *linepointer) -{ - if (strstr(linepointer, "Pid") - || strstr(linepointer, "comm") - || strstr(linepointer, "CPU") - || strstr(linepointer, "REGS") - || strstr(linepointer, "EFLAGS") - ) { - char* start; - char* end; - - start = strstr((char*)linepointer, "2.6."); - if (start) - { - end = strchr(start, ')'); - if (!end) - end = strchrnul(start, ' '); - return xstrndup(start, end-start); - } - } - - return NULL; -} - -/* - * extract_oops tries to find oops signatures in a log - */ -struct line_info { - char *ptr; - char level; -}; - -static int record_oops(GList **oopses, struct line_info* lines_info, int oopsstart, int oopsend) -{ - int q; - int len; - char *oops; - char *version; - - len = 2; - for (q = oopsstart; q <= oopsend; q++) - len += strlen(lines_info[q].ptr) + 1; - - oops = (char*)xzalloc(len); - - version = NULL; - for (q = oopsstart; q <= oopsend; q++) - { - if (!version) - version = extract_version(lines_info[q].ptr); - - if (lines_info[q].ptr[0]) - { - strcat(oops, lines_info[q].ptr); - strcat(oops, "\n"); - } - } - int rv = 1; - /* too short oopses are invalid */ - if (strlen(oops) > 100) - queue_oops(oopses, oops, version ? version : "undefined"); - else - { - VERB3 log("Dropped oops: too short"); - rv = 0; - } - free(oops); - free(version); - return rv; -} -#define REALLOC_CHUNK 1000 -int extract_oopses(GList **oopses, char *buffer, size_t buflen) -{ - char *c; - int linecount = 0; - int lines_info_alloc = 0; - struct line_info *lines_info = NULL; - - /* Split buffer into lines */ - - if (buflen != 0) - buffer[buflen - 1] = '\n'; /* the buffer usually ends with \n, but let's make sure */ - c = buffer; - while (c < buffer + buflen) - { - char linelevel; - char *c9; - char *colon; - - c9 = (char*)memchr(c, '\n', buffer + buflen - c); /* a \n will always be found */ - assert(c9); - *c9 = '\0'; /* turn the \n into a string termination */ - if (c9 == c) - goto next_line; - - /* Is it a syslog file (/var/log/messages or similar)? - * Even though _usually_ it looks like "Nov 19 12:34:38 localhost kernel: xxx", - * some users run syslog in non-C locale: - * "2010-02-22T09:24:08.156534-08:00 gnu-4 gnome-session[2048]: blah blah" - * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ !!! - * We detect it by checking for N:NN:NN pattern in first 15 chars - * (and this still is not good enough... false positive: "pci 0000:15:00.0: PME# disabled") - */ - colon = strchr(c, ':'); - if (colon && colon > c && colon < c + 15 - && isdigit(colon[-1]) /* N:... */ - && isdigit(colon[1]) /* ...N:NN:... */ - && isdigit(colon[2]) - && colon[3] == ':' - && isdigit(colon[4]) /* ...N:NN:NN... */ - && isdigit(colon[5]) - ) { - /* It's syslog file, not a bare dmesg */ - - /* Skip non-kernel lines */ - char *kernel_str = strstr(c, "kernel: "); - if (kernel_str == NULL) - { - /* if we see our own marker: - * "hostname abrt: Kerneloops: Reported 1 kernel oopses to Abrt" - * we know we submitted everything upto here already */ - if (strstr(c, "abrt:") && strstr(c, "Abrt")) - { - VERB3 log("Found our marker at line %d, restarting line count from 0", linecount); - linecount = 0; - lines_info_alloc = 0; - free(lines_info); - lines_info = NULL; - } - goto next_line; - } - c = kernel_str + sizeof("kernel: ")-1; - } - - linelevel = 0; - /* store and remove kernel log level */ - if (*c == '<' && c[1] && c[2] == '>') - { - linelevel = c[1]; - c += 3; - } - /* remove jiffies time stamp counter if present */ - if (*c == '[') - { - char *c2 = strchr(c, '.'); - char *c3 = strchr(c, ']'); - if (c2 && c3 && (c2 < c3) && (c3-c) < 14 && (c2-c) < 8) - { - c = c3 + 1; - if (*c == ' ') - c++; - } - } - if (linecount >= lines_info_alloc) - { - lines_info_alloc += REALLOC_CHUNK; - lines_info = (line_info*)xrealloc(lines_info, - lines_info_alloc * sizeof(struct line_info)); - } - lines_info[linecount].ptr = c; - lines_info[linecount].level = linelevel; - linecount++; -next_line: - c = c9 + 1; - } - - /* Analyze lines */ - - int i; - char prevlevel = 0; - int oopsstart = -1; - int inbacktrace = 0; - int oopsesfound = 0; - - i = 0; - while (i < linecount) - { - char *curline = lines_info[i].ptr; - - if (curline == NULL) - { - i++; - continue; - } - while (*curline == ' ') - curline++; - - if (oopsstart < 0) - { - /* find start-of-oops markers */ - if (strstr(curline, "general protection fault:")) - oopsstart = i; - else if (strstr(curline, "BUG:")) - oopsstart = i; - else if (strstr(curline, "kernel BUG at")) - oopsstart = i; - else if (strstr(curline, "do_IRQ: stack overflow:")) - oopsstart = i; - else if (strstr(curline, "RTNL: assertion failed")) - oopsstart = i; - else if (strstr(curline, "Eeek! page_mapcount(page) went negative!")) - oopsstart = i; - else if (strstr(curline, "near stack overflow (cur:")) - oopsstart = i; - else if (strstr(curline, "double fault:")) - oopsstart = i; - else if (strstr(curline, "Badness at")) - oopsstart = i; - else if (strstr(curline, "NETDEV WATCHDOG")) - oopsstart = i; - else if (strstr(curline, "WARNING: at ")) /* WARN_ON() generated message */ - oopsstart = i; - else if (strstr(curline, "Unable to handle kernel")) - oopsstart = i; - else if (strstr(curline, "sysctl table check failed")) - oopsstart = i; - else if (strstr(curline, "INFO: possible recursive locking detected")) - oopsstart = i; - // Not needed: "--[ cut here ]--" is always followed - // by "Badness at", "kernel BUG at", or "WARNING: at" string - //else if (strstr(curline, "------------[ cut here ]------------")) - // oopsstart = i; - else if (strstr(curline, "list_del corruption.")) - oopsstart = i; - else if (strstr(curline, "list_add corruption.")) - oopsstart = i; - if (strstr(curline, "Oops:") && i >= 3) - oopsstart = i-3; - - if (oopsstart >= 0) - { - /* debug information */ - VERB3 { - log("Found oops at line %d: '%s'", oopsstart, lines_info[oopsstart].ptr); - if (oopsstart != i) - log("Trigger line is %d: '%s'", i, c); - } - /* try to find the end marker */ - int i2 = i + 1; - while (i2 < linecount && i2 < (i+50)) - { - if (strstr(lines_info[i2].ptr, "---[ end trace")) - { - inbacktrace = 1; - i = i2; - break; - } - i2++; - } - } - } - - /* Are we entering a call trace part? */ - /* a call trace starts with "Call Trace:" or with the " [<.......>] function+0xFF/0xAA" pattern */ - if (oopsstart >= 0 && !inbacktrace) - { - if (strstr(curline, "Call Trace:")) - inbacktrace = 1; - else - if (strnlen(curline, 9) > 8 - && curline[0] == '[' && curline[1] == '<' - && strstr(curline, ">]") - && strstr(curline, "+0x") - && strstr(curline, "/0x") - ) { - inbacktrace = 1; - } - } - - /* Are we at the end of an oops? */ - else if (oopsstart >= 0 && inbacktrace) - { - int oopsend = INT_MAX; - - /* line needs to start with " [" or have "] [" if it is still a call trace */ - /* example: "[<ffffffffa006c156>] radeon_get_ring_head+0x16/0x41 [radeon]" */ - if (curline[0] != '[' - && !strstr(curline, "] [") - && !strstr(curline, "--- Exception") - && !strstr(curline, "LR =") - && !strstr(curline, "<#DF>") - && !strstr(curline, "<IRQ>") - && !strstr(curline, "<EOI>") - && !strstr(curline, "<<EOE>>") - && strncmp(curline, "Code: ", 6) != 0 - && strncmp(curline, "RIP ", 4) != 0 - && strncmp(curline, "RSP ", 4) != 0 - ) { - oopsend = i-1; /* not a call trace line */ - } - /* oops lines are always more than 8 chars long */ - else if (strnlen(curline, 8) < 8) - oopsend = i-1; - /* single oopses are of the same loglevel */ - else if (lines_info[i].level != prevlevel) - oopsend = i-1; - else if (strstr(curline, "Instruction dump:")) - oopsend = i; - /* if a new oops starts, this one has ended */ - else if (strstr(curline, "WARNING: at ") && oopsstart != i) /* WARN_ON() generated message */ - oopsend = i-1; - else if (strstr(curline, "Unable to handle") && oopsstart != i) - oopsend = i-1; - /* kernel end-of-oops marker (not including marker itself) */ - else if (strstr(curline, "---[ end trace")) - oopsend = i-1; - - if (oopsend <= i) - { - VERB3 log("End of oops at line %d (%d): '%s'", oopsend, i, lines_info[oopsend].ptr); - if (record_oops(oopses, lines_info, oopsstart, oopsend)) - oopsesfound++; - oopsstart = -1; - inbacktrace = 0; - } - } - - prevlevel = lines_info[i].level; - i++; - - if (oopsstart >= 0) - { - /* Do we have a suspiciously long oops? Cancel it */ - if (i-oopsstart > 60) - { - inbacktrace = 0; - oopsstart = -1; - VERB3 log("Dropped oops, too long"); - continue; - } - if (!inbacktrace && i-oopsstart > 40) - { - /*inbacktrace = 0; - already is */ - oopsstart = -1; - VERB3 log("Dropped oops, too long"); - continue; - } - } - } /* while (i < linecount) */ - - /* process last oops if we have one */ - if (oopsstart >= 0 && inbacktrace) - { - int oopsend = i-1; - VERB3 log("End of oops at line %d (end of file): '%s'", oopsend, lines_info[oopsend].ptr); - if (record_oops(oopses, lines_info, oopsstart, oopsend)) - oopsesfound++; - } - - free(lines_info); - return oopsesfound; -} diff --git a/src/plugins/KerneloopsSysLog.h b/src/plugins/KerneloopsSysLog.h deleted file mode 100644 index d8b4d32b..00000000 --- a/src/plugins/KerneloopsSysLog.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2007, Intel Corporation - * Copyright 2009, Red Hat Inc. - * - * This file is part of Abrt. - * - * This program file 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; version 2 of the License. - * - * 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 in a file named COPYING; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA - * - * Authors: - * Anton Arapov <anton@redhat.com> - * Arjan van de Ven <arjan@linux.intel.com> - */ - -#ifndef __INCLUDE_GUARD_KERNELOOPSSYSLOG_H_ -#define __INCLUDE_GUARD_KERNELOOPSSYSLOG_H_ - -#include "abrt_types.h" -#include <glib.h> - -int extract_oopses(GList **oopses, char *buffer, size_t buflen); - -#endif diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index 71a2fd6f..207fc860 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -1,13 +1,22 @@ -INC_PATH=$(srcdir)/../include -UTILS_PATH=$(srcdir)/../lib -AM_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) -pluginslibdir=$(PLUGINS_LIB_DIR) -libexec_SCRIPTS = \ +pluginslibdir = $(PLUGINS_LIB_DIR) + +bin_SCRIPTS = \ abrt-action-install-debuginfo.py -pluginslib_LTLIBRARIES = \ - libCCpp.la \ - libKerneloopsScanner.la +bin_PROGRAMS = \ + abrt-dump-oops \ + abrt-action-analyze-c \ + abrt-action-analyze-python \ + abrt-action-analyze-oops \ + abrt-action-generate-backtrace \ + abrt-action-bugzilla \ + abrt-action-rhtsupport \ + abrt-action-kerneloops \ + abrt-action-upload \ + abrt-action-mailx \ + abrt-action-print \ + abrt-action-install-debuginfo \ + abrt-retrace-client dist_pluginslib_DATA = \ Logger.glade \ @@ -18,6 +27,7 @@ dist_pluginslib_DATA = \ KerneloopsReporter.glade pluginsconfdir = $(PLUGINS_CONF_DIR) + dist_pluginsconf_DATA = \ CCpp.conf \ Python.conf \ @@ -28,9 +38,13 @@ dist_pluginsconf_DATA = \ RHTSupport.conf \ Upload.conf +eventsconfdir = $(EVENTS_CONF_DIR) + +dist_eventsconf_DATA = \ + ccpp_events.conf + man_MANS = \ abrt-Bugzilla.7 \ - abrt-KerneloopsScanner.7 \ abrt-KerneloopsReporter.7 \ abrt-Logger.7 \ abrt-Mailx.7 \ @@ -48,39 +62,29 @@ install-data-hook: $(DESTDIR)/$(DEBUG_INFO_DIR) sed 's: = /var/: = $(localstatedir)/:g' -i \ $(DESTDIR)$(sysconfdir)/abrt/plugins/Logger.conf -# CCpp -libCCpp_la_SOURCES = CCpp.cpp CCpp.h -libCCpp_la_LDFLAGS = -avoid-version -libCCpp_la_CPPFLAGS = -Wall -Werror \ - -I$(INC_PATH) \ - -I$(UTILS_PATH) \ - -DCCPP_HOOK_PATH=\"${libexecdir}/abrt-hook-ccpp\" \ +abrt_dump_oops_SOURCES = \ + abrt-dump-oops.c +abrt_dump_oops_CPPFLAGS = \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ + -I$(srcdir)/../lib \ + -DBIN_DIR=\"$(bindir)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' \ -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ - -DLOCALSTATEDIR='"$(localstatedir)"' -# -DHOSTILE_KERNEL - -# KerneloopsScanner -libKerneloopsScanner_la_SOURCES = KerneloopsScanner.cpp KerneloopsScanner.h KerneloopsSysLog.cpp KerneloopsSysLog.h -libKerneloopsScanner_la_LDFLAGS = -avoid-version $(GLIB_LIBS) -libKerneloopsScanner_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" $(GLIB_CFLAGS) - -libexec_PROGRAMS = \ - abrt-action-analyze-c \ - abrt-action-analyze-python \ - abrt-action-analyze-oops \ - abrt-action-generate-backtrace \ - abrt-action-bugzilla \ - abrt-action-rhtsupport \ - abrt-action-kerneloops \ - abrt-action-upload \ - abrt-action-mailx \ - abrt-action-print \ - abrt-retrace-client + -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + $(GLIB_CFLAGS) \ + -D_GNU_SOURCE \ + -Wall -Werror +abrt_dump_oops_LDADD = \ + ../lib/libreport.la abrt_action_analyze_c_SOURCES = \ abrt-action-analyze-c.c abrt_action_analyze_c_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DBIN_DIR=\"$(bindir)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ @@ -90,15 +94,16 @@ abrt_action_analyze_c_CPPFLAGS = \ -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + $(GLIB_CFLAGS) \ -D_GNU_SOURCE \ -Wall -Werror abrt_action_analyze_c_LDADD = \ - ../lib/libabrt.la + ../lib/libreport.la abrt_action_analyze_python_SOURCES = \ abrt-action-analyze-python.c abrt_action_analyze_python_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DBIN_DIR=\"$(bindir)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ @@ -108,15 +113,16 @@ abrt_action_analyze_python_CPPFLAGS = \ -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + $(GLIB_CFLAGS) \ -D_GNU_SOURCE \ -Wall -Werror abrt_action_analyze_python_LDADD = \ - ../lib/libabrt.la + ../lib/libreport.la abrt_action_analyze_oops_SOURCES = \ abrt-action-analyze-oops.c abrt_action_analyze_oops_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DBIN_DIR=\"$(bindir)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ @@ -126,15 +132,16 @@ abrt_action_analyze_oops_CPPFLAGS = \ -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + $(GLIB_CFLAGS) \ -D_GNU_SOURCE \ -Wall -Werror abrt_action_analyze_oops_LDADD = \ - ../lib/libabrt.la + ../lib/libreport.la abrt_action_generate_backtrace_SOURCES = \ abrt-action-generate-backtrace.c abrt_action_generate_backtrace_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DBIN_DIR=\"$(bindir)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ @@ -144,16 +151,17 @@ abrt_action_generate_backtrace_CPPFLAGS = \ -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + $(GLIB_CFLAGS) \ -D_GNU_SOURCE \ -Wall -Werror abrt_action_generate_backtrace_LDADD = \ - ../lib/libabrt.la \ + ../lib/libreport.la \ ../btparser/libbtparser.la abrt_action_bugzilla_SOURCES = \ abrt-action-bugzilla.cpp abrt_action_bugzilla_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DBIN_DIR=\"$(bindir)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ @@ -169,13 +177,13 @@ abrt_action_bugzilla_CPPFLAGS = \ abrt_action_bugzilla_LDADD = \ $(GLIB_LIBS) \ ../lib/libabrt_web.la \ - ../lib/libabrt.la + ../lib/libreport.la abrt_action_rhtsupport_SOURCES = \ abrt_rh_support.h abrt_rh_support.c \ - abrt-action-rhtsupport.cpp + abrt-action-rhtsupport.c abrt_action_rhtsupport_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DBIN_DIR=\"$(bindir)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ @@ -193,12 +201,12 @@ abrt_action_rhtsupport_LDADD = \ $(GLIB_LIBS) \ $(XMLRPC_LIBS) $(XMLRPC_CLIENT_LIBS) \ ../lib/libabrt_web.la \ - ../lib/libabrt.la + ../lib/libreport.la abrt_action_upload_SOURCES = \ - abrt-action-upload.cpp + abrt-action-upload.c abrt_action_upload_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DBIN_DIR=\"$(bindir)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ @@ -213,16 +221,15 @@ abrt_action_upload_CPPFLAGS = \ -D_GNU_SOURCE \ -Wall -Werror abrt_action_upload_LDFLAGS = -ltar -# Needs libABRTdUtils only for LoadPluginSettings abrt_action_upload_LDADD = \ $(GLIB_LIBS) \ $(CURL_LIBS) \ - ../lib/libabrt.la + ../lib/libreport.la abrt_action_kerneloops_SOURCES = \ - abrt-action-kerneloops.cpp + abrt-action-kerneloops.c abrt_action_kerneloops_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DBIN_DIR=\"$(bindir)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ @@ -235,15 +242,14 @@ abrt_action_kerneloops_CPPFLAGS = \ $(GLIB_CFLAGS) \ -D_GNU_SOURCE \ -Wall -Werror -# libABRTdUtils is used only because of LoadPluginSettings: abrt_action_kerneloops_LDADD = \ ../lib/libabrt_web.la \ - ../lib/libabrt.la + ../lib/libreport.la abrt_action_mailx_SOURCES = \ - abrt-action-mailx.cpp + abrt-action-mailx.c abrt_action_mailx_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DBIN_DIR=\"$(bindir)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ @@ -257,12 +263,12 @@ abrt_action_mailx_CPPFLAGS = \ -D_GNU_SOURCE \ -Wall -Werror abrt_action_mailx_LDADD = \ - ../lib/libabrt.la + ../lib/libreport.la abrt_action_print_SOURCES = \ - abrt-action-print.cpp + abrt-action-print.c abrt_action_print_CPPFLAGS = \ - -I$(srcdir)/../include \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ -I$(srcdir)/../lib \ -DBIN_DIR=\"$(bindir)\" \ -DVAR_RUN=\"$(VAR_RUN)\" \ @@ -275,9 +281,17 @@ abrt_action_print_CPPFLAGS = \ $(GLIB_CFLAGS) \ -D_GNU_SOURCE \ -Wall -Werror -# libABRTdUtils is used only because of make_description_logger: abrt_action_print_LDADD = \ - ../lib/libabrt.la + ../lib/libreport.la + +abrt_action_install_debuginfo_SOURCES = \ + abrt-action-install-debuginfo.c +abrt_action_install_debuginfo_CPPFLAGS = \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ + -I$(srcdir)/../lib \ + -D_GNU_SOURCE \ + -Wall +abrt_action_install_debuginfo_LDADD = abrt_retrace_client_SOURCES = \ abrt-retrace-client.c diff --git a/src/plugins/abrt-KerneloopsScanner.7 b/src/plugins/abrt-KerneloopsScanner.7 deleted file mode 100644 index ff094847..00000000 --- a/src/plugins/abrt-KerneloopsScanner.7 +++ /dev/null @@ -1,46 +0,0 @@ -.TH abrt "7" "1 Jun 2009" "" -.SH NAME -KerneloopsScanner plugin for abrt(8) -.SH DESCRIPTION -.P -.I abrt -is a daemon that watches for application crashes. When a crash occurs, -it collects the crash data and takes action according to -its configuration. This manual page describes the \fIKerneloopsScanner\fP -plugin for \fIabrt\fP. -.P -This plugin reads the system log file (default /var/log/messages) -and stores the kernel oops crashes, which were not already -reported, to abrt's debug dump directory. -.P -To distinguish between new crashes and crashes -that were already reported, the plugin makes its own entry -in the log file, which acts as a separator. -.SH INVOCATION -The plugin is invoked in the \fIabrt.conf\fP configuration file. -No parameters are necessary. -.SH CONFIGURATION -The \fIKerneloopsScanner.conf\fP configuration file contains one entry: -.SS SysLogFile -The file to scan. The default is -.br -SysLogFile = /var/log/messages -.SH EXAMPLES -.P -This is a snippet from the \fIabrt.conf\fP configuration file. -Every 10 seconds look if there were any kernel crashes: -.P -[common] -.br -ActionsAndReporters = Kerneloops, KerneloopsScanner -.br -[cron] -.br -10 = KerneloopsScanner -.SH "SEE ALSO" -.IR abrt (8), -.IR abrt.conf (5), -.IR abrt-plugins (7) -.SH AUTHOR -Written by Anton Arapov <anton@redhat.com>. Manual -page by Daniel Novotny <dnovotny@redhat.com>. diff --git a/src/plugins/abrt-action-analyze-c.c b/src/plugins/abrt-action-analyze-c.c index 6d8ac1b4..5def9aa1 100644 --- a/src/plugins/abrt-action-analyze-c.c +++ b/src/plugins/abrt-action-analyze-c.c @@ -52,7 +52,7 @@ static char *run_unstrip_n(const char *dump_dir_name, unsigned timeout_sec) return NULL; char *uid_str = dd_load_text(dd, FILENAME_UID); dd_close(dd); - unsigned uid = xatoi_u(uid_str); + unsigned uid = xatoi_positive(uid_str); free(uid_str); int flags = EXECFLG_INPUT_NUL | EXECFLG_OUTPUT | EXECFLG_SETGUID | EXECFLG_SETSID | EXECFLG_QUIET; @@ -104,7 +104,7 @@ static char *run_unstrip_n(const char *dump_dir_name, unsigned timeout_sec) if (status != 0) { - /* unstrip didnt exit with exitcode 0 */ + /* unstrip didnt exit with exit code 0 */ strbuf_free(buf_out); return NULL; } @@ -149,28 +149,28 @@ int main(int argc, char **argv) if (env_verbose) g_verbose = atoi(env_verbose); + const char *dump_dir_name = "."; + /* Can't keep these strings/structs static: _() doesn't support that */ const char *program_usage_string = _( - PROGNAME" [-vs] -d DIR\n\n" + PROGNAME" [-v] -d DIR\n\n" "Calculates and saves UUID of coredumps" - ); - const char *dump_dir_name = "."; + ); enum { OPT_v = 1 << 0, OPT_d = 1 << 1, - OPT_s = 1 << 2, +// OPT_s = 1 << 2, }; /* Keep enum above and order of options below in sync! */ struct options program_options[] = { OPT__VERBOSE(&g_verbose), OPT_STRING('d', NULL, &dump_dir_name, "DIR", _("Crash dump directory")), - OPT_BOOL( 's', NULL, NULL, _("Log to syslog" )), +// OPT_BOOL( 's', NULL, NULL, _("Log to syslog" )), OPT_END() }; /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); - msg_prefix = PROGNAME; //Maybe we will want this... later // if (opts & OPT_s) diff --git a/src/plugins/abrt-action-analyze-oops.c b/src/plugins/abrt-action-analyze-oops.c index 24538641..0072c71d 100644 --- a/src/plugins/abrt-action-analyze-oops.c +++ b/src/plugins/abrt-action-analyze-oops.c @@ -126,28 +126,28 @@ int main(int argc, char **argv) if (env_verbose) g_verbose = atoi(env_verbose); + const char *dump_dir_name = "."; + /* Can't keep these strings/structs static: _() doesn't support that */ const char *program_usage_string = _( PROGNAME" [-vs] -d DIR\n\n" "Calculates and saves UUID and DUPHASH of oops crash dumps" ); - const char *dump_dir_name = "."; enum { OPT_v = 1 << 0, OPT_d = 1 << 1, - OPT_s = 1 << 2, +// OPT_s = 1 << 2, }; /* Keep enum above and order of options below in sync! */ struct options program_options[] = { OPT__VERBOSE(&g_verbose), OPT_STRING('d', NULL, &dump_dir_name, "DIR", _("Crash dump directory")), - OPT_BOOL( 's', NULL, NULL, _("Log to syslog" )), +// OPT_BOOL( 's', NULL, NULL, _("Log to syslog" )), OPT_END() }; /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); - msg_prefix = PROGNAME; //Maybe we will want this... later // if (opts & OPT_s) diff --git a/src/plugins/abrt-action-analyze-python.c b/src/plugins/abrt-action-analyze-python.c index 9fe563db..feffb439 100644 --- a/src/plugins/abrt-action-analyze-python.c +++ b/src/plugins/abrt-action-analyze-python.c @@ -31,28 +31,28 @@ int main(int argc, char **argv) if (env_verbose) g_verbose = atoi(env_verbose); + const char *dump_dir_name = "."; + /* Can't keep these strings/structs static: _() doesn't support that */ const char *program_usage_string = _( - PROGNAME" [-vs] -d DIR\n\n" + PROGNAME" [-v] -d DIR\n\n" "Calculates and saves UUID and DUPHASH of python crash dumps" - ); - const char *dump_dir_name = "."; + ); enum { OPT_v = 1 << 0, OPT_d = 1 << 1, - OPT_s = 1 << 2, +// OPT_s = 1 << 2, }; /* Keep enum above and order of options below in sync! */ struct options program_options[] = { OPT__VERBOSE(&g_verbose), OPT_STRING('d', NULL, &dump_dir_name, "DIR", _("Crash dump directory")), - OPT_BOOL( 's', NULL, NULL, _("Log to syslog" )), +// OPT_BOOL( 's', NULL, NULL, _("Log to syslog" )), OPT_END() }; /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); - msg_prefix = PROGNAME; //Maybe we will want this... later // if (opts & OPT_s) diff --git a/src/plugins/abrt-action-bugzilla.cpp b/src/plugins/abrt-action-bugzilla.cpp index b396e453..2aa32b75 100644 --- a/src/plugins/abrt-action-bugzilla.cpp +++ b/src/plugins/abrt-action-bugzilla.cpp @@ -18,8 +18,10 @@ */ #include "abrtlib.h" #include "abrt_xmlrpc.h" -#include "abrt_crash_dump.h" -#include "abrt_exception.h" +#include "abrt_crash_data.h" +#include "parse_options.h" + +#define PROGNAME "abrt-action-bugzilla" #define XML_RPC_SUFFIX "/xmlrpc.cgi" #define MAX_HOPS 5 @@ -112,8 +114,8 @@ struct ctx: public abrt_xmlrpc_conn { xmlrpc_int32 get_bug_dup_id(xmlrpc_value* result_xml); void get_bug_cc(xmlrpc_value* result_xml, struct bug_info* bz); int add_plus_one_cc(xmlrpc_int32 bug_id, const char* login); - xmlrpc_int32 new_bug(const map_crash_data_t& pCrashData, int depend_on_bugno); - int add_attachments(const char* bug_id_str, const map_crash_data_t& pCrashData); + xmlrpc_int32 new_bug(crash_data_t *crash_data, int depend_on_bugno); + int add_attachments(const char* bug_id_str, crash_data_t *crash_data); int get_bug_info(struct bug_info* bz, xmlrpc_int32 bug_id); int add_comment(xmlrpc_int32 bug_id, const char* comment, bool is_private); @@ -326,7 +328,7 @@ xmlrpc_value* ctx::call_quicksearch_duphash(const char* component, const char* r { char *product = NULL; char *version = NULL; - parse_release(release, &product, &version); + parse_release_for_bz(release, &product, &version); query = xasprintf("ALL component:\"%s\" whiteboard:\"%s\" product:\"%s\"", component, duphash, product ); @@ -442,17 +444,17 @@ static const char *tainted_string(unsigned tainted) return taint_warnings[idx]; } -xmlrpc_int32 ctx::new_bug(const map_crash_data_t& pCrashData, int depend_on_bugno) +xmlrpc_int32 ctx::new_bug(crash_data_t *crash_data, int depend_on_bugno) { - const char *package = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_PACKAGE); - const char *component = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_COMPONENT); - const char *release = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_RELEASE); - const char *arch = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_ARCHITECTURE); - const char *duphash = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_DUPHASH); - const char *reason = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_REASON); - const char *function = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_CRASH_FUNCTION); - const char *analyzer = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_ANALYZER); - const char *tainted_str = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_TAINTED); + const char *package = get_crash_item_content_or_NULL(crash_data, FILENAME_PACKAGE); + const char *component = get_crash_item_content_or_NULL(crash_data, FILENAME_COMPONENT); + const char *release = get_crash_item_content_or_NULL(crash_data, FILENAME_OS_RELEASE); + const char *arch = get_crash_item_content_or_NULL(crash_data, FILENAME_ARCHITECTURE); + const char *duphash = get_crash_item_content_or_NULL(crash_data, FILENAME_DUPHASH); + const char *reason = get_crash_item_content_or_NULL(crash_data, FILENAME_REASON); + const char *function = get_crash_item_content_or_NULL(crash_data, FILENAME_CRASH_FUNCTION); + const char *analyzer = get_crash_item_content_or_NULL(crash_data, FILENAME_ANALYZER); + const char *tainted_str = get_crash_item_content_or_NULL(crash_data, FILENAME_TAINTED); struct strbuf *buf_summary = strbuf_new(); strbuf_append_strf(buf_summary, "[abrt] %s", package); @@ -466,7 +468,7 @@ xmlrpc_int32 ctx::new_bug(const map_crash_data_t& pCrashData, int depend_on_bugn if (tainted_str && analyzer && (strcmp(analyzer, "Kerneloops") == 0) ) { - unsigned long tainted = xatoi_u(tainted_str); + unsigned long tainted = xatoi_positive(tainted_str); const char *tainted_warning = tainted_string(tainted); if (tainted_warning) strbuf_append_strf(buf_summary, ": TAINTED %s", tainted_warning); @@ -474,13 +476,13 @@ xmlrpc_int32 ctx::new_bug(const map_crash_data_t& pCrashData, int depend_on_bugn char *status_whiteboard = xasprintf("abrt_hash:%s", duphash); - char *bz_dsc = make_description_bz(pCrashData); + char *bz_dsc = make_description_bz(crash_data); char *full_dsc = xasprintf("abrt version: "VERSION"\n%s", bz_dsc); free(bz_dsc); char *product = NULL; char *version = NULL; - parse_release(release, &product, &version); + parse_release_for_bz(release, &product, &version); xmlrpc_value* result = NULL; char *summary = strbuf_free_nobuf(buf_summary); @@ -534,23 +536,24 @@ xmlrpc_int32 ctx::new_bug(const map_crash_data_t& pCrashData, int depend_on_bugn return bug_id; } -int ctx::add_attachments(const char* bug_id_str, const map_crash_data_t& pCrashData) +int ctx::add_attachments(const char* bug_id_str, crash_data_t *crash_data) { - map_crash_data_t::const_iterator it = pCrashData.begin(); - for (; it != pCrashData.end(); it++) + GHashTableIter iter; + char *name; + struct crash_item *value; + g_hash_table_iter_init(&iter, crash_data); + while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value)) { - const char *itemname = it->first.c_str(); - const char *type = it->second[CD_TYPE].c_str(); - const char *content = it->second[CD_CONTENT].c_str(); + const char *content = value->content; - if ((strcmp(type, CD_TXT) == 0) - && (strlen(content) > CD_TEXT_ATT_SIZE || (strcmp(itemname, FILENAME_BACKTRACE) == 0)) + if ((value->flags & CD_FLAG_TXT) + && (strlen(content) > CD_TEXT_ATT_SIZE || (strcmp(name, FILENAME_BACKTRACE) == 0)) ) { char *encoded64 = encode_base64(content, strlen(content)); - char *filename = xasprintf("File: %s", itemname); + char *filename = xasprintf("File: %s", name); xmlrpc_value* result = call("bugzilla.addAttachment", "(s{s:s,s:s,s:s,s:s})", bug_id_str, "description", filename, - "filename", itemname, + "filename", name, "contenttype", "text/plain", "data", encoded64 ); @@ -567,7 +570,9 @@ int ctx::add_attachments(const char* bug_id_str, const map_crash_data_t& pCrashD int ctx::get_bug_info(struct bug_info* bz, xmlrpc_int32 bug_id) { - xmlrpc_value* result = call("bugzilla.getBug", "(s)", to_string(bug_id).c_str()); + char bug_id_str[sizeof(long)*3 + 2]; + sprintf(bug_id_str, "%lu", (long)bug_id); + xmlrpc_value* result = call("bugzilla.getBug", "(s)", bug_id_str); if (!result) return -1; @@ -611,15 +616,10 @@ int ctx::get_bug_info(struct bug_info* bz, xmlrpc_int32 bug_id) void ctx::login(const char* login, const char* passwd) { xmlrpc_value* result = call("User.login", "({s:s,s:s})", "login", login, "password", passwd); - if (!result) - { - char *errmsg = xasprintf("Can't login. Check Edit->Plugins->Bugzilla and /etc/abrt/plugins/Bugzilla.conf. Server said: %s", env.fault_string); - error_msg("%s", errmsg); // show error in daemon log - CABRTException e(EXCEP_PLUGIN, errmsg); - free(errmsg); - throw e; - } + error_msg_and_die("Can't login. Check Edit->Plugins->Bugzilla " + "and /etc/abrt/plugins/Bugzilla.conf. Server said: %s", + env.fault_string); xmlrpc_DECREF(result); } @@ -637,15 +637,12 @@ void ctx::logout() static void report_to_bugzilla( const char *dump_dir_name, - /*const*/ map_plugin_settings_t& settings) + map_string_h *settings) { struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) - { - throw CABRTException(EXCEP_PLUGIN, _("Can't open '%s'"), dump_dir_name); - } - map_crash_data_t pCrashData; - load_crash_data_from_crash_dump_dir(dd, pCrashData); + xfunc_die(); /* dd_opendir already emitted error msg */ + crash_data_t *crash_data = create_crash_data_from_dump_dir(dd); dd_close(dd); const char *env; @@ -656,27 +653,27 @@ static void report_to_bugzilla( bool ssl_verify; env = getenv("Bugzilla_Login"); - login = env ? env : settings["Login"].c_str(); + login = env ? env : get_map_string_item_or_empty(settings, "Login"); env = getenv("Bugzilla_Password"); - password = env ? env : settings["Password"].c_str(); + password = env ? env : get_map_string_item_or_empty(settings, "Password"); if (!login[0] || !password[0]) { VERB3 log("Empty login and password"); - throw CABRTException(EXCEP_PLUGIN, _("Empty login or password, please check %s"), PLUGINS_CONF_DIR"/Bugzilla.conf"); + error_msg_and_die(_("Empty login or password, please check %s"), PLUGINS_CONF_DIR"/Bugzilla.conf"); } env = getenv("Bugzilla_BugzillaURL"); - bugzilla_url = env ? env : settings["BugzillaURL"].c_str(); + bugzilla_url = env ? env : get_map_string_item_or_empty(settings, "BugzillaURL"); if (!bugzilla_url[0]) bugzilla_url = "https://bugzilla.redhat.com"; bugzilla_xmlrpc = xasprintf("%s"XML_RPC_SUFFIX, bugzilla_url); env = getenv("Bugzilla_SSLVerify"); - ssl_verify = string_to_bool(env ? env : settings["SSLVerify"].c_str()); + ssl_verify = string_to_bool(env ? env : get_map_string_item_or_empty(settings, "SSLVerify")); - const char *component = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_COMPONENT); - const char *duphash = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_DUPHASH); - const char *release = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_RELEASE); + const char *component = get_crash_item_content_or_NULL(crash_data, FILENAME_COMPONENT); + const char *duphash = get_crash_item_content_or_NULL(crash_data, FILENAME_DUPHASH); + const char *release = get_crash_item_content_or_NULL(crash_data, FILENAME_OS_RELEASE); ctx bz_server(bugzilla_xmlrpc, ssl_verify); @@ -687,7 +684,8 @@ static void report_to_bugzilla( char *product = NULL; char *version = NULL; - parse_release(release, &product, &version); + parse_release_for_bz(release, &product, &version); + free(version); xmlrpc_value *result; if (strcmp(product, "Fedora") == 0) @@ -704,7 +702,7 @@ static void report_to_bugzilla( if (!all_bugs) { throw_if_xml_fault_occurred(&bz_server.env); - throw CABRTException(EXCEP_PLUGIN, _("Missing mandatory member 'bugs'")); + error_msg_and_die(_("Missing mandatory member 'bugs'")); } xmlrpc_int32 bug_id = -1; @@ -723,7 +721,7 @@ static void report_to_bugzilla( { bug_info_destroy(&bz); throw_if_xml_fault_occurred(&bz_server.env); - throw CABRTException(EXCEP_PLUGIN, _("get_bug_info() failed. Could not collect all mandatory information")); + error_msg_and_die(_("get_bug_info() failed. Could not collect all mandatory information")); } if (strcmp(bz.bug_product, product) != 0) @@ -740,7 +738,7 @@ static void report_to_bugzilla( if (!all_bugs) { throw_if_xml_fault_occurred(&bz_server.env); - throw CABRTException(EXCEP_PLUGIN, _("Missing mandatory member 'bugs'")); + error_msg_and_die(_("Missing mandatory member 'bugs'")); } all_bugs_size = bz_server.get_array_size(all_bugs); @@ -756,7 +754,7 @@ static void report_to_bugzilla( { bug_info_destroy(&bz); throw_if_xml_fault_occurred(&bz_server.env); - throw CABRTException(EXCEP_PLUGIN, _("get_bug_info() failed. Could not collect all mandatory information")); + error_msg_and_die(_("get_bug_info() failed. Could not collect all mandatory information")); } } else @@ -764,7 +762,6 @@ static void report_to_bugzilla( } } free(product); - free(version); if (all_bugs_size < 0) { @@ -773,15 +770,17 @@ static void report_to_bugzilla( else if (all_bugs_size == 0) // Create new bug { log(_("Creating a new bug...")); - bug_id = bz_server.new_bug(pCrashData, depend_on_bugno); + bug_id = bz_server.new_bug(crash_data, depend_on_bugno); if (bug_id < 0) { throw_if_xml_fault_occurred(&bz_server.env); - throw CABRTException(EXCEP_PLUGIN, _("Bugzilla entry creation failed")); + error_msg_and_die(_("Bugzilla entry creation failed")); } - log("Adding attachments to bug %d...", bug_id); - int ret = bz_server.add_attachments(to_string(bug_id).c_str(), pCrashData); + log("Adding attachments to bug %ld...", (long)bug_id); + char bug_id_str[sizeof(long)*3 + 2]; + sprintf(bug_id_str, "%ld", (long) bug_id); + int ret = bz_server.add_attachments(bug_id_str, crash_data); if (ret == -1) { throw_if_xml_fault_occurred(&bz_server.env); @@ -816,7 +815,7 @@ static void report_to_bugzilla( { VERB3 log("Bugzilla could not find a parent of bug %d", (int)original_bug_id); bug_info_destroy(&bz); - throw CABRTException(EXCEP_PLUGIN, _("Bugzilla couldn't find parent of bug %d"), (int)original_bug_id); + error_msg_and_die(_("Bugzilla couldn't find parent of bug %d"), (int)original_bug_id); } log("Bug %d is a duplicate, using parent bug %d", bug_id, (int)bz.bug_dup_id); @@ -831,7 +830,7 @@ static void report_to_bugzilla( { throw_if_xml_fault_occurred(&bz_server.env); } - throw CABRTException(EXCEP_PLUGIN, _("get_bug_info() failed. Could not collect all mandatory information")); + error_msg_and_die(_("get_bug_info() failed. Could not collect all mandatory information")); } // found a bug which is not CLOSED as DUPLICATE @@ -855,13 +854,13 @@ static void report_to_bugzilla( throw_if_xml_fault_occurred(&bz_server.env); } - char *dsc = make_description_reproduce_comment(pCrashData); + char *dsc = make_description_reproduce_comment(crash_data); if (dsc) { - const char* package = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_PACKAGE); - const char* release = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_RELEASE); - const char* arch = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_ARCHITECTURE); - const char* is_private = get_crash_data_item_content_or_NULL(pCrashData, "is_private"); + const char* package = get_crash_item_content_or_NULL(crash_data, FILENAME_PACKAGE); + const char* release = get_crash_item_content_or_NULL(crash_data, FILENAME_OS_RELEASE); + const char* arch = get_crash_item_content_or_NULL(crash_data, FILENAME_ARCHITECTURE); + const char* is_private = get_crash_item_content_or_NULL(crash_data, "is_private"); char *full_dsc = xasprintf("Package: %s\n" "Architecture: %s\n" @@ -895,6 +894,7 @@ static void report_to_bugzilla( (int)bug_id ); + free_crash_data(crash_data); bug_info_destroy(&bz); } @@ -904,59 +904,50 @@ int main(int argc, char **argv) if (env_verbose) g_verbose = atoi(env_verbose); - map_plugin_settings_t settings; - + map_string_h *settings = new_map_string(); const char *dump_dir_name = "."; + GList *conf_file = NULL; + + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( + PROGNAME" [-vs] -c CONFFILE -d DIR" + "\n" + "\nReport a crash to Bugzilla" + ); enum { - OPT_s = (1 << 0), + OPT_v = 1 << 0, + OPT_s = 1 << 1, + OPT_d = 1 << 2, + OPT_c = 1 << 3, }; - int optflags = 0; - int opt; - while ((opt = getopt(argc, argv, "c:d:vs")) != -1) - { - switch (opt) - { - case 'c': - VERB1 log("Loading settings from '%s'", optarg); - LoadPluginSettings(optarg, settings); - VERB3 log("Loaded '%s'", optarg); - break; - case 'd': - dump_dir_name = optarg; - break; - case 'v': - g_verbose++; - break; - case 's': - optflags |= OPT_s; - break; - default: - /* Careful: the string below contains tabs, dont replace with spaces */ - error_msg_and_die( - "Usage: abrt-action-bugzilla -c CONFFILE -d DIR [-vs]" - "\n" - "\nReport a crash to Bugzilla" - "\n" - "\nOptions:" - "\n -c FILE Configuration file (may be given many times)" - "\n -d DIR Crash dump directory" - "\n -v Verbose" - "\n -s Log to syslog" - ); - } - } + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_BOOL( 's', NULL, NULL , _("Log to syslog")), + OPT_STRING('d', NULL, &dump_dir_name, "DIR" , _("Crash dump directory")), + OPT_LIST( 'c', NULL, &conf_file , "FILE", _("Configuration file (may be given many times)")), + OPT_END() + }; + unsigned opts = parse_opts(argc, argv, program_options, program_usage_string); putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); - //DONT! our stdout/stderr goes directly to daemon, don't want to have prefix there. -// msg_prefix = xasprintf("abrt-action-bugzilla[%u]", getpid()); - - if (optflags & OPT_s) +// msg_prefix = xasprintf(PROGNAME"[%u]", getpid()); + if (opts & OPT_s) { openlog(msg_prefix, 0, LOG_DAEMON); logmode = LOGMODE_SYSLOG; } + while (conf_file) + { + char *fn = (char *)conf_file->data; + VERB1 log("Loading settings from '%s'", fn); + load_conf_file(fn, settings, /*skip key w/o values:*/ true); + VERB3 log("Loaded '%s'", fn); + conf_file = g_list_remove(conf_file, fn); + } + VERB1 log("Initializing XML-RPC library"); xmlrpc_env env; xmlrpc_env_init(&env); @@ -965,14 +956,8 @@ int main(int argc, char **argv) error_msg_and_die("XML-RPC Fault: %s(%d)", env.fault_string, env.fault_code); xmlrpc_env_clean(&env); - try - { - report_to_bugzilla(dump_dir_name, settings); - } - catch (CABRTException& e) - { - error_msg_and_die("%s", e.what()); - } + report_to_bugzilla(dump_dir_name, settings); + free_map_string(settings); return 0; } diff --git a/src/plugins/abrt-action-generate-backtrace.c b/src/plugins/abrt-action-generate-backtrace.c index 64ce4082..a8c18e36 100644 --- a/src/plugins/abrt-action-generate-backtrace.c +++ b/src/plugins/abrt-action-generate-backtrace.c @@ -28,8 +28,9 @@ #define DEBUGINFO_CACHE_DIR LOCALSTATEDIR"/cache/abrt-di" static const char *dump_dir_name = "."; -static const char *debuginfo_dirs; -static int exec_timeout_sec = 60; +static const char *debuginfo_dirs = DEBUGINFO_CACHE_DIR; +/* 60 seconds was too limiting on slow machines */ +static int exec_timeout_sec = 240; static void create_hash(char hash_str[SHA1_RESULT_LEN*2 + 1], const char *pInput) @@ -131,7 +132,7 @@ static char* exec_vp(char **args, uid_t uid, int redirect_stderr, int *status) static char *get_backtrace(struct dump_dir *dd) { char *uid_str = dd_load_text(dd, FILENAME_UID); - uid_t uid = xatoi_u(uid_str); + uid_t uid = xatoi_positive(uid_str); free(uid_str); char *executable = dd_load_text(dd, FILENAME_EXECUTABLE); dd_close(dd); @@ -244,49 +245,49 @@ static char *get_backtrace(struct dump_dir *dd) return bt; } -static char *i_opt; -static const char abrt_action_generage_backtrace_usage[] = PROGNAME" [options] -d DIR"; -enum { - OPT_v = 1 << 0, - OPT_d = 1 << 1, - OPT_i = 1 << 2, - OPT_t = 1 << 3, - OPT_s = 1 << 4, -}; -/* Keep enum above and order of options below in sync! */ -static struct options abrt_action_generate_backtrace_options[] = { - OPT__VERBOSE(&g_verbose), - OPT_STRING( 'd', NULL, &dump_dir_name, "DIR", "Crash dump directory"), - OPT_STRING( 'i', NULL, &i_opt, "dir1[:dir2]...", "Additional debuginfo directories"), - OPT_INTEGER('t', NULL, &exec_timeout_sec, "Kill gdb if it runs for more than N seconds"), - OPT_BOOL( 's', NULL, NULL, "Log to syslog"), - OPT_END() -}; - int main(int argc, char **argv) { char *env_verbose = getenv("ABRT_VERBOSE"); if (env_verbose) g_verbose = atoi(env_verbose); - unsigned opts = parse_opts(argc, argv, abrt_action_generate_backtrace_options, - abrt_action_generage_backtrace_usage); - - debuginfo_dirs = DEBUGINFO_CACHE_DIR; - if (i_opt) - { - debuginfo_dirs = xasprintf("%s:%s", DEBUGINFO_CACHE_DIR, i_opt); - } + char *i_opt = NULL; + + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( + PROGNAME" [options] -d DIR" + ); + enum { + OPT_v = 1 << 0, + OPT_d = 1 << 1, + OPT_i = 1 << 2, + OPT_t = 1 << 3, + OPT_s = 1 << 4, + }; + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_STRING( 'd', NULL, &dump_dir_name , "DIR" , _("Crash dump directory")), + OPT_STRING( 'i', NULL, &i_opt , "dir1[:dir2]...", _("Additional debuginfo directories")), + OPT_INTEGER('t', NULL, &exec_timeout_sec, _("Kill gdb if it runs for more than N seconds")), + OPT_BOOL( 's', NULL, NULL , _("Log to syslog")), + OPT_END() + }; + unsigned opts = parse_opts(argc, argv, program_options, program_usage_string); putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); - msg_prefix = PROGNAME; - + //msg_prefix = PROGNAME; if (opts & OPT_s) { openlog(msg_prefix, 0, LOG_DAEMON); logmode = LOGMODE_SYSLOG; } + if (i_opt) + { + debuginfo_dirs = xasprintf("%s:%s", DEBUGINFO_CACHE_DIR, i_opt); + } + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) return 1; @@ -294,49 +295,70 @@ int main(int argc, char **argv) char *package = dd_load_text(dd, FILENAME_PACKAGE); char *executable = dd_load_text(dd, FILENAME_EXECUTABLE); - /* Create and store backtrace */ + /* Create gdb backtrace */ /* NB: get_backtrace() closes dd */ char *backtrace_str = get_backtrace(dd); if (!backtrace_str) { backtrace_str = xstrdup(""); - VERB3 log("get_backtrace() returns NULL, broken core/gdb?"); + log("get_backtrace() returns NULL, broken core/gdb?"); } + /* Compute backtrace hash */ + struct btp_location location; + btp_location_init(&location); + char *backtrace_str_ptr = backtrace_str; + struct btp_backtrace *backtrace = btp_backtrace_parse(&backtrace_str_ptr, &location); + + /* Store gdb backtrace */ + dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) return 1; - dd_save_text(dd, FILENAME_BACKTRACE, backtrace_str); + /* Don't be completely silent. gdb run takes a few seconds, + * it is useful to let user know it (maybe) worked. + */ + log(_("Backtrace is generated and saved, %u bytes"), (int)strlen(backtrace_str)); + free(backtrace_str); + + /* Store backtrace hash */ - /* Compute and store backtrace hash. */ - struct btp_location location; - btp_location_init(&location); - char *backtrace_str_ptr = backtrace_str; - struct btp_backtrace *backtrace = btp_backtrace_parse(&backtrace_str_ptr, &location); if (!backtrace) { + /* + * The parser failed. Compute the UUID from the executable + * and package only. This is not supposed to happen often. + */ VERB1 log(_("Backtrace parsing failed for %s"), dump_dir_name); VERB1 log("%d:%d: %s", location.line, location.column, location.message); - /* If the parser failed compute the UUID from the executable - and package only. This is not supposed to happen often. - Do not store the rating, as we do not know how good the - backtrace is. */ struct strbuf *emptybt = strbuf_new(); strbuf_prepend_str(emptybt, executable); strbuf_prepend_str(emptybt, package); + char hash_str[SHA1_RESULT_LEN*2 + 1]; create_hash(hash_str, emptybt->buf); + dd_save_text(dd, FILENAME_DUPHASH, hash_str); + /* + * Other parts of ABRT assume that if no rating is available, + * it is ok to allow reporting of the bug. To be sure no bad + * backtrace is reported, rate the backtrace with the lowest + * rating. + */ + dd_save_text(dd, FILENAME_RATING, "0"); strbuf_free(emptybt); - free(backtrace_str); free(package); free(executable); dd_close(dd); - return 2; + + /* Report success even if the parser failed, as the backtrace + * has been created and rated. The failure is caused by a flaw + * in the parser, not in the backtrace. + */ + return 0; } - free(backtrace_str); /* Compute duplication hash. */ char *str_hash_core = btp_backtrace_get_duplication_hash(backtrace); @@ -344,8 +366,10 @@ int main(int argc, char **argv) strbuf_append_str(str_hash, package); strbuf_append_str(str_hash, executable); strbuf_append_str(str_hash, str_hash_core); + char hash_str[SHA1_RESULT_LEN*2 + 1]; create_hash(hash_str, str_hash->buf); + dd_save_text(dd, FILENAME_DUPHASH, hash_str); strbuf_free(str_hash); free(str_hash_core); @@ -368,14 +392,14 @@ int main(int argc, char **argv) /* Get the function name from the crash frame. */ struct btp_frame *crash_frame = btp_backtrace_get_crash_frame(backtrace); if (crash_frame) - { + { if (crash_frame->function_name && 0 != strcmp(crash_frame->function_name, "??")) { dd_save_text(dd, FILENAME_CRASH_FUNCTION, crash_frame->function_name); } btp_frame_free(crash_frame); - } + } btp_backtrace_free(backtrace); dd_close(dd); diff --git a/src/plugins/abrt-action-install-debuginfo.c b/src/plugins/abrt-action-install-debuginfo.c new file mode 100644 index 00000000..39915e59 --- /dev/null +++ b/src/plugins/abrt-action-install-debuginfo.c @@ -0,0 +1,43 @@ +#include <unistd.h> +#include <string.h> + +#define EXECUTABLE "abrt-action-install-debuginfo.py" + +static void error_msg_and_die(const char *msg, const char *arg) +{ + write(2, msg, strlen(msg)); + if (arg) + { + write(2, " '", 2); + write(2, msg, strlen(msg)); + write(2, "'", 1); + } + write(2, "\n", 1); + exit(1); +} + + +/* A binary wrapper is needed around python scripts if we want + * to run them in sgid/suid mode. + * + * This is such a wrapper. + */ +int main(int argc, char **argv) +{ + /* + * We disallow passing of arguments which point to writable dirs. + * This way, the script will always use default arguments. + */ + char **pp = argv; + char *arg; + while ((arg = *++pp) != NULL) + { + if (strncmp(arg, "--cache", 7) == 0) + error_msg_and_die("bad option", arg); + if (strncmp(arg, "--tmpdir", 8) == 0) + error_msg_and_die("bad option", arg); + } + + execvp(EXECUTABLE, argv); + error_msg_and_die("Can't execute", EXECUTABLE); +} diff --git a/src/plugins/abrt-action-install-debuginfo.py b/src/plugins/abrt-action-install-debuginfo.py index b16cc3e9..9253c87f 100755 --- a/src/plugins/abrt-action-install-debuginfo.py +++ b/src/plugins/abrt-action-install-debuginfo.py @@ -6,6 +6,7 @@ from subprocess import Popen, PIPE import sys import os +import time import getopt import shutil from yum import _, YumBase @@ -51,7 +52,11 @@ def unmute_stdout(): def ask_yes_no(prompt, retries=4): while True: - response = raw_input(prompt) + try: + response = raw_input(prompt) + except EOFError: + log1("got eof, probably executed from helper, assuming - yes") + response = 'y' if response in ('y', 'Y'): return True if response in ('n', 'N', ''): @@ -65,30 +70,30 @@ def ask_yes_no(prompt, retries=4): # ..that can lead to: foo.c No such file and directory # files is not used... def unpack_rpm(package_nevra, files, tmp_dir, destdir, keeprpm): - package_file_suffix = ".rpm" - package_full_path = tmp_dir + "/" + package_nevra + package_file_suffix + package_name = package_nevra + ".rpm" + package_full_path = tmp_dir + "/" + package_name log1("Extracting %s to %s" % (package_full_path, destdir)) log2(files) - print (_("Extracting cpio from %s") % (package_full_path)) + print _("Extracting cpio from %s") % (package_full_path) unpacked_cpio_path = tmp_dir + "/unpacked.cpio" try: unpacked_cpio = open(unpacked_cpio_path, 'wb') except IOError, ex: - print (_("Can't write to:"), (unpacked_cpio_path,ex)) + print _("Can't write to '%s': %s") % (unpacked_cpio_path, ex) return RETURN_FAILURE rpm2cpio = Popen(["rpm2cpio", package_full_path], - stdout=unpacked_cpio, bufsize=-1) + stdout = unpacked_cpio, bufsize = -1) retcode = rpm2cpio.wait() if retcode == 0: log1("cpio written OK") if not keeprpm: log1("keeprpms = False, removing %s" % package_full_path) - print _("Removing the temporary rpm file") + #print _("Removing temporary rpm file") os.unlink(package_full_path) else: unpacked_cpio.close() - print (_("Can't extract package: %s") % package_full_path) + print _("Can't extract package '%s'") % package_full_path return RETURN_FAILURE # close the file @@ -96,18 +101,17 @@ def unpack_rpm(package_nevra, files, tmp_dir, destdir, keeprpm): # and open it for reading unpacked_cpio = open(unpacked_cpio_path, 'rb') - print (_("Caching files from %s made from %s") % - (unpacked_cpio_path, package_full_path)) + print _("Caching files from %s made from %s") % ("unpacked.cpio", package_name) cpio = Popen(["cpio","-i", "-d", "--quiet"], stdin=unpacked_cpio, cwd=destdir, bufsize=-1) retcode = cpio.wait() if retcode == 0: log1("files extracted OK") - print _("Removing the temporary cpio file") + #print _("Removing temporary cpio file") os.unlink(unpacked_cpio_path) else: - print (_("Can't extract files from: %s") % unpacked_cpio_path) + print _("Can't extract files from '%s'") % unpacked_cpio_path return RETURN_FAILURE class MyDownloadCallback(DownloadBaseCallback): @@ -115,36 +119,42 @@ class MyDownloadCallback(DownloadBaseCallback): self.total_pkgs = total_pkgs self.downloaded_pkgs = 0 self.last_pct = 0 + self.last_time = 0 DownloadBaseCallback.__init__(self) def updateProgress(self, name, frac, fread, ftime): - pct = int( frac*100 ) + pct = int(frac * 100) if pct == self.last_pct: log2("percentage is the same, not updating progress") return self.last_pct = pct - # if run from terminal we can have a fancy output + # if run from terminal we can have fancy output if sys.stdout.isatty(): - sys.stdout.write("\033[sDownloading (%i of %i) %.30s : %.3s %%\033[u" - % (self.downloaded_pkgs + 1, self.total_pkgs, - name, pct) - ) + sys.stdout.write("\033[sDownloading (%i of %i) %s: %3u%%\033[u" + % (self.downloaded_pkgs + 1, self.total_pkgs, name, pct) + ) if pct == 100: - print _("Downloading (%i of %i) %.30s : %.3s %%" - % (self.downloaded_pkgs + 1, self.total_pkgs, - name, pct) - ) + print (_("Downloading (%i of %i) %s: %3u%%") + % (self.downloaded_pkgs + 1, self.total_pkgs, name, pct) + ) # but we want machine friendly output when spawned from abrt-server else: - print (_("Downloading (%i of %i) %.30s : %.3s %%") - % (self.downloaded_pkgs + 1, self.total_pkgs, name, pct) - ) + t = time.time() + if self.last_time == 0: + self.last_time = t + # update only every 10 seconds + if pct == 100 or self.last_time > t or t - self.last_time >= 10: + print (_("Downloading (%i of %i) %s: %3u%%") + % (self.downloaded_pkgs + 1, self.total_pkgs, name, pct) + ) + self.last_time = t + if pct == 100: + self.last_time = 0 sys.stdout.flush() class DebugInfoDownload(YumBase): - """abrt-debuginfo-install --core=CORE tmpdir cachedir""" def __init__(self, cache, tmp, keep_rpms=False): self.cachedir = cache self.tmpdir = tmp @@ -166,13 +176,13 @@ class DebugInfoDownload(YumBase): if not files: return - print _("Searching the missing debuginfo packages") - # this suppress yum messages about setting up repositories - mute_stdout() + if verbose == 0: + # this suppress yum messages about setting up repositories + mute_stdout() # make yumdownloader work as non root user. if not self.setCacheDir(): - self.logger.error("Error: Could not make cachedir, exiting") + self.logger.error("Error: can't make cachedir, exiting") sys.exit(50) # disable all not needed @@ -191,13 +201,14 @@ class DebugInfoDownload(YumBase): self.repos.populateSack(mdtype='metadata', cacheonly=1) self.repos.populateSack(mdtype='filelists', cacheonly=1) - # re-enable the output to stdout - unmute_stdout() + if verbose == 0: + # re-enable the output to stdout + unmute_stdout() not_found = [] package_files_dict = {} for debuginfo_path in files: - log2("yum whatprovides %s" %debuginfo_path) + log2("yum whatprovides %s" % debuginfo_path) pkg = self.pkgSack.searchFiles(debuginfo_path) # sometimes one file is provided by more rpms, we can use either of # them, so let's use the first match @@ -210,29 +221,23 @@ class DebugInfoDownload(YumBase): installed_size += float(pkg[0].installedsize) total_pkgs += 1 - log2("found pkg for %s" % debuginfo_path) + log2("found pkg for %s: %s" % (debuginfo_path, pkg[0])) else: log2("not found pkg for %s" % debuginfo_path) not_found.append(debuginfo_path) # connect our progress update callback dnlcb = MyDownloadCallback(total_pkgs) - self.repos.setProgressBar( dnlcb ) - - log1("%i files in %i packages" % (len(files), total_pkgs)) + self.repos.setProgressBar(dnlcb) - print (_("To download: (%.2f) M / Installed size: %.2f M" % - ( - todownload_size / (1024**2), - installed_size / (1024**2)) - ) - ) - #print _("%i debug infos not found" % len(not_found)) - - log1("packages: %i\nTo download: %f \nUnpacked size: %f" % - (total_pkgs, - todownload_size / (1024**2), - installed_size / (1024**2))) + if verbose != 0 or len(not_found) != 0: + print _("Can't find packages for %u debuginfo files") % len(not_found) + if verbose != 0 or total_pkgs != 0: + print _("Found %u packages to download") % total_pkgs + print _("Downloading %.2fMb, installed size: %.2fMb") % ( + todownload_size / (1024**2), + installed_size / (1024**2) + ) # ask only if we have terminal, because for now we don't have a way # how to pass the question to gui and the response back @@ -271,13 +276,13 @@ class DebugInfoDownload(YumBase): downloaded_pkgs += 1 - if not self.keeprpms: + if not self.keeprpms and os.path.exists(self.tmpdir): print (_("All downloaded packages have been extracted, removing %s") % self.tmpdir) try: os.rmdir(self.tmpdir) except OSError: - print _("Can't remove %s, probably contains an error log") + print _("Can't remove %s, probably contains an error log" % self.tmpdir) verbose = 0 def log1(message): @@ -302,8 +307,7 @@ def extract_info_from_core(corefile): #SEP = 3 EXECUTABLE = 4 - print (_("Analyzing corefile: %(corefile_path)s") % - {"corefile_path":corefile}) + print _("Analyzing corefile '%s'") % corefile eu_unstrip_OUT = Popen(["eu-unstrip","--core=%s" % corefile, "-n"], stdout=PIPE, bufsize=-1).communicate()[0] # parse eu_unstrip_OUT and return the list of build_ids @@ -323,7 +327,7 @@ def extract_info_from_core(corefile): #print eu_unstrip_OUT # we failed to get build ids from the core -> die if not eu_unstrip_OUT: - log1("can't get build ids from the core") + print "Can't get build ids from %s" % corefile return RETURN_FAILURE lines = eu_unstrip_OUT.split('\n') @@ -344,7 +348,7 @@ def extract_info_from_core(corefile): libraries.add(library) build_ids.add(build_id) else: - log2("skipping line %s" % line) + log2("skipping line '%s'" % line) log1("Found %i build_ids" % len(build_ids)) log1("Found %i libs" % len(libraries)) return build_ids @@ -354,7 +358,7 @@ def build_ids_to_path(build_ids): build_id1=${build_id:0:2} build_id2=${build_id:2} file="usr/lib/debug/.build-id/$build_id1/$build_id2.debug" - """ + """ return ["/usr/lib/debug/.build-id/%s/%s.debug" % (b_id[:2], b_id[2:]) for b_id in build_ids] # beware this finds only missing libraries, but not the executable itself .. @@ -381,9 +385,7 @@ def clean_up(): try: shutil.rmtree(tmpdir) except OSError, ex: - print (_("Can't remove %(tmpdir_path)s: %(reason)s") - % {"tmpdir_path":tmpdir, "reason": ex } - ) + print _("Can't remove '%s': %s") % (tmpdir, ex) def sigterm_handler(signum, frame): clean_up() @@ -391,10 +393,11 @@ def sigterm_handler(signum, frame): def sigint_handler(signum, frame): clean_up() - print "\n", _("Exiting on user Command") + print "\n", _("Exiting on user command") exit(RETURN_OK) import signal + if __name__ == "__main__": # abrt-server can send SIGTERM to abort the download signal.signal(signal.SIGTERM, sigterm_handler) @@ -404,15 +407,14 @@ if __name__ == "__main__": cachedir = None tmpdir = None keeprpms = False - result = RETURN_OK noninteractive = False # localization init_gettext() - help_text = _("Usage: %s --core=<COREFILE> " - "--tmpdir=<TMPDIR> " - "--cachedir=<CACHEDIR>") % sys.argv[0] + help_text = _("Usage: %s --core=COREFILE " + "--tmpdir=TMPDIR " + "--cache=CACHEDIR") % sys.argv[0] try: opts, args = getopt.getopt(sys.argv[1:], "vyhc:", ["help", "core=", "cache=", "tmpdir=", @@ -426,7 +428,7 @@ if __name__ == "__main__": verbose += 1 elif opt == "-y": noninteractive = True - elif opt in ("--core","-c"): + elif opt in ("--core", "-c"): core = arg elif opt in ("--cache"): cachedir = arg @@ -443,30 +445,26 @@ if __name__ == "__main__": print help_text exit(RETURN_FAILURE) if not cachedir: - print _("You have to specify the path to cachedir.") - print help_text - exit(RETURN_FAILURE) + cachedir = "/var/cache/abrt-di" if not tmpdir: - print _("You have to specify the path to tmpdir.") - print help_text - exit(RETURN_FAILURE) + # security people prefer temp subdirs in app's private dir, like /var/run/abrt + # for now, we use /tmp... + tmpdir = "/tmp/abrt-tmp-debuginfo-%s.%u" % (time.strftime("%Y-%m-%d-%H:%M:%S"), os.getpid()) b_ids = extract_info_from_core(core) if b_ids == RETURN_FAILURE: exit(RETURN_FAILURE) + missing = filter_installed_debuginfos(b_ids, cachedir) if missing: log2(missing) + print _("Coredump references %u debuginfo files, %u of them are not installed") % (len(b_ids), len(missing)) downloader = DebugInfoDownload(cache=cachedir, tmp=tmpdir) result = downloader.download(missing) - else: - print _("All debuginfo seems to be available") - exit(RETURN_OK) - - missing = filter_installed_debuginfos(b_ids, cachedir) - for bid in missing: - log1("MISSING:%s" % bid) - - print _("Complete!") - exit(result) + missing = filter_installed_debuginfos(b_ids, cachedir) + for bid in missing: + print _("Missing debuginfo file: %s") % bid + exit(result) + print _("All %u debuginfo files are available") % len(b_ids) + exit(RETURN_OK) diff --git a/src/plugins/abrt-action-kerneloops.cpp b/src/plugins/abrt-action-kerneloops.c index dea6df17..8d00da52 100644 --- a/src/plugins/abrt-action-kerneloops.cpp +++ b/src/plugins/abrt-action-kerneloops.c @@ -19,8 +19,7 @@ #include <curl/curl.h> #include "abrtlib.h" -#include "abrt_crash_dump.h" -#include "abrt_exception.h" +#include "parse_options.h" #define PROGNAME "abrt-action-kerneloops" @@ -85,26 +84,21 @@ static CURLcode http_post_to_kerneloops_site(const char *url, const char *oopsda static void report_to_kerneloops( const char *dump_dir_name, - const map_plugin_settings_t& settings) + map_string_h *settings) { struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) exit(1); /* error msg is already logged */ - map_crash_data_t pCrashData; - load_crash_data_from_crash_dump_dir(dd, pCrashData); + crash_data_t *crash_data = create_crash_data_from_dump_dir(dd); dd_close(dd); - const char *backtrace = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_BACKTRACE); + const char *backtrace = get_crash_item_content_or_NULL(crash_data, FILENAME_BACKTRACE); if (!backtrace) error_msg_and_die("Error sending kernel oops due to missing backtrace"); - map_plugin_settings_t::const_iterator end = settings.end(); - map_plugin_settings_t::const_iterator it; - const char *env = getenv("KerneloopsReporter_SubmitURL"); - it = settings.find("SubmitURL"); - const char *submitURL = (env ? env : it == end ? "" : it->second.c_str()); + const char *submitURL = (env ? env : get_map_string_item_or_empty(settings, "SubmitURL")); if (!submitURL[0]) submitURL = "http://submit.kerneloops.org/submitoops.php"; @@ -114,6 +108,8 @@ static void report_to_kerneloops( if (ret != CURLE_OK) error_msg_and_die("Kernel oops has not been sent due to %s", curl_easy_strerror(ret)); + free_crash_data(crash_data); + /* Server replies with: * 200 thank you for submitting the kernel oops information * RemoteIP: 34192fd15e34bf60fac6a5f01bba04ddbd3f0558 @@ -128,67 +124,52 @@ int main(int argc, char **argv) if (env_verbose) g_verbose = atoi(env_verbose); - map_plugin_settings_t settings; - + map_string_h *settings = new_map_string(); const char *dump_dir_name = "."; + GList *conf_file = NULL; + + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( + PROGNAME" [-vs] -c CONFFILE -d DIR" + "\n" + "\nReport a kernel oops to kerneloops.org (or similar) site" + ); enum { - OPT_s = (1 << 0), + OPT_v = 1 << 0, + OPT_s = 1 << 1, + OPT_d = 1 << 2, + OPT_c = 1 << 3, }; - int optflags = 0; - int opt; - while ((opt = getopt(argc, argv, "c:d:vs")) != -1) - { - switch (opt) - { - case 'c': - VERB1 log("Loading settings from '%s'", optarg); - LoadPluginSettings(optarg, settings); - VERB3 log("Loaded '%s'", optarg); - break; - case 'd': - dump_dir_name = optarg; - break; - case 'v': - g_verbose++; - break; - case 's': - optflags |= OPT_s; - break; - default: - /* Careful: the string below contains tabs, dont replace with spaces */ - error_msg_and_die( - "Usage: "PROGNAME" -c CONFFILE -d DIR [-vs]" - "\n" - "\nReport a kernel oops to kerneloops.org (or similar) site" - "\n" - "\nOptions:" - "\n -c FILE Configuration file (may be given many times)" - "\n -d DIR Crash dump directory" - "\n -v Verbose" - "\n -s Log to syslog" - ); - } - } + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_BOOL( 's', NULL, NULL , _("Log to syslog")), + OPT_STRING('d', NULL, &dump_dir_name, "DIR" , _("Crash dump directory")), + OPT_LIST( 'c', NULL, &conf_file , "FILE", _("Configuration file (may be given many times)")), + OPT_END() + }; + unsigned opts = parse_opts(argc, argv, program_options, program_usage_string); putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); - //DONT! our stdout/stderr goes directly to daemon, don't want to have prefix there. // msg_prefix = xasprintf(PROGNAME"[%u]", getpid()); - - if (optflags & OPT_s) + if (opts & OPT_s) { openlog(msg_prefix, 0, LOG_DAEMON); logmode = LOGMODE_SYSLOG; } - try + while (conf_file) { - report_to_kerneloops(dump_dir_name, settings); - } - catch (CABRTException& e) - { - error_msg_and_die("%s", e.what()); + char *fn = (char *)conf_file->data; + VERB1 log("Loading settings from '%s'", fn); + load_conf_file(fn, settings, /*skip key w/o values:*/ true); + VERB3 log("Loaded '%s'", fn); + conf_file = g_list_remove(conf_file, fn); } + report_to_kerneloops(dump_dir_name, settings); + + free_map_string(settings); return 0; } diff --git a/src/plugins/abrt-action-mailx.cpp b/src/plugins/abrt-action-mailx.c index fa7fd8a0..3debf449 100644 --- a/src/plugins/abrt-action-mailx.cpp +++ b/src/plugins/abrt-action-mailx.c @@ -21,8 +21,6 @@ #include "abrtlib.h" #include "parse_options.h" -#include "abrt_crash_dump.h" -#include "abrt_exception.h" #define PROGNAME "abrt-action-mailx" @@ -47,73 +45,71 @@ static void exec_and_feed_input(uid_t uid, const char* text, char **args) error_msg_and_die("Error running '%s'", args[0]); } -static char** append_str_to_vector(char **vec, unsigned &size, const char *str) +static char** append_str_to_vector(char **vec, unsigned *size_p, const char *str) { //log("old vec: %p", vec); + unsigned size = *size_p; vec = (char**) xrealloc(vec, (size+2) * sizeof(vec[0])); vec[size] = xstrdup(str); //log("new vec: %p, added [%d] %p", vec, size, vec[size]); size++; vec[size] = NULL; + *size_p = size; return vec; } static void create_and_send_email( const char *dump_dir_name, - const map_plugin_settings_t& settings) + map_string_h *settings) { struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) exit(1); /* error msg is already logged by dd_opendir */ - map_crash_data_t pCrashData; - load_crash_data_from_crash_dump_dir(dd, pCrashData); + crash_data_t *crash_data = create_crash_data_from_dump_dir(dd); dd_close(dd); char* env; - map_plugin_settings_t::const_iterator end = settings.end(); - map_plugin_settings_t::const_iterator it; env = getenv("Mailx_Subject"); - it = settings.find("Subject"); - const char *subject = xstrdup(env ? env : (it != end ? it->second.c_str() : "[abrt] full crash report")); + const char *subject = (env ? env : get_map_string_item_or_NULL(settings, "Subject") ? : "[abrt] full crash report"); env = getenv("Mailx_EmailFrom"); - it = settings.find("EmailFrom"); - const char *email_from = (env ? env : (it != end ? it->second.c_str() : "user@localhost")); + const char *email_from = (env ? env : get_map_string_item_or_NULL(settings, "EmailFrom") ? : "user@localhost"); env = getenv("Mailx_EmailTo"); - it = settings.find("EmailTo"); - const char *email_to = (env ? env : (it != end ? it->second.c_str() : "root@localhost")); + const char *email_to = (env ? env : get_map_string_item_or_NULL(settings, "EmailTo") ? : "root@localhost"); env = getenv("Mailx_SendBinaryData"); - it = settings.find("SendBinaryData"); - bool send_binary_data = string_to_bool(env ? env : (it != end ? it->second.c_str() : "0")); + bool send_binary_data = string_to_bool(env ? env : get_map_string_item_or_empty(settings, "SendBinaryData")); char **args = NULL; unsigned arg_size = 0; - args = append_str_to_vector(args, arg_size, "/bin/mailx"); + args = append_str_to_vector(args, &arg_size, "/bin/mailx"); - char *dsc = make_description_mailx(pCrashData); + char *dsc = make_description_mailx(crash_data); if (send_binary_data) { - map_crash_data_t::const_iterator it_cd; - for (it_cd = pCrashData.begin(); it_cd != pCrashData.end(); it_cd++) + GHashTableIter iter; + char *name; + struct crash_item *value; + g_hash_table_iter_init(&iter, crash_data); + while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value)) { - if (it_cd->second[CD_TYPE] == CD_BIN) + if (value->flags & CD_FLAG_BIN) { - args = append_str_to_vector(args, arg_size, "-a"); - args = append_str_to_vector(args, arg_size, it_cd->second[CD_CONTENT].c_str()); + args = append_str_to_vector(args, &arg_size, "-a"); + args = append_str_to_vector(args, &arg_size, value->content); } } } - args = append_str_to_vector(args, arg_size, "-s"); - args = append_str_to_vector(args, arg_size, subject); - args = append_str_to_vector(args, arg_size, "-r"); - args = append_str_to_vector(args, arg_size, email_from); - args = append_str_to_vector(args, arg_size, email_to); + args = append_str_to_vector(args, &arg_size, "-s"); + args = append_str_to_vector(args, &arg_size, subject); + args = append_str_to_vector(args, &arg_size, "-r"); + args = append_str_to_vector(args, &arg_size, email_from); + args = append_str_to_vector(args, &arg_size, email_to); log(_("Sending an email...")); - const char *uid_str = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_UID); - exec_and_feed_input(xatoi_u(uid_str), dsc, args); + const char *uid_str = get_crash_item_content_or_NULL(crash_data, FILENAME_UID); + exec_and_feed_input(xatoi_positive(uid_str), dsc, args); free(dsc); @@ -122,6 +118,8 @@ static void create_and_send_email( args -= arg_size; free(args); + free_crash_data(crash_data); + log("Email was sent to: %s", email_to); } @@ -134,7 +132,8 @@ int main(int argc, char **argv) const char *dump_dir_name = "."; const char *conf_file = NULL; - const char *program_usage = _( + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( PROGNAME" [-v] -d DIR [-c CONFFILE]\n" "\n" "Upload compressed tarball of crash dump" @@ -151,29 +150,22 @@ int main(int argc, char **argv) OPT_STRING('c', NULL, &conf_file , "CONFFILE", _("Config file")), OPT_END() }; - - /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage); + /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); //msg_prefix = PROGNAME; - //if (optflags & OPT_s) + //if (opts & OPT_s) //{ // openlog(msg_prefix, 0, LOG_DAEMON); // logmode = LOGMODE_SYSLOG; //} - map_plugin_settings_t settings; + map_string_h *settings = new_map_string(); if (conf_file) - LoadPluginSettings(conf_file, settings); + load_conf_file(conf_file, settings, /*skip key w/o values:*/ true); - try - { - create_and_send_email(dump_dir_name, settings); - } - catch (CABRTException& e) - { - error_msg_and_die("%s", e.what()); - } + create_and_send_email(dump_dir_name, settings); + free_map_string(settings); return 0; } diff --git a/src/plugins/abrt-action-print.cpp b/src/plugins/abrt-action-print.c index 303c05f7..cc7fbb34 100644 --- a/src/plugins/abrt-action-print.cpp +++ b/src/plugins/abrt-action-print.c @@ -20,8 +20,6 @@ */ #include "abrtlib.h" #include "parse_options.h" -#include "abrt_crash_dump.h" -#include "abrt_exception.h" #define PROGNAME "abrt-action-print" @@ -35,10 +33,12 @@ int main(int argc, char **argv) if (env_verbose) g_verbose = atoi(env_verbose); - const char *program_usage = _( + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( PROGNAME" [-v] [-o FILE] -d DIR\n" "\n" - "Print information about the crash to standard output"); + "Print information about the crash to standard output" + ); enum { OPT_v = 1 << 0, OPT_d = 1 << 1, @@ -51,8 +51,7 @@ int main(int argc, char **argv) OPT_STRING('o', NULL, &output_file , "FILE", _("Output file")), OPT_END() }; - - /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage); + /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); //msg_prefix = PROGNAME; @@ -75,25 +74,17 @@ int main(int argc, char **argv) } } - try - { - struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); - if (!dd) - return 1; /* error message is already logged */ + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + return 1; /* error message is already logged */ - map_crash_data_t pCrashData; - load_crash_data_from_crash_dump_dir(dd, pCrashData); - dd_close(dd); + crash_data_t *crash_data = create_crash_data_from_dump_dir(dd); + dd_close(dd); - char *dsc = make_description_logger(pCrashData); - fputs(dsc, stdout); - free(dsc); - } - catch (CABRTException& e) - { - log("%s", e.what()); - return 1; - } + char *dsc = make_description_logger(crash_data); + fputs(dsc, stdout); + free(dsc); + free_crash_data(crash_data); if (output_file) { diff --git a/src/plugins/abrt-action-rhtsupport.cpp b/src/plugins/abrt-action-rhtsupport.c index eb69489d..94523e08 100644 --- a/src/plugins/abrt-action-rhtsupport.cpp +++ b/src/plugins/abrt-action-rhtsupport.c @@ -22,21 +22,19 @@ #include "abrt_curl.h" #include "abrt_xmlrpc.h" #include "abrt_rh_support.h" -#include "abrt_crash_dump.h" -#include "abrt_exception.h" +#include "parse_options.h" #define PROGNAME "abrt-action-rhtsupport" static void report_to_rhtsupport( const char *dump_dir_name, - const map_plugin_settings_t& settings) + map_string_h *settings) { struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) exit(1); /* error msg is already logged by dd_opendir */ - map_crash_data_t pCrashData; - load_crash_data_from_crash_dump_dir(dd, pCrashData); + crash_data_t *crash_data = create_crash_data_from_dump_dir(dd); dd_close(dd); /* Gzipping e.g. 0.5gig coredump takes a while. Let client know what we are doing */ @@ -54,34 +52,31 @@ static void report_to_rhtsupport( const char* package; char* env; - map_plugin_settings_t::const_iterator end = settings.end(); - map_plugin_settings_t::const_iterator it; - env = getenv("RHTSupport_URL"); - it = settings.find("URL"); - char *url = xstrdup(env ? env : it == end ? "https://api.access.redhat.com/rs" : it->second.c_str()); + char *url = xstrdup(env ? env : (get_map_string_item_or_NULL(settings, "URL") ? : "https://api.access.redhat.com/rs")); env = getenv("RHTSupport_Login"); - it = settings.find("Login"); - char *login = xstrdup(env ? env : it == end ? "" : it->second.c_str()); + char *login = xstrdup(env ? env : get_map_string_item_or_empty(settings, "Login")); env = getenv("RHTSupport_Password"); - it = settings.find("Password"); - char *password = xstrdup(env ? env : it == end ? "" : it->second.c_str()); + char *password = xstrdup(env ? env : get_map_string_item_or_empty(settings, "Password")); env = getenv("RHTSupport_SSLVerify"); - it = settings.find("SSLVerify"); - bool ssl_verify = string_to_bool(env ? env : it == end ? "1" : it->second.c_str()); + bool ssl_verify = string_to_bool(env ? env : get_map_string_item_or_empty(settings, "SSLVerify")); if (!login[0] || !password[0]) { - errmsg = _("Empty login or password, please check RHTSupport.conf"); - goto ret; + free_crash_data(crash_data); + free(url); + free(login); + free(password); + error_msg_and_die(_("Empty login or password, please check RHTSupport.conf")); + return; } - package = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_PACKAGE); - reason = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_REASON); - function = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_CRASH_FUNCTION); + package = get_crash_item_content_or_NULL(crash_data, FILENAME_PACKAGE); + reason = get_crash_item_content_or_NULL(crash_data, FILENAME_REASON); + function = get_crash_item_content_or_NULL(crash_data, FILENAME_CRASH_FUNCTION); { struct strbuf *buf_summary = strbuf_new(); @@ -92,7 +87,7 @@ static void report_to_rhtsupport( strbuf_append_strf(buf_summary, ": %s", reason); summary = strbuf_free_nobuf(buf_summary); - char *bz_dsc = make_description_bz(pCrashData); + char *bz_dsc = make_description_bz(crash_data); dsc = xasprintf("abrt version: "VERSION"\n%s", bz_dsc); free(bz_dsc); } @@ -100,7 +95,7 @@ static void report_to_rhtsupport( file = new_reportfile(); /* SELinux guys are not happy with /tmp, using /var/run/abrt */ - tempfile = xasprintf(LOCALSTATEDIR"/run/abrt/tmp-%lu-%lu.tar.gz", (long)getpid(), (long)time(NULL)); + tempfile = xasprintf(LOCALSTATEDIR"/run/abrt/tmp-%s-%lu.tar.gz", iso_date_string(NULL), (long)getpid()); int pipe_from_parent_to_child[2]; xpipe(pipe_from_parent_to_child); @@ -124,21 +119,23 @@ static void report_to_rhtsupport( } { - map_crash_data_t::const_iterator it = pCrashData.begin(); - for (; it != pCrashData.end(); it++) + GHashTableIter iter; + char *name; + struct crash_item *value; + g_hash_table_iter_init(&iter, crash_data); + while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value)) { - if (it->first == FILENAME_COUNT) continue; - if (it->first == CD_DUMPDIR) continue; - if (it->first == FILENAME_INFORMALL) continue; - if (it->first == FILENAME_MESSAGE) continue; // plugin's status message (if we already reported it yesterday) - if (it->first == FILENAME_DESCRIPTION) continue; // package description - - const char *content = it->second[CD_CONTENT].c_str(); - if (it->second[CD_TYPE] == CD_TXT) + if (strcmp(name, FILENAME_COUNT) == 0) continue; + if (strcmp(name, CD_DUMPDIR) == 0) continue; + if (strcmp(name, FILENAME_INFORMALL) == 0) continue; + if (strcmp(name, FILENAME_MESSAGE) == 0) continue; // plugin's status message (if we already reported it yesterday) + + const char *content = value->content; + if (value->flags & CD_FLAG_TXT) { - reportfile_add_binding_from_string(file, it->first.c_str(), content); + reportfile_add_binding_from_string(file, name, content); } - else if (it->second[CD_TYPE] == CD_BIN) + else if (value->flags & CD_FLAG_BIN) { const char *basename = strrchr(content, '/'); if (basename) @@ -148,7 +145,7 @@ static void report_to_rhtsupport( char *xml_name = concat_path_file("content", basename); reportfile_add_binding_from_namedfile(file, /*on_disk_filename */ content, - /*binding_name */ it->first.c_str(), + /*binding_name */ name, /*recorded_filename*/ xml_name, /*binary */ 1); if (tar_append_file(tar, (char*)content, xml_name) != 0) @@ -248,6 +245,7 @@ static void report_to_rhtsupport( free(url); free(login); free(password); + free_crash_data(crash_data); if (errmsg) error_msg_and_die("%s", errmsg); @@ -259,59 +257,50 @@ int main(int argc, char **argv) if (env_verbose) g_verbose = atoi(env_verbose); - map_plugin_settings_t settings; - + map_string_h *settings = new_map_string(); const char *dump_dir_name = "."; + GList *conf_file = NULL; + + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( + PROGNAME" [-vs] -c CONFFILE -d DIR" + "\n" + "\nReport a crash to RHTSupport" + ); enum { - OPT_s = (1 << 0), + OPT_v = 1 << 0, + OPT_s = 1 << 1, + OPT_d = 1 << 2, + OPT_c = 1 << 3, }; - int optflags = 0; - int opt; - while ((opt = getopt(argc, argv, "c:d:vs")) != -1) - { - switch (opt) - { - case 'c': - VERB1 log("Loading settings from '%s'", optarg); - LoadPluginSettings(optarg, settings); - VERB3 log("Loaded '%s'", optarg); - break; - case 'd': - dump_dir_name = optarg; - break; - case 'v': - g_verbose++; - break; - case 's': - optflags |= OPT_s; - break; - default: - /* Careful: the string below contains tabs, dont replace with spaces */ - error_msg_and_die( - "Usage: "PROGNAME" -c CONFFILE -d DIR [-vs]" - "\n" - "\nReport a crash to RHTSupport" - "\n" - "\nOptions:" - "\n -c FILE Configuration file (may be given many times)" - "\n -d DIR Crash dump directory" - "\n -v Verbose" - "\n -s Log to syslog" - ); - } - } + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_BOOL( 's', NULL, NULL , _("Log to syslog")), + OPT_STRING('d', NULL, &dump_dir_name, "DIR" , _("Crash dump directory")), + OPT_LIST( 'c', NULL, &conf_file , "FILE", _("Configuration file (may be given many times)")), + OPT_END() + }; + unsigned opts = parse_opts(argc, argv, program_options, program_usage_string); putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); - //DONT! our stdout/stderr goes directly to daemon, don't want to have prefix there. // msg_prefix = xasprintf(PROGNAME"[%u]", getpid()); - - if (optflags & OPT_s) + if (opts & OPT_s) { openlog(msg_prefix, 0, LOG_DAEMON); logmode = LOGMODE_SYSLOG; } + while (conf_file) + { + char *fn = (char *)conf_file->data; + VERB1 log("Loading settings from '%s'", fn); + load_conf_file(fn, settings, /*skip key w/o values:*/ true); + VERB3 log("Loaded '%s'", fn); + conf_file = g_list_remove(conf_file, fn); + } + VERB1 log("Initializing XML-RPC library"); xmlrpc_env env; xmlrpc_env_init(&env); @@ -320,14 +309,8 @@ int main(int argc, char **argv) error_msg_and_die("XML-RPC Fault: %s(%d)", env.fault_string, env.fault_code); xmlrpc_env_clean(&env); - try - { - report_to_rhtsupport(dump_dir_name, settings); - } - catch (CABRTException& e) - { - error_msg_and_die("%s", e.what()); - } + report_to_rhtsupport(dump_dir_name, settings); + free_map_string(settings); return 0; } diff --git a/src/plugins/abrt-action-upload.cpp b/src/plugins/abrt-action-upload.c index 9741f543..88380bd7 100644 --- a/src/plugins/abrt-action-upload.cpp +++ b/src/plugins/abrt-action-upload.c @@ -21,8 +21,6 @@ #include <curl/curl.h> #include "abrtlib.h" #include "parse_options.h" -#include "abrt_crash_dump.h" -#include "abrt_exception.h" #define PROGNAME "abrt-action-upload" @@ -104,7 +102,7 @@ static int send_file(const char *url, const char *filename) static int create_and_upload_archive( const char *dump_dir_name, - const map_plugin_settings_t& settings) + map_string_h *settings) { int result = 0; @@ -125,16 +123,12 @@ static int create_and_upload_archive( //ArchiveType = .tar.bz2 //ExcludeFiles = foo,bar*,b*z char* env; - map_plugin_settings_t::const_iterator end = settings.end(); - map_plugin_settings_t::const_iterator it; - env = getenv("Upload_URL"); - it = settings.find("URL"); - const char *url = (env ? env : (it == end ? NULL : it->second.c_str())); + const char *url = (env ? env : get_map_string_item_or_empty(settings, "URL")); /* Create a child gzip which will compress the data */ /* SELinux guys are not happy with /tmp, using /var/run/abrt */ - tempfile = xasprintf(LOCALSTATEDIR"/run/abrt/tmp-%lu-%lu.tar.gz", (long)getpid(), (long)time(NULL)); + tempfile = xasprintf(LOCALSTATEDIR"/run/abrt/upload-%s-%lu.tar.gz", iso_date_string(NULL), (long)getpid()); int pipe_from_parent_to_child[2]; xpipe(pipe_from_parent_to_child); child = fork(); @@ -167,7 +161,6 @@ static int create_and_upload_archive( if (strcmp(short_name, CD_DUMPDIR) == 0) goto next; if (strcmp(short_name, FILENAME_INFORMALL) == 0) goto next; if (strcmp(short_name, FILENAME_MESSAGE) == 0) goto next; // plugin's status message (if we already reported it yesterday) - if (strcmp(short_name, FILENAME_DESCRIPTION) == 0) goto next; // package description // dd_get_next_file guarantees this: //struct stat stbuf; //if (stat(full_name, &stbuf) != 0) @@ -249,7 +242,8 @@ int main(int argc, char **argv) const char *conf_file = NULL; const char *url = NULL; - const char *program_usage = _( + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( PROGNAME" [-v] -d DIR [-c CONFFILE] [-u URL]\n" "\n" "Upload compressed tarball of crash dump" @@ -268,32 +262,24 @@ int main(int argc, char **argv) OPT_STRING('u', NULL, &url , "URL" , _("Base URL to upload to")), OPT_END() }; - - /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage); + /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); //msg_prefix = PROGNAME; - //if (optflags & OPT_s) + //if (opts & OPT_s) //{ // openlog(msg_prefix, 0, LOG_DAEMON); // logmode = LOGMODE_SYSLOG; //} - map_plugin_settings_t settings; + map_string_h *settings = new_map_string(); if (url) - settings["URL"] = url; + g_hash_table_replace(settings, xstrdup("URL"), xstrdup(url)); if (conf_file) - LoadPluginSettings(conf_file, settings); + load_conf_file(conf_file, settings, /*skip key w/o values:*/ true); - int result = 0; - try - { - result = create_and_upload_archive(dump_dir_name, settings); - } - catch (CABRTException& e) - { - error_msg_and_die("%s", e.what()); - } + int result = create_and_upload_archive(dump_dir_name, settings); + free_map_string(settings); return result; } diff --git a/src/plugins/abrt-dump-oops.c b/src/plugins/abrt-dump-oops.c new file mode 100644 index 00000000..5b6a2062 --- /dev/null +++ b/src/plugins/abrt-dump-oops.c @@ -0,0 +1,710 @@ +/* + Copyright (C) 2011 ABRT team + Copyright (C) 2011 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. + + Authors: + Anton Arapov <anton@redhat.com> + Arjan van de Ven <arjan@linux.intel.com> + */ +#include <syslog.h> +#include <asm/unistd.h> /* __NR_syslog */ +#include <sys/inotify.h> +#include <sys/ioctl.h> /* ioctl(FIONREAD) */ +#include "abrtlib.h" +#include "parse_options.h" + +#define PROGNAME "abrt-dump-oops" + +static void queue_oops(GList **vec, const char *data, const char *version) +{ + char *ver_data = xasprintf("%s\n%s", version, data); + *vec = g_list_append(*vec, ver_data); +} + +/* + * extract_version tries to find the kernel version in given data + */ +static char *extract_version(const char *linepointer) +{ + if (strstr(linepointer, "Pid") + || strstr(linepointer, "comm") + || strstr(linepointer, "CPU") + || strstr(linepointer, "REGS") + || strstr(linepointer, "EFLAGS") + ) { + char* start; + char* end; + + start = strstr((char*)linepointer, "2.6."); + if (start) + { + end = strchr(start, ')'); + if (!end) + end = strchrnul(start, ' '); + return xstrndup(start, end-start); + } + } + + return NULL; +} + +/* + * extract_oops tries to find oops signatures in a log + */ +struct line_info { + char *ptr; + char level; +}; + +static int record_oops(GList **oopses, struct line_info* lines_info, int oopsstart, int oopsend) +{ + int q; + int len; + char *oops; + char *version; + + len = 2; + for (q = oopsstart; q <= oopsend; q++) + len += strlen(lines_info[q].ptr) + 1; + + oops = (char*)xzalloc(len); + + version = NULL; + for (q = oopsstart; q <= oopsend; q++) + { + if (!version) + version = extract_version(lines_info[q].ptr); + + if (lines_info[q].ptr[0]) + { + strcat(oops, lines_info[q].ptr); + strcat(oops, "\n"); + } + } + int rv = 1; + /* too short oopses are invalid */ + if (strlen(oops) > 100) + queue_oops(oopses, oops, version ? version : "undefined"); + else + { + VERB3 log("Dropped oops: too short"); + rv = 0; + } + free(oops); + free(version); + return rv; +} + +#define REALLOC_CHUNK 1000 +static int extract_oopses(GList **oopses, char *buffer, size_t buflen) +{ + char *c; + int linecount = 0; + int lines_info_alloc = 0; + struct line_info *lines_info = NULL; + + /* Split buffer into lines */ + + if (buflen != 0) + buffer[buflen - 1] = '\n'; /* the buffer usually ends with \n, but let's make sure */ + c = buffer; + while (c < buffer + buflen) + { + char linelevel; + char *c9; + char *colon; + + c9 = (char*)memchr(c, '\n', buffer + buflen - c); /* a \n will always be found */ + assert(c9); + *c9 = '\0'; /* turn the \n into a string termination */ + if (c9 == c) + goto next_line; + + /* Is it a syslog file (/var/log/messages or similar)? + * Even though _usually_ it looks like "Nov 19 12:34:38 localhost kernel: xxx", + * some users run syslog in non-C locale: + * "2010-02-22T09:24:08.156534-08:00 gnu-4 gnome-session[2048]: blah blah" + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ !!! + * We detect it by checking for N:NN:NN pattern in first 15 chars + * (and this still is not good enough... false positive: "pci 0000:15:00.0: PME# disabled") + */ + colon = strchr(c, ':'); + if (colon && colon > c && colon < c + 15 + && isdigit(colon[-1]) /* N:... */ + && isdigit(colon[1]) /* ...N:NN:... */ + && isdigit(colon[2]) + && colon[3] == ':' + && isdigit(colon[4]) /* ...N:NN:NN... */ + && isdigit(colon[5]) + ) { + /* It's syslog file, not a bare dmesg */ + + /* Skip non-kernel lines */ + char *kernel_str = strstr(c, "kernel: "); + if (kernel_str == NULL) + { + /* if we see our own marker: + * "hostname abrt: Kerneloops: Reported 1 kernel oopses to Abrt" + * we know we submitted everything upto here already */ + if (strstr(c, "abrt:") && strstr(c, "Abrt")) + { + VERB3 log("Found our marker at line %d, restarting line count from 0", linecount); + linecount = 0; + lines_info_alloc = 0; + free(lines_info); + lines_info = NULL; + } + goto next_line; + } + c = kernel_str + sizeof("kernel: ")-1; + } + + linelevel = 0; + /* store and remove kernel log level */ + if (*c == '<' && c[1] && c[2] == '>') + { + linelevel = c[1]; + c += 3; + } + /* remove jiffies time stamp counter if present */ + if (*c == '[') + { + char *c2 = strchr(c, '.'); + char *c3 = strchr(c, ']'); + if (c2 && c3 && (c2 < c3) && (c3-c) < 14 && (c2-c) < 8) + { + c = c3 + 1; + if (*c == ' ') + c++; + } + } + if (linecount >= lines_info_alloc) + { + lines_info_alloc += REALLOC_CHUNK; + lines_info = (struct line_info*)xrealloc(lines_info, + lines_info_alloc * sizeof(lines_info[0])); + } + lines_info[linecount].ptr = c; + lines_info[linecount].level = linelevel; + linecount++; +next_line: + c = c9 + 1; + } + + /* Analyze lines */ + + int i; + char prevlevel = 0; + int oopsstart = -1; + int inbacktrace = 0; + int oopsesfound = 0; + + i = 0; + while (i < linecount) + { + char *curline = lines_info[i].ptr; + + if (curline == NULL) + { + i++; + continue; + } + while (*curline == ' ') + curline++; + + if (oopsstart < 0) + { + /* Find start-of-oops markers */ + /* In some comparisons, we skip 1st letter, to avoid dealing with + * changes in capitalization in kernel. For example, I see that + * current kernel git (at 2011-01-01) has both "kernel BUG at ..." + * and "Kernel BUG at ..." messages, and I don't want to change + * the code below whenever kernel is changed to use "K" (or "k") + * uniformly. + */ + if (strstr(curline, /*g*/ "eneral protection fault:")) + oopsstart = i; + else if (strstr(curline, "BUG:")) + oopsstart = i; + else if (strstr(curline, /*k*/ "ernel BUG at")) + oopsstart = i; + else if (strstr(curline, "do_IRQ: stack overflow:")) + oopsstart = i; + else if (strstr(curline, "RTNL: assertion failed")) + oopsstart = i; + else if (strstr(curline, /*e*/ "eek! page_mapcount(page) went negative!")) + oopsstart = i; + else if (strstr(curline, /*n*/ "ear stack overflow (cur:")) + oopsstart = i; + else if (strstr(curline, /*d*/ "ouble fault:")) + oopsstart = i; + else if (strstr(curline, /*b*/ "adness at")) + oopsstart = i; + else if (strstr(curline, "NETDEV WATCHDOG")) + oopsstart = i; + else if (strstr(curline, "WARNING: at ")) /* WARN_ON() generated message */ + oopsstart = i; + else if (strstr(curline, /*u*/ "nable to handle kernel")) + oopsstart = i; + else if (strstr(curline, /*s*/ "ysctl table check failed")) + oopsstart = i; + else if (strstr(curline, "INFO: possible recursive locking detected")) + oopsstart = i; + // Not needed: "--[ cut here ]--" is always followed + // by "Badness at", "kernel BUG at", or "WARNING: at" string + //else if (strstr(curline, "------------[ cut here ]------------")) + // oopsstart = i; + else if (strstr(curline, "list_del corruption")) + oopsstart = i; + else if (strstr(curline, "list_add corruption")) + oopsstart = i; + + if (i >= 3 && strstr(curline, "Oops:")) + oopsstart = i-3; + + if (oopsstart >= 0) + { + /* debug information */ + VERB3 { + log("Found oops at line %d: '%s'", oopsstart, lines_info[oopsstart].ptr); + if (oopsstart != i) + log("Trigger line is %d: '%s'", i, c); + } + /* try to find the end marker */ + int i2 = i + 1; + while (i2 < linecount && i2 < (i+50)) + { + if (strstr(lines_info[i2].ptr, "---[ end trace")) + { + inbacktrace = 1; + i = i2; + break; + } + i2++; + } + } + } + + /* Are we entering a call trace part? */ + /* a call trace starts with "Call Trace:" or with the " [<.......>] function+0xFF/0xAA" pattern */ + if (oopsstart >= 0 && !inbacktrace) + { + if (strstr(curline, "Call Trace:")) + inbacktrace = 1; + else + if (strnlen(curline, 9) > 8 + && curline[0] == '[' && curline[1] == '<' + && strstr(curline, ">]") + && strstr(curline, "+0x") + && strstr(curline, "/0x") + ) { + inbacktrace = 1; + } + } + + /* Are we at the end of an oops? */ + else if (oopsstart >= 0 && inbacktrace) + { + int oopsend = INT_MAX; + + /* line needs to start with " [" or have "] [" if it is still a call trace */ + /* example: "[<ffffffffa006c156>] radeon_get_ring_head+0x16/0x41 [radeon]" */ + if (curline[0] != '[' + && !strstr(curline, "] [") + && !strstr(curline, "--- Exception") + && !strstr(curline, "LR =") + && !strstr(curline, "<#DF>") + && !strstr(curline, "<IRQ>") + && !strstr(curline, "<EOI>") + && !strstr(curline, "<<EOE>>") + && strncmp(curline, "Code: ", 6) != 0 + && strncmp(curline, "RIP ", 4) != 0 + && strncmp(curline, "RSP ", 4) != 0 + ) { + oopsend = i-1; /* not a call trace line */ + } + /* oops lines are always more than 8 chars long */ + else if (strnlen(curline, 8) < 8) + oopsend = i-1; + /* single oopses are of the same loglevel */ + else if (lines_info[i].level != prevlevel) + oopsend = i-1; + else if (strstr(curline, "Instruction dump:")) + oopsend = i; + /* if a new oops starts, this one has ended */ + else if (strstr(curline, "WARNING: at ") && oopsstart != i) /* WARN_ON() generated message */ + oopsend = i-1; + else if (strstr(curline, "Unable to handle") && oopsstart != i) + oopsend = i-1; + /* kernel end-of-oops marker (not including marker itself) */ + else if (strstr(curline, "---[ end trace")) + oopsend = i-1; + + if (oopsend <= i) + { + VERB3 log("End of oops at line %d (%d): '%s'", oopsend, i, lines_info[oopsend].ptr); + if (record_oops(oopses, lines_info, oopsstart, oopsend)) + oopsesfound++; + oopsstart = -1; + inbacktrace = 0; + } + } + + prevlevel = lines_info[i].level; + i++; + + if (oopsstart >= 0) + { + /* Do we have a suspiciously long oops? Cancel it */ + if (i-oopsstart > 60) + { + inbacktrace = 0; + oopsstart = -1; + VERB3 log("Dropped oops, too long"); + continue; + } + if (!inbacktrace && i-oopsstart > 40) + { + /*inbacktrace = 0; - already is */ + oopsstart = -1; + VERB3 log("Dropped oops, too long"); + continue; + } + } + } /* while (i < linecount) */ + + /* process last oops if we have one */ + if (oopsstart >= 0 && inbacktrace) + { + int oopsend = i-1; + VERB3 log("End of oops at line %d (end of file): '%s'", oopsend, lines_info[oopsend].ptr); + if (record_oops(oopses, lines_info, oopsstart, oopsend)) + oopsesfound++; + } + + free(lines_info); + return oopsesfound; +} + +#define MAX_SCAN_BLOCK (4*1024*1024) +#define READ_AHEAD (10*1024) + +static void scan_dmesg(GList **oops_list) +{ + VERB1 log("Scanning dmesg"); + + /* syslog(3) - read the last len bytes from the log buffer + * (non-destructively), but dont read more than was written + * into the buffer since the last "clear ring buffer" cmd. + * Returns the number of bytes read. + */ + char *buffer = xzalloc(16*1024); + syscall(__NR_syslog, 3, buffer, 16*1024 - 1); /* always NUL terminated */ + extract_oopses(oops_list, buffer, strlen(buffer)); + free(buffer); +} + +static int scan_syslog_file(GList **oops_list, int fd, struct stat *statbuf, int partial_line_len) +{ + /* fstat(fd, &statbuf) was just done by caller */ + + off_t cur_pos = lseek(fd, 0, SEEK_CUR); + if (statbuf->st_size <= cur_pos) + return partial_line_len; /* we are at EOF, nothing to do */ + + VERB3 log("File grew by %llu bytes, from %llu to %llu", + (long long)(statbuf->st_size - cur_pos), + (long long)(cur_pos), + (long long)(statbuf->st_size)); + + /* Do not try to allocate an absurd amount of memory. */ + int sz = MAX_SCAN_BLOCK - READ_AHEAD; + if (sz > statbuf->st_size - cur_pos) + sz = statbuf->st_size - cur_pos; + + /* Rewind to the beginning of the current line */ + if (partial_line_len > 0 && lseek(fd, -partial_line_len, SEEK_CUR) != (off_t)-1) + { + VERB3 log("Went back %u bytes", partial_line_len); + cur_pos -= partial_line_len; + sz += partial_line_len; + } + + /* + * In theory we have a race here, since someone can spew + * to /var/log/messages before we read it in... + * We try to deal with it by reading READ_AHEAD extra. + */ + sz += READ_AHEAD; + char *buffer = xzalloc(sz); + + partial_line_len = 0; + do { + int r = full_read(fd, buffer, sz-1); + if (r <= 0) + break; + VERB3 log("Read %u bytes", r); + + /* For future scans, try to find where last (incomplete) line starts */ + partial_line_len = 0; + char *last_newline = memrchr(buffer, '\n', r) ? : buffer-1; + partial_line_len = buffer+r - (last_newline+1); + if (partial_line_len > 500) /* cap it */ + partial_line_len = 500; + + extract_oopses(oops_list, buffer, r); + cur_pos += r; + } while (cur_pos < statbuf->st_size); + + free(buffer); + + return partial_line_len; +} + +/* returns number of errors */ +static int save_oops_to_dump_dir(GList *oops_list, unsigned oops_cnt) +{ + unsigned countdown = 16; /* do not report hundreds of oopses */ + unsigned idx = oops_cnt; + time_t t = time(NULL); + pid_t my_pid = getpid(); + + VERB1 log("Saving %u oopses as crash dump dirs", idx >= countdown ? countdown-1 : idx); + + char *tainted_str = NULL; + /* once tainted flag is set to 1, only restart can reset the flag to 0 */ + FILE *tainted_fp = fopen("/proc/sys/kernel/tainted", "r"); + if (tainted_fp) + { + tainted_str = xmalloc_fgetline(tainted_fp); + fclose(tainted_fp); + } + else + error_msg("/proc/sys/kernel/tainted does not exist"); + + int errors = 0; + + while (idx != 0 && --countdown != 0) + { + char path[sizeof(DEBUG_DUMPS_DIR"/kerneloops-%lu-%lu-%lu") + 3 * sizeof(long)*3]; + sprintf(path, DEBUG_DUMPS_DIR"/kerneloops-%lu-%lu-%lu", (long)t, (long)my_pid, (long)idx); + + char *first_line = (char*)g_list_nth_data(oops_list, --idx); + char *second_line = (char*)strchr(first_line, '\n'); /* never NULL */ + *second_line++ = '\0'; + + struct dump_dir *dd = dd_create(path, /*uid:*/ 0); + if (dd) + { + dd_create_basic_files(dd, /*uid:*/ 0); + dd_save_text(dd, FILENAME_ANALYZER, "Kerneloops"); + dd_save_text(dd, FILENAME_EXECUTABLE, "kernel"); + dd_save_text(dd, FILENAME_KERNEL, first_line); + dd_save_text(dd, FILENAME_CMDLINE, "not_applicable"); + dd_save_text(dd, FILENAME_BACKTRACE, second_line); + /* Optional, makes generated bz more informative */ + strchrnul(second_line, '\n')[0] = '\0'; + dd_save_text(dd, FILENAME_REASON, second_line); + + if (tainted_str && tainted_str[0] != '0') + dd_save_text(dd, FILENAME_TAINTED, tainted_str); + + free(tainted_str); + dd_close(dd); + } + else + errors++; + } + + return errors; +} + +int main(int argc, char **argv) +{ + char *env_verbose = getenv("ABRT_VERBOSE"); + if (env_verbose) + g_verbose = atoi(env_verbose); + + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( + PROGNAME" [-vsrdow] FILE\n" + "\n" + "Extract oops from syslog/dmesg file" + ); + enum { + OPT_v = 1 << 0, + OPT_s = 1 << 1, + OPT_r = 1 << 2, + OPT_d = 1 << 3, + OPT_o = 1 << 4, + OPT_w = 1 << 5, + }; + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_BOOL('s', NULL, NULL, _("Log to syslog")), + OPT_BOOL('r', NULL, NULL, _("Parse kernel's message buffer before parsing FILE")), + OPT_BOOL('d', NULL, NULL, _("Create ABRT dump for every oops found")), + OPT_BOOL('o', NULL, NULL, _("Print found oopses on standard output")), + OPT_BOOL('w', NULL, NULL, _("Do not exit, watch the file for new oopses")), + OPT_END() + }; + unsigned opts = parse_opts(argc, argv, program_options, program_usage_string); + + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + msg_prefix = PROGNAME; + if ((opts & OPT_s) + || getenv("ABRT_SYSLOG") + ) { + openlog(msg_prefix, 0, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + } + + argv += optind; + if (!argv[0]) + show_usage_and_die(program_usage_string, program_options); + const char *filename = argv[0]; + + int inotify_fd = -1; + if (opts & OPT_w) + { + inotify_fd = inotify_init(); + if (inotify_fd == -1) + perror_msg_and_die("inotify_init failed"); + //close_on_exec_on(inotify_fd); + } + + GList *oops_list = NULL; + if (opts & OPT_r) + /* Scan dmesg (only once even with -w) */ + scan_dmesg(&oops_list); + + int partial_line_len = 0; + struct stat statbuf; + int file_fd = -1; + int wd = -1; + + while (1) /* loops only if -w */ + { + /* If file is already opened, parse oopses from current pos */ + if (file_fd >= 0) + { + memset(&statbuf, 0, sizeof(statbuf)); + fstat(file_fd, &statbuf); + partial_line_len = scan_syslog_file(&oops_list, file_fd, &statbuf, partial_line_len); + + /* Was file deleted or replaced? */ + ino_t fd_ino = statbuf.st_ino; + if (stat(filename, &statbuf) != 0 || statbuf.st_ino != fd_ino) /* yes */ + { + VERB2 log("Can't stat '%s', closing fd", filename); + close(file_fd); + if (wd >= 0) + inotify_rm_watch(inotify_fd, wd); + file_fd = -1; + wd = -1; + partial_line_len = 0; + } + } + + /* If file isn't opened, try to open it and parse oopses */ + if (file_fd < 0) + { + file_fd = open(filename, O_RDONLY); + if (file_fd < 0) + { + if (!(opts & OPT_w)) + perror_msg_and_die("Can't open '%s'", filename); + /* with -w, we ignore open errors */ + } + else + { + VERB2 log("Opened '%s'", filename); + /* For -w case, if we don't have inotify watch yet, open one */ + if ((opts & OPT_w) && wd < 0) + { + wd = inotify_add_watch(inotify_fd, filename, IN_MODIFY | IN_MOVE_SELF | IN_DELETE_SELF); + if (wd < 0) + perror_msg("inotify_add_watch failed on '%s'", filename); + else + VERB2 log("Added inotify watch for '%s'", filename); + } + if (fstat(file_fd, &statbuf) == 0) + { + /* If file is large, skip the beginning. + * IOW: ignore old log messages because they are unlikely + * to have sufficiently recent data to be useful. + */ + if (statbuf.st_size > (MAX_SCAN_BLOCK - READ_AHEAD)) + lseek(file_fd, statbuf.st_size - (MAX_SCAN_BLOCK - READ_AHEAD), SEEK_SET); + /* Note that statbuf is filled by fstat by now, + * scan_syslog_file needs that + */ + partial_line_len = scan_syslog_file(&oops_list, file_fd, &statbuf, partial_line_len); + } + } + } + + /* Print and/or save oopses */ + int oops_cnt = g_list_length(oops_list); + if (!(opts & OPT_w) || oops_cnt != 0) + log("Found oopses: %d", oops_cnt); + if (oops_cnt != 0) + { + if (opts & OPT_o) + { + int i = 0; + while (i < oops_cnt) + printf("\nVersion: %s", (char*)g_list_nth_data(oops_list, i++)); + } + if (opts & OPT_d) + { + log("Creating dump directories"); + int errors = save_oops_to_dump_dir(oops_list, oops_cnt); + if (errors > 0) + log("%d errors while dumping oopses", errors); + } + } + list_free_with_free(oops_list); + oops_list = NULL; + + /* Done if no -w */ + if (!(opts & OPT_w)) + break; + + /* Even if log file grows all the time, say, a new line every 5 ms, + * we don't want to scan it all the time. Sleep a bit and let it grow + * in bigger increments. + * Sleep longer if file does not exist. + */ + sleep(file_fd >= 0 ? 1 : 59); + + /* Now wait for it to change, be moved or deleted */ + if (wd >= 0) + { + char buf[4096]; + VERB3 log("Waiting for '%s' to change", filename); + /* We block here: */ + int len = read(inotify_fd, buf, sizeof(buf)); + if (len < 0 && errno != EINTR) /* I saw EINTR here on strace attach */ + perror_msg("Error reading inotify fd"); + /* we don't actually check what happened to file - + * the code will handle all possibilities. + */ + VERB3 log("Change in '%s' detected", filename); + } + + } /* while (1) */ + + return 0; +} diff --git a/src/plugins/abrt-plugins.7 b/src/plugins/abrt-plugins.7 index 28de8f02..d8027057 100644 --- a/src/plugins/abrt-plugins.7 +++ b/src/plugins/abrt-plugins.7 @@ -33,7 +33,6 @@ stored in the \fI/etc/abrt/plugins\fP directory. .IR abrt-Bugzilla (7), .IR abrt-Upload (7), .IR abrt-KerneloopsReporter (7), -.IR abrt-KerneloopsScanner (7), .IR abrt-Logger (7), .IR abrt-Mailx (7), .SH AUTHOR diff --git a/src/plugins/abrt_rh_support.c b/src/plugins/abrt_rh_support.c index 04e2c8ef..9a48485b 100644 --- a/src/plugins/abrt_rh_support.c +++ b/src/plugins/abrt_rh_support.c @@ -212,69 +212,6 @@ reportfile_free(reportfile_t* file) // -// post_signature() -// -char* -post_signature(const char* baseURL, bool ssl_verify, const char* signature) -{ - char *URL = concat_path_file(baseURL, "/signatures"); - - abrt_post_state_t *state = new_abrt_post_state(0 - + ABRT_POST_WANT_HEADERS - + ABRT_POST_WANT_BODY - + ABRT_POST_WANT_ERROR_MSG - + (ssl_verify ? ABRT_POST_WANT_SSL_VERIFY : 0) - ); - int http_resp_code = abrt_post_string(state, URL, "application/xml", signature); - free(URL); - - char *retval; - const char *strata_msg; - switch (http_resp_code) - { - case 200: - case 201: - if (state->body) - { - retval = state->body; - state->body = NULL; - break; - } - strata_msg = find_header_in_abrt_post_state(state, "Strata-Message:"); - if (strata_msg && strcmp(strata_msg, "CREATED") != 0) { - retval = xstrdup(strata_msg); - break; - } - retval = xstrdup("Signature submitted successfully"); - break; - - default: - strata_msg = find_header_in_abrt_post_state(state, "Strata-Message:"); - if (strata_msg) - { - retval = xasprintf("Error (HTTP response %d): %s", - http_resp_code, - strata_msg); - break; - } - if (state->curl_error_msg) - { - if (http_resp_code >= 0) - retval = xasprintf("Error (HTTP response %d): %s", http_resp_code, state->curl_error_msg); - else - retval = xasprintf("Error in HTTP transaction: %s", state->curl_error_msg); - break; - } - retval = xasprintf("Error (HTTP response %d), body:\n%s", http_resp_code, state->body); - break; - } - - free_abrt_post_state(state); - return retval; -} - - -// // send_report_to_new_case() // @@ -391,6 +328,15 @@ send_report_to_new_case(const char* baseURL, char *case_location = find_header_in_abrt_post_state(case_state, "Location:"); switch (case_state->http_resp_code) { + case 404: + /* Not strictly necessary (default branch would deal with it too), + * but makes this typical error less cryptic: + * instead of returning html-encoded body, we show short concise message, + * and show offending URL (typos in which is a typical cause) */ + retval = xasprintf("error in case creation, " + "HTTP code: 404 (Not found), URL:'%s'", case_url); + break; + case 301: /* "301 Moved Permanently" (for example, used to move http:// to https://) */ case 302: /* "302 Found" (just in case) */ case 305: /* "305 Use Proxy" */ @@ -401,27 +347,16 @@ send_report_to_new_case(const char* baseURL, free_abrt_post_state(case_state); goto redirect_case; } - goto bad_resp_code; - - case 404: - /* Not strictly necessary, but makes this typical error less cryptic: - * instead of returning html-encoded body, we show short concise message, - * and show offending URL (typos in which is a typical cause) */ - retval = xasprintf("error in case creation, " - "HTTP code: 404 (Not found), URL:'%s'", case_url); - break; + /* fall through */ default: - bad_resp_code: errmsg = case_state->curl_error_msg; - if (errmsg) + if (errmsg && errmsg[0]) retval = xasprintf("error in case creation: %s", errmsg); else { - errmsg = find_header_in_abrt_post_state(case_state, "Strata-Message:"); - if ((!errmsg || !errmsg[0]) && case_state->body && case_state->body[0]) - errmsg = case_state->body; - if (errmsg) + errmsg = case_state->body; + if (errmsg && errmsg[0]) retval = xasprintf("error in case creation, HTTP code: %d, server says: '%s'", case_state->http_resp_code, errmsg); else @@ -467,9 +402,7 @@ send_report_to_new_case(const char* baseURL, default: /* Case Creation Succeeded, attachement FAILED */ - errmsg = find_header_in_abrt_post_state(atch_state, "Strata-Message:"); - if (!errmsg || !errmsg[0]) - errmsg = atch_state->curl_error_msg; + errmsg = atch_state->curl_error_msg; if (atch_state->body && atch_state->body[0]) { if (errmsg && errmsg[0] diff --git a/src/plugins/ccpp_events.conf b/src/plugins/ccpp_events.conf new file mode 100644 index 00000000..1ad57608 --- /dev/null +++ b/src/plugins/ccpp_events.conf @@ -0,0 +1,17 @@ +EVENT=post-create analyzer=CCpp abrt-action-analyze-c + +#TODO: implement this (or add this functionality to abrt-action-install-debuginfo): +#EVENT=analyze analyzer=CCpp backtrace= trim-debuginfo-cache /var/cache/abrt-di 4096m + +#TODO: can we still specify additional directories to search for debuginfos, +# or was this ability lost with move to python installer? +EVENT=analyze analyzer=CCpp backtrace= abrt-action-install-debuginfo --core="$DUMP_DIR/coredump" +EVENT=analyze analyzer=CCpp backtrace= abrt-action-generate-backtrace + +# Same as "analyze", but executed when user requests "refresh" in GUI +#EVENT=reanalyze analyzer=CCpp trim-debuginfo-cache /var/cache/abrt-di 4096m +EVENT=reanalyze analyzer=CCpp abrt-action-install-debuginfo --core="$DUMP_DIR/coredump" +EVENT=reanalyze analyzer=CCpp abrt-action-generate-backtrace + +EVENT=report_Bugzilla analyzer=CCpp abrt-action-bugzilla -c /etc/abrt/plugins/Bugzilla.conf +EVENT=report_Logger analyzer=CCpp abrt-action-print -o /var/log/abrt.log diff --git a/src/report-python/Makefile.am b/src/report-python/Makefile.am new file mode 100644 index 00000000..a00e2d7b --- /dev/null +++ b/src/report-python/Makefile.am @@ -0,0 +1,42 @@ +pyreportexecdir = $(pyexecdir)/report + +pyreportexec_PYTHON = \ + __init__.py \ + accountmanager.py + +pyreportexec_LTLIBRARIES = _pyreport.la + +_pyreport_la_SOURCES = \ + reportmodule.c \ + crash_data.c \ + dump_dir.c \ + run_event.c \ + common.h +_pyreport_la_CPPFLAGS = \ + -I$(srcdir)/../include/report -I$(srcdir)/../include \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + $(GLIB_CFLAGS) \ + $(PYTHON_CFLAGS) \ + -D_GNU_SOURCE \ + -Wall -Werror +_pyreport_la_LDFLAGS = \ + -module \ + -avoid-version \ + -export-symbols-regex init_pyreport +_pyreport_la_LIBADD = \ + ../lib/libreport.la + +# report compat: + +pyreportioexecdir = $(pyexecdir)/report/io + +pyreportioexec_PYTHON = \ + io/__init__.py \ + io/GTKIO.py \ + io/NewtIO.py \ + io/TextIO.py diff --git a/src/report-python/__init__.py b/src/report-python/__init__.py new file mode 100644 index 00000000..0b0f5685 --- /dev/null +++ b/src/report-python/__init__.py @@ -0,0 +1,130 @@ +from _pyreport import * + + +#Compatibility with report package: + +import os + +def createAlertSignature(component, hashmarkername, hashvalue, summary, alertSignature): + + SYSTEM_RELEASE_PATHS = ["/etc/system-release","/etc/redhat-release"] + ####SYSTEM_RELEASE_DEPS = ["system-release", "redhat-release"] + + _hardcoded_default_product = "" + _hardcoded_default_version = "" + + ####def getProduct_fromPRODUCT(): + #### try: + #### import product + #### return product.productName + #### except: + #### return "" + + ####def getVersion_fromPRODUCT(): + #### try: + #### import product + #### return product.productVersion + #### except: + #### return "" + + ####def getProduct_fromRPM(): + #### try: + #### import rpm + #### ts = rpm.TransactionSet() + #### for each_dep in SYSTEM_RELEASE_DEPS: + #### mi = ts.dbMatch('provides', each_dep) + #### for h in mi: + #### if h['name']: + #### return h['name'].split("-")[0].capitalize() + #### + #### return "" + #### except: + #### return "" + + ####def getVersion_fromRPM(): + #### try: + #### import rpm + #### ts = rpm.TransactionSet() + #### for each_dep in SYSTEM_RELEASE_DEPS: + #### mi = ts.dbMatch('provides', each_dep) + #### for h in mi: + #### if h['version']: + #### return str(h['version']) + #### return "" + #### except: + #### return "" + + def getProduct_fromFILE(): + for each_path in SYSTEM_RELEASE_PATHS: + try: + file = open(each_path, "r") + content = file.read() + if content.startswith("Red Hat Enterprise Linux"): + return "Red Hat Enterprise Linux" + if content.startswith("Fedora"): + return "Fedora" + i = content.find(" release") + if i > -1: + return content[0:i] + except: + pass + return "" + + def getVersion_fromFILE(): + for each_path in SYSTEM_RELEASE_PATHS: + try: + file = open(each_path, "r") + content = file.read() + if content.find("Rawhide") > -1: + return "rawhide" + clist = content.split(" ") + i = clist.index("release") + return clist[i+1] + except: + pass + return "" + + def getProduct(): + ####product = getProduct_fromPRODUCT() + ####if product: + #### return product + product = getProduct_fromFILE() + if product: + return product + ####product = getProduct_fromRPM() + ####if product: + #### return product + return _hardcoded_default_product + + def getVersion(): + ####version = getVersion_fromPRODUCT() + ####if version: + #### return version + version = getVersion_fromFILE() + if version: + return version + ####version = getVersion_fromRPM() + ####if version: + #### return version + return _hardcoded_default_version + + cd = crash_data() + cd.add("component", component) + cd.add("hashmarkername", hashmarkername) + cd.add("localhash", hashvalue) + cd.add("summary", summary) + cd.add("description", alertSignature) + cd.add("product", getProduct()) + cd.add("version", getVersion()) + return cd + +def report(cd, io_unused): + #dd = cd.create_dump_dir() + #dir_name = dd.name + #dd.close() + #r = os.spawnlp(P_WAIT, "abrt-handle-crashdump", "abrt-handle-crashdump", "-d", dirname, "-e" , "report"); + ### Silmpler alternative: + state = run_event_state() + #state.logging_callback = logfunc + r = state.run_event_on_crash_data(cd, "report") + return r diff --git a/src/report-python/accountmanager.py b/src/report-python/accountmanager.py new file mode 100644 index 00000000..db8ed117 --- /dev/null +++ b/src/report-python/accountmanager.py @@ -0,0 +1,6 @@ +""" + Compatibility with report package +""" + +class AccountManager: + pass diff --git a/src/daemon/Daemon.h b/src/report-python/common.h index 158b48fb..d6d209e9 100644 --- a/src/daemon/Daemon.h +++ b/src/report-python/common.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2009 Denys Vlasenko (dvlasenk@redhat.com) + Copyright (C) 2009 Abrt team. Copyright (C) 2009 RedHat inc. This program is free software; you can redistribute it and/or modify @@ -16,21 +16,32 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef DAEMON_H_ -#define DAEMON_H_ - -#include <pthread.h> -#include "abrt_types.h" -#include "abrt_crash_dump.h" - -class CCrashWatcher; -class CCommLayerServer; -class CPluginManager; - -/* Used for sending dbus signals */ -extern CCommLayerServer *g_pCommLayer; - -/* Collection of loaded plugins */ -extern CPluginManager* g_pPluginManager; - -#endif +#include <Python.h> + +#include "dump_dir.h" +#include "crash_data.h" +#include "run_event.h" + +/* exception object */ +extern PyObject *ReportError; + +/* type objects */ +extern PyTypeObject p_crash_data_type; +extern PyTypeObject p_dump_dir_type; +extern PyTypeObject p_run_event_state_type; + +/* module-level functions */ +PyObject *p_dd_opendir(PyObject *module, PyObject *args); +PyObject *p_dd_create(PyObject *module, PyObject *args); +PyObject *p_delete_dump_dir(PyObject *pself, PyObject *args); + +/* python objects' struct defs */ +typedef struct { + PyObject_HEAD + struct dump_dir *dd; +} p_dump_dir; + +typedef struct { + PyObject_HEAD + crash_data_t *cd; +} p_crash_data; diff --git a/src/report-python/crash_data.c b/src/report-python/crash_data.c new file mode 100644 index 00000000..217560e5 --- /dev/null +++ b/src/report-python/crash_data.c @@ -0,0 +1,137 @@ +/* + Copyright (C) 2010 Abrt team. + Copyright (C) 2010 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 <Python.h> +#include <structmember.h> + +#include <errno.h> +#include "common.h" + +static void +p_crash_data_dealloc(PyObject *pself) +{ + p_crash_data *self = (p_crash_data*)pself; + free_crash_data(self->cd); + self->cd = NULL; + self->ob_type->tp_free(pself); +} + +static PyObject * +p_crash_data_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + p_crash_data *self = (p_crash_data *)type->tp_alloc(type, 0); + if (self) + self->cd = new_crash_data(); + return (PyObject *)self; +} + +//static int +//p_crash_data_init(PyObject *pself, PyObject *args, PyObject *kwds) +//{ +// return 0; +//} + +/* +void add_to_crash_data_ext(crash_data_t *crash_data, + const char *name, + const char *content, + unsigned flags); +*/ +static PyObject *p_crash_data_add(PyObject *pself, PyObject *args) +{ + p_crash_data *self = (p_crash_data*)pself; + + const char *name; + const char *content; + int flags = 0; + if (!PyArg_ParseTuple(args, "ss|i", &name, &content, &flags)) + { + /* PyArg_ParseTuple raises the exception saying why it fails + * eg: TypeError: function takes exactly 2 arguments (1 given) + */ + return NULL; + } + add_to_crash_data_ext(self->cd, name, content, flags); + + /* every function returns PyObject, to return void we need to do this */ + Py_RETURN_NONE; +} + +/* struct crash_item *get_crash_data_item_or_NULL(crash_data_t *crash_data, const char *key); */ +static PyObject *p_get_crash_data_item(PyObject *pself, PyObject *args) +{ + p_crash_data *self = (p_crash_data*)pself; + const char *key; + if (!PyArg_ParseTuple(args, "s", &key)) + { + return NULL; + } + struct crash_item *ci = get_crash_data_item_or_NULL(self->cd, key); + if (ci == NULL) + { + Py_RETURN_NONE; + } + return Py_BuildValue("sI", ci->content, ci->flags); +} + +/* struct dump_dir *create_dump_dir_from_crash_data(crash_data_t *crash_data, const char *base_dir_name); */ +static PyObject *p_create_dump_dir_from_crash_data(PyObject *pself, PyObject *args) +{ + p_crash_data *self = (p_crash_data*)pself; + const char *base_dir_name = NULL; + if (!PyArg_ParseTuple(args, "|s", &base_dir_name)) + { + return NULL; + } + p_dump_dir *new_dd = PyObject_New(p_dump_dir, &p_dump_dir_type); + if (!new_dd) + return NULL; + struct dump_dir *dd = create_dump_dir_from_crash_data(self->cd, base_dir_name); + if (!dd) + { + PyObject_Del((PyObject*)new_dd); + PyErr_SetString(ReportError, "Can't create the dump dir"); + return NULL; + } + new_dd->dd = dd; + return (PyObject*)new_dd; +} + +//static PyMemberDef p_crash_data_members[] = { +// { NULL } +//}; + +static PyMethodDef p_crash_data_methods[] = { + /* method_name, func, flags, doc_string */ + { "add" , p_crash_data_add , METH_VARARGS }, + { "get" , p_get_crash_data_item , METH_VARARGS }, + { "create_dump_dir", p_create_dump_dir_from_crash_data, METH_VARARGS }, + { NULL } +}; + +PyTypeObject p_crash_data_type = { + PyObject_HEAD_INIT(NULL) + .tp_name = "report.crash_data", + .tp_basicsize = sizeof(p_crash_data), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_new = p_crash_data_new, + .tp_dealloc = p_crash_data_dealloc, + //.tp_init = p_crash_data_init, + //.tp_members = p_crash_data_members, + .tp_methods = p_crash_data_methods, +}; diff --git a/src/report-python/dump_dir.c b/src/report-python/dump_dir.c new file mode 100644 index 00000000..c8ff3798 --- /dev/null +++ b/src/report-python/dump_dir.c @@ -0,0 +1,264 @@ +/* + On-disk storage of crash dumps + + Copyright (C) 2010 Abrt team + Copyright (C) 2010 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 <Python.h> +#include <structmember.h> + +#include <errno.h> +#include "common.h" + +/*** init/cleanup ***/ + +static PyObject * +p_dump_dir_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + p_dump_dir *self = (p_dump_dir *)type->tp_alloc(type, 0); + if (self) + self->dd = NULL; + return (PyObject *)self; +} + +static void +p_dump_dir_dealloc(PyObject *pself) +{ + p_dump_dir *self = (p_dump_dir*)pself; + dd_close(self->dd); + self->dd = NULL; + self->ob_type->tp_free(pself); +} + +//static int +//p_dump_dir_init(PyObject *pself, PyObject *args, PyObject *kwds) +//{ +// return 0; +//} + + +/*** methods ***/ + +/* void dd_close(struct dump_dir *dd); */ +static PyObject *p_dd_close(PyObject *pself, PyObject *args) +{ + p_dump_dir *self = (p_dump_dir*)pself; + dd_close(self->dd); + self->dd = NULL; + Py_RETURN_NONE; +} + +/* void dd_delete(struct dump_dir *dd); */ +static PyObject *p_dd_delete(PyObject *pself, PyObject *args) +{ + p_dump_dir *self = (p_dump_dir*)pself; +//Do we want to disallow delete() on non-opened dd? +// if (!self->dd) +// { +// PyErr_SetString(ReportError, "dump dir is not open"); +// return NULL; +// } + dd_delete(self->dd); + self->dd = NULL; + Py_RETURN_NONE; +} + +/* int dd_exist(struct dump_dir *dd, const char *path); */ +static PyObject *p_dd_exist(PyObject *pself, PyObject *args) +{ + p_dump_dir *self = (p_dump_dir*)pself; + if (!self->dd) + { + PyErr_SetString(ReportError, "dump dir is not open"); + return NULL; + } + const char *path; + if (!PyArg_ParseTuple(args, "s", &path)) + { + return NULL; + } + return Py_BuildValue("i", dd_exist(self->dd, path)); +} + +/* DIR *dd_init_next_file(struct dump_dir *dd); */ +//static PyObject *p_dd_init_next_file(PyObject *pself, PyObject *args); +/* int dd_get_next_file(struct dump_dir *dd, char **short_name, char **full_name); */ +//static PyObject *p_dd_get_next_file(PyObject *pself, PyObject *args); + +/* char* dd_load_text_ext(const struct dump_dir *dd, const char *name, unsigned flags); */ +/* char* dd_load_text(const struct dump_dir *dd, const char *name); */ +static PyObject *p_dd_load_text(PyObject *pself, PyObject *args) +{ + p_dump_dir *self = (p_dump_dir*)pself; + if (!self->dd) + { + PyErr_SetString(ReportError, "dump dir is not open"); + return NULL; + } + const char *name; + int flags = 0; + if (!PyArg_ParseTuple(args, "s|i", &name, &flags)) + { + return NULL; + } + char *val = dd_load_text_ext(self->dd, name, flags); + PyObject *obj = Py_BuildValue("s", val); /* NB: if val is NULL, obj is None */ + free(val); + return obj; +} + +/* void dd_save_text(struct dump_dir *dd, const char *name, const char *data); */ +static PyObject *p_dd_save_text(PyObject *pself, PyObject *args) +{ + p_dump_dir *self = (p_dump_dir*)pself; + if (!self->dd) + { + PyErr_SetString(ReportError, "dump dir is not open"); + return NULL; + } + const char *name; + const char *data; + if (!PyArg_ParseTuple(args, "ss", &name, &data)) + { + return NULL; + } + dd_save_text(self->dd, name, data); + Py_RETURN_NONE; +} + +/* void dd_save_binary(struct dump_dir *dd, const char *name, const char *data, unsigned size); */ +static PyObject *p_dd_save_binary(PyObject *pself, PyObject *args) +{ + p_dump_dir *self = (p_dump_dir*)pself; + if (!self->dd) + { + PyErr_SetString(ReportError, "dump dir is not open"); + return NULL; + } + const char *name; + const char *data; + unsigned size; + if (!PyArg_ParseTuple(args, "ssI", &name, &data, &size)) + { + return NULL; + } + dd_save_binary(self->dd, name, data, size); + Py_RETURN_NONE; +} + + +/*** attribute getters/setters ***/ + +static PyObject *get_name(PyObject *pself, void *unused) +{ + p_dump_dir *self = (p_dump_dir*)pself; + if (self->dd) + return Py_BuildValue("s", self->dd->dd_dirname); + Py_RETURN_NONE; +} + +//static PyObject *set_name(PyObject *pself, void *unused) +//{ +// PyErr_SetString(ReportError, "dump dir name is not settable"); +// Py_RETURN_NONE; +//} + + +/*** type object ***/ + +static PyMethodDef p_dump_dir_methods[] = { + /* method_name, func, flags, doc_string */ + { "close" , p_dd_close, METH_NOARGS, NULL }, + { "delete" , p_dd_delete, METH_NOARGS, NULL }, + { "exist" , p_dd_exist, METH_VARARGS, NULL }, + { "load_text" , p_dd_load_text, METH_VARARGS, NULL }, + { "save_text" , p_dd_save_text, METH_VARARGS, NULL }, + { "save_binary", p_dd_save_binary, METH_VARARGS, NULL }, + { NULL } +}; + +static PyGetSetDef p_dump_dir_getset[] = { + /* attr_name, getter_func, setter_func, doc_string, void_param */ + { "name", get_name, NULL /*set_name*/ }, + { NULL } +}; + +/* Support for "dd = dd_opendir(...); if [not] dd: ..." */ +static int p_dd_is_non_null(PyObject *pself) +{ + p_dump_dir *self = (p_dump_dir*)pself; + return self->dd != NULL; +} +static PyNumberMethods p_dump_dir_number_methods = { + .nb_nonzero = p_dd_is_non_null, +}; + +PyTypeObject p_dump_dir_type = { + PyObject_HEAD_INIT(NULL) + .tp_name = "report.dump_dir", + .tp_basicsize = sizeof(p_dump_dir), + /* Py_TPFLAGS_BASETYPE means "can be subtyped": */ + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_new = p_dump_dir_new, + .tp_dealloc = p_dump_dir_dealloc, + //.tp_init = p_dump_dir_init, + //.tp_members = p_dump_dir_members, + .tp_methods = p_dump_dir_methods, + .tp_as_number = &p_dump_dir_number_methods, + .tp_getset = p_dump_dir_getset, +}; + + +/*** module-level functions ***/ + +/* struct dump_dir *dd_opendir(const char *dir, int flags); */ +PyObject *p_dd_opendir(PyObject *module, PyObject *args) +{ + const char *dir; + int flags = 0; + if (!PyArg_ParseTuple(args, "s|i", &dir, &flags)) + return NULL; + p_dump_dir *new_dd = PyObject_New(p_dump_dir, &p_dump_dir_type); + if (!new_dd) + return NULL; + new_dd->dd = dd_opendir(dir, flags); + return (PyObject*)new_dd; +} + +/* struct dump_dir *dd_create(const char *dir, uid_t uid); */ +PyObject *p_dd_create(PyObject *module, PyObject *args) +{ + const char *dir; + int uid = -1; + if (!PyArg_ParseTuple(args, "s|i", &dir, &uid)) + return NULL; + p_dump_dir *new_dd = PyObject_New(p_dump_dir, &p_dump_dir_type); + if (!new_dd) + return NULL; + new_dd->dd = dd_create(dir, uid); + return (PyObject*)new_dd; +} + +/* void delete_dump_dir(const char *dirname); */ +PyObject *p_delete_dump_dir(PyObject *pself, PyObject *args) +{ + const char *dirname; + if (!PyArg_ParseTuple(args, "s", &dirname)) + return NULL; + delete_dump_dir(dirname); + Py_RETURN_NONE; +} diff --git a/src/report-python/io/GTKIO.py b/src/report-python/io/GTKIO.py new file mode 100644 index 00000000..4cc8766e --- /dev/null +++ b/src/report-python/io/GTKIO.py @@ -0,0 +1,11 @@ +""" + Compatibility with report package +""" + +class GTKIO: + def __init__(self, loginManager = None): + pass + +#class FailDialog(): +# def __init__(self, title, message): +# pass diff --git a/src/report-python/io/NewtIO.py b/src/report-python/io/NewtIO.py new file mode 100644 index 00000000..10eae284 --- /dev/null +++ b/src/report-python/io/NewtIO.py @@ -0,0 +1,7 @@ +""" + Compatibility with report package +""" + +class NewtIO: + def __init__(self, screen = None): + pass diff --git a/src/report-python/io/TextIO.py b/src/report-python/io/TextIO.py new file mode 100644 index 00000000..6162fb8b --- /dev/null +++ b/src/report-python/io/TextIO.py @@ -0,0 +1,6 @@ +""" + Compatibility with report package +""" + +class TextIO: + pass diff --git a/src/report-python/io/__init__.py b/src/report-python/io/__init__.py new file mode 100644 index 00000000..2ae28399 --- /dev/null +++ b/src/report-python/io/__init__.py @@ -0,0 +1 @@ +pass diff --git a/src/report-python/reportmodule.c b/src/report-python/reportmodule.c new file mode 100644 index 00000000..fd58a3bd --- /dev/null +++ b/src/report-python/reportmodule.c @@ -0,0 +1,83 @@ +/* + Copyright (C) 2010 Abrt team. + Copyright (C) 2010 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 <Python.h> + +#include "common.h" + +PyObject *ReportError; + +static PyMethodDef module_methods[] = { + /* method_name, func, flags, doc_string */ + { "dd_opendir" , p_dd_opendir , METH_VARARGS }, + { "dd_create" , p_dd_create , METH_VARARGS }, + { "delete_dump_dir", p_delete_dump_dir, METH_VARARGS }, + { NULL } +}; + +#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ +#define PyMODINIT_FUNC void +#endif +PyMODINIT_FUNC +init_pyreport(void) +{ + if (PyType_Ready(&p_crash_data_type) < 0) + { + printf("PyType_Ready(&p_crash_data_type) < 0\n"); + return; + } + if (PyType_Ready(&p_dump_dir_type) < 0) + { + printf("PyType_Ready(&p_dump_dir_type) < 0\n"); + return; + } + if (PyType_Ready(&p_run_event_state_type) < 0) + { + printf("PyType_Ready(&p_run_event_state_type) < 0\n"); + return; + } + + PyObject *m = Py_InitModule("_pyreport", module_methods); + //m = Py_InitModule3("_pyreport", module_methods, "Python wrapper for libreport"); + if (!m) + { + printf("m == NULL\n"); + return; + } + + /* init the exception object */ + ReportError = PyErr_NewException("_pyreport.error", NULL, NULL); + Py_INCREF(ReportError); + PyModule_AddObject(m, "error", ReportError); + + /* init type objects */ + Py_INCREF(&p_crash_data_type); + PyModule_AddObject(m, "crash_data", (PyObject *)&p_crash_data_type); + PyModule_AddObject(m, "CD_FLAG_BIN" , Py_BuildValue("i", CD_FLAG_BIN )); + PyModule_AddObject(m, "CD_FLAG_TXT" , Py_BuildValue("i", CD_FLAG_TXT )); + PyModule_AddObject(m, "CD_FLAG_ISEDITABLE" , Py_BuildValue("i", CD_FLAG_ISEDITABLE )); + PyModule_AddObject(m, "CD_FLAG_ISNOTEDITABLE", Py_BuildValue("i", CD_FLAG_ISNOTEDITABLE)); + + Py_INCREF(&p_dump_dir_type); + PyModule_AddObject(m, "dump_dir", (PyObject *)&p_dump_dir_type); + PyModule_AddObject(m, "DD_FAIL_QUIETLY" , Py_BuildValue("i", DD_FAIL_QUIETLY )); + PyModule_AddObject(m, "DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE", Py_BuildValue("i", DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE)); + + Py_INCREF(&p_run_event_state_type); + PyModule_AddObject(m, "run_event_state", (PyObject *)&p_run_event_state_type); +} diff --git a/src/report-python/run_event.c b/src/report-python/run_event.c new file mode 100644 index 00000000..6131df8e --- /dev/null +++ b/src/report-python/run_event.c @@ -0,0 +1,220 @@ +/* + Copyright (C) 2010 Abrt team. + Copyright (C) 2010 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 <Python.h> +#include <structmember.h> + +#include <errno.h> +#include "common.h" +#include "crash_data.h" +#include "run_event.h" + +typedef struct { + PyObject_HEAD + struct run_event_state *state; + PyObject *post_run_callback; + PyObject *logging_callback; +} p_run_event_state; + + +/*** init/cleanup ***/ + +static int post_run_callback(const char *dump_dir_name, void *param); +static char *logging_callback(char *log_line, void *param); + +static PyObject *p_run_event_state_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + p_run_event_state *self = (p_run_event_state *)type->tp_alloc(type, 0); + if (self) + { + self->state = new_run_event_state(); + self->state->post_run_callback = post_run_callback; + self->state->logging_callback = logging_callback; + self->state->post_run_param = self; + self->state->logging_param = self; + } + return (PyObject *)self; +} + +static void p_run_event_state_dealloc(PyObject *pself) +{ + p_run_event_state *self = (p_run_event_state*)pself; + free_run_event_state(self->state); + self->state = NULL; + Py_XDECREF(self->post_run_callback); + self->post_run_callback = NULL; + Py_XDECREF(self->logging_callback); + self->logging_callback = NULL; + self->ob_type->tp_free(pself); +} + +//static int +//p_run_event_state_init(PyObject *pself, PyObject *args, PyObject *kwds) +//{ +// return 0; +//} + + +/*** methods ***/ + +/* First, C-level callback helpers for run_event_on_FOO(): */ +static int post_run_callback(const char *dump_dir_name, void *param) +{ + PyObject *obj = (PyObject*)param; + PyObject *ret = PyObject_CallMethod(obj, "post_run_callback", "(s)", dump_dir_name); + int r = 0; + if (ret) + { + r = PyInt_AsLong(ret); + Py_DECREF(ret); + } + // TODO: handle exceptions: if (PyErr_Occurred()) ... + return r; +} +static char *logging_callback(char *log_line, void *param) +{ + PyObject *obj = (PyObject*)param; + PyObject *ret = PyObject_CallMethod(obj, "logging_callback", "(s)", log_line); + Py_XDECREF(ret); + // TODO: handle exceptions: if (PyErr_Occurred()) ... + return log_line; /* signaling to caller that we didnt consume the string */ +} + +/* int run_event_on_dir_name(struct run_event_state *state, const char *dump_dir_name, const char *event); */ +static PyObject *p_run_event_on_dir_name(PyObject *pself, PyObject *args) +{ + p_run_event_state *self = (p_run_event_state*)pself; + const char *dump_dir_name; + const char *event; + if (!PyArg_ParseTuple(args, "ss", &dump_dir_name, &event)) + { + return NULL; + } + int r = run_event_on_dir_name(self->state, dump_dir_name, event); + PyObject *obj = Py_BuildValue("i", r); + return obj; +} + +/* int run_event_on_crash_data(struct run_event_state *state, crash_data_t *data, const char *event); */ +static PyObject *p_run_event_on_crash_data(PyObject *pself, PyObject *args) +{ + p_run_event_state *self = (p_run_event_state*)pself; + p_crash_data *cd; + const char *event; + if (!PyArg_ParseTuple(args, "O!s", &p_crash_data_type, &cd, &event)) + { + return NULL; + } + int r = run_event_on_crash_data(self->state, cd->cd, event); + PyObject *obj = Py_BuildValue("i", r); + return obj; +} + +/* TODO: char *list_possible_events(struct dump_dir *dd, const char *dump_dir_name, const char *pfx); */ + + +/*** attribute getters/setters ***/ + +static PyObject *get_post_run_callback(PyObject *pself, void *unused) +{ + p_run_event_state *self = (p_run_event_state*)pself; + if (self->post_run_callback) + { + Py_INCREF(self->post_run_callback); + return self->post_run_callback; + } + Py_RETURN_NONE; +} + +static PyObject *get_logging_callback(PyObject *pself, void *unused) +{ + p_run_event_state *self = (p_run_event_state*)pself; + if (self->logging_callback) + { + Py_INCREF(self->logging_callback); + return self->logging_callback; + } + Py_RETURN_NONE; +} + +static int set_post_run_callback(PyObject *pself, PyObject *callback, void *unused) +{ + p_run_event_state *self = (p_run_event_state*)pself; +//WRONG: we aren't a Python function, calling convention is different +// PyObject *callback; +// if (!PyArg_ParseTuple(args, "O", &callback)) +// return -1; + if (!PyCallable_Check(callback)) + { + PyErr_SetString(PyExc_TypeError, "parameter must be callable"); + return -1; + } + Py_INCREF(callback); + Py_XDECREF(self->post_run_callback); + self->post_run_callback = callback; + return 0; +} + +static int set_logging_callback(PyObject *pself, PyObject *callback, void *unused) +{ + p_run_event_state *self = (p_run_event_state*)pself; + if (!PyCallable_Check(callback)) + { + PyErr_SetString(PyExc_TypeError, "parameter must be callable"); + return -1; + } + Py_INCREF(callback); + Py_XDECREF(self->logging_callback); + self->logging_callback = callback; + return 0; +} + + +/*** type object ***/ + +//static PyMemberDef p_run_event_state_members[] = { +// { NULL } +//}; + +static PyMethodDef p_run_event_state_methods[] = { + /* method_name, func, flags, doc_string */ + { "run_event_on_dir_name" , p_run_event_on_dir_name , METH_VARARGS }, + { "run_event_on_crash_data", p_run_event_on_crash_data, METH_VARARGS }, + { NULL } +}; + +static PyGetSetDef p_run_event_state_getset[] = { + /* attr_name, getter_func, setter_func, doc_string, void_param */ + { "post_run_callback", get_post_run_callback, set_post_run_callback }, + { "logging_callback" , get_logging_callback , set_logging_callback }, + { NULL } +}; + +PyTypeObject p_run_event_state_type = { + PyObject_HEAD_INIT(NULL) + .tp_name = "report.run_event_state", + .tp_basicsize = sizeof(p_run_event_state), + /* Py_TPFLAGS_BASETYPE means "can be subtyped": */ + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_new = p_run_event_state_new, + .tp_dealloc = p_run_event_state_dealloc, + //.tp_init = p_run_event_state_init, + //.tp_members = p_run_event_state_members, + .tp_methods = p_run_event_state_methods, + .tp_getset = p_run_event_state_getset, +}; diff --git a/src/report-python/test_crash_data b/src/report-python/test_crash_data new file mode 100755 index 00000000..6f719a8f --- /dev/null +++ b/src/report-python/test_crash_data @@ -0,0 +1,21 @@ +#!/usr/bin/python + +from report import * + +cd = crash_data() +cd.add("foo", "bar") + +dd = cd.create_dump_dir() + +if dd: + print "dd is nonzero" +else: + print "dd is zero" + +print "closing" +dd.close() + +if dd: + print "dd is nonzero" +else: + print "dd is zero" diff --git a/src/report-python/test_crash_data2 b/src/report-python/test_crash_data2 new file mode 100755 index 00000000..2594f863 --- /dev/null +++ b/src/report-python/test_crash_data2 @@ -0,0 +1,10 @@ +#!/usr/bin/python + +from report import * + +cd = crash_data() +cd.add("foo", "bar") + +print "foo:", cd.get("foo") +print "nonexistent:", cd.get("nonexistent") +print "done" diff --git a/src/report-python/test_dd_create b/src/report-python/test_dd_create new file mode 100755 index 00000000..4da29b11 --- /dev/null +++ b/src/report-python/test_dd_create @@ -0,0 +1,25 @@ +#!/usr/bin/python + +from report import * + +dd = dd_create("testdir") +print dd + +if dd: + print "dd is nonzero" +else: + print "dd is zero" + +print "name:", dd.name +print "closing" +dd.close() + +if dd: + print "dd is nonzero" +else: + print "dd is zero" + +# Should fail here +dd.name = "qwe" + +print "Done" diff --git a/src/report-python/test_full b/src/report-python/test_full new file mode 100755 index 00000000..103535dd --- /dev/null +++ b/src/report-python/test_full @@ -0,0 +1,27 @@ +#!/usr/bin/python + +import sys +from report import * + +def run_event_on_crash_data(cd, event, log_function = None): + dd = cd.create_dump_dir("/tmp") + dir_name = dd.name + print "Created dump_dir:", dir_name + dd.close() + run_state = run_event_state() + if log_function: # maybe if callable(log_function)? + run_state.logging_callback = log_function + print "Running event:", event + r = run_state.run_event_on_dir_name(dir_name, event) + print "Deleting:", dir_name + delete_dump_dir(dir_name) + return r; + +def log_function(line): + print "LOG:", line + +cd = crash_data() +cd.add("foo", "bar") +cd.add("analyzer", "baz", CD_FLAG_ISNOTEDITABLE) +r = run_event_on_crash_data(cd, "post-create", log_function) +print "Result:", r diff --git a/src/report-python/test_full2 b/src/report-python/test_full2 new file mode 100755 index 00000000..734946eb --- /dev/null +++ b/src/report-python/test_full2 @@ -0,0 +1,17 @@ +#!/usr/bin/python + +import sys +from report import * + +def log_function(line): + print "LOG:", line + +cd = crash_data() +cd.add("foo", "bar") +cd.add("analyzer", "baz", CD_FLAG_ISNOTEDITABLE) + +st = run_event_state() +st.logging_callback = log_function +r = st.run_event_on_crash_data(cd, "post-create") + +print "Result:", r diff --git a/src/report-python/test_run_event_state b/src/report-python/test_run_event_state new file mode 100755 index 00000000..3e391407 --- /dev/null +++ b/src/report-python/test_run_event_state @@ -0,0 +1,13 @@ +#!/usr/bin/python + +from report import * + +def func(): + return 0 + +res = run_event_state() +print res +print res.post_run_callback +res.post_run_callback = func +res.logging_callback = func +print res.post_run_callback diff --git a/src/report-python/test_run_event_state1 b/src/report-python/test_run_event_state1 new file mode 100755 index 00000000..6c3584fe --- /dev/null +++ b/src/report-python/test_run_event_state1 @@ -0,0 +1,27 @@ +#!/usr/bin/python + +import sys +from report import * + +def post_run_callback(dump_dir_name): + return 0 + +def logging_callback(line): + print "LOG:", line + return + +res = run_event_state() +res.post_run_callback = post_run_callback +res.logging_callback = logging_callback + +dd = dd_create("testdir") +if not dd: + sys.exit(1) +dd.save_text("analyzer", "foo") +dd.close() + +res.run_event_on_dir_name("testdir", "post-create") + +dd = dd_opendir("testdir") +dd.delete() +dd.close() diff --git a/src/report-python/test_setroubleshoot_example b/src/report-python/test_setroubleshoot_example new file mode 100755 index 00000000..74428f16 --- /dev/null +++ b/src/report-python/test_setroubleshoot_example @@ -0,0 +1,18 @@ +#!/usr/bin/python + +import report +import report.io +import report.io.GTKIO +import report.accountmanager + +accounts = report.accountmanager.AccountManager() + +signature = report.createAlertSignature("selinux-policy", + "setroubleshoot", + "self.siginfo.get_hash()", + "self.summary", + "content") + +rc = report.report(signature, report.io.GTKIO.GTKIO(accounts)) + +print "rc:", rc diff --git a/src/report-python/test_setroubleshoot_example2 b/src/report-python/test_setroubleshoot_example2 new file mode 100755 index 00000000..8aebcdfe --- /dev/null +++ b/src/report-python/test_setroubleshoot_example2 @@ -0,0 +1,27 @@ +#!/usr/bin/python + +import report +import report.io +import report.io.GTKIO +import report.accountmanager + +accounts = report.accountmanager.AccountManager() + +signature = report.createAlertSignature("selinux-policy", + "setroubleshoot", + "self.siginfo.get_hash()", + "self.summary", + "content") + +# Won't send log anywhere: +#rc = report.report(signature, report.io.GTKIO.GTKIO(accounts)) + +# report.report() + logging: +def logging_callback(line): + print "LOG:", line + return +state = report.run_event_state() +state.logging_callback = logging_callback +rc = state.run_event_on_crash_data(signature, "report") + +print "rc:", rc |