summaryrefslogtreecommitdiffstats
path: root/libreport/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'libreport/src/lib')
-rw-r--r--libreport/src/lib/Makefile.am94
-rw-r--r--libreport/src/lib/abrt_dbus.c623
-rw-r--r--libreport/src/lib/abrt_types.c37
-rw-r--r--libreport/src/lib/append_to_malloced_string.c27
-rw-r--r--libreport/src/lib/binhex.c74
-rw-r--r--libreport/src/lib/concat_path_file.c25
-rw-r--r--libreport/src/lib/copy_file_recursive.c149
-rw-r--r--libreport/src/lib/copyfd.c172
-rw-r--r--libreport/src/lib/create_dump_dir.c85
-rw-r--r--libreport/src/lib/daemon_is_ok.c51
-rw-r--r--libreport/src/lib/dirsize.c110
-rw-r--r--libreport/src/lib/dump_dir.c839
-rw-r--r--libreport/src/lib/encbase64.c78
-rw-r--r--libreport/src/lib/event_config.c366
-rw-r--r--libreport/src/lib/event_xml_parser.c469
-rw-r--r--libreport/src/lib/get_cmdline.c150
-rw-r--r--libreport/src/lib/glib_support.c27
-rw-r--r--libreport/src/lib/hash_sha1.c211
-rw-r--r--libreport/src/lib/is_in_string_list.c30
-rw-r--r--libreport/src/lib/iso_date_string.c31
-rw-r--r--libreport/src/lib/kernel-tainted.c143
-rw-r--r--libreport/src/lib/load_plugin_settings.c97
-rw-r--r--libreport/src/lib/logging.c148
-rw-r--r--libreport/src/lib/make_descr.c262
-rw-r--r--libreport/src/lib/overlapping_strcpy.c22
-rw-r--r--libreport/src/lib/parse_options.c251
-rw-r--r--libreport/src/lib/parse_release.c81
-rw-r--r--libreport/src/lib/problem_data.c424
-rw-r--r--libreport/src/lib/read_write.c105
-rw-r--r--libreport/src/lib/report.c129
-rw-r--r--libreport/src/lib/report_event.conf47
-rw-r--r--libreport/src/lib/run_event.c551
-rw-r--r--libreport/src/lib/skip_whitespace.c22
-rw-r--r--libreport/src/lib/spawn.c158
-rw-r--r--libreport/src/lib/stdio_helpers.c69
-rw-r--r--libreport/src/lib/steal_directory.c58
-rw-r--r--libreport/src/lib/strbuf.c170
-rw-r--r--libreport/src/lib/xatonum.c50
-rw-r--r--libreport/src/lib/xfuncs.c410
39 files changed, 6845 insertions, 0 deletions
diff --git a/libreport/src/lib/Makefile.am b/libreport/src/lib/Makefile.am
new file mode 100644
index 00000000..f90f2f9f
--- /dev/null
+++ b/libreport/src/lib/Makefile.am
@@ -0,0 +1,94 @@
+# libreport - the stuff shared among most of abrt (like xmalloc, logging)
+lib_LTLIBRARIES = \
+ libreport.la \
+ libabrt_dbus.la
+
+# Not used just yet:
+# time.cpp
+# xconnect.cpp
+
+libreport_la_SOURCES = \
+ xfuncs.c \
+ is_in_string_list.c \
+ encbase64.c \
+ binhex.c \
+ stdio_helpers.c \
+ hash_sha1.c \
+ read_write.c \
+ logging.c \
+ copyfd.c \
+ copy_file_recursive.c \
+ concat_path_file.c \
+ append_to_malloced_string.c \
+ overlapping_strcpy.c \
+ skip_whitespace.c \
+ glib_support.c \
+ iso_date_string.c \
+ strbuf.c \
+ xatonum.c \
+ spawn.c \
+ dirsize.c \
+ dump_dir.c \
+ get_cmdline.c \
+ load_plugin_settings.c \
+ make_descr.c \
+ run_event.c \
+ problem_data.c \
+ create_dump_dir.c \
+ abrt_types.c \
+ parse_release.c \
+ parse_options.c \
+ steal_directory.c \
+ event_xml_parser.c \
+ event_config.c \
+ kernel-tainted.c \
+ report.c
+libreport_la_CPPFLAGS = \
+ -Wall -Wwrite-strings -Werror \
+ -I$(srcdir)/../include \
+ -DLOCALSTATEDIR='"$(localstatedir)"' \
+ -DVAR_RUN=\"$(VAR_RUN)\" \
+ -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \
+ -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \
+ -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \
+ -DCONF_DIR=\"$(CONF_DIR)\" \
+ -DEVENTS_DIR=\"$(EVENTS_DIR)\" \
+ -DBIN_DIR=\"$(bindir)\" \
+ $(GLIB_CFLAGS) \
+ -D_GNU_SOURCE
+libreport_la_LDFLAGS = \
+ -version-info 0:1:0
+libreport_la_LIBADD = \
+ $(GLIB_LIBS)
+
+libreportconfdir = $(CONF_DIR)
+dist_libreportconf_DATA = \
+ report_event.conf
+
+libabrt_dbus_la_SOURCES = \
+ abrt_dbus.c
+libabrt_dbus_la_CPPFLAGS = \
+ -I$(srcdir)/../include/report -I$(srcdir)/../include \
+ -DLOCALSTATEDIR='"$(localstatedir)"' \
+ -DVAR_RUN=\"$(VAR_RUN)\" \
+ -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \
+ -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \
+ -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \
+ -DCONF_DIR=\"$(CONF_DIR)\" \
+ -DEVENTS_DIR=\"$(EVENTS_DIR)\" \
+ $(GLIB_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ -Wall -Wwrite-strings -Werror \
+ -D_GNU_SOURCE
+libabrt_dbus_la_LDFLAGS = \
+ -version-info 0:1:0
+libabrt_dbus_la_LIBADD = \
+ libreport.la \
+ $(GLIB_LIBS) \
+ $(DBUS_LIBS)
+
+$(DESTDIR)/$(DEBUG_DUMPS_DIR):
+ $(mkdir_p) '$@'
+# no need to chmod it here
+#chmod 1777 '$@'
+install-data-local: $(DESTDIR)/$(DEBUG_DUMPS_DIR)
diff --git a/libreport/src/lib/abrt_dbus.c b/libreport/src/lib/abrt_dbus.c
new file mode 100644
index 00000000..d412ad5c
--- /dev/null
+++ b/libreport/src/lib/abrt_dbus.c
@@ -0,0 +1,623 @@
+/*
+ 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 <dbus/dbus.h>
+//#include "abrtlib.h"
+#include "abrt_dbus.h"
+
+DBusConnection* g_dbus_conn;
+
+
+/*
+ * Helpers for building DBus messages
+ */
+
+//void store_bool(DBusMessageIter* iter, bool val)
+//{
+// dbus_bool_t db = val;
+// if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &db))
+// die_out_of_memory();
+//}
+void store_int32(DBusMessageIter* iter, int32_t val)
+{
+ if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &val))
+ die_out_of_memory();
+}
+void store_uint32(DBusMessageIter* iter, uint32_t val)
+{
+ if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &val))
+ die_out_of_memory();
+}
+void store_int64(DBusMessageIter* iter, int64_t val)
+{
+ if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_INT64, &val))
+ die_out_of_memory();
+}
+void store_uint64(DBusMessageIter* iter, uint64_t val)
+{
+ if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &val))
+ die_out_of_memory();
+}
+
+/* dbus daemon will simply close our connection if we send broken utf8.
+ * Therefore we must never do that.
+ */
+static char *sanitize_utf8(const char *src)
+{
+ const char *initial_src = src;
+ char *sanitized = NULL;
+ unsigned sanitized_pos = 0;
+
+ while (*src)
+ {
+ int bytes = 0;
+
+ unsigned c = (unsigned char) *src;
+ if (c <= 0x7f)
+ {
+ bytes = 1;
+ goto good_byte;
+ }
+
+ /* Unicode -> utf8: */
+ /* 80-7FF -> 110yyyxx 10xxxxxx */
+ /* 800-FFFF -> 1110yyyy 10yyyyxx 10xxxxxx */
+ /* 10000-1FFFFF -> 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx */
+ /* 200000-3FFFFFF -> 111110tt 10zzzzzz 10zzyyyy 10yyyyxx 10xxxxxx */
+ /* 4000000-FFFFFFFF -> 111111tt 10tttttt 10zzzzzz 10zzyyyy 10yyyyxx 10xxxxxx */
+ do {
+ c <<= 1;
+ bytes++;
+ } while ((c & 0x80) && bytes < 6);
+ if (bytes == 1)
+ {
+ /* A bare "continuation" byte. Say, 80 */
+ goto bad_byte;
+ }
+
+ c = (uint8_t)(c) >> bytes;
+ {
+ const char *pp = src;
+ int cnt = bytes;
+ while (--cnt)
+ {
+ unsigned ch = (unsigned char) *++pp;
+ if ((ch & 0xc0) != 0x80) /* Missing "continuation" byte. Example: e0 80 */
+ {
+ goto bad_byte;
+ }
+ c = (c << 6) + (ch & 0x3f);
+ }
+ }
+ /* TODO */
+ /* Need to check that c isn't produced by overlong encoding */
+ /* Example: 11000000 10000000 converts to NUL */
+ /* 11110000 10000000 10000100 10000000 converts to 0x100 */
+ /* correct encoding: 11000100 10000000 */
+ if (c <= 0x7f) /* crude check: only catches bad encodings which map to chars <= 7f */
+ {
+ goto bad_byte;
+ }
+
+ good_byte:
+ while (--bytes >= 0)
+ {
+ c = (unsigned char) *src++;
+ if (sanitized)
+ {
+ sanitized = (char*) xrealloc(sanitized, sanitized_pos + 2);
+ sanitized[sanitized_pos++] = c;
+ sanitized[sanitized_pos] = '\0';
+ }
+ }
+ continue;
+
+ bad_byte:
+ if (!sanitized)
+ {
+ sanitized_pos = src - initial_src;
+ sanitized = xstrndup(initial_src, sanitized_pos);
+ }
+ sanitized = (char*) xrealloc(sanitized, sanitized_pos + 5);
+ sanitized[sanitized_pos++] = '[';
+ c = (unsigned char) *src++;
+ sanitized[sanitized_pos++] = "0123456789ABCDEF"[c >> 4];
+ sanitized[sanitized_pos++] = "0123456789ABCDEF"[c & 0xf];
+ sanitized[sanitized_pos++] = ']';
+ sanitized[sanitized_pos] = '\0';
+ }
+
+ if (sanitized)
+ VERB2 log("note: bad utf8, converted '%s' -> '%s'", initial_src, sanitized);
+
+ return sanitized; /* usually NULL: the whole string is ok */
+}
+void store_string(DBusMessageIter* iter, const char* val)
+{
+ const char *sanitized = sanitize_utf8(val);
+ if (!dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, sanitized ? &sanitized : &val))
+ die_out_of_memory();
+ free((char*)sanitized);
+}
+
+
+/*
+ * Helpers for parsing DBus messages
+ */
+
+//int load_bool(DBusMessageIter* iter, bool& val)
+//{
+// int type = dbus_message_iter_get_arg_type(iter);
+// if (type != DBUS_TYPE_BOOLEAN)
+// error_msg_and_die("%s expected in dbus message, but not found ('%c')", "bool", type);
+// dbus_bool_t db;
+// dbus_message_iter_get_basic(iter, &db);
+// val = db;
+// return dbus_message_iter_next(iter);
+//}
+int load_int32(DBusMessageIter* iter, int32_t *val)
+{
+ int type = dbus_message_iter_get_arg_type(iter);
+ if (type != DBUS_TYPE_INT32)
+ {
+ error_msg("%s expected in dbus message, but not found ('%c')", "int32", type);
+ return -1;
+ }
+ dbus_message_iter_get_basic(iter, val);
+ return dbus_message_iter_next(iter);
+}
+int load_uint32(DBusMessageIter* iter, uint32_t *val)
+{
+ int type = dbus_message_iter_get_arg_type(iter);
+ if (type != DBUS_TYPE_UINT32)
+ {
+ error_msg("%s expected in dbus message, but not found ('%c')", "uint32", type);
+ return -1;
+ }
+ dbus_message_iter_get_basic(iter, val);
+ return dbus_message_iter_next(iter);
+}
+int load_int64(DBusMessageIter* iter, int64_t *val)
+{
+ int type = dbus_message_iter_get_arg_type(iter);
+ if (type != DBUS_TYPE_INT64)
+ {
+ error_msg("%s expected in dbus message, but not found ('%c')", "int64", type);
+ return -1;
+ }
+ dbus_message_iter_get_basic(iter, val);
+ return dbus_message_iter_next(iter);
+}
+int load_uint64(DBusMessageIter* iter, uint64_t *val)
+{
+ int type = dbus_message_iter_get_arg_type(iter);
+ if (type != DBUS_TYPE_UINT64)
+ {
+ error_msg("%s expected in dbus message, but not found ('%c')", "uint64", type);
+ return -1;
+ }
+ dbus_message_iter_get_basic(iter, val);
+ return dbus_message_iter_next(iter);
+}
+int load_charp(DBusMessageIter* iter, const char** val)
+{
+ *val = NULL;
+
+ int type = dbus_message_iter_get_arg_type(iter);
+ if (type != DBUS_TYPE_STRING)
+ {
+ error_msg("%s expected in dbus message, but not found ('%c')", "string", type);
+ return -1;
+ }
+ dbus_message_iter_get_basic(iter, val);
+//log("load_charp:'%s'", *val);
+ return dbus_message_iter_next(iter);
+}
+
+
+/*
+ * Glib integration machinery
+ */
+
+/* Callback: "glib says dbus fd is active" */
+static gboolean handle_dbus_fd(GIOChannel *gio, GIOCondition condition, gpointer data)
+{
+ DBusWatch *watch = (DBusWatch*)data;
+
+ VERB3 log("%s(gio, condition:%x [bits:IN/PRI/OUT/ERR/HUP...], data)", __func__, (int)condition);
+
+ /* Notify the D-Bus library when a previously-added watch
+ * is ready for reading or writing, or has an exception such as a hangup.
+ */
+ int glib_flags = (int)condition;
+ int dbus_flags = 0;
+ if (glib_flags & G_IO_IN) dbus_flags |= DBUS_WATCH_READABLE;
+ if (glib_flags & G_IO_OUT) dbus_flags |= DBUS_WATCH_WRITABLE;
+ if (glib_flags & G_IO_ERR) dbus_flags |= DBUS_WATCH_ERROR;
+ if (glib_flags & G_IO_HUP) dbus_flags |= DBUS_WATCH_HANGUP;
+ /*
+ * TODO:
+ * If dbus_watch_handle returns FALSE, then the file descriptor
+ * may still be ready for reading or writing, but more memory
+ * is needed in order to do the reading or writing. If you ignore
+ * the FALSE return, your application may spin in a busy loop
+ * on the file descriptor until memory becomes available,
+ * but nothing more catastrophic should happen.
+ */
+ dbus_watch_handle(watch, dbus_flags);
+
+ while (dbus_connection_dispatch(g_dbus_conn) == DBUS_DISPATCH_DATA_REMAINS)
+ VERB3 log("%s: more data to process, looping", __func__);
+ return TRUE; /* "glib, do not remove this event source!" */
+}
+
+typedef struct watch_app_info_t
+{
+ GIOChannel *channel;
+ guint event_source_id;
+ bool watch_enabled;
+} watch_app_info_t;
+/* Callback: "dbus_watch_get_enabled() may return a different value than it did before" */
+static void toggled_watch(DBusWatch *watch, void* data)
+{
+ VERB3 log("%s(watch:%p, data)", __func__, watch);
+
+ watch_app_info_t* app_info = (watch_app_info_t*)dbus_watch_get_data(watch);
+ if (dbus_watch_get_enabled(watch))
+ {
+ if (!app_info->watch_enabled)
+ {
+ app_info->watch_enabled = true;
+ int dbus_flags = dbus_watch_get_flags(watch);
+ int glib_flags = 0;
+ if (dbus_flags & DBUS_WATCH_READABLE) glib_flags |= G_IO_IN;
+ if (dbus_flags & DBUS_WATCH_WRITABLE) glib_flags |= G_IO_OUT;
+ VERB3 log(" adding watch to glib main loop. dbus_flags:%x glib_flags:%x", dbus_flags, glib_flags);
+ app_info->event_source_id = g_io_add_watch(app_info->channel, (GIOCondition)glib_flags, handle_dbus_fd, watch);
+ }
+ /* else: it was already enabled */
+ }
+ else
+ {
+ if (app_info->watch_enabled)
+ {
+ app_info->watch_enabled = false;
+ /* does it free the hidden GSource too? */
+ VERB3 log(" removing watch from glib main loop");
+ g_source_remove(app_info->event_source_id);
+ }
+ /* else: it was already disabled */
+ }
+}
+/* Callback: "libdbus needs a new watch to be monitored by the main loop" */
+static dbus_bool_t add_watch(DBusWatch *watch, void* data)
+{
+ VERB3 log("%s(watch:%p, data)", __func__, watch);
+
+ watch_app_info_t* app_info = (watch_app_info_t*)xzalloc(sizeof(*app_info));
+ dbus_watch_set_data(watch, app_info, free);
+
+ int fd = dbus_watch_get_unix_fd(watch);
+ VERB3 log(" dbus_watch_get_unix_fd():%d", fd);
+ app_info->channel = g_io_channel_unix_new(fd);
+ /* _unconditionally_ adding it to event loop would be an error */
+ toggled_watch(watch, data);
+ return TRUE;
+}
+/* Callback: "libdbus no longer needs a watch to be monitored by the main loop" */
+static void remove_watch(DBusWatch *watch, void* data)
+{
+ VERB3 log("%s()", __func__);
+ watch_app_info_t* app_info = (watch_app_info_t*)dbus_watch_get_data(watch);
+ if (app_info->watch_enabled)
+ {
+ app_info->watch_enabled = false;
+ g_source_remove(app_info->event_source_id);
+ }
+ g_io_channel_unref(app_info->channel);
+}
+
+/* Callback: "libdbus needs a new timeout to be monitored by the main loop" */
+static dbus_bool_t add_timeout(DBusTimeout *timeout, void* data)
+{
+ VERB3 log("%s()", __func__);
+ return TRUE;
+}
+/* Callback: "libdbus no longer needs a timeout to be monitored by the main loop" */
+static void remove_timeout(DBusTimeout *timeout, void* data)
+{
+ VERB3 log("%s()", __func__);
+}
+/* Callback: "dbus_timeout_get_enabled() may return a different value than it did before" */
+static void timeout_toggled(DBusTimeout *timeout, void* data)
+{
+//seems to be never called, let's make it noisy
+ error_msg_and_die("%s(): FIXME: some dbus machinery is missing here", __func__);
+}
+
+/* Callback: "DBusObjectPathVTable is unregistered (or its connection is freed)" */
+static void unregister_vtable(DBusConnection *conn, void* data)
+{
+ VERB3 log("%s()", __func__);
+}
+
+
+/*
+ * 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
+ * libdbus calls back add_watch(watch:0x2341090, data), this watch is for writing
+ * we call toggled_watch, but it finds that watch is not to be enabled yet
+ * libdbus calls back add_watch(watch:0x23410e0, data), this watch is for reading
+ * we call toggled_watch, it adds watch's fd to glib main loop with POLLIN
+ * (note: these watches are different objects, but they have the same fd)
+ * we call dbus_connection_set_timeout_functions
+ * we call dbus_connection_register_object_path
+ *
+ * Note: if user will later call dbus_bus_request_name(conn, ...):
+ * libdbus calls back add_timeout()
+ * libdbus calls back remove_timeout()
+ * note - no callback to timeout_toggled()!
+ * (therefore there is no code yet in timeout_toggled (see above), it's not used)
+ */
+void attach_dbus_conn_to_glib_main_loop(DBusConnection* conn,
+ const char* object_path,
+ DBusHandlerResult (*message_received_func)(DBusConnection *conn, DBusMessage *msg, void* data)
+) {
+ if (g_dbus_conn)
+ error_msg_and_die("Internal bug: can't connect to more than one dbus");
+ g_dbus_conn = conn;
+
+//do we need this? why?
+//log("dbus_connection_set_dispatch_status_function");
+// dbus_connection_set_dispatch_status_function(conn,
+// dispatch, /* void dispatch(DBusConnection *conn, DBusDispatchStatus new_status, void* data) */
+// NULL, /* data */
+// NULL /* free_data_function */
+// )
+ VERB3 log("dbus_connection_set_watch_functions");
+ if (!dbus_connection_set_watch_functions(conn,
+ add_watch,
+ remove_watch,
+ toggled_watch,
+ NULL, /* data */
+ NULL /* free_data_function */
+ )
+ ) {
+ die_out_of_memory();
+ }
+ VERB3 log("dbus_connection_set_timeout_functions");
+ if (!dbus_connection_set_timeout_functions(conn,
+ add_timeout,
+ remove_timeout,
+ timeout_toggled,
+ NULL, /* data */
+ NULL /* free_data_function */
+ )
+ ) {
+ die_out_of_memory();
+ }
+
+ if (object_path && message_received_func)
+ {
+ /* Table */
+ const DBusObjectPathVTable vtable = {
+ /* .unregister_function = */ unregister_vtable,
+ /* .message_function = */ message_received_func,
+ };
+ VERB3 log("dbus_connection_register_object_path");
+ if (!dbus_connection_register_object_path(conn,
+ object_path,
+ &vtable,
+ NULL /* data */
+ )
+ ) {
+ die_out_of_memory();
+ }
+ }
+}
+
+
+/*
+ * 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", 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/libreport/src/lib/abrt_types.c b/libreport/src/lib/abrt_types.c
new file mode 100644
index 00000000..0b51821d
--- /dev/null
+++ b/libreport/src/lib/abrt_types.c
@@ -0,0 +1,37 @@
+/*
+ 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 "libreport.h"
+
+map_string_h *new_map_string(void)
+{
+ return g_hash_table_new_full(g_str_hash, g_str_equal, free, free);
+}
+
+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/libreport/src/lib/append_to_malloced_string.c b/libreport/src/lib/append_to_malloced_string.c
new file mode 100644
index 00000000..c55a258f
--- /dev/null
+++ b/libreport/src/lib/append_to_malloced_string.c
@@ -0,0 +1,27 @@
+/*
+ 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 "libreport.h"
+
+char *append_to_malloced_string(char *mstr, const char *append)
+{
+ unsigned mlen = strlen(mstr);
+ mstr = (char*) xrealloc(mstr, mlen + strlen(append) + 1);
+ strcpy(mstr + mlen, append);
+ return mstr;
+}
diff --git a/libreport/src/lib/binhex.c b/libreport/src/lib/binhex.c
new file mode 100644
index 00000000..238fda0c
--- /dev/null
+++ b/libreport/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 "libreport.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/libreport/src/lib/concat_path_file.c b/libreport/src/lib/concat_path_file.c
new file mode 100644
index 00000000..a17739e3
--- /dev/null
+++ b/libreport/src/lib/concat_path_file.c
@@ -0,0 +1,25 @@
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2001 Erik Andersen
+ *
+ * Licensed under GPLv2 or later.
+ */
+
+/* Concatenate path and filename to new allocated buffer.
+ * Add '/' only as needed (no duplicate // are produced).
+ * If path is NULL, it is assumed to be "/".
+ * filename should not be NULL.
+ */
+
+#include "libreport.h"
+
+char *concat_path_file(const char *path, const char *filename)
+{
+ if (!path)
+ path = "";
+ const char *end = path + strlen(path);
+ while (*filename == '/')
+ filename++;
+ return xasprintf("%s%s%s", path, (end != path && end[-1] != '/' ? "/" : ""), filename);
+}
diff --git a/libreport/src/lib/copy_file_recursive.c b/libreport/src/lib/copy_file_recursive.c
new file mode 100644
index 00000000..ebfd8dae
--- /dev/null
+++ b/libreport/src/lib/copy_file_recursive.c
@@ -0,0 +1,149 @@
+/*
+ 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 "libreport.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;
+ } else {
+ /* (Try to) copy atime and mtime */
+ struct timeval atime_mtime[2];
+ atime_mtime[0].tv_sec = source_stat.st_atime;
+ // note: if "st_atim.tv_nsec" doesn't compile, try "st_atimensec":
+ atime_mtime[0].tv_usec = source_stat.st_atim.tv_nsec / 1000;
+ atime_mtime[1].tv_sec = source_stat.st_mtime;
+ atime_mtime[1].tv_usec = source_stat.st_mtim.tv_nsec / 1000;
+ // note: can use utimensat when it is more widely supported:
+ utimes(dest, atime_mtime);
+ }
+ goto ret;
+ }
+
+ /* Neither dir not regular file: skip */
+
+ skip:
+ log("Skipping '%s'", source);
+ ret:
+ return retval;
+}
diff --git a/libreport/src/lib/copyfd.c b/libreport/src/lib/copyfd.c
new file mode 100644
index 00000000..8d805069
--- /dev/null
+++ b/libreport/src/lib/copyfd.c
@@ -0,0 +1,172 @@
+/*
+ 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.
+*/
+
+/*
+ * Utility routines.
+ *
+ */
+#include "libreport.h"
+
+#define CONFIG_FEATURE_COPYBUF_KB 4
+
+static const char msg_write_error[] = "write error";
+static const char msg_read_error[] = "read error";
+
+static off_t full_fd_action(int src_fd, int dst_fd, off_t size, int flags)
+{
+ int status = -1;
+ off_t total = 0;
+ int last_was_seek = 0;
+#if CONFIG_FEATURE_COPYBUF_KB <= 4
+ char buffer[CONFIG_FEATURE_COPYBUF_KB * 1024];
+ enum { buffer_size = sizeof(buffer) };
+#else
+ char *buffer;
+ int buffer_size;
+
+ /* We want page-aligned buffer, just in case kernel is clever
+ * and can do page-aligned io more efficiently */
+ buffer = mmap(NULL, CONFIG_FEATURE_COPYBUF_KB * 1024,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANON,
+ /* ignored: */ -1, 0);
+ buffer_size = CONFIG_FEATURE_COPYBUF_KB * 1024;
+ if (buffer == MAP_FAILED) {
+ buffer = alloca(4 * 1024);
+ buffer_size = 4 * 1024;
+ }
+#endif
+
+ if (src_fd < 0)
+ goto out;
+
+ if (!size) {
+ size = buffer_size;
+ status = 1; /* copy until eof */
+ }
+
+ while (1) {
+ ssize_t rd;
+
+ rd = safe_read(src_fd, buffer, size > buffer_size ? buffer_size : size);
+
+ if (!rd) { /* eof - all done */
+ if (last_was_seek) {
+ if (lseek(dst_fd, -1, SEEK_CUR) < 0
+ || safe_write(dst_fd, "", 1) != 1
+ ) {
+ perror_msg("%s", msg_write_error);
+ break;
+ }
+ }
+ status = 0;
+ break;
+ }
+ if (rd < 0) {
+ perror_msg("%s", msg_read_error);
+ break;
+ }
+ /* dst_fd == -1 is a fake, else... */
+ if (dst_fd >= 0) {
+ if (flags & COPYFD_SPARSE) {
+ ssize_t cnt = rd;
+ while (--cnt >= 0)
+ if (buffer[cnt] != 0)
+ goto need2write;
+ if (lseek(dst_fd, rd, SEEK_CUR) < 0) {
+ flags &= ~COPYFD_SPARSE;
+ goto need2write;
+ }
+ last_was_seek = 1;
+ } else {
+ need2write:
+ {
+ ssize_t wr = full_write(dst_fd, buffer, rd);
+ if (wr < rd) {
+ perror_msg("%s", msg_write_error);
+ break;
+ }
+ last_was_seek = 0;
+ }
+ }
+ }
+ total += rd;
+ if (status < 0) { /* if we aren't copying till EOF... */
+ size -= rd;
+ if (!size) {
+ /* 'size' bytes copied - all done */
+ status = 0;
+ break;
+ }
+ }
+ }
+ out:
+
+#if CONFIG_FEATURE_COPYBUF_KB > 4
+ if (buffer_size != 4 * 1024)
+ munmap(buffer, buffer_size);
+#endif
+ return status ? -1 : total;
+}
+
+off_t copyfd_size(int fd1, int fd2, off_t size, int flags)
+{
+ if (size) {
+ return full_fd_action(fd1, fd2, size, flags);
+ }
+ return 0;
+}
+
+void copyfd_exact_size(int fd1, int fd2, off_t size)
+{
+ off_t sz = copyfd_size(fd1, fd2, size, /*flags:*/ 0);
+ if (sz == size)
+ return;
+ if (sz != -1)
+ error_msg_and_die("short read");
+ /* if sz == -1, copyfd_XX already complained */
+ xfunc_die();
+}
+
+off_t copyfd_eof(int fd1, int fd2, int flags)
+{
+ return full_fd_action(fd1, fd2, 0, flags);
+}
+
+off_t copy_file(const char *src_name, const char *dst_name, int mode)
+{
+ off_t r;
+ int src = open(src_name, O_RDONLY);
+ if (src < 0)
+ {
+ perror_msg("Can't open '%s'", src_name);
+ return -1;
+ }
+ int dst = open(dst_name, O_WRONLY | O_TRUNC | O_CREAT, mode);
+ if (dst < 0)
+ {
+ close(src);
+ perror_msg("Can't open '%s'", dst_name);
+ return -1;
+ }
+ r = copyfd_eof(src, dst, /*flags:*/ 0);
+ close(src);
+ close(dst);
+ return r;
+}
diff --git a/libreport/src/lib/create_dump_dir.c b/libreport/src/lib/create_dump_dir.c
new file mode 100644
index 00000000..33d9bd7c
--- /dev/null
+++ b/libreport/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 "libreport.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, 0640);
+ if (dd)
+ dd_create_basic_files(dd, (uid_t)-1L);
+ free(path);
+ return dd;
+}
+
+struct dump_dir *create_dump_dir_from_problem_data(problem_data_t *problem_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 problem_item *value;
+ g_hash_table_iter_init(&iter, problem_data);
+ while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value))
+ {
+ if (name[0] == '.' || strchr(name, '/'))
+ {
+ error_msg("Problem 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/libreport/src/lib/daemon_is_ok.c b/libreport/src/lib/daemon_is_ok.c
new file mode 100644
index 00000000..b4856b14
--- /dev/null
+++ b/libreport/src/lib/daemon_is_ok.c
@@ -0,0 +1,51 @@
+/*
+ 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 "libreport.h"
+
+int daemon_is_ok()
+{
+ int fd = open(VAR_RUN"/abrtd.pid", O_RDONLY);
+ if (fd < 0)
+ {
+ return 0;
+ }
+
+ char pid[sizeof(pid_t)*3 + 2];
+ int len = read(fd, pid, sizeof(pid)-1);
+ close(fd);
+ if (len <= 0)
+ return 0;
+
+ pid[len] = '\0';
+ *strchrnul(pid, '\n') = '\0';
+ /* paranoia: we don't want to check /proc//stat or /proc///stat */
+ if (pid[0] == '\0' || pid[0] == '/')
+ return 0;
+
+ char path[sizeof("/proc/%s/stat") + sizeof(pid)];
+ sprintf(path, "/proc/%s/stat", pid);
+ struct stat sb;
+ if (stat(path, &sb) == -1)
+ {
+ return 0;
+ }
+
+ /* TODO: maybe readlink /proc/PID/exe and check that it is "xxx/abrt"? */
+
+ return 1;
+}
diff --git a/libreport/src/lib/dirsize.c b/libreport/src/lib/dirsize.c
new file mode 100644
index 00000000..88733ae0
--- /dev/null
+++ b/libreport/src/lib/dirsize.c
@@ -0,0 +1,110 @@
+/*
+ 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 "libreport.h"
+
+double get_dirsize(const char *pPath)
+{
+ DIR *dp = opendir(pPath);
+ if (dp == NULL)
+ return 0;
+
+ struct dirent *ep;
+ struct stat statbuf;
+ double size = 0;
+ while ((ep = readdir(dp)) != NULL)
+ {
+ if (dot_or_dotdot(ep->d_name))
+ continue;
+ char *dname = concat_path_file(pPath, ep->d_name);
+ if (lstat(dname, &statbuf) != 0)
+ {
+ free(dname);
+ continue;
+ }
+ if (S_ISDIR(statbuf.st_mode))
+ {
+ size += get_dirsize(dname);
+ }
+ else if (S_ISREG(statbuf.st_mode))
+ {
+ size += statbuf.st_size;
+ }
+ free(dname);
+ }
+ closedir(dp);
+ return size;
+}
+
+double get_dirsize_find_largest_dir(
+ const char *pPath,
+ char **worst_dir,
+ const char *excluded)
+{
+ if (worst_dir)
+ *worst_dir = NULL;
+
+ DIR *dp = opendir(pPath);
+ if (dp == NULL)
+ return 0;
+
+ struct dirent *ep;
+ struct stat statbuf;
+ double size = 0;
+ double maxsz = 0;
+ while ((ep = readdir(dp)) != NULL)
+ {
+ if (dot_or_dotdot(ep->d_name))
+ continue;
+ char *dname = concat_path_file(pPath, ep->d_name);
+ if (lstat(dname, &statbuf) != 0)
+ {
+ free(dname);
+ continue;
+ }
+ if (S_ISDIR(statbuf.st_mode))
+ {
+ double sz = get_dirsize(dname);
+ size += sz;
+
+ if (worst_dir && (!excluded || strcmp(excluded, ep->d_name) != 0))
+ {
+ /* Calculate "weighted" size and age
+ * w = sz_kbytes * age_mins */
+ sz /= 1024;
+ long age = (time(NULL) - statbuf.st_mtime) / 60;
+ if (age > 0)
+ sz *= age;
+
+ if (sz > maxsz)
+ {
+ maxsz = sz;
+ free(*worst_dir);
+ *worst_dir = xstrdup(ep->d_name);
+ }
+ }
+ }
+ else if (S_ISREG(statbuf.st_mode))
+ {
+ size += statbuf.st_size;
+ }
+ free(dname);
+ }
+ closedir(dp);
+ return size;
+}
diff --git a/libreport/src/lib/dump_dir.c b/libreport/src/lib/dump_dir.c
new file mode 100644
index 00000000..8891f911
--- /dev/null
+++ b/libreport/src/lib/dump_dir.c
@@ -0,0 +1,839 @@
+/*
+ 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 <sys/utsname.h>
+#include "libreport.h"
+#include "strbuf.h"
+
+// 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.
+//
+// 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);
+
+static bool isdigit_str(const char *str)
+{
+ do
+ {
+ if (*str < '0' || *str > '9') return false;
+ str++;
+ } while (*str);
+ return true;
+}
+
+static bool exist_file_dir(const char *path)
+{
+ struct stat buf;
+ if (stat(path, &buf) == 0)
+ {
+ if (S_ISDIR(buf.st_mode) || S_ISREG(buf.st_mode))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Return values:
+ * -1: error (in this case, errno is 0 if error message is already logged)
+ * 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)
+ {
+ if (errno != ENOENT && errno != ENOTDIR && errno != EACCES)
+ {
+ perror_msg("Can't create lock file '%s'", lock_file);
+ errno = 0;
+ }
+ return -1;
+ }
+
+ char pid_buf[sizeof(pid_t)*3 + 4];
+ ssize_t r = readlink(lock_file, pid_buf, sizeof(pid_buf) - 1);
+ if (r < 0)
+ {
+ if (errno == ENOENT)
+ {
+ /* Looks like lock_file was deleted */
+ usleep(SYMLINK_RETRY_USLEEP); /* avoid CPU eating loop */
+ continue;
+ }
+ perror_msg("Can't read lock file '%s'", lock_file);
+ errno = 0;
+ return -1;
+ }
+ pid_buf[r] = '\0';
+
+ if (strcmp(pid_buf, pid) == 0)
+ {
+ log("Lock file '%s' is already locked by us", lock_file);
+ return 0;
+ }
+ if (isdigit_str(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 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("Can't remove stale lock file '%s'", lock_file);
+ errno = 0;
+ return -1;
+ }
+ }
+
+ VERB1 log("Locked '%s'", lock_file);
+ return 1;
+}
+
+static int dd_lock(struct dump_dir *dd, unsigned sleep_usec, int flags)
+{
+ if (dd->locked)
+ error_msg_and_die("Locking bug on '%s'", dd->dd_dirname);
+
+ char pid_buf[sizeof(long)*3 + 2];
+ sprintf(pid_buf, "%lu", (long)getpid());
+
+ 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)
+ {
+ 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)
+{
+ if (dd->locked)
+ {
+ dd->locked = 0;
+
+ 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);
+ }
+}
+
+static inline struct dump_dir *dd_init(void)
+{
+ return (struct dump_dir*)xzalloc(sizeof(struct dump_dir));
+}
+
+int dd_exist(struct dump_dir *dd, const char *path)
+{
+ char *full_path = concat_path_file(dd->dd_dirname, path);
+ int ret = exist_file_dir(full_path);
+ free(full_path);
+ return ret;
+}
+
+void dd_close(struct dump_dir *dd)
+{
+ if (!dd)
+ return;
+
+ dd_unlock(dd);
+ if (dd->next_dir)
+ {
+ closedir(dd->next_dir);
+ /* free(dd->next_dir); - WRONG! */
+ }
+
+ free(dd->dd_dirname);
+ free(dd);
+}
+
+static char* rm_trailing_slashes(const char *dir)
+{
+ unsigned len = strlen(dir);
+ while (len != 0 && dir[len-1] == '/')
+ len--;
+ return xstrndup(dir, len);
+}
+
+struct dump_dir *dd_opendir(const char *dir, int flags)
+{
+ struct dump_dir *dd = dd_init();
+
+ dir = dd->dd_dirname = rm_trailing_slashes(dir);
+
+ struct stat stat_buf;
+ stat(dir, &stat_buf);
+ /* & 0666 should remove the executable bit */
+ dd->mode = (stat_buf.st_mode & 0666);
+
+ errno = 0;
+ if (dd_lock(dd, WAIT_FOR_OTHER_PROCESS_USLEEP, flags) < 0)
+ {
+ if ((flags & DD_OPEN_READONLY) && errno == EACCES)
+ {
+ /* Directory is not writable. If it seems to be readable,
+ * return "read only" dd, not NULL */
+ 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 "."!
+ */
+ error_msg("'%s' is not a dump directory", dir);
+ }
+ else if (errno == ENOENT || errno == ENOTDIR)
+ {
+ if (!(flags & DD_FAIL_QUIETLY_ENOENT))
+ error_msg("'%s' does not exist", dir);
+ }
+ else
+ {
+ if (!(flags & DD_FAIL_QUIETLY_EACCES))
+ perror_msg("Can't access '%s'", dir);
+ }
+ dd_close(dd);
+ return NULL;
+ }
+
+ dd->dd_uid = (uid_t)-1L;
+ dd->dd_gid = (gid_t)-1L;
+ if (geteuid() == 0)
+ {
+ /* 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))
+ {
+ error_msg("Can't stat '%s', or it is not a directory", dir);
+ dd_close(dd);
+ return NULL;
+ }
+ dd->dd_uid = stat_buf.st_uid;
+ dd->dd_gid = stat_buf.st_gid;
+ }
+
+ return dd;
+}
+
+/* Create a fresh empty debug dump dir.
+ *
+ * Security: we should not allow users to write new files or write
+ * into existing ones, but they should be able to read them.
+ *
+ * @param uid
+ * Crashed application's User Id
+ *
+ * We currently have only three callers:
+ * kernel oops hook: uid -> not saved, so everyone can steal and work with it
+ * this hook runs under 0:0
+ * ccpp hook: uid=uid of crashed user's binary
+ * this hook runs under 0:0
+ * python hook: uid=uid of crashed user's script
+ * this hook runs under abrt:gid
+ *
+ * Currently, we set dir's gid to passwd(uid)->pw_gid parameter, and we set uid to
+ * abrt's user id. We do not allow write access to group.
+ */
+struct dump_dir *dd_create(const char *dir, uid_t uid, mode_t mode)
+{
+ /* a little trick to copy read bits from file mode to exec bit of dir mode*/
+ mode_t dir_mode = mode | ((mode & 0444) >> 2);
+ struct dump_dir *dd = dd_init();
+
+ dd->mode = mode;
+
+ /* Unlike dd_opendir, can't use realpath: the directory doesn't exist yet,
+ * realpath will always return NULL. We don't really have to:
+ * dd_opendir(".") makes sense, dd_create(".") does not.
+ */
+ dir = dd->dd_dirname = rm_trailing_slashes(dir);
+
+ const char *last_component = strrchr(dir, '/');
+ if (last_component)
+ last_component++;
+ else
+ last_component = dir;
+ if (dot_or_dotdot(last_component))
+ {
+ /* dd_create("."), dd_create(".."), dd_create("dir/."),
+ * dd_create("dir/..") and similar are madness, refuse them.
+ */
+ error_msg("Bad dir name '%s'", dir);
+ dd_close(dd);
+ return NULL;
+ }
+
+ 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, dir_mode) == -1)
+ {
+ 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, /*flags:*/ 0) < 0)
+ {
+ dd_close(dd);
+ return NULL;
+ }
+
+ /* mkdir's mode (above) can be affected by umask, fix it */
+ if (chmod(dir, dir_mode) == -1)
+ {
+ perror_msg("can't change mode of '%s'", dir);
+ dd_close(dd);
+ return NULL;
+ }
+
+ dd->dd_uid = (uid_t)-1L;
+ dd->dd_gid = (gid_t)-1L;
+ if (uid != (uid_t)-1L)
+ {
+ /* 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);
+
+ /* it doesn't make sense to create the uid file if uid == -1 */
+ if (uid != (uid_t)-1L)
+ {
+ sprintf(long_str, "%li", (long)uid);
+ dd_save_text(dd, FILENAME_UID, long_str);
+ }
+
+ struct utsname buf;
+ uname(&buf); /* never fails */
+ dd_save_text(dd, FILENAME_KERNEL, buf.release);
+ dd_save_text(dd, FILENAME_ARCHITECTURE, buf.machine);
+ dd_save_text(dd, FILENAME_HOSTNAME, buf.nodename);
+
+ 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);
+}
+
+void dd_sanitize_mode_and_owner(struct dump_dir *dd)
+{
+ /* Don't sanitize if we aren't run under root:
+ * we assume that during file creation (by whatever means,
+ * even by "hostname >file" in abrt_event.conf)
+ * normal umask-based mode setting takes care of correct mode,
+ * and uid:gid is, of course, set to user's uid and gid.
+ *
+ * For root operating on /var/spool/abrt/USERS_PROBLEM, this isn't true:
+ * "hostname >file", for example, would create file OWNED BY ROOT!
+ * This routine resets mode and uid:gid for all such files.
+ */
+ if (dd->dd_uid == (uid_t)-1)
+ return;
+
+ if (!dd->locked)
+ error_msg_and_die("dump_dir is not opened"); /* bug */
+
+ DIR *d = opendir(dd->dd_dirname);
+ if (!d)
+ return;
+
+ struct dirent *dent;
+ while ((dent = readdir(d)) != NULL)
+ {
+ if (dent->d_name[0] == '.') /* ".lock", ".", ".."? skip */
+ continue;
+ char *full_path = concat_path_file(dd->dd_dirname, dent->d_name);
+ struct stat statbuf;
+ if (lstat(full_path, &statbuf) == 0 && S_ISREG(statbuf.st_mode))
+ {
+ if ((statbuf.st_mode & 0777) != dd->mode)
+ chmod(full_path, dd->mode);
+ if (statbuf.st_uid != dd->dd_uid || statbuf.st_gid != dd->dd_gid)
+ {
+ if (chown(full_path, dd->dd_uid, dd->dd_gid) != 0)
+ {
+ perror_msg("can't change '%s' ownership to %lu:%lu", full_path,
+ (long)dd->dd_uid, (long)dd->dd_gid);
+ }
+ }
+ }
+ free(full_path);
+ }
+ closedir(d);
+}
+
+static int delete_file_dir(const char *dir, bool skip_lock_file)
+{
+ DIR *d = opendir(dir);
+ if (!d)
+ {
+ /* 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)
+ {
+ int err = 0;
+ if (errno == EISDIR)
+ {
+ errno = 0;
+ err = delete_file_dir(full_path, /*skip_lock_file:*/ false);
+ }
+ if (errno || err)
+ {
+ perror_msg("Can't remove '%s'", full_path);
+ free(full_path);
+ closedir(d);
+ return -1;
+ }
+ }
+ free(full_path);
+ }
+ closedir(d);
+
+ /* 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)
+ {
+ 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;
+}
+
+int dd_delete(struct dump_dir *dd)
+{
+ 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)
+{
+ FILE *fp = fopen(path, "r");
+ if (!fp)
+ {
+ if (!(flags & DD_FAIL_QUIETLY_ENOENT))
+ perror_msg("Can't open file '%s'", path);
+ return (flags & DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE ? NULL : xstrdup(""));
+ }
+
+ struct strbuf *buf_content = strbuf_new();
+ int oneline = 0;
+ int ch;
+ while ((ch = fgetc(fp)) != EOF)
+ {
+//TODO? \r -> \n?
+//TODO? strip trailing spaces/tabs?
+ if (ch == '\n')
+ oneline = (oneline << 1) | 1;
+ if (ch == '\0')
+ ch = ' ';
+ if (isspace(ch) || ch >= ' ') /* used !iscntrl, but it failed on unicode */
+ strbuf_append_char(buf_content, ch);
+ }
+ fclose(fp);
+
+ char last = oneline != 0 ? buf_content->buf[buf_content->len - 1] : 0;
+ if (last == '\n')
+ {
+ /* If file contains exactly one '\n' and it is at the end, remove it.
+ * This enables users to use simple "echo blah >file" in order to create
+ * short string items in dump dirs.
+ */
+ if (oneline == 1)
+ buf_content->buf[--buf_content->len] = '\0';
+ }
+ else /* last != '\n' */
+ {
+ /* Last line is unterminated, fix it */
+ /* Cases: */
+ /* oneline=0: "qwe" - DONT fix this! */
+ /* oneline=1: "qwe\nrty" - two lines in fact */
+ /* oneline>1: "qwe\nrty\uio" */
+ if (oneline >= 1)
+ strbuf_append_char(buf_content, '\n');
+ }
+
+ return strbuf_free_nobuf(buf_content);
+}
+
+static bool save_binary_file(const char *path, const char* data, unsigned size, uid_t uid, gid_t gid, mode_t mode)
+{
+ /* the mode is set by the caller, see dd_create() for security analysis */
+ unlink(path);
+ int fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
+ if (fd < 0)
+ {
+ perror_msg("Can't open file '%s'", path);
+ return false;
+ }
+
+ if (uid != (uid_t)-1L)
+ {
+ 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)
+ {
+ error_msg("Can't save file '%s'", path);
+ return false;
+ }
+
+ return true;
+}
+
+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 */
+
+ /* 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);
+
+ return ret;
+}
+
+char* dd_load_text(const struct dump_dir *dd, const char *name)
+{
+ return dd_load_text_ext(dd, name, /*flags:*/ 0);
+}
+
+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_dirname, name);
+ save_binary_file(full_path, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode);
+ free(full_path);
+}
+
+void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, unsigned size)
+{
+ if (!dd->locked)
+ error_msg_and_die("dump_dir is not opened"); /* bug */
+
+ char *full_path = concat_path_file(dd->dd_dirname, name);
+ save_binary_file(full_path, data, size, dd->dd_uid, dd->dd_gid, dd->mode);
+ free(full_path);
+}
+
+void add_reported_to(struct dump_dir *dd, const char *line)
+{
+ if (!dd->locked)
+ error_msg_and_die("dump_dir is not opened"); /* bug */
+
+ char *reported_to = dd_load_text_ext(dd, FILENAME_REPORTED_TO, DD_FAIL_QUIETLY_ENOENT | DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE);
+ if (reported_to)
+ {
+ unsigned len_line = strlen(line);
+ char *p = reported_to;
+ while (*p)
+ {
+ if (strncmp(p, line, len_line) == 0 && (p[len_line] == '\n' || p[len_line] == '\0'))
+ goto ret;
+ p = strchrnul(p, '\n');
+ if (!*p)
+ break;
+ p++;
+ }
+ if (p != reported_to && p[-1] != '\n')
+ reported_to = append_to_malloced_string(reported_to, "\n");
+ reported_to = append_to_malloced_string(reported_to, line);
+ reported_to = append_to_malloced_string(reported_to, "\n");
+ }
+ else
+ reported_to = xasprintf("%s\n", line);
+ dd_save_text(dd, FILENAME_REPORTED_TO, reported_to);
+ ret:
+ free(reported_to);
+}
+
+DIR *dd_init_next_file(struct dump_dir *dd)
+{
+// 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_dirname);
+ if (!dd->next_dir)
+ {
+ error_msg("Can't open directory '%s'", dd->dd_dirname);
+ }
+
+ return dd->next_dir;
+}
+
+int dd_get_next_file(struct dump_dir *dd, char **short_name, char **full_name)
+{
+ if (dd->next_dir == NULL)
+ return 0;
+
+ struct dirent *dent;
+ while ((dent = readdir(dd->next_dir)) != NULL)
+ {
+ 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_dirname, dent->d_name);
+ return 1;
+ }
+ }
+
+ closedir(dd->next_dir);
+ dd->next_dir = NULL;
+ return 0;
+}
+
+/* Utility function */
+void delete_dump_dir(const char *dirname)
+{
+ struct dump_dir *dd = dd_opendir(dirname, /*flags:*/ 0);
+ if (dd)
+ {
+ dd_delete(dd);
+ }
+}
diff --git a/libreport/src/lib/encbase64.c b/libreport/src/lib/encbase64.c
new file mode 100644
index 00000000..52e02b7f
--- /dev/null
+++ b/libreport/src/lib/encbase64.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2006 Rob Landley <rob@landley.net>
+ *
+ * Licensed under GPLv2 or later.
+ */
+#include "libreport.h" /* xmalloc */
+
+/* Conversion table for base 64 */
+static const char tbl_base64[65 /*+ 2*/] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3',
+ '4', '5', '6', '7', '8', '9', '+', '/',
+ '=' /* termination character */,
+ // '\n', '\0' /* needed for uudecode.c */
+};
+
+/* Conversion table for uuencode
+const char tbl_uuencode[65] ALIGN1 = {
+ '`', '!', '"', '#', '$', '%', '&', '\'',
+ '(', ')', '*', '+', ',', '-', '.', '/',
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', ':', ';', '<', '=', '>', '?',
+ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
+ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
+ 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
+ '`'
+};
+*/
+
+/*
+ * Encode bytes at S of length LENGTH.
+ * Result will be 0-terminated, and must point to a writable
+ * buffer of at least 1+BASE64_LENGTH(length) bytes,
+ * where BASE64_LENGTH(len) = 4 * ((LENGTH + 2) / 3)
+ */
+static void encode_64bit(char *p, const void *src, int length, const char *tbl)
+{
+ const unsigned char *s = (const unsigned char *)src;
+
+ /* Transform the 3x8 bits to 4x6 bits */
+ while (length > 0) {
+ unsigned s1, s2;
+
+ /* Are s[1], s[2] valid or should be assumed 0? */
+ s1 = s2 = 0;
+ length -= 3; /* can be >=0, -1, -2 */
+ if (length >= -1) {
+ s1 = s[1];
+ if (length >= 0)
+ s2 = s[2];
+ }
+ *p++ = tbl[s[0] >> 2];
+ *p++ = tbl[((s[0] & 3) << 4) + (s1 >> 4)];
+ *p++ = tbl[((s1 & 0xf) << 2) + (s2 >> 6)];
+ *p++ = tbl[s2 & 0x3f];
+ s += 3;
+ }
+ /* Zero-terminate */
+ *p = '\0';
+ /* If length is -2 or -1, pad last char or two */
+ while (length) {
+ *--p = tbl[64];
+ length++;
+ }
+}
+
+char *encode_base64(const void *src, int length)
+{
+ char *dst = (char *)xmalloc(4 * ((length + 2) / 3) + 1);
+ encode_64bit(dst, src, length, tbl_base64);
+ return dst;
+}
diff --git a/libreport/src/lib/event_config.c b/libreport/src/lib/event_config.c
new file mode 100644
index 00000000..35ed8d36
--- /dev/null
+++ b/libreport/src/lib/event_config.c
@@ -0,0 +1,366 @@
+/*
+ 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 "libreport.h"
+
+GHashTable *g_event_config_list;
+static GHashTable *g_event_config_symlinks;
+
+event_option_t *new_event_option(void)
+{
+ return xzalloc(sizeof(event_option_t));
+}
+
+event_config_t *new_event_config(void)
+{
+ return xzalloc(sizeof(event_config_t));
+}
+
+void free_event_option(event_option_t *p)
+{
+ if (!p)
+ return;
+ free(p->eo_name);
+ free(p->eo_value);
+ free(p->eo_label);
+ free(p->eo_note_html);
+ //free(p->eo_description);
+ //free(p->eo_allowed_value);
+ free(p);
+}
+
+void free_event_config(event_config_t *p)
+{
+ if (!p)
+ return;
+
+ free(p->screen_name);
+ free(p->description);
+ free(p->long_descr);
+ free(p->ec_creates_items);
+ free(p->ec_requires_items);
+ free(p->ec_exclude_items_by_default);
+ free(p->ec_include_items_by_default);
+ free(p->ec_exclude_items_always);
+ GList *opt;
+ for (opt = p->options; opt; opt = opt->next)
+ free_event_option(opt->data);
+ g_list_free(p->options);
+
+ free(p);
+}
+
+
+static int cmp_event_option_name_with_string(gconstpointer a, gconstpointer b)
+{
+ const event_option_t *evopt = a;
+ return !evopt->eo_name || strcmp(evopt->eo_name, (char *)b) != 0;
+}
+
+event_option_t *get_event_option_from_list(const char *name, GList *options)
+{
+ GList *elem = g_list_find_custom(options, name, &cmp_event_option_name_with_string);
+ if (elem)
+ return (event_option_t *)elem->data;
+ return NULL;
+}
+
+static void load_config_files(const char *dir_path)
+{
+ DIR *dir;
+ struct dirent *dent;
+
+ /* Load .conf files */
+ dir = opendir(dir_path);
+ if (!dir)
+ return;
+ while ((dent = readdir(dir)) != NULL)
+ {
+ char *ext = strrchr(dent->d_name, '.');
+ if (!ext)
+ continue;
+ if (strcmp(ext + 1, "conf") != 0)
+ continue;
+
+ char *fullname = concat_path_file(dir_path, dent->d_name);
+
+ *ext = '\0';
+ event_config_t *event_config = get_event_config(dent->d_name);
+ bool new_config = (!event_config);
+ if (new_config)
+ event_config = new_event_config();
+
+ map_string_h *keys_and_values = new_map_string();
+
+ load_conf_file(fullname, keys_and_values, /*skipKeysWithoutValue:*/ false);
+ free(fullname);
+
+ /* Insert or replace every key/value from keys_and_values to event_config->option */
+ GHashTableIter iter;
+ char *name;
+ char *value;
+ g_hash_table_iter_init(&iter, keys_and_values);
+ while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value))
+ {
+ event_option_t *opt;
+ GList *elem = g_list_find_custom(event_config->options, name,
+ cmp_event_option_name_with_string);
+ if (elem)
+ {
+ opt = elem->data;
+ // log("conf: replacing '%s' value:'%s'->'%s'", name, opt->value, value);
+ free(opt->eo_value);
+ }
+ else
+ {
+ // log("conf: new value %s='%s'", name, value);
+ opt = new_event_option();
+ opt->eo_name = xstrdup(name);
+ }
+ opt->eo_value = xstrdup(value);
+ if (!elem)
+ event_config->options = g_list_append(event_config->options, opt);
+ }
+
+ free_map_string(keys_and_values);
+
+ if (new_config)
+ g_hash_table_replace(g_event_config_list, xstrdup(dent->d_name), event_config);
+ }
+ closedir(dir);
+}
+
+/* (Re)loads data from /etc/abrt/events/foo.{xml,conf} and ~/.abrt/events/foo.conf */
+void load_event_config_data(void)
+{
+ free_event_config_data();
+
+ if (!g_event_config_list)
+ g_event_config_list = g_hash_table_new_full(
+ /*hash_func*/ g_str_hash,
+ /*key_equal_func:*/ g_str_equal,
+ /*key_destroy_func:*/ free,
+ /*value_destroy_func:*/ (GDestroyNotify) free_event_config
+ );
+ if (!g_event_config_symlinks)
+ g_event_config_symlinks = g_hash_table_new_full(
+ /*hash_func*/ g_str_hash,
+ /*key_equal_func:*/ g_str_equal,
+ /*key_destroy_func:*/ free,
+ /*value_destroy_func:*/ free
+ );
+
+ DIR *dir;
+ struct dirent *dent;
+
+ /* Load .xml files */
+ dir = opendir(EVENTS_DIR);
+ if (!dir)
+ return;
+ while ((dent = readdir(dir)) != NULL)
+ {
+ char *ext = strrchr(dent->d_name, '.');
+ if (!ext)
+ continue;
+ if (strcmp(ext + 1, "xml") != 0)
+ continue;
+
+ char *fullname = concat_path_file(EVENTS_DIR, dent->d_name);
+ *ext = '\0';
+
+ struct stat buf;
+ if (0 != lstat(fullname, &buf))
+ continue;
+ if (S_ISLNK(buf.st_mode))
+ {
+ GError *error = NULL;
+ gchar *link = g_file_read_link(fullname, &error);
+ if (error != NULL)
+ error_msg_and_die("Error reading symlink '%s': %s", fullname, error->message);
+
+ gchar *target = g_path_get_basename(link);
+ char *ext = strrchr(target, '.');
+ if (!ext || 0 != strcmp(ext + 1, "xml"))
+ error_msg_and_die("Invalid event symlink '%s': expected it to"
+ " point to another xml file", fullname);
+ *ext = '\0';
+ g_hash_table_replace(g_event_config_symlinks, xstrdup(dent->d_name), target);
+ g_free(link);
+ /* don't free target, it is owned by the hash table now */
+ continue;
+ }
+
+ event_config_t *event_config = get_event_config(dent->d_name);
+ bool new_config = (!event_config);
+ if (new_config)
+ event_config = new_event_config();
+
+ load_event_description_from_file(event_config, fullname);
+ free(fullname);
+
+ if (new_config)
+ g_hash_table_replace(g_event_config_list, xstrdup(dent->d_name), event_config);
+ }
+ closedir(dir);
+
+ load_config_files(EVENTS_DIR);
+
+ char *HOME = getenv("HOME");
+ if (!HOME || !HOME[0])
+ return;
+ HOME = concat_path_file(HOME, ".abrt/events");
+ load_config_files(HOME);
+ free(HOME);
+}
+
+/* Frees all loaded data */
+void free_event_config_data(void)
+{
+ if (g_event_config_list)
+ {
+ g_hash_table_destroy(g_event_config_list);
+ g_event_config_list = NULL;
+ }
+ if (g_event_config_symlinks)
+ {
+ g_hash_table_destroy(g_event_config_symlinks);
+ g_event_config_symlinks = NULL;
+ }
+}
+
+event_config_t *get_event_config(const char *name)
+{
+ if (!g_event_config_list)
+ return NULL;
+ if (g_event_config_symlinks)
+ {
+ char *link = g_hash_table_lookup(g_event_config_symlinks, name);
+ if (link)
+ name = link;
+ }
+ return g_hash_table_lookup(g_event_config_list, name);
+}
+
+GList *export_event_config(const char *event_name)
+{
+ GList *env_list = NULL;
+
+ event_config_t *config = get_event_config(event_name);
+ if (config)
+ {
+ GList *lopt;
+ for (lopt = config->options; lopt; lopt = lopt->next)
+ {
+ event_option_t *opt = lopt->data;
+ if (!opt->eo_value)
+ continue;
+ char *var_val = xasprintf("%s=%s", opt->eo_name, opt->eo_value);
+ VERB3 log("Exporting '%s'", var_val);
+ env_list = g_list_prepend(env_list, var_val);
+ putenv(var_val);
+ }
+ }
+
+ return env_list;
+}
+
+void unexport_event_config(GList *env_list)
+{
+ while (env_list)
+ {
+ char *var_val = env_list->data;
+ VERB3 log("Unexporting '%s'", var_val);
+ safe_unsetenv(var_val);
+ env_list = g_list_remove(env_list, var_val);
+ free(var_val);
+ }
+}
+
+/* return NULL if successful otherwise appropriate error message */
+static char *validate_event_option(event_option_t *opt)
+{
+ if (!opt->eo_allow_empty && (!opt->eo_value || !opt->eo_value[0]))
+ return xstrdup(_("Missing mandatory value"));
+
+ /* if value is NULL and allow-empty yes than it doesn't make sence to check it */
+ if (!opt->eo_value)
+ return NULL;
+
+ const gchar *s = NULL;
+ if (!g_utf8_validate(opt->eo_value, -1, &s))
+ return xasprintf(_("Invalid utf8 character '%c'"), *s);
+
+ switch (opt->eo_type) {
+ case OPTION_TYPE_TEXT:
+ case OPTION_TYPE_PASSWORD:
+ break;
+ case OPTION_TYPE_NUMBER:
+ {
+ char *endptr;
+ errno = 0;
+ long r = strtol(opt->eo_value, &endptr, 10);
+ (void) r;
+ if (errno != 0 || endptr == opt->eo_value || *endptr != '\0')
+ return xasprintf(_("Invalid number '%s'"), opt->eo_value);
+
+ break;
+ }
+ case OPTION_TYPE_BOOL:
+ if (strcmp(opt->eo_value, "yes") != 0
+ && strcmp(opt->eo_value, "no") != 0
+ && strcmp(opt->eo_value, "on") != 0
+ && strcmp(opt->eo_value, "off") != 0
+ && strcmp(opt->eo_value, "1") != 0
+ && strcmp(opt->eo_value, "0") != 0)
+ {
+ return xasprintf(_("Invalid boolean value '%s'"), opt->eo_value);
+ }
+ break;
+ case OPTION_TYPE_HINT_HTML:
+ return NULL;
+ default:
+ return xstrdup(_("Unsupported option type"));
+ };
+
+ return NULL;
+}
+
+GHashTable *validate_event(const char *event_name)
+{
+ event_config_t *config = get_event_config(event_name);
+ if (!config)
+ return NULL;
+
+ GHashTable *errors = g_hash_table_new_full(g_str_hash, g_str_equal, free, free);
+ GList *li;
+
+ for (li = config->options; li; li = li->next)
+ {
+ event_option_t *opt = (event_option_t *)li->data;
+ char *err = validate_event_option(opt);
+ if (err)
+ g_hash_table_insert(errors, xstrdup(opt->eo_name), err);
+ }
+
+ if (g_hash_table_size(errors))
+ return errors;
+
+ g_hash_table_destroy(errors);
+
+ return NULL;
+}
diff --git a/libreport/src/lib/event_xml_parser.c b/libreport/src/lib/event_xml_parser.c
new file mode 100644
index 00000000..4a2e4493
--- /dev/null
+++ b/libreport/src/lib/event_xml_parser.c
@@ -0,0 +1,469 @@
+/*
+ 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 "libreport.h"
+#include "event_config.h"
+
+#define EVENT_ELEMENT "event"
+#define LABEL_ELEMENT "label"
+#define DESCRIPTION_ELEMENT "description"
+#define LONG_DESCR_ELEMENT "long-description"
+#define ALLOW_EMPTY_ELEMENT "allow-empty"
+#define NOTE_HTML_ELEMENT "note-html"
+#define CREATES_ELEMENT "creates-items"
+#define OPTION_ELEMENT "option"
+//#define ACTION_ELEMENT "action"
+#define NAME_ELEMENT "name"
+#define DEFAULT_VALUE_ELEMENT "default-value"
+
+#define REQUIRES_ELEMENT "requires-items"
+#define EXCL_BY_DEFAULT_ELEMENT "exclude-items-by-default"
+#define INCL_BY_DEFAULT_ELEMENT "include-items-by-default"
+#define EXCL_ALWAYS_ELEMENT "exclude-items-always"
+#define EXCL_BINARY_ELEMENT "exclude-binary-items"
+
+
+struct my_parse_data
+{
+ event_config_t *event_config;
+ event_option_t *cur_option;
+ const char *cur_locale;
+ char *attribute_lang;
+};
+
+static const char *const option_types[] =
+{
+ [OPTION_TYPE_TEXT ] = "text",
+ [OPTION_TYPE_BOOL ] = "bool",
+ [OPTION_TYPE_PASSWORD ] = "password",
+ [OPTION_TYPE_NUMBER ] = "number",
+ [OPTION_TYPE_HINT_HTML] = "hint-html",
+ [OPTION_TYPE_INVALID ] = NULL
+};
+
+// Return xml:lang value for <foo xml:lang="value"> if value matches current locale,
+// "" if foo has no xml:lang attribute at all,
+// else (if xml:lang is for some other locale) return NULL
+//
+static char *get_element_lang(struct my_parse_data *parse_data, const gchar **att_names, const gchar **att_values)
+{
+ char *short_locale_end = strchr(parse_data->cur_locale, '_');
+ VERB3 log("locale: %s", parse_data->cur_locale);
+ int i;
+ for (i = 0; att_names[i] != NULL; ++i)
+ {
+ VERB3 log("attr: %s:%s", att_names[i], att_values[i]);
+ if (strcmp(att_names[i], "xml:lang") == 0)
+ {
+ if (strcmp(att_values[i], parse_data->cur_locale) == 0)
+ {
+ VERB3 log("found translation for: %s", parse_data->cur_locale);
+ return xstrdup(att_values[i]);
+ }
+
+ /* try to match shorter locale
+ * e.g: "cs" with cs_CZ
+ */
+ if (short_locale_end
+ && strncmp(att_values[i], parse_data->cur_locale, short_locale_end - parse_data->cur_locale) == 0
+ ) {
+ VERB3 log("found translation for shortlocale: %s", parse_data->cur_locale);
+ return xstrndup(att_values[i], short_locale_end - parse_data->cur_locale);
+ }
+ }
+ }
+ /* if the element has no attribute then it's a default non-localized value */
+ if (i == 0)
+ return xstrdup("");
+ /* if the element is in different language than the current locale */
+ return NULL;
+}
+
+static int cmp_event_option_name_with_string(gconstpointer a, gconstpointer b)
+{
+ const event_option_t *evopt = a;
+ /* "When it is not a match?" */
+ return !evopt->eo_name || strcmp(evopt->eo_name, (char *)b) != 0;
+}
+
+static void consume_cur_option(struct my_parse_data *parse_data)
+{
+ event_option_t *opt = parse_data->cur_option;
+ if (!opt)
+ return;
+ parse_data->cur_option = NULL;
+
+ event_config_t *event_config = parse_data->event_config;
+
+ /* Example of "nameless" option: <option type="hint-html">
+ * The remaining code does not like "nameless" options
+ * (strcmp would segfault, etc), so provide invented name:
+ */
+ if (!opt->eo_name)
+ opt->eo_name = xasprintf("%u", (unsigned)g_list_length(event_config->options));
+
+ GList *elem = g_list_find_custom(event_config->options, opt->eo_name, cmp_event_option_name_with_string);
+ if (elem)
+ {
+ /* we already have option with such name */
+ event_option_t *old_opt = elem->data;
+ if (old_opt->eo_value)
+ {
+ /* ...and it already has a value, which
+ * overrides xml-defined default one:
+ */
+ free(opt->eo_value);
+ opt->eo_value = old_opt->eo_value;
+ old_opt->eo_value = NULL;
+ }
+ //log("xml: replacing '%s' value:'%s'->'%s'", opt->eo_name, old_opt->eo_value, opt->eo_value);
+ free_event_option(old_opt);
+ elem->data = opt;
+ }
+ else
+ {
+ //log("xml: new value %s='%s'", opt->eo_name, opt->eo_value);
+ event_config->options = g_list_append(event_config->options, opt);
+ }
+}
+
+// Called for opening tags <foo bar="baz">
+static void start_element(GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ //log("start: %s", element_name);
+
+ struct my_parse_data *parse_data = user_data;
+
+ if (strcmp(element_name, OPTION_ELEMENT) == 0)
+ {
+ if (parse_data->cur_option)
+ {
+ error_msg("error, option nested in option");
+ return;
+ }
+
+ event_option_t *opt = parse_data->cur_option = new_event_option();
+ int i;
+
+ for (i = 0; attribute_names[i] != NULL; ++i)
+ {
+ VERB2 log("attr: %s:%s", attribute_names[i], attribute_values[i]);
+ if (strcmp(attribute_names[i], "name") == 0)
+ {
+ free(opt->eo_name);
+ opt->eo_name = xstrdup(attribute_values[i]);
+ }
+ else if (strcmp(attribute_names[i], "type") == 0)
+ {
+ option_type_t type;
+ for (type = OPTION_TYPE_TEXT; type < OPTION_TYPE_INVALID; ++type)
+ {
+ if (strcmp(option_types[type], attribute_values[i]) == 0)
+ opt->eo_type = type;
+ }
+ }
+ }
+ }
+ else
+ if (strcmp(element_name, LABEL_ELEMENT) == 0
+ || strcmp(element_name, DESCRIPTION_ELEMENT) == 0
+ || strcmp(element_name, LONG_DESCR_ELEMENT) == 0
+ || strcmp(element_name, NAME_ELEMENT) == 0
+ || strcmp(element_name, NOTE_HTML_ELEMENT) == 0
+ ) {
+ free(parse_data->attribute_lang);
+ parse_data->attribute_lang = get_element_lang(parse_data, attribute_names, attribute_values);
+ }
+}
+
+// Called for close tags </foo>
+static void end_element(GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ struct my_parse_data *parse_data = user_data;
+
+ free(parse_data->attribute_lang);
+ parse_data->attribute_lang = NULL;
+
+ if (strcmp(element_name, OPTION_ELEMENT) == 0)
+ {
+ consume_cur_option(parse_data);
+ }
+ if (strcmp(element_name, EVENT_ELEMENT) == 0)
+ {
+ consume_cur_option(parse_data);
+ }
+}
+
+// Called for character data
+// text is not nul-terminated
+static void text(GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ struct my_parse_data *parse_data = user_data;
+ event_config_t *ui = parse_data->event_config;
+
+ const gchar *inner_element = g_markup_parse_context_get_element(context);
+ char *text_copy = xstrndup(text, text_len);
+ event_option_t *opt = parse_data->cur_option;
+ if (opt)
+ {
+ if (strcmp(inner_element, LABEL_ELEMENT) == 0)
+ {
+ if (parse_data->attribute_lang != NULL) /* if it isn't for other locale */
+ {
+ /* set the value only if we found a value for the current locale
+ * OR the label is still not set and we found the default value
+ */
+ if (parse_data->attribute_lang[0] != '\0'
+ || !opt->eo_label /* && parse_data->attribute_lang is "" - always true */
+ ) {
+ VERB2 log("new label:'%s'", text_copy);
+ free(opt->eo_label);
+ opt->eo_label = text_copy;
+ }
+ }
+ return;
+ }
+ /*
+ * we can add a separate field for the default value
+ * in that case we can implement features like "reset to default value"
+ * but for now using "value" should be enough and clients doesn't
+ * have to know about the "defaul-value"
+ */
+ if (strcmp(inner_element, DEFAULT_VALUE_ELEMENT) == 0)
+ {
+ VERB2 log("default value:'%s'", text_copy);
+ free(opt->eo_value);
+ opt->eo_value = text_copy;
+ return;
+ }
+
+ if (strcmp(inner_element, NOTE_HTML_ELEMENT) == 0)
+ {
+ if (parse_data->attribute_lang != NULL) /* if it isn't for other locale */
+ {
+ /* set the value only if we found a value for the current locale
+ * OR the label is still not set and we found the default value
+ */
+ if (parse_data->attribute_lang[0] != '\0'
+ || !opt->eo_note_html /* && parse_data->attribute_lang is "" - always true */
+ ) {
+ VERB2 log("html note:'%s'", text_copy);
+ free(opt->eo_note_html);
+ opt->eo_note_html = text_copy;
+ }
+ }
+ return;
+ }
+
+ if (strcmp(inner_element, ALLOW_EMPTY_ELEMENT) == 0)
+ {
+ VERB2 log("allow-empty:'%s'", text_copy);
+ opt->eo_allow_empty = string_to_bool(text_copy);
+ return;
+ }
+ /*
+ if (strcmp(inner_element, DESCRIPTION_ELEMENT) == 0)
+ {
+ VERB2 log("tooltip:'%s'", text_copy);
+ free(opt->eo_description);
+ opt->eo_description = text_copy;
+ return;
+ }
+ */
+ }
+ else
+ {
+ /* we're not in option, so the description is for the event */
+ /*
+ if (strcmp(inner_element, ACTION_ELEMENT) == 0)
+ {
+ VERB2 log("action description:'%s'", text_copy);
+ free(ui->action);
+ ui->action = text_copy;
+ return;
+ }
+ */
+ if (strcmp(inner_element, CREATES_ELEMENT) == 0)
+ {
+ VERB2 log("ec_creates_items:'%s'", text_copy);
+ free(ui->ec_creates_items);
+ ui->ec_creates_items = text_copy;
+ return;
+ }
+ if (strcmp(inner_element, NAME_ELEMENT) == 0)
+ {
+ if (parse_data->attribute_lang != NULL) /* if it isn't for other locale */
+ {
+ /* set the value only if we found a value for the current locale
+ * OR the label is still not set and we found the default value
+ */
+ if (parse_data->attribute_lang[0] != '\0'
+ || !ui->screen_name /* && parse_data->attribute_lang is "" - always true */
+ ) {
+ VERB2 log("event name:'%s'", text_copy);
+ free(ui->screen_name);
+ ui->screen_name = text_copy;
+ }
+ }
+ return;
+ }
+ if (strcmp(inner_element, DESCRIPTION_ELEMENT) == 0)
+ {
+ VERB3 log("event description:'%s'", text_copy);
+
+ if (parse_data->attribute_lang != NULL) /* if it isn't for other locale */
+ {
+ /* set the value only if we found a value for the current locale
+ * OR the description is still not set and we found the default value
+ */
+ if (parse_data->attribute_lang[0] != '\0'
+ || !ui->description /* && parse_data->attribute_lang is "" - always true */
+ ) {
+ free(ui->description);
+ ui->description = text_copy;
+ }
+ }
+ return;
+ }
+ if (strcmp(inner_element, LONG_DESCR_ELEMENT) == 0)
+ {
+ VERB3 log("event long description:'%s'", text_copy);
+
+ if (parse_data->attribute_lang != NULL) /* if it isn't for other locale */
+ {
+ /* set the value only if we found a value for the current locale
+ * OR the description is still not set and we found the default value
+ */
+ if (parse_data->attribute_lang[0] != '\0'
+ || !ui->long_descr /* && parse_data->attribute_lang is "" - always true */
+ ) {
+ free(ui->long_descr);
+ ui->long_descr = text_copy;
+ }
+ }
+ return;
+ }
+ if (strcmp(inner_element, REQUIRES_ELEMENT) == 0)
+ {
+ free(ui->ec_requires_items);
+ ui->ec_requires_items = text_copy;
+ return;
+ }
+ if (strcmp(inner_element, EXCL_BY_DEFAULT_ELEMENT) == 0)
+ {
+ free(ui->ec_exclude_items_by_default);
+ ui->ec_exclude_items_by_default = text_copy;
+ return;
+ }
+ if (strcmp(inner_element, INCL_BY_DEFAULT_ELEMENT) == 0)
+ {
+ free(ui->ec_include_items_by_default);
+ ui->ec_include_items_by_default = text_copy;
+ return;
+ }
+ if (strcmp(inner_element, EXCL_ALWAYS_ELEMENT) == 0)
+ {
+ free(ui->ec_exclude_items_always);
+ ui->ec_exclude_items_always = text_copy;
+ return;
+ }
+ if (strcmp(inner_element, EXCL_BINARY_ELEMENT) == 0)
+ {
+ ui->ec_exclude_binary_items = string_to_bool(text_copy);
+ free(text_copy);
+ return;
+ }
+ }
+ free(text_copy);
+}
+
+ // Called for strings that should be re-saved verbatim in this same
+ // position, but are not otherwise interpretable. At the moment
+ // this includes comments and processing instructions.
+ // text is not nul-terminated
+static void passthrough(GMarkupParseContext *context,
+ const gchar *passthrough_text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ VERB3 log("passthrough");
+}
+
+// Called on error, including one set by other
+// methods in the vtable. The GError should not be freed.
+static void error(GMarkupParseContext *context,
+ GError *error,
+ gpointer user_data)
+{
+ error_msg("error in XML parsing");
+}
+
+/* this function takes 2 parameters
+ * ui -> pointer to event_config_t
+ * filename -> filename to read
+ * event_config_t contains list of options, which is malloced by hits function
+ * and must be freed by the caller
+ */
+
+void load_event_description_from_file(event_config_t *event_config, const char* filename)
+{
+ struct my_parse_data parse_data = { event_config, NULL, NULL , NULL };
+ parse_data.cur_locale = setlocale(LC_ALL, NULL);
+
+ GMarkupParser parser;
+ memset(&parser, 0, sizeof(parser)); /* just in case */
+ parser.start_element = &start_element;
+ parser.end_element = &end_element;
+ parser.text = &text;
+ parser.passthrough = &passthrough;
+ parser.error = &error;
+
+ GMarkupParseContext *context = g_markup_parse_context_new(
+ &parser, G_MARKUP_TREAT_CDATA_AS_TEXT,
+ &parse_data, /*GDestroyNotify:*/ NULL);
+
+ FILE* fin = fopen(filename, "r");
+ if (fin != NULL)
+ {
+ size_t read_bytes = 0;
+ char buff[1024];
+ while ((read_bytes = fread(buff, 1, 1024, fin)) != 0)
+ {
+ g_markup_parse_context_parse(context, buff, read_bytes, NULL);
+ }
+ fclose(fin);
+ }
+
+ g_markup_parse_context_free(context);
+
+ consume_cur_option(&parse_data); /* just in case */
+ free(parse_data.attribute_lang); /* just in case */
+}
diff --git a/libreport/src/lib/get_cmdline.c b/libreport/src/lib/get_cmdline.c
new file mode 100644
index 00000000..04254660
--- /dev/null
+++ b/libreport/src/lib/get_cmdline.c
@@ -0,0 +1,150 @@
+/*
+ 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 "libreport.h"
+
+/* If s is a string with only printable ASCII chars
+ * and has no spaces, ", ', and \, copy it verbatim.
+ * Else, encapsulate it in single quotes, and
+ * encode ', " and \ with \c escapes.
+ * Control chars are encoded as \r, \n, \t, or \xNN.
+ * In all cases, terminating NUL is added
+ * and the pointer to it is returned.
+ */
+static char *append_escaped(char *start, const char *s)
+{
+ char *dst = start;
+ const unsigned char *p = (unsigned char *)s;
+
+ while (1)
+ {
+ const unsigned char *old_p = p;
+ while (*p > ' ' && *p <= 0x7e && *p != '\"' && *p != '\'' && *p != '\\')
+ p++;
+ if (dst == start)
+ {
+ if (p != (unsigned char *)s && *p == '\0')
+ {
+ /* entire word does not need escaping and quoting */
+ strcpy(dst, s);
+ dst += strlen(s);
+ return dst;
+ }
+ *dst++ = '\'';
+ }
+
+ strncpy(dst, (char *)old_p, (p - old_p));
+ dst += (p - old_p);
+
+ if (*p == '\0')
+ {
+ *dst++ = '\'';
+ *dst = '\0';
+ return dst;
+ }
+
+ char hex_char_buf[5];
+ const char *a;
+ switch (*p)
+ {
+ case '\r': a = "\\r"; break;
+ case '\n': a = "\\n"; break;
+ case '\t': a = "\\t"; break;
+ case '\'': a = "\\\'"; break;
+ case '\"': a = "\\\""; break;
+ case '\\': a = "\\\\"; break;
+ case ' ': a = " "; break;
+ default:
+ /* Build \xNN string */
+ hex_char_buf[0] = '\\';
+ hex_char_buf[1] = 'x';
+ hex_char_buf[2] = "0123456789abcdef"[*p >> 4];
+ hex_char_buf[3] = "0123456789abcdef"[*p & 0xf];
+ hex_char_buf[4] = '\0';
+ a = hex_char_buf;
+ }
+ strcpy(dst, a);
+ dst += strlen(a);
+ p++;
+ }
+}
+
+static char* get_escaped(const char *path, char separator)
+{
+ char *escaped = NULL;
+
+ int fd = open(path, O_RDONLY);
+ if (fd >= 0)
+ {
+ char *dst = NULL;
+ unsigned total_esc_len = 0;
+ while (total_esc_len < 1024 * 1024) /* paranoia check */
+ {
+ /* read and escape one block */
+ char buffer[4 * 1024 + 1];
+ int len = read(fd, buffer, sizeof(buffer) - 1);
+ if (len <= 0)
+ break;
+ buffer[len] = '\0';
+
+ /* string CC can expand into '\xNN\xNN' and thus needs len*4 + 3 bytes,
+ * including terminating NUL.
+ * We add +1 for possible \n added at the very end.
+ */
+ escaped = xrealloc(escaped, total_esc_len + len*4 + 4);
+ char *src = buffer;
+ dst = escaped + total_esc_len;
+ while (1)
+ {
+ /* escape till next NUL char */
+ char *d = append_escaped(dst, src);
+ total_esc_len += (d - dst);
+ dst = d;
+ src += strlen(src) + 1;
+ if ((src - buffer) >= len)
+ break;
+ *dst++ = separator;
+ }
+
+ }
+
+ if (dst)
+ {
+ if (separator == '\n')
+ *dst++ = separator;
+ *dst = '\0';
+ }
+
+ close(fd);
+ }
+
+ return escaped;
+}
+
+char* get_cmdline(pid_t pid)
+{
+ char path[sizeof("/proc/%lu/cmdline") + sizeof(long)*3];
+ sprintf(path, "/proc/%lu/cmdline", (long)pid);
+ return get_escaped(path, ' ');
+}
+
+char* get_environ(pid_t pid)
+{
+ char path[sizeof("/proc/%lu/environ") + sizeof(long)*3];
+ sprintf(path, "/proc/%lu/environ", (long)pid);
+ return get_escaped(path, '\n');
+}
diff --git a/libreport/src/lib/glib_support.c b/libreport/src/lib/glib_support.c
new file mode 100644
index 00000000..b59b37c0
--- /dev/null
+++ b/libreport/src/lib/glib_support.c
@@ -0,0 +1,27 @@
+/*
+ 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 "libreport.h"
+
+void list_free_with_free(GList *list)
+{
+ GList *li;
+ for (li = list; li; li = g_list_next(li))
+ free(li->data);
+ g_list_free(list);
+}
diff --git a/libreport/src/lib/hash_sha1.c b/libreport/src/lib/hash_sha1.c
new file mode 100644
index 00000000..fafdae56
--- /dev/null
+++ b/libreport/src/lib/hash_sha1.c
@@ -0,0 +1,211 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Based on shasum from http://www.netsw.org/crypto/hash/
+ * Majorly hacked up to use Dr Brian Gladman's sha1 code
+ *
+ * Copyright (C) 2002 Dr Brian Gladman <brg@gladman.me.uk>, Worcester, UK.
+ * Copyright (C) 2003 Glenn L. McGrath
+ * Copyright (C) 2003 Erik Andersen
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * ---------------------------------------------------------------------------
+ * Issue Date: 10/11/2002
+ *
+ * This is a byte oriented version of SHA1 that operates on arrays of bytes
+ * stored in memory. It runs at 22 cycles per byte on a Pentium P4 processor
+ *
+ * ---------------------------------------------------------------------------
+ */
+#include "libreport.h"
+#include <byteswap.h>
+
+#if defined(__BIG_ENDIAN__) && __BIG_ENDIAN__
+# define SHA1_BIG_ENDIAN 1
+# define SHA1_LITTLE_ENDIAN 0
+#elif __BYTE_ORDER == __BIG_ENDIAN
+# define SHA1_BIG_ENDIAN 1
+# define SHA1_LITTLE_ENDIAN 0
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+# define SHA1_BIG_ENDIAN 0
+# define SHA1_LITTLE_ENDIAN 1
+#else
+# error "Can't determine endianness"
+#endif
+
+#define rotl32(x,n) (((x) << (n)) | ((x) >> (32 - (n))))
+/* for sha256: */
+#define rotr32(x,n) (((x) >> (n)) | ((x) << (32 - (n))))
+/* for sha512: */
+#define rotr64(x,n) (((x) >> (n)) | ((x) << (64 - (n))))
+
+
+/* Generic 64-byte helpers for 64-byte block hashes */
+static void common64_hash(sha1_ctx_t *ctx, const void *buffer, size_t len);
+static void common64_end(sha1_ctx_t *ctx, int swap_needed);
+
+
+/* sha1 specific code */
+
+static void sha1_process_block64(sha1_ctx_t *ctx)
+{
+ static const uint32_t rconsts[] = {
+ 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6
+ };
+ int i, j;
+ int cnt;
+ uint32_t W[16+16];
+ uint32_t a, b, c, d, e;
+
+ /* On-stack work buffer frees up one register in the main loop
+ * which otherwise will be needed to hold ctx pointer */
+ for (i = 0; i < 16; i++)
+ if (SHA1_BIG_ENDIAN)
+ W[i] = W[i+16] = ((uint32_t*)ctx->wbuffer)[i];
+ else
+ W[i] = W[i+16] = bswap_32(((uint32_t*)ctx->wbuffer)[i]);
+
+ a = ctx->hash[0];
+ b = ctx->hash[1];
+ c = ctx->hash[2];
+ d = ctx->hash[3];
+ e = ctx->hash[4];
+
+ /* 4 rounds of 20 operations each */
+ cnt = 0;
+ for (i = 0; i < 4; i++) {
+ j = 19;
+ do {
+ uint32_t work;
+
+ work = c ^ d;
+ if (i == 0) {
+ work = (work & b) ^ d;
+ if (j <= 3)
+ goto ge16;
+ /* Used to do bswap_32 here, but this
+ * requires ctx (see comment above) */
+ work += W[cnt];
+ } else {
+ if (i == 2)
+ work = ((b | c) & d) | (b & c);
+ else /* i = 1 or 3 */
+ work ^= b;
+ ge16:
+ W[cnt] = W[cnt+16] = rotl32(W[cnt+13] ^ W[cnt+8] ^ W[cnt+2] ^ W[cnt], 1);
+ work += W[cnt];
+ }
+ work += e + rotl32(a, 5) + rconsts[i];
+
+ /* Rotate by one for next time */
+ e = d;
+ d = c;
+ c = /* b = */ rotl32(b, 30);
+ b = a;
+ a = work;
+ cnt = (cnt + 1) & 15;
+ } while (--j >= 0);
+ }
+
+ ctx->hash[0] += a;
+ ctx->hash[1] += b;
+ ctx->hash[2] += c;
+ ctx->hash[3] += d;
+ ctx->hash[4] += e;
+}
+
+void sha1_begin(sha1_ctx_t *ctx)
+{
+ ctx->hash[0] = 0x67452301;
+ ctx->hash[1] = 0xefcdab89;
+ ctx->hash[2] = 0x98badcfe;
+ ctx->hash[3] = 0x10325476;
+ ctx->hash[4] = 0xc3d2e1f0;
+ ctx->total64 = 0;
+ /* for sha256: ctx->process_block = sha1_process_block64; */
+}
+
+void sha1_hash(sha1_ctx_t *ctx, const void *buffer, size_t len)
+{
+ common64_hash(ctx, buffer, len);
+}
+
+/* May be used also for sha256 */
+void sha1_end(sha1_ctx_t *ctx, void *resbuf)
+{
+ unsigned hash_size;
+
+ /* SHA stores total in BE, need to swap on LE arches: */
+ common64_end(ctx, /*swap_needed:*/ SHA1_LITTLE_ENDIAN);
+
+ hash_size = 5; /* (ctx->process_block == sha1_process_block64) ? 5 : 8; */
+ /* This way we do not impose alignment constraints on resbuf: */
+ if (SHA1_LITTLE_ENDIAN) {
+ unsigned i;
+ for (i = 0; i < hash_size; ++i)
+ ctx->hash[i] = bswap_32(ctx->hash[i]);
+ }
+ memcpy(resbuf, ctx->hash, sizeof(ctx->hash[0]) * hash_size);
+}
+
+
+/* Generic 64-byte helpers for 64-byte block hashes */
+
+/*#define PROCESS_BLOCK(ctx) ctx->process_block(ctx)*/
+#define PROCESS_BLOCK(ctx) sha1_process_block64(ctx)
+
+/* Feed data through a temporary buffer.
+ * The internal buffer remembers previous data until it has 64
+ * bytes worth to pass on.
+ */
+static void common64_hash(sha1_ctx_t *ctx, const void *buffer, size_t len)
+{
+ unsigned bufpos = ctx->total64 & 63;
+
+ ctx->total64 += len;
+
+ while (1) {
+ unsigned remaining = 64 - bufpos;
+ if (remaining > len)
+ remaining = len;
+ /* Copy data into aligned buffer */
+ memcpy(ctx->wbuffer + bufpos, buffer, remaining);
+ len -= remaining;
+ buffer = (const char *)buffer + remaining;
+ bufpos += remaining;
+ /* clever way to do "if (bufpos != 64) break; ... ; bufpos = 0;" */
+ bufpos -= 64;
+ if (bufpos != 0)
+ break;
+ /* Buffer is filled up, process it */
+ PROCESS_BLOCK(ctx);
+ /*bufpos = 0; - already is */
+ }
+}
+
+/* Process the remaining bytes in the buffer */
+static void common64_end(sha1_ctx_t *ctx, int swap_needed)
+{
+ unsigned bufpos = ctx->total64 & 63;
+ /* Pad the buffer to the next 64-byte boundary with 0x80,0,0,0... */
+ ctx->wbuffer[bufpos++] = 0x80;
+
+ /* This loop iterates either once or twice, no more, no less */
+ while (1) {
+ unsigned remaining = 64 - bufpos;
+ memset(ctx->wbuffer + bufpos, 0, remaining);
+ /* Do we have enough space for the length count? */
+ if (remaining >= 8) {
+ /* Store the 64-bit counter of bits in the buffer */
+ uint64_t t = ctx->total64 << 3;
+ if (swap_needed)
+ t = bswap_64(t);
+ /* wbuffer is suitably aligned for this */
+ *(uint64_t *) (&ctx->wbuffer[64 - 8]) = t;
+ }
+ PROCESS_BLOCK(ctx);
+ if (remaining >= 8)
+ break;
+ bufpos = 0;
+ }
+}
diff --git a/libreport/src/lib/is_in_string_list.c b/libreport/src/lib/is_in_string_list.c
new file mode 100644
index 00000000..2d7bb61b
--- /dev/null
+++ b/libreport/src/lib/is_in_string_list.c
@@ -0,0 +1,30 @@
+/*
+ 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 "libreport.h"
+
+bool is_in_string_list(const char *name, char **v)
+{
+ while (*v)
+ {
+ if (strcmp(*v, name) == 0)
+ return true;
+ v++;
+ }
+ return false;
+}
diff --git a/libreport/src/lib/iso_date_string.c b/libreport/src/lib/iso_date_string.c
new file mode 100644
index 00000000..475f7863
--- /dev/null
+++ b/libreport/src/lib/iso_date_string.c
@@ -0,0 +1,31 @@
+/*
+ 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 "libreport.h"
+
+char *iso_date_string(time_t *pt)
+{
+ 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/libreport/src/lib/kernel-tainted.c b/libreport/src/lib/kernel-tainted.c
new file mode 100644
index 00000000..ace7c29e
--- /dev/null
+++ b/libreport/src/lib/kernel-tainted.c
@@ -0,0 +1,143 @@
+/*
+ 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 "libreport.h"
+
+/* From RHEL6 kernel/panic.c: */
+static const int tnts_short[] = {
+ 'P' ,
+ 'F' ,
+ 'S' ,
+ 'R' ,
+ 'M' ,
+ 'B' ,
+ 'U' ,
+ 'D' ,
+ 'A' ,
+ 'W' ,
+ 'C' ,
+ 'I' ,
+ '-' ,
+ '-' ,
+ '-' ,
+ '-' ,
+ '-' ,
+ '-' ,
+ '-' ,
+ '-' ,
+ '-' ,
+ '-' ,
+ '-' ,
+ '-' ,
+ '-' ,
+ '-' ,
+ '-' ,
+ '-' ,
+ 'H' ,
+ 'T' ,
+};
+
+/**
+ * print_tainted - return a string to represent the kernel taint state.
+ *
+ * 'P' - Proprietary module has been loaded.
+ * 'F' - Module has been forcibly loaded.
+ * 'S' - SMP with CPUs not designed for SMP.
+ * 'R' - User forced a module unload.
+ * 'M' - System experienced a machine check exception.
+ * 'B' - System has hit bad_page.
+ * 'U' - Userspace-defined naughtiness.
+ * 'D' - Kernel has oopsed before
+ * 'A' - ACPI table overridden.
+ * 'W' - Taint on warning.
+ * 'C' - modules from drivers/staging are loaded.
+ * 'I' - Working around severe firmware bug.
+ * 'H' - Hardware is unsupported.
+ * T - Tech_preview
+ */
+
+
+static const char *const tnts_long[] = {
+ "Proprietary module has been loaded.",
+ "Module has been forcibly loaded.",
+ "SMP with CPUs not designed for SMP.",
+ "User forced a module unload.",
+ "System experienced a machine check exception.",
+ "System has hit bad_page.",
+ "Userspace-defined naughtiness.",
+ "Kernel has oopsed before.",
+ "ACPI table overridden.",
+ "Taint on warning.",
+ "Modules from drivers/staging are loaded.",
+ "Working around severe firmware bug.",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "Hardware is unsupported.",
+ "Tech_preview",
+};
+
+char *kernel_tainted_short(unsigned tainted)
+{
+ char *tnt = xzalloc(ARRAY_SIZE(tnts_short) + 1);
+ int i = 0;
+ while (tainted)
+ {
+ if (0x1 & tainted)
+ tnt[i] = tnts_short[i];
+ else
+ tnt[i] = '-';
+
+ ++i;
+ tainted >>= 1;
+ }
+
+ return tnt;
+}
+
+GList *kernel_tainted_long(unsigned tainted)
+{
+ int i = 0;
+ GList *tnt = NULL;
+
+ while (tainted)
+ {
+ if (0x1 & tainted && tnts_long[i])
+ tnt = g_list_append(tnt, xstrdup(tnts_long[i]));
+
+ ++i;
+ tainted >>= 1;
+ }
+
+ return tnt;
+}
diff --git a/libreport/src/lib/load_plugin_settings.c b/libreport/src/lib/load_plugin_settings.c
new file mode 100644
index 00000000..e07ea7d4
--- /dev/null
+++ b/libreport/src/lib/load_plugin_settings.c
@@ -0,0 +1,97 @@
+/*
+ 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 "libreport.h"
+
+/* Returns false if open failed.
+ * Returns empty hash if conf file is empty.
+ * TODO: better error detection?
+ */
+bool load_conf_file(const char *pPath, map_string_h *settings, bool skipKeysWithoutValue)
+{
+ FILE *fp = stdin;
+ if (strcmp(pPath, "-") != 0)
+ {
+ fp = fopen(pPath, "r");
+ if (!fp)
+ return false;
+ }
+
+ char *line;
+ while ((line = xmalloc_fgetline(fp)) != NULL)
+ {
+ bool in_quote = false;
+ /* We are reusing line buffer to form temporary
+ * "key\0value\0..." in its beginning
+ */
+ char *value = NULL;
+ char *src;
+ char *dst;
+ for (src = dst = line; *src; src++)
+ {
+ char c = *src;
+ if (c == '"')
+ {
+ in_quote = !in_quote;
+ }
+ if (!in_quote)
+ {
+ if (isspace(c))
+ {
+ continue;
+ }
+ if (c == '#' && dst == line)
+ {
+ break;
+ }
+ if (c == '=')
+ {
+ *dst++ = '\0'; /* terminate key */
+ value = dst; /* remember where value starts */
+ continue;
+ }
+ }
+ *dst++ = c; /* store next key or value char */
+ }
+ *dst = '\0'; /* terminate value */
+
+ /* Skip broken or empty lines. */
+ if (!value)
+ goto free_line;
+
+ /* Skip lines with empty key. */
+ if (line[0] == '\0')
+ goto free_line;
+
+ if (skipKeysWithoutValue && value[0] == '\0')
+ goto free_line;
+
+ /* Skip lines with unclosed quotes. */
+ if (in_quote)
+ goto free_line;
+
+ g_hash_table_replace(settings, xstrdup(line), xstrdup(value));
+ free_line:
+ free(line);
+ }
+
+ if (fp != stdin)
+ fclose(fp);
+
+ return true;
+}
diff --git a/libreport/src/lib/logging.c b/libreport/src/lib/logging.c
new file mode 100644
index 00000000..72aa2423
--- /dev/null
+++ b/libreport/src/lib/logging.c
@@ -0,0 +1,148 @@
+/*
+ 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.
+*/
+
+/*
+ * Utility routines.
+ *
+ */
+#include "libreport.h"
+
+void (*g_custom_logger)(const char*);
+const char *msg_prefix = "";
+const char *msg_eol = "\n";
+int logmode = LOGMODE_STDIO;
+int xfunc_error_retval = EXIT_FAILURE;
+int g_verbose;
+
+void xfunc_die(void)
+{
+ exit(xfunc_error_retval);
+}
+
+static void verror_msg_helper(const char *s,
+ va_list p,
+ const char* strerr,
+ int flags)
+{
+ char *msg;
+ int prefix_len, strerr_len, msgeol_len, used;
+
+ if (!logmode)
+ return;
+
+ used = vasprintf(&msg, s, p);
+ if (used < 0)
+ return;
+
+ /* This is ugly and costs +60 bytes compared to multiple
+ * fprintf's, but is guaranteed to do a single write.
+ * This is needed for e.g. when multiple children
+ * can produce log messages simultaneously. */
+
+ prefix_len = msg_prefix[0] ? strlen(msg_prefix) + 2 : 0;
+ strerr_len = strerr ? strlen(strerr) : 0;
+ msgeol_len = strlen(msg_eol);
+ /* +3 is for ": " before strerr and for terminating NUL */
+ msg = (char*) xrealloc(msg, prefix_len + used + strerr_len + msgeol_len + 3);
+ /* TODO: maybe use writev instead of memmoving? Need full_writev? */
+ if (prefix_len) {
+ char *p;
+ memmove(msg + prefix_len, msg, used);
+ used += prefix_len;
+ p = stpcpy(msg, msg_prefix);
+ p[0] = ':';
+ p[1] = ' ';
+ }
+ if (strerr) {
+ if (s[0]) {
+ msg[used++] = ':';
+ msg[used++] = ' ';
+ }
+ strcpy(&msg[used], strerr);
+ used += strerr_len;
+ }
+ strcpy(&msg[used], msg_eol);
+
+ if (flags & LOGMODE_STDIO) {
+ fflush(stdout);
+ full_write(STDERR_FILENO, msg, used + msgeol_len);
+ }
+ msg[used] = '\0'; /* remove msg_eol (usually "\n") */
+ if (flags & LOGMODE_SYSLOG) {
+ syslog(LOG_ERR, "%s", msg + prefix_len);
+ }
+ if ((flags & LOGMODE_CUSTOM) && g_custom_logger) {
+ g_custom_logger(msg + prefix_len);
+ }
+ free(msg);
+}
+
+void log_msg(const char *s, ...)
+{
+ va_list p;
+
+ va_start(p, s);
+ verror_msg_helper(s, p, NULL, logmode);
+ va_end(p);
+}
+
+void error_msg(const char *s, ...)
+{
+ va_list p;
+
+ va_start(p, s);
+ verror_msg_helper(s, p, NULL, (logmode | LOGMODE_CUSTOM));
+ va_end(p);
+}
+
+void error_msg_and_die(const char *s, ...)
+{
+ va_list p;
+
+ va_start(p, s);
+ verror_msg_helper(s, p, NULL, (logmode | LOGMODE_CUSTOM));
+ va_end(p);
+ xfunc_die();
+}
+
+void perror_msg(const char *s, ...)
+{
+ va_list p;
+
+ va_start(p, s);
+ /* Guard against "<error message>: Success" */
+ verror_msg_helper(s, p, errno ? strerror(errno) : NULL, (logmode | LOGMODE_CUSTOM));
+ va_end(p);
+}
+
+void perror_msg_and_die(const char *s, ...)
+{
+ va_list p;
+
+ va_start(p, s);
+ /* Guard against "<error message>: Success" */
+ verror_msg_helper(s, p, errno ? strerror(errno) : NULL, (logmode | LOGMODE_CUSTOM));
+ va_end(p);
+ xfunc_die();
+}
+
+void die_out_of_memory(void)
+{
+ error_msg_and_die("Out of memory, exiting");
+}
diff --git a/libreport/src/lib/make_descr.c b/libreport/src/lib/make_descr.c
new file mode 100644
index 00000000..adee54af
--- /dev/null
+++ b/libreport/src/lib/make_descr.c
@@ -0,0 +1,262 @@
+/*
+ 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 "libreport.h"
+
+
+char *make_description(problem_data_t *problem_data, char **names_to_skip, unsigned max_text_size, unsigned desc_flags)
+{
+ struct strbuf *buf_dsc = strbuf_new();
+
+ GList *list = g_hash_table_get_keys(problem_data);
+ list = g_list_sort(list, (GCompareFunc)strcmp);
+ GList *l;
+
+ /* Print one-liners. Format:
+ * NAME1: <maybe more spaces>VALUE1
+ * NAME2: <maybe more spaces>VALUE2
+ */
+ bool empty = true;
+ l = list;
+ while (l)
+ {
+ const char *key = l->data;
+ l = l->next;
+
+ /* Skip items we are not interested in */
+//TODO: optimize by doing this once, not 3 times:
+ if (names_to_skip && is_in_string_list(key, names_to_skip))
+ continue;
+
+ struct problem_item *item = g_hash_table_lookup(problem_data, key);
+ if (!item)
+ continue;
+
+ if ((desc_flags & MAKEDESC_SHOW_ONLY_LIST) && !(item->flags & CD_FLAG_LIST))
+ continue;
+
+ if ((item->flags & CD_FLAG_TXT)
+ && strlen(item->content) <= max_text_size
+ ) {
+ char *formatted = format_problem_item(item);
+ char *output = formatted ? formatted : item->content;
+ char *eol = strchr(output, '\n');
+ if (!eol)
+ {
+ int pad = 16 - (strlen(key) + 2);
+ if (pad < 0) pad = 0;
+ strbuf_append_strf(buf_dsc, "%s: %*s%s\n", key, pad, "", output);
+ empty = false;
+ }
+ free(formatted);
+ }
+ }
+
+ bool append_empty_line = !empty;
+ if (desc_flags & MAKEDESC_SHOW_FILES)
+ {
+ /* Print file info. Format:
+ * <empty line if needed>
+ * NAME1: <maybe more spaces>Binary file, NNN bytes
+ * NAME2: <maybe more spaces>Text file, NNN bytes
+ *
+ * In many cases, it is useful to know how big binary files are
+ * (for example, helps with diagnosing bug upload problems)
+ */
+ l = list;
+ while (l)
+ {
+ const char *key = l->data;
+ l = l->next;
+
+ /* Skip items we are not interested in */
+ if (names_to_skip && is_in_string_list(key, names_to_skip))
+ continue;
+
+ struct problem_item *item = g_hash_table_lookup(problem_data, key);
+ if (!item)
+ continue;
+
+ if ((desc_flags & MAKEDESC_SHOW_ONLY_LIST) && !(item->flags & CD_FLAG_LIST))
+ continue;
+
+ if ((item->flags & CD_FLAG_BIN)
+ || ((item->flags & CD_FLAG_TXT) && strlen(item->content) > max_text_size)
+ ) {
+ if (append_empty_line)
+ strbuf_append_char(buf_dsc, '\n');
+ append_empty_line = false;
+
+ struct stat statbuf;
+ int stat_err = 0;
+ if (item->flags & CD_FLAG_BIN)
+ stat_err = stat(item->content, &statbuf);
+ else
+ statbuf.st_size = strlen(item->content);
+
+ /* We don't print item->content for CD_FLAG_BIN, as it is
+ * always "/path/to/dump/dir/KEY" - not informative.
+ */
+ int pad = 16 - (strlen(key) + 2);
+ if (pad < 0) pad = 0;
+ strbuf_append_strf(buf_dsc,
+ (!stat_err ? "%s: %*s%s file, %llu bytes\n" : "%s: %*s%s file\n"),
+ key,
+ pad, "",
+ ((item->flags & CD_FLAG_BIN) ? "Binary" : "Text"),
+ (long long)statbuf.st_size
+ );
+ empty = false;
+ }
+ }
+ }
+
+ if (desc_flags & MAKEDESC_SHOW_MULTILINE)
+ {
+ /* Print multi-liners. Format:
+ * <empty line if needed>
+ * NAME:
+ * :LINE1
+ * :LINE2
+ * :LINE3
+ */
+ l = list;
+ while (l)
+ {
+ const char *key = l->data;
+ l = l->next;
+
+ /* Skip items we are not interested in */
+ if (names_to_skip && is_in_string_list(key, names_to_skip))
+ continue;
+
+ struct problem_item *item = g_hash_table_lookup(problem_data, key);
+ if (!item)
+ continue;
+
+ if ((desc_flags & MAKEDESC_SHOW_ONLY_LIST) && !(item->flags & CD_FLAG_LIST))
+ continue;
+
+ if ((item->flags & CD_FLAG_TXT)
+ && strlen(item->content) <= max_text_size
+ ) {
+ char *formatted = format_problem_item(item);
+ char *output = formatted ? formatted : item->content;
+ char *eol = strchr(output, '\n');
+ if (eol)
+ {
+ if (!empty)
+ strbuf_append_char(buf_dsc, '\n');
+ strbuf_append_str(buf_dsc, key);
+ strbuf_append_str(buf_dsc, ":\n");
+ for (;;)
+ {
+ eol = strchrnul(output, '\n');
+ strbuf_append_strf(buf_dsc, ":%.*s\n", (int)(eol - output), output);
+ if (*eol == '\0' || eol[1] == '\0')
+ break;
+ output = eol + 1;
+ }
+ empty = false;
+ }
+ free(formatted);
+ }
+ }
+ }
+
+ g_list_free(list);
+
+ return strbuf_free_nobuf(buf_dsc);
+}
+
+char* make_description_mailx(problem_data_t *problem_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();
+
+ GHashTableIter iter;
+ char *name;
+ struct problem_item *value;
+ g_hash_table_iter_init(&iter, problem_data);
+ while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value))
+ {
+ if (value->flags & CD_FLAG_TXT)
+ {
+ 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", name, value->content);
+ }
+ 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", name, value->content);
+ }
+ }
+
+ char *common_files = strbuf_free_nobuf(buf_common_files);
+ char *duphash_file = strbuf_free_nobuf(buf_duphash_file);
+ char *additional_files = strbuf_free_nobuf(buf_additional_files);
+
+ strbuf_append_strf(buf_dsc, "Duplicate check\n=====\n%s\n\n", duphash_file);
+ strbuf_append_strf(buf_dsc, "Common information\n=====\n%s\n\n", common_files);
+ strbuf_append_strf(buf_dsc, "Additional information\n=====\n%s\n", additional_files);
+
+ free(common_files);
+ free(duphash_file);
+ free(additional_files);
+
+ return strbuf_free_nobuf(buf_dsc);
+}
+
+/* Items we don't want to include to bz / logger */
+static const char *const blacklisted_items[] = {
+ CD_DUMPDIR ,
+ FILENAME_ANALYZER ,
+ FILENAME_COREDUMP ,
+ FILENAME_HOSTNAME ,
+ FILENAME_DUPHASH ,
+ FILENAME_UUID ,
+ FILENAME_COUNT ,
+ FILENAME_TAINTED_SHORT,
+ NULL
+};
+
+char* make_description_bz(problem_data_t *problem_data)
+{
+ return make_description(
+ problem_data,
+ (char**)blacklisted_items,
+ /*max_text_size:*/ CD_TEXT_ATT_SIZE,
+ MAKEDESC_SHOW_FILES | MAKEDESC_SHOW_MULTILINE
+ );
+}
+
+char* make_description_logger(problem_data_t *problem_data)
+{
+ return make_description(
+ problem_data,
+ (char**)blacklisted_items,
+ /*max_text_size:*/ CD_TEXT_ATT_SIZE,
+ MAKEDESC_SHOW_FILES | MAKEDESC_SHOW_MULTILINE
+ );
+}
diff --git a/libreport/src/lib/overlapping_strcpy.c b/libreport/src/lib/overlapping_strcpy.c
new file mode 100644
index 00000000..3301024f
--- /dev/null
+++ b/libreport/src/lib/overlapping_strcpy.c
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+#include "libreport.h"
+
+/* Like strcpy but can copy overlapping strings. */
+void overlapping_strcpy(char *dst, const char *src)
+{
+ /* Cheap optimization for dst == src case -
+ * better to have it here than in many callers.
+ */
+ if (dst != src)
+ {
+ while ((*dst = *src) != '\0')
+ {
+ dst++;
+ src++;
+ }
+ }
+}
diff --git a/libreport/src/lib/parse_options.c b/libreport/src/lib/parse_options.c
new file mode 100644
index 00000000..f2f876b3
--- /dev/null
+++ b/libreport/src/lib/parse_options.c
@@ -0,0 +1,251 @@
+/*
+ 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 "libreport.h"
+#include "parse_options.h"
+
+#define USAGE_OPTS_WIDTH 30
+#define USAGE_GAP 2
+
+const char *g_progname;
+
+const char *abrt_init(char **argv)
+{
+ char *env_verbose = getenv("ABRT_VERBOSE");
+ if (env_verbose)
+ g_verbose = atoi(env_verbose);
+
+ g_progname = strrchr(argv[0], '/');
+ if (g_progname)
+ g_progname++;
+ else
+ g_progname = argv[0];
+
+ char *pfx = getenv("ABRT_PROG_PREFIX");
+ if (pfx && string_to_bool(pfx))
+ msg_prefix = g_progname;
+
+ return g_progname;
+}
+
+void export_abrt_envvars(int pfx)
+{
+ putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose));
+ if (pfx)
+ {
+ putenv((char*)"ABRT_PROG_PREFIX=1");
+ msg_prefix = g_progname;
+ }
+}
+
+void show_usage_and_die(const char *usage, const struct options *opt)
+{
+ fputs(_("Usage: "), stderr);
+ while (*usage)
+ {
+ int len = strchrnul(usage, '\b') - usage;
+ if (len > 0)
+ {
+ fprintf(stderr, "%.*s", len, usage);
+ usage += len;
+ }
+ if (*usage == '\b')
+ {
+ fputs(g_progname, stderr);
+ usage++;
+ }
+ }
+ fputs("\n\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);
+
+ if (opt->short_name && opt->long_name)
+ pos += fprintf(stderr, ", ");
+
+ if (opt->long_name)
+ pos += fprintf(stderr, "--%s", opt->long_name);
+
+ if (opt->argh)
+ {
+ const char *fmt = " %s";
+ if (opt->type == OPTION_OPTSTRING)
+ fmt = "[%s]";
+ pos += fprintf(stderr, fmt, opt->argh);
+ }
+
+ if (pos <= USAGE_OPTS_WIDTH)
+ pad = USAGE_OPTS_WIDTH - pos;
+ else
+ {
+ fputc('\n', stderr);
+ pad = USAGE_OPTS_WIDTH;
+ }
+ fprintf(stderr, "%*s%s\n", pad + USAGE_GAP, "", opt->help);
+ }
+ fputc('\n', stderr);
+ exit(1);
+}
+
+static int parse_opt_size(const struct options *opt)
+{
+ unsigned size = 0;
+ for (; opt->type != OPTION_END; opt++)
+ size++;
+
+ return size;
+}
+
+unsigned parse_opts(int argc, char **argv, const struct options *opt,
+ const char *usage)
+{
+ int help = 0;
+ int size = parse_opt_size(opt);
+ const int LONGOPT_OFFSET = 256;
+
+ struct strbuf *shortopts = strbuf_new();
+
+ struct option *longopts = xzalloc(sizeof(longopts[0]) * (size+2));
+ struct option *curopt = longopts;
+ int ii;
+ for (ii = 0; ii < size; ++ii)
+ {
+ curopt->name = opt[ii].long_name;
+ /*curopt->flag = 0; - xzalloc did it */
+ if (opt[ii].short_name)
+ curopt->val = opt[ii].short_name;
+ else
+ curopt->val = LONGOPT_OFFSET + ii;
+
+ switch (opt[ii].type)
+ {
+ case OPTION_BOOL:
+ curopt->has_arg = no_argument;
+ if (opt[ii].short_name)
+ strbuf_append_char(shortopts, opt[ii].short_name);
+ 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);
+ break;
+ case OPTION_OPTSTRING:
+ curopt->has_arg = optional_argument;
+ if (opt[ii].short_name)
+ strbuf_append_strf(shortopts, "%c::", opt[ii].short_name);
+ break;
+ case OPTION_GROUP:
+ case OPTION_END:
+ break;
+ }
+ //log("curopt[%d].name:'%s' .has_arg:%d .flag:%p .val:%d", (int)(curopt-longopts),
+ // curopt->name, curopt->has_arg, curopt->flag, curopt->val);
+ /*
+ * getopt_long() thinks that NULL name marks the end of longopts.
+ * Example:
+ * [0] name:'verbose' val:'v'
+ * [1] name:NULL val:'c'
+ * [2] name:'force' val:'f'
+ * ... ... ...
+ * In this case, --force won't be accepted!
+ * Therefore we can only advance if name is not NULL.
+ */
+ if (curopt->name)
+ curopt++;
+ }
+ curopt->name = "help";
+ curopt->has_arg = no_argument;
+ curopt->flag = &help;
+ curopt->val = 1;
+ /* xzalloc did it already:
+ curopt++;
+ curopt->name = NULL;
+ curopt->has_arg = 0;
+ curopt->flag = NULL;
+ curopt->val = 0;
+ */
+
+ unsigned retval = 0;
+ while (1)
+ {
+ int c = getopt_long(argc, argv, shortopts->buf, longopts, NULL);
+
+ if (c == -1)
+ break;
+
+ if (c == '?' || help)
+ {
+ free(longopts);
+ strbuf_free(shortopts);
+ show_usage_and_die(usage, opt);
+ }
+
+ for (ii = 0; ii < size; ++ii)
+ {
+ if (opt[ii].short_name == c || LONGOPT_OFFSET + ii == c)
+ {
+ if (ii < sizeof(retval)*8)
+ retval |= (1 << ii);
+
+ if (opt[ii].value != NULL) switch (opt[ii].type)
+ {
+ case OPTION_BOOL:
+ *(int*)(opt[ii].value) += 1;
+ break;
+ case OPTION_INTEGER:
+ *(int*)(opt[ii].value) = xatoi(optarg);
+ break;
+ case OPTION_STRING:
+ case OPTION_OPTSTRING:
+ if (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;
+ }
+ }
+ }
+ }
+
+ free(longopts);
+ strbuf_free(shortopts);
+
+ return retval;
+}
diff --git a/libreport/src/lib/parse_release.c b/libreport/src/lib/parse_release.c
new file mode 100644
index 00000000..0071cfe3
--- /dev/null
+++ b/libreport/src/lib/parse_release.c
@@ -0,0 +1,81 @@
+/*
+ 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 "libreport.h"
+
+// caller is reposible for freeing *product* and *version*
+static void parse_release(const char *release, char** product, char** version, bool append_rhel_version)
+{
+ if (strstr(release, "Rawhide"))
+ {
+ *product = xstrdup("Fedora");
+ *version = xstrdup("rawhide");
+ VERB3 log("%s: version:'%s' product:'%s'", __func__, *version, *product);
+ return;
+ }
+
+ struct strbuf *buf_product = strbuf_new();
+ 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");
+ 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)
+ {
+ space++;
+ while (*space != '\0' && *space != ' ')
+ {
+ /* Eat string like "5.2" */
+ strbuf_append_char(buf_version, *space);
+ 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++;
+ }
+ }
+
+ *version = strbuf_free_nobuf(buf_version);
+ *product = strbuf_free_nobuf(buf_product);
+
+ 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/libreport/src/lib/problem_data.c b/libreport/src/lib/problem_data.c
new file mode 100644
index 00000000..ed88f9d8
--- /dev/null
+++ b/libreport/src/lib/problem_data.c
@@ -0,0 +1,424 @@
+/*
+ Copyright (C) 2010 Denys Vlasenko (dvlasenk@redhat.com)
+ 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 "libreport.h"
+
+static void free_problem_item(void *ptr)
+{
+ if (ptr)
+ {
+ struct problem_item *item = (struct problem_item *)ptr;
+ free(item->content);
+ free(item);
+ }
+}
+
+char *format_problem_item(struct problem_item *item)
+{
+ if (!item)
+ return xstrdup("(nullitem)");
+
+ if (item->flags & CD_FLAG_UNIXTIME)
+ {
+ errno = 0;
+ char *end;
+ time_t time = strtol(item->content, &end, 10);
+ if (!errno && !*end && end != item->content)
+ {
+ char timeloc[256];
+ int success = strftime(timeloc, sizeof(timeloc), "%c", localtime(&time));
+ if (success)
+ return xstrdup(timeloc);
+ }
+ }
+ return NULL;
+}
+
+/* problem_data["name"] = { "content", CD_FLAG_foo_bits } */
+
+problem_data_t *new_problem_data(void)
+{
+ return g_hash_table_new_full(g_str_hash, g_str_equal,
+ free, free_problem_item);
+}
+
+void add_basics_to_problem_data(problem_data_t *pd)
+{
+ const char *analyzer = get_problem_item_content_or_NULL(pd, FILENAME_ANALYZER);
+ if (analyzer == NULL)
+ add_to_problem_data(pd, "analyzer", "libreport");
+
+ /* If application didn't provide dupe hash, we generate it
+ * from all components, so we at least eliminate the exact same
+ * reports
+ */
+ if (get_problem_item_content_or_NULL(pd, FILENAME_DUPHASH) == NULL)
+ {
+ /* start hash */
+ sha1_ctx_t sha1ctx;
+ sha1_begin(&sha1ctx);
+
+ /*
+ * To avoid spurious hash differences, sort keys so that elements are
+ * always processed in the same order:
+ */
+ GList *list = g_hash_table_get_keys(pd);
+ list = g_list_sort(list, (GCompareFunc)strcmp);
+ GList *l = list;
+ while (l)
+ {
+ const char *key = l->data;
+ l = l->next;
+ struct problem_item *item = g_hash_table_lookup(pd, key);
+ /* do not hash items which are binary (item->flags & CD_FLAG_BIN).
+ * Their ->content is full file name, with path. Path is always
+ * different and will make hash differ even if files are the same.
+ */
+ if (item->flags & CD_FLAG_BIN)
+ continue;
+ sha1_hash(&sha1ctx, item->content, strlen(item->content));
+ }
+ g_list_free(list);
+
+ /* end hash */
+ char hash_bytes[SHA1_RESULT_LEN];
+ sha1_end(&sha1ctx, hash_bytes);
+ char hash_str[SHA1_RESULT_LEN*2 + 1];
+ bin2hex(hash_str, hash_bytes, SHA1_RESULT_LEN)[0] = '\0';
+
+ add_to_problem_data(pd, FILENAME_DUPHASH, hash_str);
+ }
+
+ pid_t pid = getpid();
+ if (pid > 0)
+ {
+ char buf[PATH_MAX+1];
+ char *exe = xasprintf("/proc/%u/exe", pid);
+ ssize_t read = readlink(exe, buf, PATH_MAX);
+ if (read > 0)
+ {
+ buf[read] = 0;
+ VERB2 log("reporting initiated from: %s", buf);
+ add_to_problem_data(pd, FILENAME_EXECUTABLE, buf);
+ }
+ free(exe);
+
+//#ifdef WITH_RPM
+ /* FIXME: component should be taken from rpm using librpm
+ * which means we need to link against it :(
+ * or run rpm -qf executable ??
+ */
+ /* Fedora/RHEL rpm specific piece of code */
+ const char *component = get_problem_item_content_or_NULL(pd, FILENAME_COMPONENT);
+ //FIXME: this REALLY needs to go away, or every report will be assigned to abrt
+ if (component == NULL) // application didn't specify component
+ add_to_problem_data(pd, FILENAME_COMPONENT, "abrt");
+//#endif
+ }
+}
+
+void add_to_problem_data_ext(problem_data_t *problem_data,
+ const char *name,
+ const char *content,
+ unsigned flags)
+{
+ if (!(flags & CD_FLAG_BIN))
+ flags |= CD_FLAG_TXT;
+ if (!(flags & CD_FLAG_ISEDITABLE))
+ flags |= CD_FLAG_ISNOTEDITABLE;
+
+ struct problem_item *item = (struct problem_item *)xzalloc(sizeof(*item));
+ item->content = xstrdup(content);
+ item->flags = flags;
+ g_hash_table_replace(problem_data, xstrdup(name), item);
+}
+
+void add_to_problem_data(problem_data_t *problem_data,
+ const char *name,
+ const char *content)
+{
+ add_to_problem_data_ext(problem_data, name, content, CD_FLAG_TXT + CD_FLAG_ISNOTEDITABLE);
+}
+
+const char *get_problem_item_content_or_die(problem_data_t *problem_data, const char *key)
+{
+ struct problem_item *item = get_problem_data_item_or_NULL(problem_data, key);
+ if (!item)
+ error_msg_and_die("Error accessing problem data: no ['%s']", key);
+ return item->content;
+}
+
+const char *get_problem_item_content_or_NULL(problem_data_t *problem_data, const char *key)
+{
+ struct problem_item *item = get_problem_data_item_or_NULL(problem_data, key);
+ if (!item)
+ return NULL;
+ return item->content;
+}
+
+
+/* Miscellaneous helpers */
+
+static const char *const editable_files[] = {
+ FILENAME_COMMENT ,
+ FILENAME_BACKTRACE,
+ NULL
+};
+static bool is_editable_file(const char *file_name)
+{
+ return is_in_string_list(file_name, (char**)editable_files);
+}
+
+static const char *const always_text_files[] = {
+ FILENAME_CMDLINE ,
+ FILENAME_BACKTRACE,
+ NULL
+};
+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
+ * that file containing just "0" is not text (!!)
+ * So, we do it ourself.
+ */
+
+ int fd = open(name, O_RDONLY);
+ if (fd < 0)
+ return NULL; /* it's not text (because it does not exist! :) */
+
+ /* Maybe 64k limit is small. But _some_ limit is necessary:
+ * fields declared "text" may end up in editing fields and such.
+ * We don't want to accidentally end up with 100meg text in a textbox!
+ * So, don't remove this. If you really need to, raise the limit.
+ *
+ * Bumped up to 200k: saw 124740 byte /proc/PID/smaps file
+ */
+ off_t size = lseek(fd, 0, SEEK_END);
+ if (size < 0 || size > 200*1024)
+ {
+ close(fd);
+ return NULL; /* it's not a SMALL text */
+ }
+ lseek(fd, 0, SEEK_SET);
+
+ char *buf = (char*)xmalloc(*sz);
+ ssize_t r = full_read(fd, buf, *sz);
+ close(fd);
+ if (r < 0)
+ {
+ free(buf);
+ return NULL; /* it's not text (because we can't read it) */
+ }
+ if (r < *sz)
+ buf[r] = '\0';
+ *sz = r;
+
+ /* Some files in our dump directories are known to always be textual */
+ const char *base = strrchr(name, '/');
+ if (base)
+ {
+ base++;
+ if (is_in_string_list(base, (char**)always_text_files))
+ return buf;
+ }
+
+ /* Every once in a while, even a text file contains a few garbled
+ * or unexpected non-ASCII chars. We should not declare it "binary".
+ */
+ const unsigned RATIO = 50;
+ unsigned total_chars = r + RATIO;
+ unsigned bad_chars = 1; /* 1 prevents division by 0 later */
+ while (--r >= 0)
+ {
+ if (buf[r] >= 0x7f
+ /* among control chars, only '\t','\n' etc are allowed */
+ || (buf[r] < ' ' && !isspace(buf[r]))
+ ) {
+ if (buf[r] == '\0')
+ {
+ /* We don't like NULs very much. Not text for sure! */
+ free(buf);
+ return NULL;
+ }
+ bad_chars++;
+ }
+ }
+
+ if ((total_chars / bad_chars) >= RATIO)
+ return buf; /* looks like text to me */
+
+ free(buf);
+ return NULL; /* it's binary */
+}
+
+void load_problem_data_from_dump_dir(problem_data_t *problem_data, struct dump_dir *dd, char **excluding)
+{
+ char *short_name;
+ char *full_name;
+
+ dd_init_next_file(dd);
+ while (dd_get_next_file(dd, &short_name, &full_name))
+ {
+ if (excluding && is_in_string_list(short_name, excluding))
+ {
+ //log("Excluded:'%s'", short_name);
+ goto next;
+ }
+
+ ssize_t sz = 4*1024;
+ char *text = NULL;
+ bool editable = is_editable_file(short_name);
+
+ if (!editable)
+ {
+ text = is_text_file(full_name, &sz);
+ if (!text)
+ {
+ add_to_problem_data_ext(problem_data,
+ short_name,
+ full_name,
+ CD_FLAG_BIN + CD_FLAG_ISNOTEDITABLE
+ );
+ goto next;
+ }
+ }
+
+ char *content;
+ if (sz < 4*1024) /* did is_text_file read entire file? */
+ {
+ /* yes */
+ content = text;
+ }
+ else
+ {
+ /* no, need to read it all */
+ free(text);
+ content = dd_load_text(dd, short_name);
+ }
+ /* Strip '\n' from one-line elements: */
+ char *nl = strchr(content, '\n');
+ if (nl && nl[1] == '\0')
+ *nl = '\0';
+
+ int flags = 0;
+
+ if (editable)
+ flags |= CD_FLAG_TXT | CD_FLAG_ISEDITABLE;
+ else
+ flags |= CD_FLAG_TXT | CD_FLAG_ISNOTEDITABLE;
+
+ static const char *const list_files[] = {
+ FILENAME_UID ,
+ FILENAME_PACKAGE ,
+ FILENAME_EXECUTABLE,
+ FILENAME_TIME ,
+ FILENAME_COUNT ,
+ NULL
+ };
+ if (is_in_string_list(short_name, (char**)list_files))
+ flags |= CD_FLAG_LIST;
+
+ if (strcmp(short_name, FILENAME_TIME) == 0)
+ flags |= CD_FLAG_UNIXTIME;
+
+ add_to_problem_data_ext(problem_data,
+ short_name,
+ content,
+ flags
+ );
+ free(content);
+ next:
+ free(short_name);
+ free(full_name);
+ }
+}
+
+problem_data_t *create_problem_data_from_dump_dir(struct dump_dir *dd)
+{
+ problem_data_t *problem_data = new_problem_data();
+ load_problem_data_from_dump_dir(problem_data, dd, NULL);
+ return problem_data;
+}
+
+/*
+ * 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 comma_separated_list is NULL or "", returns NULL.
+ */
+static char **build_exclude_vector(const char *comma_separated_list)
+{
+ char **exclude_items = NULL;
+ if (comma_separated_list && comma_separated_list[0])
+ {
+ /* even w/o commas, we'll need two elements:
+ * exclude_items[0] = "name"
+ * exclude_items[1] = NULL
+ */
+ unsigned cnt = 2;
+
+ const char *cp = comma_separated_list;
+ while (*cp)
+ if (*cp++ == ',')
+ cnt++;
+
+ /* We place the string directly after the char *vector[cnt]: */
+ exclude_items = xzalloc(cnt * sizeof(exclude_items[0]) + (cp - comma_separated_list) + 1);
+ char *p = strcpy((char*)&exclude_items[cnt], comma_separated_list);
+
+ char **pp = exclude_items;
+ *pp++ = p;
+ while (*p)
+ {
+ if (*p++ == ',')
+ {
+ p[-1] = '\0';
+ *pp++ = p;
+ }
+ }
+ }
+
+ return exclude_items;
+}
+
+problem_data_t *create_problem_data_for_reporting(const char *dump_dir_name)
+{
+ char **exclude_items = build_exclude_vector(getenv("EXCLUDE_FROM_REPORT"));
+ struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0);
+ if (!dd)
+ return NULL; /* dd_opendir already emitted error msg */
+ problem_data_t *problem_data = new_problem_data();
+ load_problem_data_from_dump_dir(problem_data, dd, exclude_items);
+ dd_close(dd);
+ free(exclude_items);
+ return problem_data;
+}
+
+void log_problem_data(problem_data_t *problem_data, const char *pfx)
+{
+ GHashTableIter iter;
+ char *name;
+ struct problem_item *value;
+ g_hash_table_iter_init(&iter, problem_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/libreport/src/lib/read_write.c b/libreport/src/lib/read_write.c
new file mode 100644
index 00000000..a3bbb58f
--- /dev/null
+++ b/libreport/src/lib/read_write.c
@@ -0,0 +1,105 @@
+/*
+ * Utility routines.
+ *
+ * Licensed under GPLv2 or later, see file COPYING in this tarball for details.
+ */
+#include "libreport.h"
+
+/* Die with an error message if we can't read the entire buffer. */
+void xread(int fd, void *buf, size_t count)
+{
+ if (count)
+ {
+ ssize_t size = full_read(fd, buf, count);
+ if ((size_t)size != count)
+ error_msg_and_die("short read");
+ }
+}
+
+ssize_t safe_read(int fd, void *buf, size_t count)
+{
+ ssize_t n;
+
+ do {
+ n = read(fd, buf, count);
+ } while (n < 0 && errno == EINTR);
+
+ return n;
+}
+
+ssize_t safe_write(int fd, const void *buf, size_t count)
+{
+ ssize_t n;
+
+ do {
+ n = write(fd, buf, count);
+ } while (n < 0 && errno == EINTR);
+
+ return n;
+}
+
+ssize_t full_read(int fd, void *buf, size_t len)
+{
+ ssize_t cc;
+ ssize_t total;
+
+ total = 0;
+
+ while (len)
+ {
+ cc = safe_read(fd, buf, len);
+
+ if (cc < 0)
+ {
+ if (total)
+ {
+ /* we already have some! */
+ /* user can do another read to know the error code */
+ return total;
+ }
+ return cc; /* read() returns -1 on failure. */
+ }
+ if (cc == 0)
+ break;
+ buf = ((char *)buf) + cc;
+ total += cc;
+ len -= cc;
+ }
+
+ return total;
+}
+
+ssize_t full_write(int fd, const void *buf, size_t len)
+{
+ ssize_t cc;
+ ssize_t total;
+
+ total = 0;
+
+ while (len)
+ {
+ cc = safe_write(fd, buf, len);
+
+ if (cc < 0)
+ {
+ if (total)
+ {
+ /* we already wrote some! */
+ /* user can do another write to know the error code */
+ return total;
+ }
+ return cc; /* write() returns -1 on failure. */
+ }
+
+ total += cc;
+ buf = ((const char *)buf) + cc;
+ len -= cc;
+ }
+
+ return total;
+}
+
+ssize_t full_write_str(int fd, const char *buf)
+{
+ return full_write(fd, buf, strlen(buf));
+}
diff --git a/libreport/src/lib/report.c b/libreport/src/lib/report.c
new file mode 100644
index 00000000..9b5df02d
--- /dev/null
+++ b/libreport/src/lib/report.c
@@ -0,0 +1,129 @@
+/*
+ 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 "libreport.h"
+#include "report.h"
+
+int report_problem_in_dir(const char *dirname, int flags)
+{
+ const char *path;
+ /*
+ if is isatty
+ -> run cli reporter
+ path = "cli"
+ */
+
+ char *args[5], **pp;
+ pp = args;
+ *pp++ = (char *)"bug-reporting-wizard";
+ if (!(flags & LIBREPORT_ANALYZE))
+ *pp++ = (char *)"--report-only";
+ *pp++ = (char *)"--";
+ *pp++ = (char *)dirname;
+ *pp++ = NULL;
+
+ pid_t pid = vfork();
+ if (pid < 0) /* error */
+ {
+ perror_msg("vfork");
+ return -1;
+ }
+
+ if (pid == 0) /* child */
+ {
+ /* Some callers set SIGCHLD to SIG_IGN.
+ * However, reporting spawns child processes.
+ * Suppressing child death notification terribly confuses some of them.
+ * Just in case, undo it.
+ * Note that we do it in the child, so the parent is never affected.
+ */
+ signal(SIGCHLD, SIG_DFL);
+ path = BIN_DIR"/bug-reporting-wizard";
+ VERB1 log("Executing: %s", path);
+ execv(path, args);
+ /* Did not find the desired executable in the installation directory.
+ * Trying to find it in PATH
+ */
+ path = "bug-reporting-wizard";
+ execvp(path, args);
+ perror_msg_and_die("Can't execute %s", path);
+ }
+
+ /* parent */
+ if (flags & LIBREPORT_WAIT)
+ {
+ int status;
+ pid_t p = waitpid(pid, &status, 0);
+ if (p <= 0)
+ {
+ perror_msg("can't waitpid");
+ return -1;
+ }
+ if (WIFEXITED(status))
+ {
+ VERB2 log("reporting finished with exitcode %d", WEXITSTATUS(status));
+ return WEXITSTATUS(status);
+ }
+ /* child died from a signal: WIFSIGNALED(status) should be true */
+ VERB2 log("reporting killed by signal %d", WTERMSIG(status));
+ return WTERMSIG(status) + 128;
+ }
+
+ return 0;
+}
+
+int report_problem_in_memory(problem_data_t *pd, int flags)
+{
+ int result = 0;
+ struct dump_dir *dd = create_dump_dir_from_problem_data(pd, "/tmp"/* /var/tmp ?? */);
+ if (!dd)
+ return -1;
+ char *dir_name = xstrdup(dd->dd_dirname);
+ dd_close(dd);
+ VERB2 log("Temp problem dir: '%s'", dir_name);
+
+// TODO: if !LIBREPORT_WAIT pass LIBREPORT_DEL_DIR, and teach bug-reporting-wizard
+// an option to delete directory after reporting?
+// It will make !LIBREPORT_WAIT reporting possible
+ result = report_problem_in_dir(dir_name, flags);
+
+ /* If we wait for reporter to finish, we should clean the tmp dir.
+ * We can also reload the problem data if requested.
+ */
+ if (flags & LIBREPORT_WAIT)
+ {
+ if (flags & LIBREPORT_RELOAD_DATA)
+ g_hash_table_remove_all(pd);
+ dd = dd_opendir(dir_name, 0);
+ if (dd)
+ {
+ if (flags & LIBREPORT_RELOAD_DATA)
+ load_problem_data_from_dump_dir(pd, dd, NULL);
+ dd_delete(dd);
+ }
+ }
+
+ free(dir_name);
+ return result;
+}
+
+int report_problem(problem_data_t *pd)
+{
+ return report_problem_in_memory(pd, LIBREPORT_WAIT);
+}
diff --git a/libreport/src/lib/report_event.conf b/libreport/src/lib/report_event.conf
new file mode 100644
index 00000000..a99ce8ab
--- /dev/null
+++ b/libreport/src/lib/report_event.conf
@@ -0,0 +1,47 @@
+# This configuration file specifies which programs should be run
+# when the specified event occurs in dump directory lifetime.
+#
+# It consists of directives and rules.
+#
+# Directives start with a reserved word. Currently, there is only one
+# directive, "include". Its format is "include FILE".
+# It causes files which match FILE to be read and
+# parsed as if they are inserted textually where this directive
+# occurs. FILE can use shell pattern metacharacters (*,?,etc) to
+# specify multiple files. Relative paths are interpreted relative
+# to current file.
+#
+# 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).
+#
+# Rules specify which programs to run on the dump directory.
+# Each rule may have conditions to be checked before the program is run.
+#
+# Conditions have form VAR=VAL or VAL~=REGEX, where VAR is either
+# word "EVENT" or a name of dump directory element to be checked
+# (for example, "executable", "package", hostname" etc).
+#
+# 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 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 rule is read
+# and processed. This process is repeated until the end of this file.
+
+include events.d/*.conf
+
+EVENT=report_Dummy analyzer=libreport echo "Hello world" >> /tmp/libreport.log
diff --git a/libreport/src/lib/run_event.c b/libreport/src/lib/run_event.c
new file mode 100644
index 00000000..0457364a
--- /dev/null
+++ b/libreport/src/lib/run_event.c
@@ -0,0 +1,551 @@
+/*
+ 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 <glob.h>
+#include <regex.h>
+#include "libreport.h"
+
+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);
+ }
+}
+
+
+/* Asynchronous 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 command.
+ *
+ * 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?
+ *
+ * We used to 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.
+ *
+ * This proved to be bad for use cases where, for example, post-create rule
+ * for a specified package needs to run:
+ *
+ * EVENT=post-create
+ * abrt-action-save-package-data
+ * EVENT=post-create component=mypkg
+ * my_handling
+ *
+ * Problem here is that "component" element is created by
+ * abrt-action-save-package-data! Pre-selecting rules excludes second rule.
+ *
+ * Now we read entire config but do NOT select commands to execute,
+ * we check conditions of every next rule *directly before its execution*.
+ *
+ * It's possible we'd want to switch to an algorightm which makes in unnecessary
+ * to properly order rules in config files(s). Now these two rules must not
+ * be reordered, or else second one won't work:
+ * EVENT=post-create echo foo >bar
+ * EVENT=post-create foo=bar do_something
+ * but this might be not so easy to ensure when include files are involved...
+ *
+ * Anyway, 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 the way we select rules to execute.
+ */
+struct rule {
+ GList *conditions;
+ char *command; /* never NULL */
+};
+
+static void free_rule_list(GList *rule_list)
+{
+ while (rule_list)
+ {
+ struct rule *cur_rule = rule_list->data;
+ list_free_with_free(cur_rule->conditions);
+ free(cur_rule->command);
+ free(cur_rule);
+
+ GList *next = rule_list->next;
+ g_list_free_1(rule_list);
+ rule_list = next;
+ }
+}
+
+/* Stop-gap measure against infinite recursion */
+#define MAX_recursion_depth 32
+
+static GList *load_rule_list(GList *rule_list,
+ const char *conf_file_name,
+ unsigned recursion_depth
+) {
+ FILE *conffile = fopen(conf_file_name, "r");
+ if (!conffile)
+ {
+ error_msg("Can't open '%s'", conf_file_name);
+ return rule_list;
+ }
+
+ /* Read and remember rules */
+ char *next_line = xmalloc_fgetline(conffile);
+ while (next_line)
+ {
+ /* Read and concatenate all lines in a rule */
+ 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);
+ }
+
+ char *p = skip_whitespace(line);
+ if (*p == '\0' || *p == '#')
+ goto next_line; /* empty or comment line, skip */
+
+ //VERB3 log("%s: line '%s'", __func__, p);
+
+ /* Handle "include" directive */
+ if (recursion_depth < MAX_recursion_depth
+ && 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);
+ rule_list = load_rule_list(rule_list, *name, recursion_depth + 1);
+ //VERB3 log("%s: returned from '%s'", __func__, *name);
+ name++;
+ }
+ globfree(&globbuf);
+ goto next_line;
+ }
+
+ /* Rule has form: [VAR=VAL]... PROG [ARGS] */
+ struct rule *cur_rule = xzalloc(sizeof(*cur_rule));
+
+ while (1) /* word loop */
+ {
+ char *end_word = skip_non_whitespace(p);
+
+ /* If there is no '=' in this word... */
+ char *line_val = strchr(p, '=');
+ if (!line_val || line_val >= end_word)
+ break; /* ...we found the start of a command */
+
+ cur_rule->conditions = g_list_append(cur_rule->conditions, xstrndup(p, end_word - p));
+
+ /* Go to next word */
+ p = skip_whitespace(end_word);
+ } /* end of word loop */
+
+ VERB1 log("Adding '%s'", p);
+ cur_rule->command = xstrdup(p);
+
+ rule_list = g_list_append(rule_list, cur_rule);
+
+ next_line:
+ free(line);
+ } /* end of line loop */
+
+ fclose(conffile);
+
+ return rule_list;
+}
+
+static int regcmp_lines(char *val, const char *regex)
+{
+ regex_t rx;
+ int r = regcomp(&rx, regex, REG_NOSUB); //TODO: and REG_EXTENDED?
+ //log("REGEX:'%s':%d", regex, r);
+ if (r)
+ {
+ //char errbuf[256];
+ //size_t needsz = regerror(r, &rx, errbuf, sizeof(errbuf));
+ error_msg("Bad regexp '%s'", regex); // TODO: use errbuf?
+ return r;
+ }
+
+ /* Check every line */
+ while (1)
+ {
+ char *eol = strchr(val, '\n');
+ if (eol)
+ *eol = '\0';
+ r = regexec(&rx, val, 0, NULL, /*eflags:*/ 0);
+ //log("REGCMP:'%s':%d", val, r);
+ if (eol)
+ *eol = '\n';
+ if (r == 0 || !eol)
+ break;
+ val = eol + 1;
+ }
+ /* Here, r == 0 if match was found */
+ regfree(&rx);
+ return r;
+}
+
+/* Deletes rules in *pp_rule_list, starting from first (remaining) rule,
+ * until it finds a rule with all conditions satisfied.
+ * In this case, it deletes this rule and returns this rule's cmd.
+ * Else (if it didn't find such rule), it deletes all rules and returns NULL.
+ * In case of error (dump_dir can't be opened), deletes all rules and returns NULL.
+ *
+ * Intended usage:
+ * list = load_rule_list(...);
+ * while ((cmd = pop_next_command(&list, ...)) != NULL)
+ * run(cmd);
+ */
+static char* pop_next_command(GList **pp_rule_list,
+ char **pp_event_name, /* reports EVENT value thru this, if not NULL on entry */
+ struct dump_dir **pp_dd, /* use *pp_dd for access to dump dir, if non-NULL */
+ const char *dump_dir_name,
+ const char *pfx,
+ unsigned pfx_len
+)
+{
+ char *command = NULL;
+ struct dump_dir *dd = pp_dd ? *pp_dd : NULL;
+
+ GList *rule_list = *pp_rule_list;
+ while (rule_list)
+ {
+ struct rule *cur_rule = rule_list->data;
+
+ GList *condition = cur_rule->conditions;
+ while (condition)
+ {
+ const char *cond_str = condition->data;
+ const char *eq_sign = strchr(cond_str, '=');
+
+ /* Is it "EVENT=foo"? */
+ if (strncmp(cond_str, "EVENT=", 6) == 0)
+ {
+ if (strncmp(eq_sign + 1, pfx, pfx_len) != 0)
+ goto next_rule; /* prefix doesn't match */
+ if (pp_event_name)
+ {
+ free(*pp_event_name);
+ *pp_event_name = xstrdup(eq_sign + 1);
+ }
+ }
+ else
+ {
+ /* Read from dump dir and compare */
+ if (!dd)
+ {
+ /* Without dir to match, we assume match for all conditions */
+ if (!dump_dir_name)
+ goto next_cond;
+ dd = dd_opendir(dump_dir_name, /*flags:*/ 0);
+ if (!dd)
+ {
+ free_rule_list(rule_list);
+ *pp_rule_list = NULL;
+ goto ret; /* error (note: dd_opendir logged error msg) */
+ }
+ }
+ /* Is it "VAR~=REGEX"? */
+ int regex = (eq_sign > cond_str && eq_sign[-1] == '~');
+ char *var_name = xstrndup(cond_str, eq_sign - cond_str - regex);
+ char *real_val = dd_load_text_ext(dd, var_name, DD_FAIL_QUIETLY_ENOENT);
+ free(var_name);
+ int vals_differ = regex ? regcmp_lines(real_val, eq_sign + 1) : strcmp(real_val, eq_sign + 1);
+ free(real_val);
+
+ /* Do values match? */
+ if (vals_differ) /* no */
+ {
+ //VERB3 log("var '%s': '%.*s'!='%s', skipping line",
+ // p,
+ // (int)(strchrnul(real_val, '\n') - real_val), real_val,
+ // eq_sign);
+ goto next_rule;
+ }
+ }
+ next_cond:
+ /* We are here if current condition is satisfied */
+
+ condition = condition->next;
+ }
+ /* We are here if all conditions are satisfied */
+
+ command = cur_rule->command;
+
+ next_rule:
+ *pp_rule_list = rule_list->next;
+ g_list_free_1(rule_list);
+
+ list_free_with_free(cur_rule->conditions);
+ /*free(cur_rule->command); - WRONG! we might be returning it! */
+ if (command)
+ {
+ /* We found rule to run, return it */
+ free(cur_rule);
+ break;
+ }
+ free(cur_rule->command); /* _now_ it is ok */
+ free(cur_rule);
+
+ rule_list = *pp_rule_list;
+ } /* while (rule_list) */
+
+ ret:
+ if (pp_dd)
+ *pp_dd = dd;
+ else
+ dd_close(dd);
+ return command;
+}
+
+void free_commands(struct run_event_state *state)
+{
+ free_rule_list(state->rule_list);
+ state->rule_list = NULL;
+ state->command_out_fd = -1;
+ state->command_pid = 0;
+}
+
+int prepare_commands(struct run_event_state *state,
+ const char *dump_dir_name,
+ const char *event
+) {
+ free_commands(state);
+
+ state->children_count = 0;
+
+ GList *rule_list = load_rule_list(NULL, CONF_DIR"/report_event.conf", /*recursion_depth:*/ 0);
+ state->rule_list = rule_list;
+ return rule_list != NULL;
+}
+
+int spawn_next_command(struct run_event_state *state,
+ const char *dump_dir_name,
+ const char *event
+) {
+ char *cmd = pop_next_command(&state->rule_list,
+ NULL, /* don't return event_name */
+ NULL, /* NULL &dd: we match by... */
+ dump_dir_name, /* ...dirname */
+ event, strlen(event)+1 /* for this event name exactly (not prefix) */
+ );
+ if (!cmd)
+ 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++;
+
+ VERB1 log("Executing '%s'", cmd);
+
+ /* Export some useful environment variables for children */
+ char *env_vec[3];
+ /* 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);
+ env_vec[0] = xasprintf("DUMP_DIR=%s", (full_name ? full_name : dump_dir_name));
+ free(full_name);
+ env_vec[1] = xasprintf("EVENT=%s", event);
+ env_vec[2] = NULL;
+
+ char *argv[4];
+ argv[0] = (char*)"/bin/sh"; // TODO: honor $SHELL?
+ 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,
+ /* env_vec: */ env_vec,
+ /* dir: */ dump_dir_name,
+ /* uid(unused): */ 0
+ );
+ state->command_out_fd = pipefds[0];
+
+ free(env_vec[0]);
+ free(env_vec[1]);
+ free(cmd);
+
+ return 0;
+}
+
+
+/* Synchronous 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)
+ {
+ 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(state->command_pid, &status, 0);
+
+ retval = WEXITSTATUS(status);
+ if (WIFSIGNALED(status))
+ retval = WTERMSIG(status) + 128;
+ if (retval != 0)
+ break;
+
+ if (state->post_run_callback)
+ {
+ retval = state->post_run_callback(dump_dir_name, state->post_run_param);
+ if (retval != 0)
+ break;
+ }
+ }
+
+ free_commands(state);
+
+ return retval;
+}
+
+int run_event_on_problem_data(struct run_event_state *state, problem_data_t *data, const char *event)
+{
+ state->children_count = 0;
+
+ struct dump_dir *dd = create_dump_dir_from_problem_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, /*flags:*/ 0);
+ free(dir_name);
+ if (dd)
+ {
+ load_problem_data_from_dump_dir(data, dd, NULL);
+ dd_delete(dd);
+ }
+
+ return r;
+}
+
+char *list_possible_events(struct dump_dir *dd, const char *dump_dir_name, const char *pfx)
+{
+ struct strbuf *result = strbuf_new();
+
+ GList *rule_list = load_rule_list(NULL, CONF_DIR"/report_event.conf", /*recursion_depth:*/ 0);
+
+ unsigned pfx_len = strlen(pfx);
+ for (;;)
+ {
+ /* Retrieve each cmd, and fetch its EVENT=foo value */
+ char *event_name = NULL;
+ char *cmd = pop_next_command(&rule_list,
+ &event_name, /* return event_name */
+ (dd ? &dd : NULL), /* match this dd... */
+ dump_dir_name, /* ...or if NULL, this dirname */
+ pfx, pfx_len /* for events with this prefix */
+ );
+ if (!cmd)
+ break;
+ free(cmd);
+
+ if (event_name)
+ {
+ /* Append "EVENT\n" - only if it is not there yet */
+ unsigned e_len = strlen(event_name);
+ char *p = result->buf;
+ while (p && *p)
+ {
+ if (strncmp(p, event_name, e_len) == 0 && p[e_len] == '\n')
+ goto skip; /* This event is already in the result */
+ p = strchr(p, '\n');
+ if (p)
+ p++;
+ }
+ strbuf_append_strf(result, "%s\n", event_name);
+ skip:
+ free(event_name);
+ }
+ }
+
+ return strbuf_free_nobuf(result);
+}
diff --git a/libreport/src/lib/skip_whitespace.c b/libreport/src/lib/skip_whitespace.c
new file mode 100644
index 00000000..b2864e6f
--- /dev/null
+++ b/libreport/src/lib/skip_whitespace.c
@@ -0,0 +1,22 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (C) 2003 Manuel Novoa III <mjn3@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+#include "libreport.h"
+
+char* skip_whitespace(const char *s)
+{
+ /* NB: isspace('\0') returns 0 */
+ while (isspace(*s)) ++s;
+
+ return (char *) s;
+}
+
+char* skip_non_whitespace(const char *s)
+{
+ while (*s && !isspace(*s)) ++s;
+
+ return (char *) s;
+}
diff --git a/libreport/src/lib/spawn.c b/libreport/src/lib/spawn.c
new file mode 100644
index 00000000..2e09da27
--- /dev/null
+++ b/libreport/src/lib/spawn.c
@@ -0,0 +1,158 @@
+/*
+ * Utility routines.
+ *
+ * Licensed under GPLv2, see file COPYING in this tarball for details.
+ */
+#include "libreport.h"
+
+static char *concat_str_vector(char **strings)
+{
+ if (!strings[0])
+ return xzalloc(1); // returns ""
+
+ unsigned len = 0;
+ char **spp = strings;
+ while (*spp)
+ len += strlen(*spp++) + 1;
+
+ char *result = xmalloc(len);
+
+ char *r = result;
+ spp = strings;
+ while (*spp) {
+ r = stpcpy(r, *spp++);
+ *r++ = ' ';
+ }
+ *--r = '\0';
+
+ return result;
+}
+
+/* Returns pid */
+pid_t fork_execv_on_steroids(int flags,
+ char **argv,
+ int *pipefds,
+ char **env_vec,
+ const char *dir,
+ uid_t uid)
+{
+ pid_t child;
+ /* Reminder: [0] is read end, [1] is write end */
+ int pipe_to_child[2];
+ int pipe_fm_child[2];
+
+ /* Sanitize flags */
+ if (!pipefds)
+ flags &= ~(EXECFLG_INPUT | EXECFLG_OUTPUT);
+
+ if (flags & EXECFLG_INPUT)
+ xpipe(pipe_to_child);
+ if (flags & EXECFLG_OUTPUT)
+ xpipe(pipe_fm_child);
+
+ fflush(NULL);
+ child = fork();
+ if (child == -1) {
+ perror_msg_and_die("fork");
+ }
+ if (child == 0) {
+ /* Child */
+
+ if (dir)
+ xchdir(dir);
+
+ if (flags & EXECFLG_SETGUID) {
+ struct passwd* pw = getpwuid(uid);
+ gid_t gid = pw ? pw->pw_gid : uid;
+ setgroups(1, &gid);
+ xsetregid(gid, gid);
+ xsetreuid(uid, uid);
+ }
+
+ if (env_vec) {
+ /* Note: we use the glibc extension that putenv("var")
+ * *unsets* $var if "var" string has no '=' */
+ while (*env_vec)
+ putenv(*env_vec++);
+ }
+
+ /* Play with stdio descriptors */
+ if (flags & EXECFLG_INPUT) {
+ xmove_fd(pipe_to_child[0], STDIN_FILENO);
+ close(pipe_to_child[1]);
+ } else if (flags & EXECFLG_INPUT_NUL) {
+ xmove_fd(xopen("/dev/null", O_RDWR), STDIN_FILENO);
+ }
+ if (flags & EXECFLG_OUTPUT) {
+ xmove_fd(pipe_fm_child[1], STDOUT_FILENO);
+ close(pipe_fm_child[0]);
+ } else if (flags & EXECFLG_OUTPUT_NUL) {
+ xmove_fd(xopen("/dev/null", O_RDWR), STDOUT_FILENO);
+ }
+
+ /* This should be done BEFORE stderr redirect */
+ VERB1 {
+ char *r = concat_str_vector(argv);
+ log("Executing: %s", r);
+ free(r);
+ }
+
+ if (flags & EXECFLG_ERR2OUT) {
+ /* Want parent to see errors in the same stream */
+ xdup2(STDOUT_FILENO, STDERR_FILENO);
+ } else if (flags & EXECFLG_ERR_NUL) {
+ xmove_fd(xopen("/dev/null", O_RDWR), STDERR_FILENO);
+ }
+
+ if (flags & EXECFLG_SETSID)
+ setsid();
+
+ execvp(argv[0], argv);
+ if (!(flags & EXECFLG_QUIET))
+ perror_msg("Can't execute '%s'", argv[0]);
+ exit(127); /* shell uses this exit code in this case */
+ }
+
+ if (flags & EXECFLG_INPUT) {
+ close(pipe_to_child[0]);
+ pipefds[1] = pipe_to_child[1];
+ }
+ if (flags & EXECFLG_OUTPUT) {
+ close(pipe_fm_child[1]);
+ pipefds[0] = pipe_fm_child[0];
+ }
+
+ return child;
+}
+
+char *run_in_shell_and_save_output(int flags,
+ const char *cmd,
+ const char *dir,
+ size_t *size_p)
+{
+ flags |= EXECFLG_OUTPUT;
+ flags &= ~EXECFLG_INPUT;
+
+ const char *argv[] = { "/bin/sh", "-c", cmd, NULL };
+ int pipeout[2];
+ pid_t child = fork_execv_on_steroids(flags, (char **)argv, pipeout,
+ /*env_vec:*/ NULL, dir, /*uid (unused):*/ 0);
+
+ size_t pos = 0;
+ char *result = NULL;
+ while (1) {
+ result = (char*) xrealloc(result, pos + 4*1024 + 1);
+ size_t sz = safe_read(pipeout[0], result + pos, 4*1024);
+ if (sz <= 0) {
+ break;
+ }
+ pos += sz;
+ }
+ result[pos] = '\0';
+ if (size_p)
+ *size_p = pos;
+ close(pipeout[0]);
+ waitpid(child, NULL, 0);
+
+ return result;
+}
diff --git a/libreport/src/lib/stdio_helpers.c b/libreport/src/lib/stdio_helpers.c
new file mode 100644
index 00000000..2ce20777
--- /dev/null
+++ b/libreport/src/lib/stdio_helpers.c
@@ -0,0 +1,69 @@
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2001 Matt Krai
+ * Copyright (C) 2004 Erik Andersen <andersen@codepoet.org>
+ * Copyright (C) 2005, 2006 Rob Landley <rob@landley.net>
+ * Copyright (C) 2010 ABRT Team
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+#include "libreport.h"
+
+//TODO: add sanitizing upper limit (e.g 64K, 1M, or configurable).
+//This is why we don't use GNU's getline: it doesn't have
+//any upper sanity bound on line size.
+
+static char *xmalloc_fgets_internal(FILE *file, int *sizep)
+{
+ unsigned idx = 0;
+ char *linebuf = NULL;
+
+ while (1) {
+ char *r;
+
+ linebuf = xrealloc(linebuf, idx + 0x100);
+ r = fgets(&linebuf[idx], 0x100, file);
+ if (!r) {
+ /* need to terminate the line */
+ linebuf[idx] = '\0';
+ break;
+ }
+
+ /* stupid. fgets knows the len, it should report it somehow */
+ unsigned len = strlen(&linebuf[idx]);
+
+ idx += len;
+ if (len < 0xff || linebuf[idx - 1] == '\n')
+ break; /* we found \n or EOF */
+ }
+
+ *sizep = idx;
+
+ if (!idx) {
+ /* The very first fgets returned NULL. It's EOF (or error) */
+ free(linebuf);
+ linebuf = NULL;
+ }
+ return linebuf;
+}
+
+char *xmalloc_fgets(FILE *file)
+{
+ int sz;
+ char *r = xmalloc_fgets_internal(file, &sz);
+ if (!r)
+ return r;
+ return xrealloc(r, sz + 1);
+}
+
+char *xmalloc_fgetline(FILE *file)
+{
+ int sz;
+ char *r = xmalloc_fgets_internal(file, &sz);
+ if (!r)
+ return r;
+ if (r[sz - 1] == '\n')
+ r[--sz] = '\0';
+ return xrealloc(r, sz + 1);
+}
diff --git a/libreport/src/lib/steal_directory.c b/libreport/src/lib/steal_directory.c
new file mode 100644
index 00000000..4bcd192e
--- /dev/null
+++ b/libreport/src/lib/steal_directory.c
@@ -0,0 +1,58 @@
+/*
+ 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 "libreport.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, 0640);
+ 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/libreport/src/lib/strbuf.c b/libreport/src/lib/strbuf.c
new file mode 100644
index 00000000..f2adeda7
--- /dev/null
+++ b/libreport/src/lib/strbuf.c
@@ -0,0 +1,170 @@
+/*
+ String buffer implementation
+
+ 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 "libreport.h"
+
+int prefixcmp(const char *str, const char *prefix)
+{
+ for (; ; str++, prefix++)
+ if (!*prefix)
+ return 0;
+ else if (*str != *prefix)
+ return (unsigned char)*prefix - (unsigned char)*str;
+}
+
+int suffixcmp(const char *str, const char *suffix)
+{
+ int len_minus_suflen = strlen(str) - strlen(suffix);
+ if (len_minus_suflen < 0)
+ return len_minus_suflen;
+ else
+ return strcmp(str + len_minus_suflen, suffix);
+}
+
+/*
+ * Trims whitespace characters both from left and right side of a string.
+ * Modifies the string in-place. Returns the trimmed string.
+ */
+char *strtrim(char *str)
+{
+ if (!str)
+ return NULL;
+
+ // Remove leading spaces.
+ overlapping_strcpy(str, skip_whitespace(str));
+
+ // Remove trailing spaces.
+ int i = strlen(str);
+ while (--i >= 0)
+ {
+ if (!isspace(str[i]))
+ break;
+ }
+ str[++i] = '\0';
+ return str;
+}
+
+struct strbuf *strbuf_new(void)
+{
+ struct strbuf *buf = xzalloc(sizeof(*buf));
+ /*buf->len = 0; - done by xzalloc */
+ buf->alloc = 8;
+ buf->buf = xzalloc(8);
+ return buf;
+}
+
+void strbuf_free(struct strbuf *strbuf)
+{
+ if (!strbuf)
+ return;
+ free(strbuf->buf);
+ free(strbuf);
+}
+
+char *strbuf_free_nobuf(struct strbuf *strbuf)
+{
+ char *ret = strbuf->buf;
+ free(strbuf);
+ return ret;
+}
+
+
+void strbuf_clear(struct strbuf *strbuf)
+{
+ assert(strbuf->alloc > 0);
+ strbuf->len = 0;
+ strbuf->buf[0] = '\0';
+}
+
+/* Ensures that the buffer can be extended by N+1 characters
+ * without touching malloc/realloc.
+ * Returns pointer where appended chars can be stored by the caller;
+ * increments ->len by N (therefore callers don't need to do it).
+ */
+static char *strbuf_grow(struct strbuf *strbuf, unsigned increment)
+{
+ unsigned len = strbuf->len;
+ unsigned need = strbuf->len = len + increment;
+ unsigned cur_size = strbuf->alloc;
+ if (cur_size <= need)
+ {
+ while (cur_size <= need)
+ cur_size += 64 + cur_size / 8;
+ strbuf->alloc = cur_size;
+ strbuf->buf = xrealloc(strbuf->buf, cur_size);
+ }
+ char *p = strbuf->buf + len;
+ return p;
+}
+
+struct strbuf *strbuf_append_char(struct strbuf *strbuf, char c)
+{
+ char *p = strbuf_grow(strbuf, 1);
+ *p++ = c;
+ *p = '\0';
+ return strbuf;
+}
+
+struct strbuf *strbuf_append_str(struct strbuf *strbuf, const char *str)
+{
+ unsigned len = strlen(str);
+ char *p = strbuf_grow(strbuf, len);
+ assert(strbuf->len < strbuf->alloc);
+ strcpy(p, str);
+ return strbuf;
+}
+
+struct strbuf *strbuf_prepend_str(struct strbuf *strbuf, const char *str)
+{
+ unsigned cur_len = strbuf->len;
+ unsigned inc_len = strlen(str);
+ strbuf_grow(strbuf, inc_len);
+ assert(strbuf->len < strbuf->alloc);
+ memmove(strbuf->buf + inc_len, strbuf->buf, cur_len);
+ memcpy(strbuf->buf, str, inc_len);
+ return strbuf;
+}
+
+struct strbuf *strbuf_append_strf(struct strbuf *strbuf, const char *format, ...)
+{
+ va_list p;
+ char *string_ptr;
+
+ va_start(p, format);
+ string_ptr = xvasprintf(format, p);
+ va_end(p);
+
+ strbuf_append_str(strbuf, string_ptr);
+ free(string_ptr);
+ return strbuf;
+}
+
+struct strbuf *strbuf_prepend_strf(struct strbuf *strbuf, const char *format, ...)
+{
+ va_list p;
+ char *string_ptr;
+
+ va_start(p, format);
+ string_ptr = xvasprintf(format, p);
+ va_end(p);
+
+ strbuf_prepend_str(strbuf, string_ptr);
+ free(string_ptr);
+ return strbuf;
+}
diff --git a/libreport/src/lib/xatonum.c b/libreport/src/lib/xatonum.c
new file mode 100644
index 00000000..b6b90a98
--- /dev/null
+++ b/libreport/src/lib/xatonum.c
@@ -0,0 +1,50 @@
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2003 Manuel Novoa III <mjn3@codepoet.org>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libreport.h"
+
+unsigned xatou(const char *numstr)
+{
+ unsigned long r;
+ int old_errno;
+ char *e;
+
+ if (*numstr < '0' || *numstr > '9')
+ goto inval;
+
+ old_errno = errno;
+ errno = 0;
+ r = strtoul(numstr, &e, 10);
+ if (errno || numstr == e || *e != '\0' || r > UINT_MAX)
+ goto inval; /* error / no digits / illegal trailing chars */
+ errno = old_errno; /* Ok. So restore errno. */
+ return r;
+
+inval:
+ error_msg_and_die("invalid number '%s'", numstr);
+}
+
+int xatoi_positive(const char *numstr)
+{
+ unsigned r = xatou(numstr);
+ if (r > (unsigned)INT_MAX)
+ error_msg_and_die("invalid number '%s'", numstr);
+ return r;
+}
+
+int xatoi(const char *numstr)
+{
+ unsigned r;
+
+ if (*numstr != '-')
+ return xatoi_positive(numstr);
+
+ r = xatou(numstr + 1);
+ if (r > (unsigned)INT_MAX + 1)
+ error_msg_and_die("invalid number '%s'", numstr);
+ return - (int)r;
+}
diff --git a/libreport/src/lib/xfuncs.c b/libreport/src/lib/xfuncs.c
new file mode 100644
index 00000000..67b61d40
--- /dev/null
+++ b/libreport/src/lib/xfuncs.c
@@ -0,0 +1,410 @@
+/*
+ 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.
+*/
+
+/*
+ * Utility routines.
+ *
+ */
+#include "libreport.h"
+
+/* Turn on nonblocking I/O on a fd */
+int ndelay_on(int fd)
+{
+ 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)
+{
+ 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)
+{
+ return fcntl(fd, F_SETFD, FD_CLOEXEC);
+}
+
+// Die if we can't allocate size bytes of memory.
+void* xmalloc(size_t size)
+{
+ void *ptr = malloc(size);
+ if (ptr == NULL && size != 0)
+ die_out_of_memory();
+ return ptr;
+}
+
+// Die if we can't resize previously allocated memory. (This returns a pointer
+// to the new memory, which may or may not be the same as the old memory.
+// It'll copy the contents to a new chunk and free the old one if necessary.)
+void* xrealloc(void *ptr, size_t size)
+{
+ ptr = realloc(ptr, size);
+ if (ptr == NULL && size != 0)
+ die_out_of_memory();
+ return ptr;
+}
+
+// Die if we can't allocate and zero size bytes of memory.
+void* xzalloc(size_t size)
+{
+ void *ptr = xmalloc(size);
+ memset(ptr, 0, size);
+ return ptr;
+}
+
+// Die if we can't copy a string to freshly allocated memory.
+char* xstrdup(const char *s)
+{
+ char *t;
+ if (s == NULL)
+ return NULL;
+
+ t = strdup(s);
+
+ if (t == NULL)
+ die_out_of_memory();
+
+ return t;
+}
+
+// Die if we can't allocate n+1 bytes (space for the null terminator) and copy
+// the (possibly truncated to length n) string into it.
+char* xstrndup(const char *s, int n)
+{
+ int m;
+ char *t;
+
+ /* We can just xmalloc(n+1) and strncpy into it, */
+ /* but think about xstrndup("abc", 10000) wastage! */
+ m = n;
+ t = (char*) s;
+ while (m)
+ {
+ if (!*t) break;
+ m--;
+ t++;
+ }
+ n -= m;
+ t = (char*) xmalloc(n + 1);
+ t[n] = '\0';
+
+ return (char*) memcpy(t, s, n);
+}
+
+void xpipe(int filedes[2])
+{
+ if (pipe(filedes))
+ perror_msg_and_die("can't create pipe");
+}
+
+void xdup(int from)
+{
+ if (dup(from) < 0)
+ perror_msg_and_die("can't duplicate file descriptor");
+}
+
+void xdup2(int from, int to)
+{
+ if (dup2(from, to) != to)
+ perror_msg_and_die("can't duplicate file descriptor");
+}
+
+// "Renumber" opened fd
+void xmove_fd(int from, int to)
+{
+ if (from == to)
+ return;
+ xdup2(from, to);
+ close(from);
+}
+
+// Die with an error message if we can't write the entire buffer.
+void xwrite(int fd, const void *buf, size_t count)
+{
+ if (count == 0)
+ return;
+ ssize_t size = full_write(fd, buf, count);
+ if ((size_t)size != count)
+ error_msg_and_die("short write");
+}
+
+void xwrite_str(int fd, const char *str)
+{
+ xwrite(fd, str, strlen(str));
+}
+
+// Die with an error message if we can't lseek to the right spot.
+off_t xlseek(int fd, off_t offset, int whence)
+{
+ off_t off = lseek(fd, offset, whence);
+ if (off == (off_t)-1) {
+ if (whence == SEEK_SET)
+ perror_msg_and_die("lseek(%llu)", (long long)offset);
+ perror_msg_and_die("lseek");
+ }
+ return off;
+}
+
+void xchdir(const char *path)
+{
+ if (chdir(path))
+ perror_msg_and_die("chdir(%s)", path);
+}
+
+char* xvasprintf(const char *format, va_list p)
+{
+ int r;
+ char *string_ptr;
+
+#if 1
+ // GNU extension
+ r = vasprintf(&string_ptr, format, p);
+#else
+ // Bloat for systems that haven't got the GNU extension.
+ va_list p2;
+ va_copy(p2, p);
+ r = vsnprintf(NULL, 0, format, p);
+ string_ptr = xmalloc(r+1);
+ r = vsnprintf(string_ptr, r+1, format, p2);
+ va_end(p2);
+#endif
+
+ if (r < 0)
+ die_out_of_memory();
+ return string_ptr;
+}
+
+// Die with an error message if we can't malloc() enough space and do an
+// sprintf() into that space.
+char* xasprintf(const char *format, ...)
+{
+ va_list p;
+ char *string_ptr;
+
+ va_start(p, format);
+ string_ptr = xvasprintf(format, p);
+ va_end(p);
+
+ return string_ptr;
+}
+
+void xsetenv(const char *key, const char *value)
+{
+ if (setenv(key, value, 1))
+ die_out_of_memory();
+}
+
+void safe_unsetenv(const char *var_val)
+{
+ //char *name = xstrndup(var_val, strchrnul(var_val, '=') - var_val);
+ //unsetenv(name);
+ //free(name);
+
+ /* Avoid malloc/free (name is usually very short) */
+ unsigned len = strchrnul(var_val, '=') - var_val;
+ char name[len + 1];
+ memcpy(name, var_val, len);
+ name[len] = '\0';
+ unsetenv(name);
+}
+
+// Die with an error message if we can't open a new socket.
+int xsocket(int domain, int type, int protocol)
+{
+ int r = socket(domain, type, protocol);
+ if (r < 0)
+ {
+ const char *s = "INET";
+ if (domain == AF_PACKET) s = "PACKET";
+ if (domain == AF_NETLINK) s = "NETLINK";
+ if (domain == AF_INET6) s = "INET6";
+ perror_msg_and_die("socket(AF_%s)", s);
+ }
+
+ return r;
+}
+
+// Die with an error message if we can't bind a socket to an address.
+void xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen)
+{
+ if (bind(sockfd, my_addr, addrlen))
+ perror_msg_and_die("bind");
+}
+
+// Die with an error message if we can't listen for connections on a socket.
+void xlisten(int s, int backlog)
+{
+ if (listen(s, backlog))
+ perror_msg_and_die("listen");
+}
+
+// Die with an error message if sendto failed.
+// Return bytes sent otherwise
+ssize_t xsendto(int s, const void *buf, size_t len,
+ const struct sockaddr *to,
+ socklen_t tolen)
+{
+ ssize_t ret = sendto(s, buf, len, 0, to, tolen);
+ if (ret < 0)
+ {
+ close(s);
+ perror_msg_and_die("sendto");
+ }
+ return ret;
+}
+
+// xstat() - a stat() which dies on failure with meaningful error message
+void xstat(const char *name, struct stat *stat_buf)
+{
+ if (stat(name, stat_buf))
+ perror_msg_and_die("can't stat '%s'", name);
+}
+
+// Die if we can't open a file and return a fd
+int xopen3(const char *pathname, int flags, int mode)
+{
+ int ret;
+ ret = open(pathname, flags, mode);
+ if (ret < 0)
+ perror_msg_and_die("can't open '%s'", pathname);
+ return ret;
+}
+
+// Die if we can't open an existing file and return a fd
+int xopen(const char *pathname, int flags)
+{
+ return xopen3(pathname, flags, 0666);
+}
+
+void xunlink(const char *pathname)
+{
+ if (unlink(pathname))
+ perror_msg_and_die("Can't remove file '%s'", pathname);
+}
+
+#if 0 //UNUSED
+// Warn if we can't open a file and return a fd.
+int open3_or_warn(const char *pathname, int flags, int mode)
+{
+ int ret;
+ ret = open(pathname, flags, mode);
+ if (ret < 0)
+ perror_msg("can't open '%s'", pathname);
+ return ret;
+}
+
+// Warn if we can't open a file and return a fd.
+int open_or_warn(const char *pathname, int flags)
+{
+ return open3_or_warn(pathname, flags, 0666);
+}
+#endif
+
+/* Just testing dent->d_type == DT_REG is wrong: some filesystems
+ * do not report the type, they report DT_UNKNOWN for every dirent
+ * (and this is not a bug in filesystem, this is allowed by standards).
+ */
+int is_regular_file(struct dirent *dent, const char *dirname)
+{
+ if (dent->d_type == DT_REG)
+ return 1;
+ if (dent->d_type != DT_UNKNOWN)
+ return 0;
+
+ char *fullname = xasprintf("%s/%s", dirname, dent->d_name);
+ struct stat statbuf;
+ int r = lstat(fullname, &statbuf);
+ free(fullname);
+
+ return r == 0 && S_ISREG(statbuf.st_mode);
+}
+
+/* Is it "." or ".."? */
+/* abrtlib candidate */
+bool dot_or_dotdot(const char *filename)
+{
+ if (filename[0] != '.') return false;
+ if (filename[1] == '\0') return true;
+ if (filename[1] != '.') return false;
+ if (filename[2] == '\0') return true;
+ return false;
+}
+
+/* Find out if the last character of a string matches the one given.
+ * Don't underrun the buffer if the string length is 0.
+ */
+char *last_char_is(const char *s, int c)
+{
+ if (s && *s)
+ {
+ s += strlen(s) - 1;
+ if ((unsigned char)*s == c)
+ return (char*)s;
+ }
+ return NULL;
+}
+
+bool string_to_bool(const char *s)
+{
+ if (s[0] == '1' && s[1] == '\0')
+ return true;
+ if (strcasecmp(s, "on") == 0)
+ return true;
+ if (strcasecmp(s, "yes") == 0)
+ return true;
+ if (strcasecmp(s, "true") == 0)
+ return true;
+ return false;
+}
+
+void xseteuid(uid_t euid)
+{
+ if (seteuid(euid) != 0)
+ perror_msg_and_die("can't set %cid %lu", 'u', (long)euid);
+}
+
+void xsetegid(gid_t egid)
+{
+ if (setegid(egid) != 0)
+ perror_msg_and_die("can't set %cid %lu", 'g', (long)egid);
+}
+
+void xsetreuid(uid_t ruid, uid_t euid)
+{
+ if (setreuid(ruid, euid) != 0)
+ perror_msg_and_die("can't set %cid %lu", 'u', (long)ruid);
+}
+
+void xsetregid(gid_t rgid, gid_t egid)
+{
+ if (setregid(rgid, egid) != 0)
+ perror_msg_and_die("can't set %cid %lu", 'g', (long)rgid);
+}
+
+const char *get_home_dir(uid_t uid)
+{
+ struct passwd* pw = getpwuid(uid);
+ // TODO: handle errno
+ return pw ? pw->pw_dir : NULL;
+}