From 8a28fc69bf1e99a7bf79b6362c4194d5628d6045 Mon Sep 17 00:00:00 2001 From: Jakub Filak Date: Wed, 12 Mar 2014 15:20:58 +0100 Subject: [PATCH] Make the agent configurable via configuration file Related to #34 Signed-off-by: Jakub Filak --- README | 34 +- etc/CMakeLists.txt | 6 + etc/java.conf | 36 ++ src/CMakeLists.txt | 5 +- src/abrt-checker.c | 247 ++--------- src/abrt-checker.h | 82 ++++ src/configuration.c | 414 ++++++++++++++++++ src/internal_libabrt.h | 1 + test/CMakeLists.txt | 5 +- test/unittests/CMakeLists.txt | 14 + test/unittests/check_abrt_java_connector.c | 138 ++++++ .../config_file_all_entries_populated.conf | 7 + 12 files changed, 762 insertions(+), 227 deletions(-) create mode 100644 etc/java.conf create mode 100644 src/configuration.c create mode 100644 test/unittests/CMakeLists.txt create mode 100644 test/unittests/check_abrt_java_connector.c create mode 100644 test/unittests/config_file_all_entries_populated.conf diff --git a/README b/README index 590db7e..c0b8ae9 100644 --- a/README +++ b/README @@ -16,10 +16,20 @@ It needs to be compiled as a shared native library (.so) and then loaded into JVM using -agentlib command line parameter. +Configuration +------------- + +The agent can be configured either via configuration file or via command line +arguments. The command line arguments have higher priority and overwrites +values loaded from the configuration file. + +The default path to the configuration file is '/etc/abrt/plugins/java.conf'. + + Examples -------- -Before you can run the example, you need to make sure that +Before you can run the examples, you need to make sure that libabrt-java-connector.so is placed somewhere in the ld searched directories or configure LD_LIBRARY_PATH environment variable to point to a directory containing the library. @@ -76,7 +86,7 @@ $ java -agentlib:abrt-java-connector=journald=off $MyClass -platform.jvmtiSuppo $ java -agentlib:abrt-java-connector=syslog=on $MyClass -platform.jvmtiSupported true Example5: -- this example shows how configure abrt-java-connector to fill 'executable' +- this example shows how to configure abrt-java-connector to fill 'executable' ABRT file with a path to a class on the bottom of the stack trace (the first method of thread) - this feature can be enabled via 'executable' option which can contain either @@ -86,3 +96,23 @@ $ java -agentlib:abrt-java-connector=executable=threadclass $MyClass -platform. - 'mainclass' is used when 'executable' option is not passed and 'executable' file is filled with full path $MyClass + + +Example6: +- this example shows how to enrich the exception report with extra debug information +- abrt-java-connector is capable to call a static method returning String at + time of processing exception +- while creating the exception report methods from the list stored in + 'debugmethod' option are called, not all of them, but only those whose defining + class was already loaded by 'System Class Loader' + +$ java -agentlib:abrt-java-connector=debugmethod=com.example.$MyClass.getMethod $MyClass + + +Example7: +- this example shows how to change the path to configuration file +- the default configuration file path is '/etc/abrt/plugins/java.conf' +- empty 'conffile' option means do not read any configuration file + + +$ java -agentlib:abrt-java-connector=conffile=/etc/foo/example.conf $MyClass diff --git a/etc/CMakeLists.txt b/etc/CMakeLists.txt index e7b4521..0d1d891 100644 --- a/etc/CMakeLists.txt +++ b/etc/CMakeLists.txt @@ -6,3 +6,9 @@ install(FILES java_event.conf.5 bugzilla_format_java.conf.5 bugzilla_formatdup_j install(FILES bugzilla_format_java.conf bugzilla_formatdup_java.conf DESTINATION ${SYSCONF_INSTALL_DIR}/libreport/plugins) + +install(FILES java.conf + DESTINATION ${SYSCONF_INSTALL_DIR}/abrt/plugins) + +install(FILES java.conf + DESTINATION ${SHARE_INSTALL_PREFIX}/abrt/conf.d/plugins) diff --git a/etc/java.conf b/etc/java.conf new file mode 100644 index 0000000..50e627e --- /dev/null +++ b/etc/java.conf @@ -0,0 +1,36 @@ +# Which frame of a stack trace to use for 'executable' +# Allowed options are 'mainclass' or 'threadclass' where +# 'mainclass' is used when this option is not configured. +# executable = threadclass + +# Enables reporting of exceptions to ABRT +# Default value: off +abrt = on + +# If enabled, exception reports are written to syslog +# Default value: off +# syslog = on + +# If enabled, exception reports are written to systemd-journald +# Default value: on +# journald = off + +# Path to directory or file for writing exception reports. +# Default value: +# output = /tmp/ + +# Comma separated list of exception types that are reported even +# if they are caught. +# Default value: +# caught = java.lang.UnsatisfiedLinkError, java.lang.ClassCastException + +# Comma separated list of methods whose return values are +# included in exception report. +# Methods in the list must be static, without arguments and return +# java.lang.String +# Default value: empty +# +# http://mail.openjdk.java.net/pipermail/distro-pkg-dev/2014-March/026533.html +# http://mail.openjdk.java.net/pipermail/distro-pkg-dev/2014-March/026551.html +# +debugmethod = net.sourceforge.jnlp.runtime.JNLPRuntime.getHistory diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d084401..fd2c144 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,13 +3,15 @@ include_directories(${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2}) pkg_check_modules(PC_JOURNALD REQUIRED libsystemd-journal) include_directories(${PC_LIBREPORT_INCLUDE_DIRS}) +include_directories(${PC_ABRT_INCLUDE_DIRS}) include_directories(${PC_JOURNALD_INCLUDE_DIRS}) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=c99 -pedantic") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0 -DVERBOSE") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DSILENT") -set(AbrtChecker_SRCS abrt-checker.c jthrowable_circular_buf.c jthread_map.c) +set(AbrtChecker_SRCS configuration.c abrt-checker.c + jthrowable_circular_buf.c jthread_map.c) add_library(AbrtChecker SHARED ${AbrtChecker_SRCS}) set_target_properties( @@ -18,6 +20,7 @@ set_target_properties( OUTPUT_NAME abrt-java-connector) target_link_libraries(AbrtChecker ${PC_LIBREPORT_LIBRARIES}) +target_link_libraries(AbrtChecker ${PC_ABRT_LIBRARIES}) target_link_libraries(AbrtChecker ${PC_JOURNALD_LIBRARIES}) install(TARGETS AbrtChecker DESTINATION ${LIB_INSTALL_DIR}) diff --git a/src/abrt-checker.c b/src/abrt-checker.c index d525284..3dfff70 100644 --- a/src/abrt-checker.c +++ b/src/abrt-checker.c @@ -110,9 +110,6 @@ /* Default main class name */ #define UNKNOWN_CLASS_NAME "*unknown*" -/* A pointer determining that log output is disabled */ -#define DISABLED_LOG_OUTPUT ((void *)-1) - /* The standard stack trace caused by header */ #define CAUSED_STACK_TRACE_HEADER "Caused by: " @@ -185,16 +182,6 @@ typedef struct { -/* - * Flags for specification of destination for error reports - */ -typedef enum { - ED_TERMINAL = 1, ///< Report errors to the terminal - ED_ABRT = ED_TERMINAL << 1, ///< Submit error reports to ABRT - ED_SYSLOG = ED_ABRT << 1, ///< Submit error reports to syslog - ED_JOURNALD = ED_SYSLOG << 1, ///< Submit error reports to journald -} T_errorDestination; - /* Global monitor lock */ jrawMonitorID shared_lock; @@ -215,31 +202,14 @@ T_jvmEnvironment jvmEnvironment; /* Structure containing process properties. */ T_processProperties processProperties; -/* Global configuration of report destination */ -T_errorDestination reportErrosTo = ED_JOURNALD; - -/* Path (not necessary absolute) to output file */ -char *outputFileName = DISABLED_LOG_OUTPUT; - -/* Path (not necessary absolute) to output file */ -char **reportedCaughExceptionTypes; - -/* Determines which resource is used as executable */ -enum { - ABRT_EXECUTABLE_MAIN = 0, - ABRT_EXECUTABLE_THREAD = 1, -}; -int executableFlags = 0; - - /* Map of buffer for already reported exceptions to prevent re-reporting */ T_jthreadMap *threadMap; /* Map of uncaught exceptions. There should be only 1 per thread.*/ T_jthreadMap *uncaughtExceptionMap; -/* Only for testing, will be removed soon. */ -const char *fqdnDebugMethods[] = { "DataMethodTest.debugStringData", NULL }; +/* Configuration */ +T_configuration globalConfig; /* forward headers */ static char* get_path_to_class(jvmtiEnv *jvmti_env, JNIEnv *jni_env, jclass class, char *class_name, const char *stringize_method_name); @@ -411,10 +381,10 @@ static FILE *get_log_file() /* Log file */ if (NULL == fout - && DISABLED_LOG_OUTPUT != outputFileName) + && DISABLED_LOG_OUTPUT != globalConfig.outputFileName) { /* try to open output log file */ - const char *fn = outputFileName; + const char *fn = globalConfig.outputFileName; if (NULL != fn) { struct stat sb; @@ -428,7 +398,7 @@ static FILE *get_log_file() } else if (S_ISDIR(sb.st_mode)) { - fn = append_file_to_path(&outputFileName, get_default_log_file_name()); + fn = append_file_to_path(&globalConfig.outputFileName, get_default_log_file_name()); } } else @@ -446,8 +416,8 @@ static FILE *get_log_file() fout = fopen(fn, "wt"); if (NULL == fout) { - free(outputFileName); - outputFileName = DISABLED_LOG_OUTPUT; + free(globalConfig.outputFileName); + globalConfig.outputFileName = DISABLED_LOG_OUTPUT; fprintf(stderr, __FILE__ ":" STRINGIZE(__LINE__) ": can not create output file %s. Disabling logging.\n", fn); } } @@ -565,7 +535,7 @@ static int exception_is_intended_to_be_reported( { int retval = 0; - if (reportedCaughExceptionTypes != NULL) + if (globalConfig.reportedCaughExceptionTypes != NULL) { if (NULL == *exception_type) { @@ -575,7 +545,7 @@ static int exception_is_intended_to_be_reported( } /* special cases for selected exceptions */ - for (char **cursor = reportedCaughExceptionTypes; *cursor; ++cursor) + for (char **cursor = globalConfig.reportedCaughExceptionTypes; *cursor; ++cursor) { if (strcmp(*cursor, *exception_type) == 0) { @@ -666,7 +636,7 @@ static void register_abrt_event( const char *backtrace, T_infoPair *additional_info) { - if ((reportErrosTo & ED_ABRT) == 0) + if ((globalConfig.reportErrosTo & ED_ABRT) == 0) { VERBOSE_PRINT("ABRT reporting is disabled\n"); return; @@ -712,13 +682,13 @@ static void report_stacktrace( const char *stacktrace, T_infoPair *additional_info) { - if (reportErrosTo & ED_SYSLOG) + if (globalConfig.reportErrosTo & ED_SYSLOG) { VERBOSE_PRINT("Reporting stack trace to syslog\n"); syslog(LOG_ERR, "%s\n%s", message, stacktrace); } - if (reportErrosTo & ED_JOURNALD) + if (globalConfig.reportErrosTo & ED_JOURNALD) { VERBOSE_PRINT("Reporting stack trace to JournalD\n"); sd_journal_send("MESSAGE=%s", message, @@ -1370,13 +1340,13 @@ static T_infoPair *collect_additional_debug_information( jvmtiEnv *jvmti_env, JNIEnv *jni_env) { - if (NULL == fqdnDebugMethods) + if (NULL == globalConfig.fqdnDebugMethods) { return NULL; } size_t cnt = 0; - const char *const *iter = (const char *const *)fqdnDebugMethods; + const char *const *iter = (const char *const *)globalConfig.fqdnDebugMethods; for ( ; NULL != *iter; ++iter) { ++cnt; @@ -1390,7 +1360,7 @@ static T_infoPair *collect_additional_debug_information( } T_infoPair *info = ret_val; - iter = (const char *const *)fqdnDebugMethods; + iter = (const char *const *)globalConfig.fqdnDebugMethods; for( ; NULL != *iter; ++iter) { char *debug_class_name_str = strdup(*iter); @@ -2334,7 +2304,7 @@ static void JNICALL callback_on_exception( jlocation catch_location __UNUSED_VAR) { /* This is caught exception and no caught exception is to be reported */ - if (NULL != catch_method && NULL == reportedCaughExceptionTypes) + if (NULL != catch_method && NULL == globalConfig.reportedCaughExceptionTypes) return; char *exception_type_name = NULL; @@ -2397,7 +2367,7 @@ static void JNICALL callback_on_exception( char *executable = NULL; char *stack_trace_str = generate_thread_stack_trace(jvmti_env, jni_env, tname, exception_object, - (executableFlags & ABRT_EXECUTABLE_THREAD) ? &executable : NULL); + (globalConfig.executableFlags & ABRT_EXECUTABLE_THREAD) ? &executable : NULL); T_infoPair *additional_info = collect_additional_debug_information(jvmti_env, jni_env); @@ -3000,175 +2970,6 @@ jvmtiError print_jvmti_version(jvmtiEnv *jvmti_env __UNUSED_VAR) -/* - * Returns NULL-terminated char *vector[]. Result itself must be freed, - * but do no free list elements. IOW: do free(result), but never free(result[i])! - * If separated_list is NULL or "", returns NULL. - */ -static char **build_string_vector(const char *separated_list, char separator) -{ - char **vector = NULL; - if (separated_list && separated_list[0]) - { - /* even w/o commas, we'll need two elements: - * vector[0] = "name" - * vector[1] = NULL - */ - unsigned cnt = 2; - - const char *cp = separated_list; - while (*cp) - cnt += (*cp++ == separator); - - /* We place the string directly after the char *vector[cnt]: */ - const size_t vector_num_bytes = cnt * sizeof(vector[0]) + (cp - separated_list) + 1; - vector = malloc(vector_num_bytes); - if (vector == NULL) - { - fprintf(stderr, __FILE__ ":" STRINGIZE(__LINE__) ": malloc(): out of memory"); - return NULL; - } - vector[cnt-1] = NULL; - - /* Copy the origin string right behind the pointer region */ - char *p = strcpy((char*)&vector[cnt], separated_list); - - char **pp = vector; - *pp++ = p; - while (*p) - { - if (*p++ == separator) - { - /* Replace 'separator' by '\0' */ - p[-1] = '\0'; - /* Save pointer to the beginning of next string in the pointer region */ - *pp++ = p; - } - } - } - - return vector; -} - - - -/* - * Parses options passed from the command line and save results in global variables. - * The function expects string in the following format: - * [key[=value][,key[=value]]...] - * - * - separator is ',' - * - keys without values are allowed - * - empty keys are allowed - * - multiple occurrences of a single key are allowed - * - empty values are allowed - */ -void parse_commandline_options(char *options) -{ - if (NULL == options) - { - return; - } - - char *savedptr_key = NULL; - for (char *key = options; /*break inside*/; options=NULL) - { - key = strtok_r(options, ",", &savedptr_key); - if (key == NULL) - { - break; - } - - char *value = strchr(key, '='); - if (value != NULL) - { - value[0] = '\0'; - value += 1; - } - - VERBOSE_PRINT("Parsed option '%s' = '%s'\n", key, (value ? value : "(None)")); - if (strcmp("abrt", key) == 0) - { - if (value != NULL && (strcasecmp("on", value) == 0 || strcasecmp("yes", value) == 0)) - { - VERBOSE_PRINT("Enabling errors reporting to ABRT\n"); - reportErrosTo |= ED_ABRT; - } - } - else if (strcmp("syslog", key) == 0) - { - if (value != NULL && (strcasecmp("on", value) == 0 || strcasecmp("yes", value) == 0)) - { - VERBOSE_PRINT("Enabling errors reporting to syslog\n"); - reportErrosTo |= ED_SYSLOG; - } - } - else if (strcmp("journald", key) == 0) - { - if (value != NULL && (strcasecmp("off", value) == 0 || strcasecmp("no", value) == 0)) - { - VERBOSE_PRINT("Disable errors reporting to JournalD\n"); - reportErrosTo &= ~ED_JOURNALD; - } - } - else if(strcmp("output", key) == 0) - { - if (DISABLED_LOG_OUTPUT != outputFileName) - { - free(outputFileName); - } - - if (value == NULL || value[0] == '\0') - { - VERBOSE_PRINT("Disabling output to log file\n"); - outputFileName = DISABLED_LOG_OUTPUT; - } - else - { - outputFileName = strdup(value); - if (outputFileName == NULL) - { - fprintf(stderr, __FILE__ ":" STRINGIZE(__LINE__) ": strdup(output): out of memory\n"); - VERBOSE_PRINT("Can not configure output file to desired value\n"); - /* keep NULL in outputFileName -> the default name will be used */ - } - } - } - else if(strcmp("caught", key) == 0) - { - reportedCaughExceptionTypes = build_string_vector(value, ':'); - } - else if (strcmp("executable", key) == 0) - { - if (NULL == value || '\0' == value[0]) - { - fprintf(stderr, "A value of '%s' option cannot be empty\n", key); - } - else if (strcmp("threadclass", value) == 0) - { - VERBOSE_PRINT("Use a thread class for 'executable'\n"); - executableFlags |= ABRT_EXECUTABLE_THREAD; - } - else if (strcmp("mainclass", value) == 0) - { - /* Unset ABRT_EXECUTABLE_THREAD bit */ - VERBOSE_PRINT("Use the main class for 'executable'\n"); - executableFlags &= ~ABRT_EXECUTABLE_THREAD; - } - else - { - fprintf(stderr, "Unknown '%s' option's value '%s'\n", key, value); - } - } - else - { - fprintf(stderr, "Unknown option '%s'\n", key); - } - } -} - - - /* * Called when agent is loading into JVM. */ @@ -3192,7 +2993,13 @@ JNIEXPORT jint JNICALL Agent_OnLoad( INFO_PRINT("Agent_OnLoad\n"); VERBOSE_PRINT("VERBOSE OUTPUT ENABLED\n"); - parse_commandline_options(options); + + configuration_initialize(&globalConfig); + parse_commandline_options(&globalConfig, options); + if (globalConfig.configurationFileName) + { + parse_configuration_file(&globalConfig, globalConfig.configurationFileName); + } /* check if JVM TI version is correct */ result = (*jvm)->GetEnv(jvm, (void **) &jvmti_env, JVMTI_VERSION_1_0); @@ -3274,12 +3081,8 @@ JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm __UNUSED_VAR) pthread_mutex_destroy(&abrt_print_mutex); INFO_PRINT("Agent_OnUnLoad\n"); - if (outputFileName != DISABLED_LOG_OUTPUT) - { - free(outputFileName); - } - free(reportedCaughExceptionTypes); + configuration_destroy(&globalConfig); if (fout != NULL) { diff --git a/src/abrt-checker.h b/src/abrt-checker.h index 1bbd7bd..f0f4091 100644 --- a/src/abrt-checker.h +++ b/src/abrt-checker.h @@ -57,6 +57,88 @@ pthread_mutex_t abrt_print_mutex __UNUSED_VAR; } while(0) #endif // SILENT + + +/* + * Flags for specification of destination for error reports + */ +typedef enum { + ED_TERMINAL = 1, ///< Report errors to the terminal + ED_ABRT = ED_TERMINAL << 1, ///< Submit error reports to ABRT + ED_SYSLOG = ED_ABRT << 1, ///< Submit error reports to syslog + ED_JOURNALD = ED_SYSLOG << 1, ///< Submit error reports to journald +} T_errorDestination; + + + +/* + * Determines which resource is used as executable + */ +enum { + ABRT_EXECUTABLE_MAIN = 0, + ABRT_EXECUTABLE_THREAD = 1, +}; + + + +/* A pointer determining that log output is disabled */ +#define DISABLED_LOG_OUTPUT ((void *)-1) + + + +typedef struct { + /* Global configuration of report destination */ + T_errorDestination reportErrosTo; + + /* Which frame use for the executable field */ + int executableFlags; + + /* Path (not necessary absolute) to output file */ + char *outputFileName; + + /* Path (not necessary absolute) to configuration file */ + char *configurationFileName; + + /* NULL terminated list of exception types to report when caught */ + char **reportedCaughExceptionTypes; + + /* NULL terminated list of debug methods called when an exceptions is to be + * reported */ + char **fqdnDebugMethods; + + int configured; +} T_configuration; + + + +/* + * Initializes an configuration structure + */ +void configuration_initialize(T_configuration *conf); + + + +/* + * Releases all resources + */ +void configuration_destroy(T_configuration *conf); + + + +/* + * Parses an options string in form of JVM agent options + */ +void parse_commandline_options(T_configuration *conf, char *options); + + + +/* + * Parses a configuration file written in libreport configuration file format + */ +void parse_configuration_file(T_configuration *conf, const char *filename); + + + #endif // __ABRT_CHECKER__ diff --git a/src/configuration.c b/src/configuration.c new file mode 100644 index 0000000..42fbd6e --- /dev/null +++ b/src/configuration.c @@ -0,0 +1,414 @@ +#include "abrt-checker.h" + +/* ABRT include file */ +#include "internal_libabrt.h" + +#include +#include +#include + + + +enum { + OPT_abrt = 1 << 0, + OPT_syslog = 1 << 1, + OPT_journald = 1 << 2, + OPT_output = 1 << 3, + OPT_caught = 1 << 4, + OPT_executable = 1 << 5, + OPT_conffile = 1 << 6, + OPT_debugmethod = 1 << 7, +}; + + + +/* + * Used by load_abrt_plugin_conf_file() + */ +static const char *const s_defaultConfFile = "java.conf"; + + + +typedef struct { + int primarySource; + const char *listDelimiter; +} T_context; + + + +void configuration_initialize(T_configuration *conf) +{ + memset(conf, 0, sizeof(*conf)); + conf->reportErrosTo = ED_JOURNALD; + conf->outputFileName = DISABLED_LOG_OUTPUT; + conf->configurationFileName = (char *)s_defaultConfFile; +} + + + +void configuration_destroy(T_configuration *conf) +{ + if (conf->outputFileName != DISABLED_LOG_OUTPUT) + { + free(conf->outputFileName); + } + + if (conf->configurationFileName != s_defaultConfFile) + { + free(conf->configurationFileName); + } + + free(conf->reportedCaughExceptionTypes); + free(conf->fqdnDebugMethods); +} + + + +static int skip_separator(const char **input, const char *separator) +{ + size_t i = 0; + while ((*input)[i] == separator[i]) + { + if (separator[i] == '\0') + { + goto separator_equals; + } + + ++i; + } + + if (separator[i] == '\0') + { +separator_equals: + (*input) += i; + return 1; + } + + ++(*input); + return 0; +} + + +/* + * Returns NULL-terminated char *vector[]. Result itself must be freed, + * but do no free list elements. IOW: do free(result), but never free(result[i])! + * If separated_list is NULL or "", returns NULL. + */ +static char **build_string_vector(const char *separated_list, const char *separator) +{ + char **vector = NULL; + if (separated_list && separated_list[0]) + { + /* even w/o commas, we'll need two elements: + * vector[0] = "name" + * vector[1] = NULL + */ + unsigned cnt = 2; + + const char *cp = separated_list; + while (*cp) + cnt += skip_separator(&cp, separator); + + /* We place the string directly after the char *vector[cnt]: */ + const size_t vector_num_bytes = cnt * sizeof(vector[0]) + (cp - separated_list) + 1; + vector = malloc(vector_num_bytes); + if (vector == NULL) + { + fprintf(stderr, __FILE__ ":" STRINGIZE(__LINE__) ": malloc(): out of memory"); + return NULL; + } + vector[cnt-1] = NULL; + + /* Copy the origin string right behind the pointer region */ + char *p = strcpy((char*)&vector[cnt], separated_list); + + char **pp = vector; + *pp++ = p; + const size_t sep_len = strlen(separator); + while (*p) + { + if (skip_separator((const char **)&p, separator)) + { + /* Replace 'separator' by '\0' */ + p[-sep_len] = '\0'; + /* Save pointer to the beginning of next string in the pointer region */ + *pp++ = p; + } + } + } + + return vector; +} + + + +static int parse_option_abrt(T_configuration *conf, const char *value, T_context *context __UNUSED_VAR) +{ + if (value != NULL && (strcasecmp("on", value) == 0 || strcasecmp("yes", value) == 0)) + { + VERBOSE_PRINT("Enabling errors reporting to ABRT\n"); + conf->reportErrosTo |= ED_ABRT; + } + + return 0; +} + + + +static int parse_option_syslog(T_configuration *conf, const char *value, T_context *context __UNUSED_VAR) +{ + if (value != NULL && (strcasecmp("on", value) == 0 || strcasecmp("yes", value) == 0)) + { + VERBOSE_PRINT("Enabling errors reporting to syslog\n"); + conf->reportErrosTo |= ED_SYSLOG; + } + + return 0; +} + + + +static int parse_option_journald(T_configuration *conf, const char *value, T_context *context __UNUSED_VAR) +{ + if (value != NULL && (strcasecmp("off", value) == 0 || strcasecmp("no", value) == 0)) + { + VERBOSE_PRINT("Disable errors reporting to JournalD\n"); + conf->reportErrosTo &= ~ED_JOURNALD; + } + + return 0; +} + + + +static int parse_option_output(T_configuration *conf, const char *value, T_context *context __UNUSED_VAR) +{ + if (DISABLED_LOG_OUTPUT != conf->outputFileName) + { + free(conf->outputFileName); + } + + if (value == NULL || value[0] == '\0') + { + VERBOSE_PRINT("Disabling output to log file\n"); + conf->outputFileName = DISABLED_LOG_OUTPUT; + } + else + { + conf->outputFileName = strdup(value); + if (conf->outputFileName == NULL) + { + fprintf(stderr, __FILE__ ":" STRINGIZE(__LINE__) ": strdup(output): out of memory\n"); + VERBOSE_PRINT("Can not configure output file to desired value\n"); + /* keep NULL in outputFileName -> the default name will be used */ + return 1; + } + } + + return 0; +} + + + +static int parse_option_caught(T_configuration *conf, const char *value, T_context *context) +{ + if (NULL != conf->reportedCaughExceptionTypes) + { + free(conf->reportedCaughExceptionTypes); + } + + conf->reportedCaughExceptionTypes = build_string_vector(value, context->listDelimiter); + + return 0; +} + + + +static int parse_option_executable(T_configuration *conf, const char *value, T_context *context __UNUSED_VAR) +{ + if (NULL == value || '\0' == value[0]) + { + fprintf(stderr, "Value cannot be empty\n"); + return 1; + } + else if (strcmp("threadclass", value) == 0) + { + VERBOSE_PRINT("Use a thread class for 'executable'\n"); + conf->executableFlags |= ABRT_EXECUTABLE_THREAD; + } + else if (strcmp("mainclass", value) == 0) + { + /* Unset ABRT_EXECUTABLE_THREAD bit */ + VERBOSE_PRINT("Use the main class for 'executable'\n"); + conf->executableFlags &= ~ABRT_EXECUTABLE_THREAD; + } + else + { + fprintf(stderr, "Unknown value '%s'\n", value); + return 1; + } + + return 0; +} + + + +static int parse_option_conffile(T_configuration *conf, const char *value, T_context *context __UNUSED_VAR) +{ + if (conf->configurationFileName != s_defaultConfFile) + { + free(conf->configurationFileName); + } + + if (NULL == value || '\0' == value[0]) + { + /* Value cannot be empty */ + VERBOSE_PRINT("Disabling configuration file\n"); + conf->configurationFileName = NULL; + return 0; + } + + conf->configurationFileName = strdup(value); + if (conf->configurationFileName == NULL) + { + fprintf(stderr, __FILE__ ":" STRINGIZE(__LINE__) ": strdup(output): out of memory\n"); + VERBOSE_PRINT("Can not configure output file to desired value\n"); + /* keep NULL in outputFileName -> the default name will be used */ + return 1; + } + + return 0; +} + + + +static int parse_option_debugmethod(T_configuration *conf, const char *value, T_context *context) +{ + if (NULL != conf->fqdnDebugMethods) + { + free(conf->fqdnDebugMethods); + } + + conf->fqdnDebugMethods = build_string_vector(value, context->listDelimiter); + + return 0; +} + + + +static void parse_key_value(T_configuration *conf, const char *key, const char *value, T_context *context) +{ + static struct parse_pair { + int flag; + const char *key; + int (*parser)(T_configuration *, const char *, T_context *); + } arguments[] = { + { OPT_abrt, "abrt", parse_option_abrt }, + { OPT_syslog, "syslog", parse_option_syslog }, + { OPT_journald, "journald", parse_option_journald }, + { OPT_output, "output", parse_option_output }, + { OPT_caught, "caught", parse_option_caught }, + { OPT_executable, "executable", parse_option_executable }, + { OPT_conffile, "conffile", parse_option_conffile }, + { OPT_debugmethod, "debugmethod", parse_option_debugmethod }, + }; + + for (size_t i = 0; i < sizeof(arguments)/sizeof(arguments[0]); ++i) + { + if (strcmp(key, arguments[i].key) == 0) + { + if ((conf->configured & arguments[i].flag) && !context->primarySource) + { + return; + } + + conf->configured |= arguments[i].flag; + + if (arguments[i].parser(conf, value, context)) + { + fprintf(stderr, "Error while parsing option '%s'\n", key); + } + return; + } + } + + fprintf(stderr, "Unknown option '%s'\n", key); +} + + + +/* + * Parses options passed from the command line and save results in global variables. + * The function expects string in the following format: + * [key[=value][,key[=value]]...] + * + * - separator is ',' + * - keys without values are allowed + * - empty keys are allowed + * - multiple occurrences of a single key are allowed + * - empty values are allowed + */ +void parse_commandline_options(T_configuration *conf, char *options) +{ + if (NULL == options) + { + return; + } + + T_context ctx = { + .primarySource = 1, + .listDelimiter = ":", + }; + + char *savedptr_key = NULL; + for (char *key = options; /*break inside*/; options=NULL) + { + key = strtok_r(options, ",", &savedptr_key); + if (key == NULL) + { + break; + } + + char *value = strchr(key, '='); + if (value != NULL) + { + value[0] = '\0'; + value += 1; + } + + VERBOSE_PRINT("Parsed option '%s' = '%s'\n", key, (value ? value : "(None)")); + parse_key_value(conf, key, value, &ctx); + } +} + + + +void parse_configuration_file(T_configuration *conf, const char *filename) +{ + /* Remains empty if any of the loading functions below fails */ + map_string_t *settings = new_map_string(); + if (filename[0] == '/') + { + load_conf_file(filename, settings, /*skip empty*/0); + } + else + { + load_abrt_plugin_conf_file(filename, settings); + } + + T_context ctx = { + .primarySource = 0, /* do not overwrite already loaded options */ + .listDelimiter = ", ", + }; + + map_string_iter_t iter; + init_map_string_iter(&iter, settings); + const char *key; + const char *value; + while(next_map_string_iter(&iter, &key, &value)) + { + parse_key_value(conf, key, value, &ctx); + } + + free_map_string(settings); +} diff --git a/src/internal_libabrt.h b/src/internal_libabrt.h index a05155f..e40d642 100644 --- a/src/internal_libabrt.h +++ b/src/internal_libabrt.h @@ -25,6 +25,7 @@ /* libreport include file */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-pedantic" +#include #include #pragma GCC diagnostic pop diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7407bd0..193fc4a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -5,7 +5,7 @@ set(SERVER_URL "http://localhost") find_package(Java REQUIRED) -subdirs(outputs) +subdirs(outputs unittests) function(_get_target_names variable) foreach(tmp_name ${ARGN}) @@ -125,7 +125,7 @@ _add_test_target( run SimpleTest DEPENDS ${TEST_JAVA_TARGETS} - AGENT_OPTIONS caught=java.lang.ArrayIndexOutOfBoundsException:java.lang.NullPointerException + AGENT_OPTIONS caught=java.lang.ArrayIndexOutOfBoundsException:java.lang.NullPointerException,debugmethod=DataMethodTest.debugStringData ) _add_test(run 2) @@ -331,6 +331,7 @@ _add_test_target( DataMethodTest 2 DEPENDS ${TEST_JAVA_TARGETS} + AGENT_OPTIONS debugmethod=DataMethodTest.debugStringData ) _add_test(run_data_method 2) diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt new file mode 100644 index 0000000..5f73a4a --- /dev/null +++ b/test/unittests/CMakeLists.txt @@ -0,0 +1,14 @@ +pkg_check_modules(PC_CHECK REQUIRED check) + +add_definitions(-DCONFIG_FILE_ALL_ENTRIES_POPULATED="${CMAKE_CURRENT_SOURCE_DIR}/config_file_all_entries_populated") + +include_directories(${PC_ABRT_INCLUDE_DIRS}) +include_directories(${PC_CHECK_INCLUDE_DIRS}) +include_directories("${CMAKE_SOURCE_DIR}/src") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=c99 -pedantic -O0 -g") + +add_executable(testsuite check_abrt_java_connector.c) +target_link_libraries(testsuite ${PC_CHECK_LIBRARIES}) +target_link_libraries(testsuite AbrtChecker) + +add_test(unit_tests ./testsuite) diff --git a/test/unittests/check_abrt_java_connector.c b/test/unittests/check_abrt_java_connector.c new file mode 100644 index 0000000..911bcaf --- /dev/null +++ b/test/unittests/check_abrt_java_connector.c @@ -0,0 +1,138 @@ +#include "abrt-checker.h" +#include "internal_libabrt.h" + +#include +#include + +void assert_str_vector_eq(const char **expected, const char **tested) +{ + while (*expected && *tested) + { + ck_assert_str_eq(*expected, *tested); + + ++expected; + ++tested; + } + + ck_assert(*expected == *tested); +} + +void assert_conf_populated(T_configuration *conf) +{ + ck_assert_int_eq((conf->reportErrosTo & ED_ABRT), ED_ABRT); + ck_assert_int_eq((conf->reportErrosTo & ED_SYSLOG), ED_SYSLOG); + ck_assert_int_eq((conf->reportErrosTo & ED_JOURNALD), 0); + + ck_assert_int_eq((conf->executableFlags & ABRT_EXECUTABLE_MAIN), 0); + ck_assert_int_eq((conf->executableFlags & ABRT_EXECUTABLE_THREAD), ABRT_EXECUTABLE_THREAD); + + ck_assert_str_eq(conf->outputFileName, "test.log"); + + ck_assert_str_eq(conf->configurationFileName, "java.conf"); + + ck_assert(conf->reportedCaughExceptionTypes != NULL); + const char *caughtTypes[] = { "n.s.Ex1", "n.s.Ex2", "n.s.Ex3", NULL }; + assert_str_vector_eq((const char **)caughtTypes, (const char **)conf->reportedCaughExceptionTypes); + + ck_assert(conf->fqdnDebugMethods != NULL); + const char *debugMethods[] = { "n.s.cls.M1", "n.s.cls2.M2", "n.s.cls3.M3", NULL }; + assert_str_vector_eq((const char **)debugMethods, (const char **)conf->fqdnDebugMethods); +} + +START_TEST(test_config_file_all_entries_populated) +{ + T_configuration conf; + configuration_initialize(&conf); + + mark_point(); + parse_configuration_file(&conf, CONFIG_FILE_ALL_ENTRIES_POPULATED".conf"); + + mark_point(); + assert_conf_populated(&conf); + + configuration_destroy(&conf); +} +END_TEST + +START_TEST(test_command_line_conf_all_entries_populated) +{ + T_configuration conf; + configuration_initialize(&conf); + + char *opts = strdup( + "abrt=on,syslog=on,journald=off,executable=threadclass,output=test.log," + "caught=n.s.Ex1:n.s.Ex2:n.s.Ex3,debugmethod=n.s.cls.M1:n.s.cls2.M2:n.s.cls3.M3"); + + ck_assert_msg(NULL != opts, "Out of memory"); + + mark_point(); + parse_commandline_options(&conf, opts); + + mark_point(); + assert_conf_populated(&conf); + + configuration_destroy(&conf); +} +END_TEST + +START_TEST(test_conf_file_no_overwrite) +{ + T_configuration conf; + configuration_initialize(&conf); + + char *opts = strdup( + "abrt=off,syslog=off,journald=on,executable=mainclass,output=," + "conffile=,caught=,debugmethod="); + + ck_assert_msg(NULL != opts, "Out of memory"); + + mark_point(); + parse_commandline_options(&conf, opts); + + mark_point(); + parse_configuration_file(&conf, CONFIG_FILE_ALL_ENTRIES_POPULATED".conf"); + + ck_assert_int_eq((conf.reportErrosTo & ED_ABRT), 0); + ck_assert_int_eq((conf.reportErrosTo & ED_SYSLOG), 0); + ck_assert_int_eq((conf.reportErrosTo & ED_JOURNALD), ED_JOURNALD); + + ck_assert_int_eq((conf.executableFlags & ABRT_EXECUTABLE_MAIN), ABRT_EXECUTABLE_MAIN); + ck_assert_int_eq((conf.executableFlags & ABRT_EXECUTABLE_THREAD), 0); + + ck_assert(conf.outputFileName == DISABLED_LOG_OUTPUT); + + ck_assert(NULL == conf.configurationFileName); + + ck_assert(NULL == conf.reportedCaughExceptionTypes); + + ck_assert(NULL == conf.fqdnDebugMethods); + + configuration_destroy(&conf); +} +END_TEST + +Suite *abrt_checker_suite(void) +{ + Suite *s = suite_create ("abrt-checker"); + + /* Configuration test case */ + TCase *tc_configuration = tcase_create("Configuration"); + tcase_add_test(tc_configuration, test_config_file_all_entries_populated); + tcase_add_test(tc_configuration, test_command_line_conf_all_entries_populated); + tcase_add_test(tc_configuration, test_conf_file_no_overwrite); + suite_add_tcase(s, tc_configuration); + + return s; +} + + +int main(void) +{ + int number_failed; + Suite *s = abrt_checker_suite(); + SRunner *sr = srunner_create(s); + srunner_run_all(sr, CK_NORMAL); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/test/unittests/config_file_all_entries_populated.conf b/test/unittests/config_file_all_entries_populated.conf new file mode 100644 index 0000000..75ba2cc --- /dev/null +++ b/test/unittests/config_file_all_entries_populated.conf @@ -0,0 +1,7 @@ +abrt = on +syslog = on +journald = off +output = test.log +caught = n.s.Ex1, n.s.Ex2, n.s.Ex3 +executable = threadclass +debugmethod = n.s.cls.M1, n.s.cls2.M2, n.s.cls3.M3