diff options
Diffstat (limited to 'libreport/src/lib')
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; +} |