[PATCH v5] track#549 : gnome-keyring replaced by Secret Service API
by Jakub Filak
Signed-off-by: Jakub Filak <jfilak(a)redhat.com>
---
configure.ac | 1 +
libreport.spec.in | 7 -
src/gtk-helpers/Makefile.am | 6 +-
src/gtk-helpers/abrt-keyring.c | 130 ----
src/gtk-helpers/event_config_dialog.c | 71 +--
src/gtk-helpers/internal_libreport_gtk.h | 15 +-
src/gtk-helpers/secrets.c | 1050 ++++++++++++++++++++++++++++++
src/gui-wizard-gtk/main.c | 2 +-
8 files changed, 1072 insertions(+), 210 deletions(-)
delete mode 100644 src/gtk-helpers/abrt-keyring.c
create mode 100644 src/gtk-helpers/secrets.c
diff --git a/configure.ac b/configure.ac
index 25d44e5..06896a9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -82,6 +82,7 @@ AC_SUBST(XMLRPC_CLIENT_LIBS)
PKG_CHECK_MODULES([GTK], [gtk+-3.0])
PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.21])
+PKG_CHECK_MODULES([GIO], [gio-2.0])
PKG_CHECK_MODULES([DBUS], [dbus-1])
PKG_CHECK_MODULES([LIBXML], [libxml-2.0])
PKG_CHECK_MODULES([NSS], [nss])
diff --git a/libreport.spec.in b/libreport.spec.in
index f65c87d..ebe247a 100644
--- a/libreport.spec.in
+++ b/libreport.spec.in
@@ -35,13 +35,6 @@ Requires: libreport-filesystem
# FIXME: can be removed when F15 will EOLed, needs to stay in rhel6!
Requires: libreport-python
-# for rhel6
-%if 0%{?rhel} >= 6
-BuildRequires: gnome-keyring-devel
-%else
-BuildRequires: libgnome-keyring-devel
-%endif
-
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
%description
diff --git a/src/gtk-helpers/Makefile.am b/src/gtk-helpers/Makefile.am
index 188e48f..68ec928 100644
--- a/src/gtk-helpers/Makefile.am
+++ b/src/gtk-helpers/Makefile.am
@@ -10,7 +10,7 @@ lib_LTLIBRARIES = \
libreport_gtk_la_SOURCES = \
event_config_dialog.c \
- abrt-keyring.c \
+ secrets.c \
hyperlinks.c \
autowrapped_label.c
@@ -20,14 +20,14 @@ libreport_gtk_la_CPPFLAGS = \
-I$(srcdir)/../lib \
$(GTK_CFLAGS) \
$(GLIB_CFLAGS) \
- $(GNOME_KEYRING_CFLAGS) \
+ $(GIO_CFLAGS) \
-D_GNU_SOURCE
libreport_gtk_la_LDFLAGS = \
-version-info 0:1:0
libreport_gtk_la_LIBADD = \
$(GTK_LIBS) \
$(GLIB_LIBS) \
- $(GNOME_KEYRING_LIBS) \
+ $(GIO_LIBS) \
../lib/libreport.la
EXTRA_DIST = libreport-gtk.pc.in
diff --git a/src/gtk-helpers/abrt-keyring.c b/src/gtk-helpers/abrt-keyring.c
deleted file mode 100644
index 4e830ca..0000000
--- a/src/gtk-helpers/abrt-keyring.c
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- 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 <gnome-keyring.h>
-#include "internal_libreport.h"
-#include "internal_libreport_gtk.h"
-
-static char *keyring_name;
-static bool got_keyring = 0;
-bool g_keyring_available = 1; //by default we assume that keyring is available
-
-guint32 find_keyring_item_id_for_event(const char *event_name)
-{
- GnomeKeyringAttributeList *attrs = gnome_keyring_attribute_list_new();
- GList *found = NULL;
- gnome_keyring_attribute_list_append_string(attrs, "libreportEventConfig", event_name);
- GnomeKeyringResult result = gnome_keyring_find_items_sync(
- GNOME_KEYRING_ITEM_GENERIC_SECRET,
- attrs,
- &found);
- gnome_keyring_attribute_list_free(attrs);
-
- //let's hope 0 is not valid item_id
- guint32 item_id = 0;
- if (result != GNOME_KEYRING_RESULT_OK)
- goto ret;
- if (found)
- item_id = ((GnomeKeyringFound *)found->data)->item_id;
- ret:
- if (found)
- gnome_keyring_found_list_free(found);
- VERB2 log("keyring has %sconfiguration for event '%s'", (item_id != 0) ? "" : "no ", event_name);
- return item_id;
-}
-
-static void abrt_keyring_load_settings(const char *event_name, event_config_t *ec)
-{
- //don't bother when we already know that keyring is not available
- if (!g_keyring_available)
- return;
-
- guint item_id = find_keyring_item_id_for_event(event_name);
- if (!item_id)
- return;
- GnomeKeyringAttributeList *attrs = NULL;
- GnomeKeyringResult result = gnome_keyring_item_get_attributes_sync(
- keyring_name,
- item_id,
- &attrs);
- if (result == GNOME_KEYRING_RESULT_OK && attrs)
- {
- VERB3 log("num attrs %i", attrs->len);
- guint index;
- for (index = 0; index < attrs->len; index++)
- {
- char *name = g_array_index(attrs, GnomeKeyringAttribute, index).name;
- VERB2 log("keyring has name '%s'", name);
- event_option_t *option = get_event_option_from_list(name, ec->options);
- if (option)
- {
- free(option->eo_value);
- option->eo_value = xstrdup(g_array_index(attrs, GnomeKeyringAttribute, index).value.string);
- VERB2 log("added or replaced in event config:'%s=%s'", name, option->eo_value);
- }
- }
- }
- if (attrs)
- gnome_keyring_attribute_list_free(attrs);
-}
-
-static void init_keyring()
-{
- /* Called again? */
- if (got_keyring || !g_keyring_available)
- return;
-
- if (!gnome_keyring_is_available())
- {
- g_keyring_available = 0;
- VERB1 error_msg("Cannot connect to Gnome keyring daemon");
- return;
- }
-
- GnomeKeyringResult result = gnome_keyring_get_default_keyring_sync(&keyring_name);
- if (result != GNOME_KEYRING_RESULT_OK)
- {
- error_msg("Can't get default keyring (result:%d)", result);
- return;
- }
-
- got_keyring = 1;
- /*
- * Note: The default keyring name can be NULL. It is a valid name.
- */
- VERB2 log("keyring:'%s'", keyring_name);
-}
-
-static void load_event_config(gpointer key, gpointer value, gpointer user_data)
-{
- char* event_name = (char*)key;
- event_config_t *ec = (event_config_t *)value;
- VERB2 log("loading event '%s' configuration from keyring", event_name);
- abrt_keyring_load_settings(event_name, ec);
-}
-
-/*
- * Tries to load settings for all events in g_event_config_list
-*/
-void load_event_config_data_from_keyring(void)
-{
- init_keyring();
- if (!got_keyring)
- return;
- g_hash_table_foreach(g_event_config_list, &load_event_config, NULL);
-}
diff --git a/src/gtk-helpers/event_config_dialog.c b/src/gtk-helpers/event_config_dialog.c
index 5a564fe..190dad1 100644
--- a/src/gtk-helpers/event_config_dialog.c
+++ b/src/gtk-helpers/event_config_dialog.c
@@ -17,7 +17,6 @@
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <gtk/gtk.h>
-#include <gnome-keyring.h>
#include "internal_libreport_gtk.h"
static GtkWindow *g_event_list_window;
@@ -298,63 +297,6 @@ static void dehydrate_config_dialog()
g_list_foreach(option_widget_list, &save_value_from_widget, NULL);
}
-static void save_settings_to_keyring(const char *event_name)
-{
- //don't bother when we already know that keyring is not available
- if (!g_keyring_available)
- return;
- char *keyring_name = NULL;
- GnomeKeyringResult result = gnome_keyring_get_default_keyring_sync(&keyring_name);
- if (result != GNOME_KEYRING_RESULT_OK)
- {
- error_msg("Can't get default keyring (result:%d)", result);
- return;
- }
-
- const char *store_passwords_s = get_user_setting("store_passwords");
- bool store_passwords = !(store_passwords_s && !strcmp(store_passwords_s, "no"));
-
- GnomeKeyringAttributeList *attrs = gnome_keyring_attribute_list_new();
- event_config_t *ec = get_event_config(event_name);
- /* add string id which we use to search for items */
- gnome_keyring_attribute_list_append_string(attrs, "libreportEventConfig", event_name);
- GList *l;
- for (l = g_list_first(ec->options); l != NULL; l = g_list_next(l))
- {
- event_option_t *op = (event_option_t *)l->data;
- gnome_keyring_attribute_list_append_string(attrs, op->eo_name,
- (!store_passwords && op->eo_type == OPTION_TYPE_PASSWORD) ? "" : op->eo_value);
- }
-
- guint32 item_id = find_keyring_item_id_for_event(event_name);
- if (item_id)
- {
- /* found existing item, so just update the values */
- result = gnome_keyring_item_set_attributes_sync(keyring_name, item_id, attrs);
- VERB2 log("updated item with id: %i", item_id);
- }
- else
- {
- /* did't find existing item, so create a new one */
- result = gnome_keyring_item_create_sync(keyring_name,
- GNOME_KEYRING_ITEM_GENERIC_SECRET, /* type */
- event_name, /* display name */
- attrs, /* attributes */
- "", /* secret - no special handling for password it's stored in attrs */
- 1, /* update if exist */
- &item_id);
- VERB2 log("created new item with id: %i", item_id);
- }
- gnome_keyring_attribute_list_free(attrs);
-
- if (result != GNOME_KEYRING_RESULT_OK)
- {
- error_msg("Error saving event '%s' configuration to keyring", event_name);
- return;
- }
- VERB2 log("saved event '%s' configuration to keyring", event_name);
-}
-
int show_event_config_dialog(const char *event_name, GtkWindow *parent)
{
if (option_widget_list != NULL)
@@ -425,14 +367,14 @@ int show_event_config_dialog(const char *event_name, GtkWindow *parent)
if (g_list_length(gtk_container_get_children(GTK_CONTAINER(g_adv_option_table))) > 0)
gtk_box_pack_start(GTK_BOX(content), adv_expander, false, false, 0);
- /* add warning if keyring is not available showing the nagging dialog
+ /* add warning if secrets service is not available showing the nagging dialog
* is considered "too heavy UI" be designers
*/
- if (!g_keyring_available)
+ if (!is_event_config_user_storage_available())
{
GtkWidget *keyring_warn_lbl =
gtk_label_new(
- _("Gnome Keyring is not available, your settings won't be saved!"));
+ _("Secret Service is not available, your settings won't be saved!"));
static const GdkColor red = { .red = 0xffff };
gtk_widget_modify_fg(keyring_warn_lbl, GTK_STATE_NORMAL, &red);
gtk_box_pack_start(GTK_BOX(content), keyring_warn_lbl, false, false, 0);
@@ -444,7 +386,10 @@ int show_event_config_dialog(const char *event_name, GtkWindow *parent)
if (result == GTK_RESPONSE_APPLY)
{
dehydrate_config_dialog();
- save_settings_to_keyring(event_name);
+ const char *const store_passwords_s = get_user_setting("store_passwords");
+ save_event_config_data_to_user_storage(event_name,
+ get_event_config(event_name),
+ !(store_passwords_s && !strcmp(store_passwords_s, "no")));
}
//else if (result == GTK_RESPONSE_CANCEL)
// log("log");
@@ -460,7 +405,7 @@ void show_events_list_dialog(GtkWindow *parent)
if (g_event_config_list == NULL)
{
load_event_config_data();
- load_event_config_data_from_keyring();
+ load_event_config_data_from_user_storage(g_event_config_list);
}
GtkWidget *event_list_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
diff --git a/src/gtk-helpers/internal_libreport_gtk.h b/src/gtk-helpers/internal_libreport_gtk.h
index 092bfad..dd9ff87 100644
--- a/src/gtk-helpers/internal_libreport_gtk.h
+++ b/src/gtk-helpers/internal_libreport_gtk.h
@@ -27,19 +27,22 @@
extern "C" {
#endif
-extern bool g_keyring_available;
-
#define make_label_autowrap_on_resize libreport_make_label_autowrap_on_resize
void make_label_autowrap_on_resize(GtkLabel *label);
#define show_events_list_dialog libreport_show_events_list_dialog
void show_events_list_dialog(GtkWindow *parent);
-#define load_event_config_data_from_keyring libreport_load_event_config_data_from_keyring
-void load_event_config_data_from_keyring(void);
+#define is_event_config_user_storage_available libreport_is_event_config_user_storage_available
+bool is_event_config_user_storage_available();
+
+#define load_event_config_data_from_user_storage libreport_load_event_config_data_from_user_storage
+void load_event_config_data_from_user_storage(GHashTable *event_config_list);
-#define find_keyring_item_id_for_event libreport_find_keyring_item_id_for_event
-guint32 find_keyring_item_id_for_event(const char *event_name);
+#define save_event_config_data_to_user_storage libreport_save_event_config_data_to_user_storage
+void save_event_config_data_to_user_storage(const char *event_name,
+ const event_config_t *event_config,
+ bool store_password);
#define show_event_config_dialog libreport_show_event_config_dialog
int show_event_config_dialog(const char *event_name, GtkWindow *parent);
diff --git a/src/gtk-helpers/secrets.c b/src/gtk-helpers/secrets.c
new file mode 100644
index 0000000..29f3ee3
--- /dev/null
+++ b/src/gtk-helpers/secrets.c
@@ -0,0 +1,1050 @@
+/*
+ Copyright (C) 2012 ABRT Team
+ Copyright (C) 2012 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 "internal_libreport.h"
+#include "internal_libreport_gtk.h"
+
+#include <gio/gio.h>
+
+#define secrets_api "org.freedesktop.secrets"
+#define SECRETS_NAME_SPACE(interface) "org.freedesktop.Secret."interface
+
+/* label and alias have to be same because of ksecrets */
+#define SECRETS_COLLECTION_LABEL SECRETS_COLLECTION_ALIAS
+#define SECRETS_COLLECTION_ALIAS "default"
+
+#define SECRETS_SEARCH_ATTRIBUTE "libreportEventConfig"
+
+/* 5s timeout*/
+#define SECRETS_CALL_DEFAULT_TIMEOUT 5000
+
+/* Well known errors for which we have workarounds */
+#define NOT_IMPLEMENTED_READ_ALLIAS_ERROR_MESSAGE "GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: No such method 'ReadAlias' in interface 'org.freedesktop.Secret.Service' at object path '/org/freedesktop/secrets' (signature 's')"
+#define INVALID_PROPERTIES_ARGUMENTS "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Invalid properties argument"
+
+
+enum secrets_api_state
+{
+ /* intial state */
+ SBS_INITIAL = 0,
+ /* after opening a secrets service session (different than D-Bus session) */
+ SBS_CONNECTED,
+ /* secrets service is not available do not bother user anymore */
+ SBS_UNAVAILABLE,
+};
+
+struct secrets_object
+{
+ GDBusProxy *proxy;
+ /* http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfac... */
+ GDBusProxy *property_proxy;
+ const gchar *interface_name;
+};
+
+/* Glib DBus object org.freedesktop.secrets */
+GDBusConnection *g_connection;
+
+/* State of the service */
+enum secrets_api_state g_state;
+
+/* http://standards.freedesktop.org/secret-service/re01.html */
+/* xorg.freedesktop.Secret.Service interface object */
+struct secrets_object *g_service;
+
+/* http://standards.freedesktop.org/secret-service/ch06.html */
+/* xorg.freedesktop.Secret.Session interface object */
+GDBusProxy *g_session;
+
+/*******************************************************************************/
+/* struct secrets_object */
+/*******************************************************************************/
+
+static struct secrets_object *secrets_object_new_from_proxy(GDBusProxy *proxy)
+{
+ struct secrets_object *obj = xmalloc(sizeof(*obj));
+ obj->proxy = proxy;
+ obj->property_proxy = NULL;
+ obj->interface_name = g_dbus_proxy_get_interface_name(proxy);
+
+ return obj;
+}
+
+static struct secrets_object *secrets_object_new(const gchar *path,
+ const gchar *interface,
+ GError **error)
+{
+ GDBusProxy *const proxy =
+ g_dbus_proxy_new_sync(g_connection, G_DBUS_PROXY_FLAGS_NONE, NULL,
+ secrets_api, path, interface, NULL, error);
+
+ if (*error)
+ return NULL;
+
+ return secrets_object_new_from_proxy(proxy);
+}
+
+static void secrets_object_change_path(struct secrets_object *obj,
+ const gchar *path,
+ GError **error)
+{
+ GDBusProxy *const proxy =
+ g_dbus_proxy_new_sync(g_connection, G_DBUS_PROXY_FLAGS_NONE, NULL,
+ secrets_api, path, obj->interface_name, NULL, error);
+
+ if (!*error)
+ {
+ if (obj->proxy)
+ g_object_unref(obj->proxy);
+
+ obj->proxy = proxy;
+ obj->interface_name = g_dbus_proxy_get_interface_name(proxy);
+ }
+}
+
+static void secrets_object_delete(struct secrets_object *obj)
+{
+ if (obj->proxy)
+ g_object_unref(obj->proxy);
+
+ if (obj->property_proxy)
+ g_object_unref(obj->property_proxy);
+
+ free(obj);
+}
+
+static GDBusProxy *secrets_object_get_properties_proxy(struct secrets_object *obj, GError **error)
+{
+ if (!obj->property_proxy)
+ {
+ obj->property_proxy
+ = g_dbus_proxy_new_sync(g_connection, G_DBUS_PROXY_FLAGS_NONE, NULL,
+ secrets_api, g_dbus_proxy_get_object_path(obj->proxy),
+ "org.freedesktop.DBus.Properties", NULL, error);
+
+ if (*error)
+ return NULL;
+ }
+
+ return obj->property_proxy;
+}
+
+
+static void secrets_object_set_dbus_property(struct secrets_object *obj,
+ const char *property,
+ GVariant *value,
+ GError **error)
+{
+ GDBusProxy *properties = secrets_object_get_properties_proxy(obj, error);
+
+ if (!*error)
+ {
+ GVariant *const ret
+ = g_dbus_proxy_call_sync(properties, "Set",
+ g_variant_new("(ssv)", obj->interface_name, property, value),
+ G_DBUS_PROXY_FLAGS_NONE, SECRETS_CALL_DEFAULT_TIMEOUT, NULL,
+ error);
+
+ if (ret)
+ g_variant_unref(ret);
+ }
+}
+
+/*******************************************************************************/
+/* struct secrets_api */
+/*******************************************************************************/
+
+static void secrets_api_set_unavailable();
+
+static void secrets_api_error(GError* error, const char *comment)
+{
+ if (comment)
+ {
+ log("%s : %s\n", comment, error->message);
+ }
+ else
+ {
+ log("%s\n", error->message);
+ }
+
+ g_error_free(error);
+ secrets_api_set_unavailable();
+}
+
+/*
+ * Initialize all global variables
+ */
+static enum secrets_api_state secrets_api_connect()
+{
+ GError *error = NULL;
+
+ g_connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (g_connection == NULL)
+ {
+ secrets_api_error(error, "failed to open connection to D-Bus session");
+ return SBS_UNAVAILABLE;
+ }
+
+ g_service = secrets_object_new("/org/freedesktop/secrets", SECRETS_NAME_SPACE("Service"), &error);
+ if (error)
+ {
+ secrets_api_error(error, "failed to create service");
+ return SBS_UNAVAILABLE;
+ }
+
+ GVariant *const alg_in = g_variant_new_string("");
+ GVariant *const args = g_variant_new("(sv)", "plain", alg_in);
+ GVariant *const ret =
+ g_dbus_proxy_call_sync(g_service->proxy, "OpenSession", args, G_DBUS_PROXY_FLAGS_NONE,
+ SECRETS_CALL_DEFAULT_TIMEOUT, NULL, &error);
+
+ g_variant_unref(alg_in);
+
+ if (error)
+ {
+ secrets_api_error(error, "cannot open session Secrets session");
+ return SBS_UNAVAILABLE;
+ }
+
+ GVariant *const var = g_variant_get_child_value(ret, 1);
+ g_variant_unref(ret);
+
+ const gchar *const session_path = g_variant_get_string(var, NULL);
+ g_session = g_dbus_proxy_new_sync(g_connection, G_DBUS_PROXY_FLAGS_NONE, NULL, secrets_api,
+ session_path, SECRETS_NAME_SPACE("Session"), NULL, &error);
+
+ g_variant_unref(var);
+
+ if (error)
+ {
+ secrets_api_error(error, "cannot create Proxy for Secrets session");
+ return SBS_UNAVAILABLE;
+ }
+
+ g_state = SBS_CONNECTED;
+
+ return g_state;
+}
+
+static void secrets_api_init()
+{
+ if (g_state == SBS_INITIAL)
+ secrets_api_connect();
+}
+
+static void secrets_api_close()
+{
+ if (g_state == SBS_INITIAL || g_state == SBS_UNAVAILABLE)
+ return;
+
+ GError *error = NULL;
+ GVariant *const ret =
+ g_dbus_proxy_call_sync(g_session, "Close", g_variant_new("()"), G_DBUS_PROXY_FLAGS_NONE,
+ SECRETS_CALL_DEFAULT_TIMEOUT, NULL, &error);
+
+ if (error)
+ {
+ VERB1 log("error on closing Secrets session : %s\n", error->message);
+ g_error_free(error);
+ }
+ else
+ g_variant_unref(ret);
+
+ g_object_unref(g_session);
+
+ secrets_object_delete(g_service);
+
+ g_object_unref(g_connection);
+
+ g_connection = NULL;
+ g_service = NULL;
+ g_session = NULL;
+ g_state = SBS_INITIAL;
+}
+
+static void secrets_api_set_unavailable()
+{
+ secrets_api_close();
+
+ g_state = SBS_UNAVAILABLE;
+}
+
+static int is_secrets_api_available()
+{
+ return g_state != SBS_UNAVAILABLE;
+}
+
+/*******************************************************************************/
+/* org.freedesktop.Secret.Prompt */
+/* http://standards.freedesktop.org/secret-service/re05.html */
+/*******************************************************************************/
+
+struct prompt_source
+{
+ GSource source;
+ GDBusProxy *prompt_proxy;
+ GError *error;
+ GMainLoop *loop;
+};
+
+struct prompt_call_back_args
+{
+ gboolean dismissed;
+ GVariant *result;
+ GMainLoop *loop;
+};
+
+static void prompt_quit_loop(GMainLoop* loop)
+{
+ if (g_main_loop_is_running(loop))
+ g_main_loop_quit(loop);
+}
+
+static gboolean prompt_g_idle_prepare(GSource *source_data, gint *timeout)
+{
+ return TRUE;
+}
+
+static gboolean prompt_g_idle_check(GSource *source)
+{
+ return TRUE;
+}
+
+static gboolean prompt_g_idle_dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
+{
+ struct prompt_source *const prompt_source = (struct prompt_source *)source;
+ GDBusProxy *const prompt_proxy = prompt_source->prompt_proxy;
+ GVariant *const args = g_variant_new("(s)","");
+ GVariant *const ret =
+ g_dbus_proxy_call_sync(prompt_proxy, "Prompt", args, G_DBUS_PROXY_FLAGS_NONE,
+ SECRETS_CALL_DEFAULT_TIMEOUT, NULL, &prompt_source->error);
+
+ if (prompt_source->error)
+ prompt_quit_loop(prompt_source->loop);
+ else
+ g_variant_unref(ret);
+
+ /* always return FALSE, it means that this dispatch is to be removed from idle loop */
+ return FALSE;
+}
+
+static void prompt_call_back(GDBusConnection *connection, const gchar *sender_name,
+ const gchar *object_path, const gchar *interface_name,
+ const gchar *signal_name, GVariant *parameters,
+ gpointer user_data)
+{
+ struct prompt_call_back_args *const args = (struct prompt_call_back_args *)user_data;
+ GVariant *const result = NULL;
+
+ g_variant_get(parameters, "(bv)", &args->dismissed, &result);
+
+ if (!args->dismissed)
+ args->result = result;
+ else
+ g_variant_unref(result);
+
+ prompt_quit_loop(args->loop);
+}
+
+static bool is_prompt_required(const char *prompt_path)
+{
+ return prompt_path && !(prompt_path[0] == '/' && prompt_path[1] == '\0');
+}
+
+static bool secrets_prompt(const char *prompt_path,
+ GVariant **result,
+ GError **error)
+{
+ static GSourceFuncs idle_funcs = {
+ .prepare = prompt_g_idle_prepare,
+ .check = prompt_g_idle_check,
+ .dispatch = prompt_g_idle_dispatch,
+ .finalize = NULL,
+ .closure_callback = NULL,
+ .closure_marshal = NULL,
+ };
+
+ GDBusProxy *const prompt_proxy =
+ g_dbus_proxy_new_sync(g_connection, G_DBUS_PROXY_FLAGS_NONE, NULL, secrets_api,
+ prompt_path, SECRETS_NAME_SPACE("Prompt"), NULL, error);
+
+ if (*error)
+ return true;
+
+ GMainContext *context = g_main_context_get_thread_default();
+ GMainLoop *loop = g_main_loop_new(context, FALSE);
+
+ struct prompt_call_back_args args = {
+ .result=NULL,
+ .dismissed=FALSE,
+ .loop=loop
+ };
+
+ const guint signal_ret =
+ g_dbus_connection_signal_subscribe(g_connection, secrets_api, SECRETS_NAME_SPACE("Prompt"),
+ "Completed", prompt_path, NULL, G_DBUS_SIGNAL_FLAGS_NONE,
+ prompt_call_back, &args, NULL);
+
+ /* prepare prompt source */
+ struct prompt_source *const prompt_source =
+ (struct prompt_source*)g_source_new(&idle_funcs, sizeof(*prompt_source));
+
+ prompt_source->prompt_proxy = prompt_proxy;
+ prompt_source->error = NULL;
+
+ g_source_attach((GSource*)prompt_source, context);
+
+ g_main_loop_run(loop);
+
+ /* save error */
+ *error = prompt_source->error;
+ if (args.result)
+ {
+ if (result)
+ *result = args.result;
+ else
+ g_variant_unref(args.result);
+ }
+
+ /* destroy prompt source */
+ g_object_unref(prompt_proxy);
+ g_source_destroy((GSource*)prompt_source);
+
+ g_dbus_connection_signal_unsubscribe(g_connection, signal_ret);
+ g_main_loop_unref(loop);
+
+ return args.dismissed;
+}
+
+/*******************************************************************************/
+/* org.freedesktop.Secret.Service */
+/* http://standards.freedesktop.org/secret-service/re01.html */
+/*******************************************************************************/
+
+static bool secrets_service_unlock(const gchar *const *objects,
+ GVariant **unlocked,
+ GError **error)
+{
+ bool dismissed = false;
+ GVariant *const args = g_variant_new("(^ao)", objects);
+ GVariant *const ret =
+ g_dbus_proxy_call_sync(g_service->proxy, "Unlock", args, G_DBUS_PROXY_FLAGS_NONE,
+ SECRETS_CALL_DEFAULT_TIMEOUT, NULL, error);
+
+ if (*error)
+ return dismissed;
+
+ gchar *prompt;
+ GVariant *result;
+ g_variant_get(ret, "(@aoo)", &result, &prompt);
+
+ if (is_prompt_required(prompt))
+ {
+ g_variant_unref(result);
+ result = NULL;
+ dismissed = secrets_prompt(prompt, &result, error);
+ }
+
+ if (result)
+ { /* in case of error or dismissed the result variable has NULL value */
+ if (unlocked)
+ *unlocked = result;
+ else
+ g_variant_unref(result);
+ }
+
+ g_variant_unref(ret);
+
+ return dismissed;
+}
+
+static bool secrets_service_unlock_object(struct secrets_object *obj,
+ GError **error)
+{
+ const gchar *const locked[] = {
+ g_dbus_proxy_get_object_path(obj->proxy),
+ NULL
+ };
+
+ GVariant *result = NULL;
+ bool dismissed = secrets_service_unlock(locked, &result, error);
+
+ if (!*error && !dismissed && result)
+ {
+ gsize length;
+ const gchar *const *unlocked = g_variant_get_objv(result, &length);
+
+ /* ksecrets doesn't set dismissed correctly */
+ if (length)
+ {
+ secrets_object_change_path(obj, *unlocked, error);
+ g_free((gpointer)unlocked);
+ }
+ else
+ dismissed = true;
+
+ g_variant_unref(result);
+ }
+
+ return dismissed;
+}
+
+/*
+ * KSecretsService doesn't implement ReadAlias
+ */
+static struct secrets_object *secrets_service_find_collection(const char *alias, GError **error)
+{
+ /* do not unref this proxy, the proxy is owned by secrets object */
+ GDBusProxy *const properties = secrets_object_get_properties_proxy(g_service, error);
+
+ if (*error)
+ return NULL;
+
+ /* cannot use g_dbus_proxy_get_cached_property() because of */
+ /* errors in case when secrets service was restarted during session */
+ GVariant *const prop =
+ g_dbus_proxy_call_sync(properties, "Get",
+ g_variant_new("(ss)", SECRETS_NAME_SPACE("Service"), "Collections"),
+ G_DBUS_PROXY_FLAGS_NONE, SECRETS_CALL_DEFAULT_TIMEOUT, NULL, error);
+
+ if (*error)
+ return NULL;
+
+ struct secrets_object *collection = NULL;
+
+ /* Returned value */
+ GVariant *const value = g_variant_get_child_value(prop,0);
+ /* Contentet of returned value */
+ GVariant *const list = g_variant_get_child_value(value,0);
+
+ GVariantIter *iter;
+ char *path;
+ g_variant_get(g_variant_get_child_value(value,0), "ao", &iter);
+
+ while (g_variant_iter_loop(iter, "o", &path))
+ {
+ GDBusProxy *const collection_proxy =
+ g_dbus_proxy_new_sync(g_connection, G_DBUS_PROXY_FLAGS_NONE, NULL, secrets_api,
+ path, SECRETS_NAME_SPACE("Collection"), NULL, error);
+
+ if (*error)
+ break;
+
+ GVariant *const lbl_var = g_dbus_proxy_get_cached_property(collection_proxy,
+ "Label");
+
+ const gchar *const label = g_variant_get_string(lbl_var, NULL);
+ const int found = label && (strcmp(label, alias) == 0);
+
+ /* found is introduced because of break if collecion is found */
+ /* and I want to write the following statement just once */
+ /* the label variable is invalid after the following statement */
+ g_variant_unref(lbl_var);
+
+ if (found)
+ {
+ collection = secrets_object_new_from_proxy(collection_proxy);
+ break;
+ }
+
+ g_object_unref(collection_proxy);
+ }
+
+ g_variant_iter_free(iter);
+ g_variant_unref(list);
+ g_variant_unref(value);
+ g_variant_unref(prop);
+
+ return collection;
+}
+
+static struct secrets_object *secrets_service_read_allias(const char *alias, GError **error)
+{
+ struct secrets_object *collection = NULL;
+ GVariant *const ret =
+ g_dbus_proxy_call_sync(g_service->proxy, "ReadAlias", g_variant_new("(s)", alias),
+ G_DBUS_PROXY_FLAGS_NONE, SECRETS_CALL_DEFAULT_TIMEOUT,
+ NULL, error);
+
+ if (!*error)
+ {
+ GVariant *const obj_path = g_variant_get_child_value(ret, 0);
+ const gchar* const coll_path = g_variant_get_string(obj_path, NULL);
+
+ if (strcmp(coll_path,"/"))
+ collection = secrets_object_new(coll_path,
+ SECRETS_NAME_SPACE("Collection"),
+ error);
+
+ g_variant_unref(obj_path);
+ g_variant_unref(ret);
+ }
+ else if (!strcmp((*error)->message, NOT_IMPLEMENTED_READ_ALLIAS_ERROR_MESSAGE))
+ {
+ /* ksecrets doesn't implement ReadAlias, thus search by label is performed */
+ VERB1 log("ReadAlias failed, going to search on the client side : %s", (*error)->message);
+ g_error_free(*error);
+ *error = NULL;
+ collection = secrets_service_find_collection(alias, error);
+ }
+
+ return collection;
+}
+
+static struct secrets_object *secrets_service_create_collection(const gchar *label,
+ const gchar *alias,
+ bool *dismissed,
+ GError **error)
+{
+ GVariantBuilder *const prop_builder = g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);
+
+ g_variant_builder_add(prop_builder, "{sv}", "org.freedesktop.Secret.Collection.Label",
+ g_variant_new_string(label) );
+
+ GVariant *const call_args =
+ g_variant_new("(@a{sv}s)", g_variant_builder_end(prop_builder), alias);
+
+ GVariant *const ret =
+ g_dbus_proxy_call_sync(g_service->proxy, "CreateCollection", call_args, G_DBUS_PROXY_FLAGS_NONE,
+ SECRETS_CALL_DEFAULT_TIMEOUT, NULL, error);
+
+ *dismissed = false;
+
+ if (*error)
+ return NULL;
+
+ gchar *prompt;
+ GVariant *collection_var;
+ g_variant_get(ret, "(@oo)", &collection_var, &prompt);
+
+ if (is_prompt_required(prompt))
+ {
+ g_variant_unref(collection_var);
+ collection_var = NULL;
+
+ *dismissed = secrets_prompt(prompt, &collection_var, error);
+ }
+
+ struct secrets_object *collection = NULL;
+ if (collection_var)
+ {
+ gsize length;
+ const gchar *const coll_path = g_variant_get_string(collection_var, &length);
+
+ /* ksecrets doens't properly set dismissed */
+ /* if dismissmised is false and result is empty */
+ /* then ksecrets didn't properly set dismissed */
+ if (length)
+ collection = secrets_object_new(coll_path, SECRETS_NAME_SPACE("Collection"), error);
+ else
+ *dismissed = true;
+
+ g_variant_unref(collection_var);
+ }
+
+ g_variant_unref(ret);
+
+ return collection;
+}
+
+/*******************************************************************************/
+/* org.freedesktop.Secret.Collection */
+/* http://standards.freedesktop.org/secret-service/re02.html */
+/*******************************************************************************/
+
+static GVariant *create_item_call_args(const char *event_name,
+ GVariant *attributes,
+ GDBusProxy *session,
+ bool full_property_name)
+{
+ /* ksecrets accepts properties without namespace */
+ /* gnome-keyring accepts only with namespace */
+ const char *const lbl_name = full_property_name ? SECRETS_NAME_SPACE("Item.Label")
+ : "Label";
+ const char *const att_name = full_property_name ? SECRETS_NAME_SPACE("Item.Attributes")
+ : "Attributes";
+
+ GVariantBuilder *const prop_builder = g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);
+
+ g_variant_builder_add(prop_builder, "{sv}", lbl_name, g_variant_new_string(event_name));
+ g_variant_builder_add(prop_builder, "{sv}", att_name, attributes);
+
+ GVariantBuilder *const param_builder = g_variant_builder_new(G_VARIANT_TYPE("ay"));
+ GVariantBuilder *const value_builder = g_variant_builder_new(G_VARIANT_TYPE("ay"));
+
+ GVariant *const secret = g_variant_new("(o@ay@ays)",
+ g_dbus_proxy_get_object_path(session),
+ g_variant_builder_end(param_builder),
+ g_variant_builder_end(value_builder),
+ "text/plain, charset=utf8");
+
+ return g_variant_new("(@a{sv}@(oayays)b)", g_variant_builder_end(prop_builder), secret, FALSE);
+}
+
+static void secrets_collection_create_text_item(struct secrets_object *collection,
+ const char *event_name,
+ GVariant *attributes,
+ GError **error)
+{
+ GVariant *call_args, *ret;
+
+ /* ksecrets silently drops properties with full names */
+ /* gnome-keyring returns an error if a property key is not full property name */
+ /* the first iteration sends property names without namespace */
+ /* if NO error was returned service is ksecrets and everything is ok */
+ /* if error was returned service is gnome-keyring and and the second iteration */
+ /* must be performed */
+ /* the second iteration sends property names with namespace */
+ static bool const options[] = {FALSE, TRUE};
+ for (size_t choice = 0; choice < sizeof(options)/sizeof(*options); ++choice)
+ {
+ if (*error)
+ { /* this code is here because I want to return an error */
+ /* it cannot be on the end of the loop because I need unfreed error after the last iteration */
+ g_error_free(*error);
+ *error = NULL;
+ }
+
+ call_args = create_item_call_args(event_name, g_variant_ref(attributes),
+ g_session, options[choice]);
+
+ ret = g_dbus_proxy_call_sync(collection->proxy, "CreateItem", call_args,
+ G_DBUS_PROXY_FLAGS_NONE, SECRETS_CALL_DEFAULT_TIMEOUT,
+ NULL, error);
+
+ if (*error)
+ {
+ if (!strcmp(INVALID_PROPERTIES_ARGUMENTS, (*error)->message))
+ {
+ VERB2 log("CreateItem failed, going to use other properties names : %s", (*error)->message);
+ continue;
+ }
+ else
+ /* if the error wasn't about invalid properties we have an another problem */
+ break;
+ }
+
+ gchar *prompt, *item;
+ g_variant_get(ret, "(oo)", &item, &prompt);
+
+ if (is_prompt_required(prompt))
+ {
+ const bool dismissed = secrets_prompt(prompt, NULL, error);
+ if (*error || dismissed)
+ secrets_api_set_unavailable();
+
+ g_variant_unref(ret);
+ break;
+ }
+
+ g_variant_unref(ret);
+ }
+
+ g_variant_unref(attributes);
+}
+
+/*
+ * Performs org.freedesktop.Secret.Collection.SearchItems and returns the first
+ * item from result. A found item can be possibly locked.
+ */
+static struct secrets_object *secrets_collection_search_one_item(
+ const struct secrets_object *collection,
+ GVariant *search_attrs,
+ GError **error)
+{
+ struct secrets_object *item = NULL;
+ GVariant *const value =
+ g_dbus_proxy_call_sync(collection->proxy, "SearchItems", g_variant_new("(@a{ss})", search_attrs),
+ G_DBUS_PROXY_FLAGS_NONE, SECRETS_CALL_DEFAULT_TIMEOUT, NULL, error);
+
+ if (*error)
+ return NULL;
+
+ /* gnome-keyring returns (unlocked,locked) */
+ /* ksecretsservice returns (unlocked) */
+ /* all childs has to be taken into account */
+ const gsize n_results = g_variant_n_children(value);
+ for(gsize child = 0; !item && child < n_results; ++child)
+ {
+ if (*error)
+ {
+ VERB1 log("search items result error : %s", (*error)->message);
+ free(*error);
+ *error = NULL;
+ }
+
+ GVariant *const paths = g_variant_get_child_value(value, 0);
+ const gchar *const *item_paths = g_variant_get_objv(paths, NULL);
+
+ /* the first found object path is to be returned */
+ if (*item_paths)
+ item = secrets_object_new(*item_paths, SECRETS_NAME_SPACE("Item"), error);
+
+ g_free((gpointer)item_paths);
+ g_variant_unref(paths);
+ }
+
+ g_variant_unref(value);
+
+ return item;
+}
+
+/*******************************************************************************/
+/* utility functions */
+/*******************************************************************************/
+
+static struct secrets_object *get_default_collection(GError **error)
+{
+ bool dismissed = false;
+ struct secrets_object *collection =
+ secrets_service_read_allias(SECRETS_COLLECTION_ALIAS, error);
+
+ if (!*error && collection)
+ {
+ dismissed = secrets_service_unlock_object(collection, error);
+
+ if (*error || dismissed)
+ {
+ secrets_object_delete(collection);
+ collection = NULL;
+ }
+ }
+
+ if (dismissed)
+ /* A user canceled an unlock prompt */
+ /* Secrets BUS is set to unvailable in order to not bother a user */
+ /* with an another unlock prompt */
+ secrets_api_set_unavailable();
+
+ /* no matter if error occured or prompt was dismissed */
+ /* in these cases the collection holds NULL */
+ return collection;
+}
+
+static void load_event_options_from_item(GList *options,
+ struct secrets_object *item)
+{
+ GVariant *const attributes =
+ g_dbus_proxy_get_cached_property(item->proxy, "Attributes");
+
+ GVariantIter *iter;
+ g_variant_get(attributes, "a{ss}", &iter);
+
+ char *name, *value;
+ while (g_variant_iter_loop(iter, "{ss}", &name, &value))
+ {
+ event_option_t *const option = get_event_option_from_list(name, options);
+ if (option)
+ {
+ free(option->eo_value);
+ option->eo_value = xstrdup(value);
+ VERB2 log("loaded event option : '%s => %s'", name, option->eo_value);
+ }
+ }
+
+ g_variant_unref(attributes);
+}
+
+static GVariant *create_attributes_from_options(GList *options,
+ const char *event_name,
+ bool store_passwords)
+{
+ GVariantBuilder *const attr_builder = g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);
+
+ for (GList *iter = g_list_first(options); iter; iter = g_list_next(iter))
+ { /* we store data in the search attributes */
+ event_option_t *const op = (event_option_t *)iter->data;
+ if (op->eo_type != OPTION_TYPE_PASSWORD || store_passwords)
+ g_variant_builder_add(attr_builder, "{ss}", op->eo_name, op->eo_value);
+ }
+
+ /* and finaly, add extra attribute used for searching */
+ g_variant_builder_add(attr_builder, "{ss}", SECRETS_SEARCH_ATTRIBUTE, event_name);
+
+ return g_variant_builder_end(attr_builder);
+}
+
+static struct secrets_object *find_item_by_event_name(const struct secrets_object *collection,
+ const char* event_name,
+ GError **error)
+{
+ GVariantBuilder *const attrs = g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);
+
+ g_variant_builder_add(attrs, "{ss}", SECRETS_SEARCH_ATTRIBUTE, event_name);
+
+ return secrets_collection_search_one_item(collection, g_variant_builder_end(attrs), error);
+}
+
+static void load_settings(struct secrets_object *collection, const char *event_name, event_config_t *ec)
+{
+ /* Locked property is not supported by ksecrets */
+ /* thus we have to perform Unlock on each call of load settings */
+ /* because a collection can be locked by the service at anytime */
+ GError *error = NULL;
+ const bool dismissed = secrets_service_unlock_object(collection, &error);
+
+ if (!error && !dismissed)
+ {
+ struct secrets_object *item = find_item_by_event_name(collection, event_name, &error);
+
+ if (!error && item)
+ {
+ VERB2 log("loading event config : '%s'", event_name);
+ load_event_options_from_item(ec->options, item);
+ secrets_object_delete(item);
+ }
+ }
+
+ if (dismissed)
+ secrets_api_set_unavailable();
+
+ if (error)
+ secrets_api_error(error, NULL);
+}
+
+static void load_event_config(gpointer key, gpointer value, gpointer user_data)
+{
+ char *const event_name = (char*)key;
+ event_config_t *const ec = (event_config_t *)value;
+ struct secrets_object *collection = (struct secrets_object *)user_data;
+
+ if (is_secrets_api_available())
+ load_settings(collection, event_name, ec);
+}
+
+static void save_options(struct secrets_object *collection,
+ const char* event_name,
+ GList *options,
+ bool store_passwords,
+ GError **error)
+{
+ struct secrets_object *item;
+ item = find_item_by_event_name(collection, event_name, error);
+
+ if (*error)
+ return;
+
+ GVariant *const attributes = create_attributes_from_options(options,
+ event_name,
+ store_passwords);
+
+ if (item)
+ { /* item exists */
+ VERB2 log("updating event config : '%s'", event_name);
+ secrets_object_set_dbus_property(item, "Attributes", attributes, error);
+ secrets_object_delete(item);
+ }
+ else /* create a new item */
+ {
+ VERB2 log("creating event config : '%s'", event_name);
+ secrets_collection_create_text_item(collection, event_name, attributes, error);
+ }
+}
+
+static void save_event_config(const char *event_name,
+ GList *options,
+ bool store_passwords)
+{
+ bool dismissed = false;
+ GError *error = NULL;
+ struct secrets_object *collection = get_default_collection(&error);
+
+ if (error)
+ goto save_event_config_end;
+
+ if (!collection)
+ {
+ VERB2 log("going to create a new default collection '"SECRETS_COLLECTION_LABEL"'"
+ " with alias '"SECRETS_COLLECTION_ALIAS"'");
+
+ collection =
+ secrets_service_create_collection(SECRETS_COLLECTION_LABEL, SECRETS_COLLECTION_ALIAS,
+ &dismissed, &error);
+
+ if (error || dismissed)
+ goto save_event_config_end;
+ }
+
+ save_options(collection, event_name, options, store_passwords, &error);
+
+save_event_config_end:
+ if (dismissed)
+ secrets_api_set_unavailable();
+
+ if (error)
+ secrets_api_error(error, NULL);
+}
+
+/******************************************************************************/
+/* Public interface */
+/******************************************************************************/
+
+bool is_event_config_user_storage_available()
+{
+ return is_secrets_api_available();
+}
+
+/*
+ * Loads event config options for passed events
+ *
+ * @param event_config_list Events configs
+ */
+void load_event_config_data_from_user_storage(GHashTable *event_config_list)
+{
+ secrets_api_init();
+
+ if (is_secrets_api_available())
+ {
+ GError *error = NULL;
+ struct secrets_object *collection = get_default_collection(&error);
+
+ if (collection)
+ {
+ g_hash_table_foreach(event_config_list, &load_event_config, collection);
+ secrets_object_delete(collection);
+ }
+
+ if (error)
+ secrets_api_error(error, "D-Bus secrets API cannot provide default collection");
+ }
+ else
+ {
+ VERB1 log("cannot load user's configuration due to unavailability of D-Bus secrets API");
+ }
+}
+
+/*
+ * Saves an event_config options to some kind of session storage.
+ *
+ * @param event_name Lookup key
+ * @param event_config Event data
+ * @param store_passwords If TRUE stores options with passwords, otherwise skips them
+ */
+void save_event_config_data_to_user_storage(const char *event_name,
+ const event_config_t *event_config,
+ bool store_passwords)
+{
+ secrets_api_init();
+
+ if (is_secrets_api_available())
+ save_event_config(event_name, event_config->options, store_passwords);
+ else
+ {
+ VERB1 log("cannot save user's configuration due to unavailability of D-Bus secrets API");
+ }
+}
diff --git a/src/gui-wizard-gtk/main.c b/src/gui-wizard-gtk/main.c
index 05d01e9..e9632c3 100644
--- a/src/gui-wizard-gtk/main.c
+++ b/src/gui-wizard-gtk/main.c
@@ -140,7 +140,7 @@ int main(int argc, char **argv)
/* load /etc/abrt/events/foo.{conf,xml} stuff
and $XDG_CACHE_HOME/abrt/events/foo.conf */
load_event_config_data();
- load_event_config_data_from_keyring();
+ load_event_config_data_from_user_storage(g_event_config_list);
load_user_settings("report-gtk");
reload_problem_data_from_dump_dir();
--
1.7.7.6
11 years, 9 months
[PATCH 0/7] track#554 : Unify of GTK and CLI reporters implementation
by Jakub Filak
This set of patches unify implementation of GTK and CLI reporters. In the first
step, run event on dir API is enriched of callbacks which allows to handle
event messages from various frontends. The following step moves the event
configuration checking from report-gtk to libreport. Next step introduces run
event list API which hides an implementation details of processing of events in
reporter applications. Next step introduces asynchronous API for run event
list. The asynchronous run event list is implement with pthreads. The lasts
steps simply uses newly introduced functions in report-gtk and report-cli.
Jakub Filak (7):
track#554 : run event on rewritten with callbacks
track#554 : add validate_event_config used byt validate_event to
refine complexity
track#554 : move event configuration checking to common lib
track#554 : add API for runnig of lists of events
track#554 : use run event list API in report-cli
track#554 : run event list in thread API
track#554 : reworked report-gtk with usage of run event list in
thread
libreport.spec.in | 3 +
po/POTFILES.in | 1 +
src/cli/cli.c | 160 ++--
src/gui-wizard-gtk/main.c | 24 +-
src/gui-wizard-gtk/wizard.c | 1544 +++++++++++++++--------------------
src/gui-wizard-gtk/wizard.glade | 10 +-
src/gui-wizard-gtk/wizard.h | 2 +
src/include/Makefile.am | 5 +-
src/include/event_config.h | 1 +
src/include/event_usability.h | 59 ++
src/include/run_event.h | 134 ++-
src/include/run_event_list.h | 209 +++++
src/include/run_event_list_thread.h | 174 ++++
src/lib/Makefile.am | 6 +-
src/lib/event_config.c | 12 +-
src/lib/event_usability.c | 201 +++++
src/lib/run_event.c | 440 ++++++++--
src/lib/run_event_list.c | 618 ++++++++++++++
src/lib/run_event_list_thread.c | 284 +++++++
19 files changed, 2836 insertions(+), 1051 deletions(-)
create mode 100644 src/include/event_usability.h
create mode 100644 src/include/run_event_list.h
create mode 100644 src/include/run_event_list_thread.h
create mode 100644 src/lib/event_usability.c
create mode 100644 src/lib/run_event_list.c
create mode 100644 src/lib/run_event_list_thread.c
--
1.7.10.2
11 years, 9 months
[PATCH 1/2] report-gtk : move the warnings are from a page to the main report window
by Jakub Filak
Signed-off-by: Jakub Filak <jfilak(a)redhat.com>
---
src/gui-wizard-gtk/wizard.c | 31 ++++++++++++--
src/gui-wizard-gtk/wizard.glade | 89 +++------------------------------------
2 files changed, 33 insertions(+), 87 deletions(-)
diff --git a/src/gui-wizard-gtk/wizard.c b/src/gui-wizard-gtk/wizard.c
index 30c25ee..713e3d2 100644
--- a/src/gui-wizard-gtk/wizard.c
+++ b/src/gui-wizard-gtk/wizard.c
@@ -2455,9 +2455,7 @@ static void add_pages(void)
g_eb_comment = GTK_EVENT_BOX( gtk_builder_get_object(g_builder, "eb_comment"));
g_cb_no_comment = GTK_CHECK_BUTTON( gtk_builder_get_object(g_builder, "cb_no_comment"));
g_tv_details = GTK_TREE_VIEW( gtk_builder_get_object(g_builder, "tv_details"));
- g_box_warning_labels = GTK_BOX( gtk_builder_get_object(g_builder, "box_warning_labels"));
g_tb_approve_bt = GTK_TOGGLE_BUTTON(gtk_builder_get_object(g_builder, "cb_approve_bt"));
- g_widget_warnings_area = GTK_WIDGET( gtk_builder_get_object(g_builder, "box_warning_area"));
g_search_entry_bt = GTK_ENTRY( gtk_builder_get_object(g_builder, "entry_search_bt"));
g_container_details1 = GTK_CONTAINER( gtk_builder_get_object(g_builder, "container_details1"));
g_container_details2 = GTK_CONTAINER( gtk_builder_get_object(g_builder, "container_details2"));
@@ -2469,7 +2467,6 @@ static void add_pages(void)
g_spinner_event_log = GTK_SPINNER( gtk_builder_get_object(g_builder, "spinner_event_log"));
gtk_widget_set_no_show_all(GTK_WIDGET(g_spinner_event_log), true);
- gtk_widget_hide(g_widget_warnings_area);
gtk_widget_modify_font(GTK_WIDGET(g_tv_event_log), g_monospace_font);
fix_all_wrapped_labels(GTK_WIDGET(g_assistant));
@@ -2591,8 +2588,36 @@ void create_assistant(void)
gtk_box_pack_start(g_box_buttons, w, true, true, 5);
gtk_box_pack_start(g_box_buttons, g_btn_next, false, false, 5);
+ /* Warnings area widget definition start */
+ GtkWidget *alignment_top = gtk_alignment_new(0.5,0.5,1,1);
+ gtk_widget_set_visible(alignment_top, TRUE);
+
+ GtkWidget *alignment_bottom = gtk_alignment_new(0.5,0.5,1,1);
+ gtk_widget_set_visible(alignment_bottom, TRUE);
+
+ g_box_warning_labels = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
+ gtk_widget_set_visible(GTK_WIDGET(g_box_warning_labels), TRUE);
+
+ GtkBox *vbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
+ gtk_widget_set_visible(GTK_WIDGET(vbox), TRUE);
+ gtk_box_pack_start(vbox, alignment_top, false, false, 5);
+ gtk_box_pack_start(vbox, GTK_WIDGET(g_box_warning_labels), false, false, 5);
+ gtk_box_pack_start(vbox, alignment_bottom, false, false, 5);
+
+ GtkWidget *image = gtk_image_new_from_stock(GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_DIALOG);
+ gtk_widget_set_visible(image, TRUE);
+
+ g_widget_warnings_area = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
+ gtk_widget_set_visible(g_widget_warnings_area, FALSE);
+ gtk_widget_set_no_show_all(g_widget_warnings_area, TRUE);
+ gtk_box_pack_start(GTK_BOX(g_widget_warnings_area), image, false, false, 5);
+ gtk_box_pack_start(GTK_BOX(g_widget_warnings_area), GTK_WIDGET(vbox), false, false, 5);
+ /* Warnings area widget definition end */
+
g_box_assistant = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
gtk_box_pack_start(g_box_assistant, GTK_WIDGET(g_assistant), true, true, 5);
+
+ gtk_box_pack_start(g_box_assistant, GTK_WIDGET(g_widget_warnings_area), false, false, 5);
gtk_box_pack_start(g_box_assistant, GTK_WIDGET(g_box_buttons), false, false, 5);
gtk_widget_show_all(GTK_WIDGET(g_box_buttons));
diff --git a/src/gui-wizard-gtk/wizard.glade b/src/gui-wizard-gtk/wizard.glade
index 41924eb..782cf4a 100644
--- a/src/gui-wizard-gtk/wizard.glade
+++ b/src/gui-wizard-gtk/wizard.glade
@@ -159,6 +159,7 @@
<child>
<object class="GtkCheckButton" id="cb_no_comment">
<property name="label" translatable="yes">I don't know what caused this problem</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
@@ -204,6 +205,7 @@
<child>
<object class="GtkButton" id="button_cfg1">
<property name="label">gtk-preferences</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
@@ -302,90 +304,7 @@
<property name="can_focus">False</property>
<property name="border_width">5</property>
<child>
- <object class="GtkHBox" id="box_warning_area">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="no_show_all">True</property>
- <child>
- <object class="GtkImage" id="image1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="stock">gtk-dialog-warning</property>
- <property name="icon-size">6</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkVBox" id="vbox6">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkAlignment" id="alignment1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <placeholder/>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkVBox" id="box_warning_labels">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkAlignment" id="alignment2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <placeholder/>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="padding">1</property>
- <property name="position">2</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">False</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="padding">1</property>
- <property name="position">0</property>
- </packing>
+ <placeholder/>
</child>
<child>
<object class="GtkVBox" id="vbox5">
@@ -553,6 +472,7 @@
<child>
<object class="GtkButton" id="btn_add_file">
<property name="label" translatable="yes">Attach a file</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
@@ -577,6 +497,7 @@
<child>
<object class="GtkCheckButton" id="cb_approve_bt">
<property name="label" translatable="yes">I reviewed the data and _agree with submitting it</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
--
1.7.10.2
11 years, 9 months
[PATCH 1/4] report_problem_in_dir() never uses report-only option (always perform analysis)
by Jakub Filak
Signed-off-by: Jakub Filak <jfilak(a)redhat.com>
---
src/include/report.h | 5 ++++-
src/lib/report.c | 8 +-------
src/report-python/reportmodule.c | 3 ++-
3 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/src/include/report.h b/src/include/report.h
index 21557ac..9100c7c 100644
--- a/src/include/report.h
+++ b/src/include/report.h
@@ -31,7 +31,8 @@ enum {
LIBREPORT_GETPID = (1 << 1), /* return pid of child. Use with LIBREPORT_NOWAIT. */
/* Note: without LIBREPORT_GETPID, child will be detached */
/* (reparented to init) */
- LIBREPORT_ANALYZE = (1 << 2), /* run analyzers? */
+ /* Deprecated option. Configure your events properly. */
+ /* LIBREPORT_ANALYZE = (1 << 2);*/ /* run analyzers? */
/* ("run reporters" is always on, has no flag (for now?)) */
LIBREPORT_RELOAD_DATA = (1 << 5), /* reload problem data after run (needs WAIT) */
LIBREPORT_DEL_DIR = (1 << 6), /* delete directory after reporting (passes --delete to child) */
@@ -39,6 +40,8 @@ enum {
LIBREPORT_RUN_NEWT = (1 << 8), /* run 'report-newt' */
};
+
+
typedef enum {
LR_OK = 0,
LR_MISSING_ITEM = (1 << 1),
diff --git a/src/lib/report.c b/src/lib/report.c
index 09dbaac..7d7fbd6 100644
--- a/src/lib/report.c
+++ b/src/lib/report.c
@@ -44,8 +44,6 @@ int report_problem_in_dir(const char *dirname, int flags)
*pp++ = (char *)"report-gtk";
if (flags & LIBREPORT_DEL_DIR)
*pp++ = (char *)"--delete";
- if (!(flags & LIBREPORT_ANALYZE))
- *pp++ = (char *)"--report-only";
*pp++ = (char *)"--";
*pp++ = (char *)dirname;
*pp = NULL;
@@ -65,7 +63,6 @@ int report_problem_in_dir(const char *dirname, int flags)
*pp++ = (char *)"report-newt";
if (flags & LIBREPORT_DEL_DIR)
*pp++ = (char *)"--delete";
- *pp++ = (char *)"-o"; /* report only, newt can't analyze */
*pp++ = (char *)"--";
*pp++ = (char *)dirname;
*pp = NULL;
@@ -80,10 +77,7 @@ int report_problem_in_dir(const char *dirname, int flags)
*pp++ = (char *)"report-cli";
if (flags & LIBREPORT_DEL_DIR)
*pp++ = (char *)"--delete";
- if (!(flags & LIBREPORT_ANALYZE))
- *pp++ = (char *)"-ro"; /* only report */
- else
- *pp++ = (char *)"-r"; /* analyze and report */
+ *pp++ = (char *)"-r"; /* analyze and report */
*pp++ = (char *)"--";
*pp++ = (char *)dirname;
*pp = NULL;
diff --git a/src/report-python/reportmodule.c b/src/report-python/reportmodule.c
index 5fdb786..3f998b9 100644
--- a/src/report-python/reportmodule.c
+++ b/src/report-python/reportmodule.c
@@ -91,7 +91,8 @@ init_pyreport(void)
PyModule_AddObject(m, "LIBREPORT_NOWAIT" , Py_BuildValue("i", LIBREPORT_NOWAIT ));
PyModule_AddObject(m, "LIBREPORT_WAIT" , Py_BuildValue("i", LIBREPORT_WAIT ));
PyModule_AddObject(m, "LIBREPORT_GETPID" , Py_BuildValue("i", LIBREPORT_GETPID ));
- PyModule_AddObject(m, "LIBREPORT_ANALYZE" , Py_BuildValue("i", LIBREPORT_ANALYZE ));
+ /* Deprecated option. Configure your events properly. */
+ /* PyModule_AddObject(m, "LIBREPORT_ANALYZE" , Py_BuildValue("i", LIBREPORT_ANALYZE )); */
PyModule_AddObject(m, "LIBREPORT_RELOAD_DATA", Py_BuildValue("i", LIBREPORT_RELOAD_DATA));
PyModule_AddObject(m, "LIBREPORT_DEL_DIR" , Py_BuildValue("i", LIBREPORT_DEL_DIR ));
PyModule_AddObject(m, "LIBREPORT_RUN_CLI" , Py_BuildValue("i", LIBREPORT_RUN_CLI ));
--
1.7.10.2
11 years, 9 months
[PATCH] add missing dd_close() to save_dump_dir_from_problem_data()
by Jakub Filak
- this missing dd_close() causes trac#608
Signed-off-by: Jakub Filak <jfilak(a)redhat.com>
---
src/lib/create_dump_dir.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/lib/create_dump_dir.c b/src/lib/create_dump_dir.c
index 175cb61..2195a80 100644
--- a/src/lib/create_dump_dir.c
+++ b/src/lib/create_dump_dir.c
@@ -171,12 +171,15 @@ LibreportError save_dump_dir_from_problem_data(problem_data_t *problem_data, cha
VERB2 log("Renaming from'%s' to '%s'", dd->dd_dirname, new_path);
if (dd_rename(dd, new_path) != 0)
{
+ free(new_path);
+ dd_close(dd);
+
free(*problem_id);
*problem_id = NULL;
return LR_ERROR;
}
- free(new_path);
+ free(new_path);
dd_close(dd);
return LR_OK;
--
1.7.10.2
11 years, 9 months
[PATCH 1/2] abrtd: allow parallel runs of "post-create" events
by Denys Vlasenko
Before this patch, abrtd will be stuck waiting for "post-create to finish,
not accepting, for example, socket connections while it runs.
(On my system, "post-create" takes 1 minute because of broken sendmail).
This is the first part of the fix. It creates and hooks up a GIO
source made from "post-create" stdout to main loop, and consumes its output
concurrently with other tasks.
--
vda
diff -x '*.po' -d -urpN abrt.2/src/daemon/abrtd.c abrt.3/src/daemon/abrtd.c
--- abrt.2/src/daemon/abrtd.c 2012-06-29 12:32:07.457638564 +0200
+++ abrt.3/src/daemon/abrtd.c 2012-06-29 13:10:05.545587500 +0200
@@ -61,7 +61,7 @@ static int socket_client_count = 0;
/* Helpers */
-static pid_t spawn_event_handler_child(const char *dump_dir_name, const char *event_name, FILE **fpp)
+static pid_t spawn_event_handler_child(const char *dump_dir_name, const char *event_name, int *fdp)
{
char *args[6];
args[0] = (char *) LIBEXEC_DIR"/abrt-handle-event";
@@ -75,12 +75,12 @@ static pid_t spawn_event_handler_child(c
int flags = EXECFLG_INPUT_NUL | EXECFLG_OUTPUT | EXECFLG_QUIET | EXECFLG_ERR2OUT;
VERB1 flags &= ~EXECFLG_QUIET;
+ pipeout[0] = -1;
pid_t child = fork_execv_on_steroids(flags, args, pipeout,
- /*env_vec:*/ NULL, /*dir:*/ NULL, 0);
-
- *fpp = fdopen(pipeout[0], "r");
- if (!*fpp)
- die_out_of_memory();
+ /*env_vec:*/ NULL, /*dir:*/ NULL,
+ /*uid(unused):*/ 0);
+ if (fdp)
+ *fdp = pipeout[0];
return child;
}
@@ -280,55 +280,85 @@ static gboolean handle_signal_cb(GIOChan
}
-/* Inotify handler */
-
-static problem_data_t *load_problem_data(const char *dump_dir_name)
+/* Event-processing child output handler (such as "post-create" event) */
+struct event_processing_state
{
- struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0);
- if (!dd)
- return NULL;
-
- problem_data_t *problem_data = create_problem_data_from_dump_dir(dd);
- dd_close(dd);
-
- add_to_problem_data_ext(problem_data, CD_DUMPDIR, dump_dir_name,
- CD_FLAG_TXT + CD_FLAG_ISNOTEDITABLE);
-
- return problem_data;
+ pid_t child_pid;
+ int child_stdout_fd;
+ struct strbuf *cmd_output;
+ char *dirname;
+ char *dup_of_dir;
+};
+static struct event_processing_state *new_event_processing_state(void)
+{
+ struct event_processing_state *p = xzalloc(sizeof(*p));
+ p->child_pid = -1;
+ p->child_stdout_fd = -1;
+ p->cmd_output = strbuf_new();
+ return p;
+}
+static void free_event_processing_state(struct event_processing_state *p)
+{
+ if (!p)
+ return;
+ if (p->child_stdout_fd >= 0)
+ close(p->child_stdout_fd);
+ strbuf_free(p->cmd_output);
+ free(p->dirname);
+ free(p->dup_of_dir);
+ free(p);
}
-typedef enum {
- MW_OK, /* No error */
- MW_OCCURRED, /* No error, but thus dir is a dup */
- MW_ERROR, /* Other error (such as "post-create" event exited with nonzero */
-} mw_result_t;
-
-static mw_result_t run_post_create_and_load_data(const char *dump_dir_name, problem_data_t **problem_data)
+static gboolean handle_event_output_cb(GIOChannel *gio, GIOCondition condition, gpointer ptr)
{
- FILE *fp;
- pid_t child = spawn_event_handler_child(dump_dir_name, "post-create", &fp);
+ struct event_processing_state *state = ptr;
- char *buf;
- char *dup_of_dir = NULL;
- while ((buf = xmalloc_fgetline(fp)) != NULL)
+ /* Read streamed data and split lines */
+ for (;;)
{
- /* Hmm, DUP_OF_DIR: ends up in syslog. move log() into 'else'? */
- log("%s", buf);
+ char buf[250]; /* usually we get one line, no need to have big buf */
+ errno = 0;
+ gsize r = 0;
+ g_io_channel_read_chars(gio, buf, sizeof(buf) - 1, &r, NULL);
+ if (r <= 0)
+ break;
+ buf[r] = '\0';
- if (strncmp("DUP_OF_DIR: ", buf, strlen("DUP_OF_DIR: ")) == 0)
+ /* split lines in the current buffer */
+ char *raw = buf;
+ char *newline;
+ while ((newline = strchr(raw, '\n')) != NULL)
{
- overlapping_strcpy(buf, buf + strlen("DUP_OF_DIR: "));
- free(dup_of_dir);
- dup_of_dir = buf;
+ *newline = '\0';
+ strbuf_append_str(state->cmd_output, raw);
+ char *msg = state->cmd_output->buf;
+
+ /* Hmm, DUP_OF_DIR: ends up in syslog. move log() into 'else'? */
+ log("%s", msg);
+ if (strncmp("DUP_OF_DIR: ", msg, strlen("DUP_OF_DIR: ")) == 0)
+ {
+ free(state->dup_of_dir);
+ state->dup_of_dir = xstrdup(msg + strlen("DUP_OF_DIR: "));
+ }
+
+ strbuf_clear(state->cmd_output);
+ /* jump to next line */
+ raw = newline + 1;
}
- else
- free(buf);
+
+ /* beginning of next line. the line continues by next read */
+ strbuf_append_str(state->cmd_output, raw);
}
- fclose(fp);
- /* Prevent having zombie child process */
+ if (errno == EAGAIN)
+ /* We got all buffered data, but fd is still open. Done for now */
+ return TRUE; /* "please don't remove this event (yet)" */
+
+ /* EOF/error */
+
+ /* Wait for child to actually exit, collect status */
int status;
- safe_waitpid(child, &status, 0);
+ safe_waitpid(state->child_pid, &status, 0);
/* exit 0 means "this is a good, non-dup dir" */
/* exit with 1 + "DUP_OF_DIR: dir" string => dup */
@@ -337,30 +367,24 @@ static mw_result_t run_post_create_and_l
if (WIFSIGNALED(status))
{
log("'post-create' on '%s' killed by signal %d",
- dump_dir_name, WTERMSIG(status));
- return MW_ERROR;
+ state->dirname, WTERMSIG(status));
}
/* else: it is WIFEXITED(status) */
- if (!dup_of_dir)
+ else if (!state->dup_of_dir)
{
log("'post-create' on '%s' exited with %d",
- dump_dir_name, WEXITSTATUS(status));
- return MW_ERROR;
+ state->dirname, WEXITSTATUS(status));
}
- dump_dir_name = dup_of_dir;
+ goto delete_bad_dir;
}
- /* Loads problem_data (from the *first dir* if this one is a dup)
- * Returns:
- * MW_OCCURRED: "count is != 1" (iow: it is > 1 - dup)
- * MW_OK: "count is 1" (iow: this is a new problem, not a dup)
- * else: MW_ERROR
- */
- mw_result_t res = MW_ERROR;
- struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0);
+ char *work_dir = state->dup_of_dir ? state->dup_of_dir : state->dirname;
+
+ /* Load problem_data (from the *first dir* if this one is a dup) */
+ struct dump_dir *dd = dd_opendir(work_dir, /*flags:*/ 0);
if (!dd)
/* dd_opendir already emitted error msg */
- goto ret;
+ goto delete_bad_dir;
/* Reset mode/uig/gid to correct values for all files created by event run */
dd_sanitize_mode_and_owner(dd);
@@ -370,9 +394,9 @@ static mw_result_t run_post_create_and_l
unsigned long count = strtoul(count_str, NULL, 10);
/* Don't increase crash count if we are working with newly uploaded
- * directory (remote crash) which already has it's crash count set.
+ * directory (remote crash) which already has its crash count set.
*/
- if((status != 0 && dup_of_dir) || count == 0)
+ if ((status != 0 && state->dup_of_dir) || count == 0)
{
count++;
char new_count_str[sizeof(long)*3 + 2];
@@ -381,23 +405,53 @@ static mw_result_t run_post_create_and_l
}
dd_close(dd);
- *problem_data = load_problem_data(dump_dir_name);
- if (*problem_data != NULL)
+ if (!state->dup_of_dir)
+ log("New problem directory %s, processing", work_dir);
+ else
{
- res = MW_OK;
- if (count > 1)
- {
- log("Problem directory is a duplicate of %s", dump_dir_name);
- res = MW_OCCURRED;
- }
+ log("Deleting problem directory %s (dup of %s)",
+ strrchr(state->dirname, '/') + 1,
+ strrchr(state->dup_of_dir, '/') + 1);
+ delete_dump_dir(state->dirname);
}
- /* else: load_problem_data already emitted error msg */
+
+ /* Run "notify[-dup]" event */
+//TODO: also handle it as long-running one
+ int fd;
+ pid_t child = spawn_event_handler_child(
+ work_dir,
+ (state->dup_of_dir ? "notify-dup" : "notify"),
+ &fd
+ );
+ FILE *fp = fdopen(fd, "r");
+ if (!fp)
+ die_out_of_memory();
+ for (;;)
+ {
+ char *buf = xmalloc_fgetline(fp);
+ if (!buf) break;
+ log("%s", buf);
+ free(buf);
+ }
+ fclose(fp);
+ /* Prevent having zombie child process */
+ safe_waitpid(child, NULL, 0);
+
+ goto ret;
+
+ delete_bad_dir:
+ log("Corrupted or bad directory '%s', deleting", state->dirname);
+ delete_dump_dir(state->dirname);
ret:
- free(dup_of_dir);
- return res;
+ free_event_processing_state(state);
+
+ return FALSE; /* "glib, please remove this events source!" */
}
+
+/* Inotify handler */
+
static gboolean handle_inotify_cb(GIOChannel *gio, GIOCondition condition, gpointer ptr_unused)
{
/* Default size: 128 simultaneous actions (about 1/2 meg) */
@@ -495,55 +549,17 @@ static gboolean handle_inotify_cb(GIOCha
}
}
- char *fullname = concat_path_file(g_settings_dump_location, name);
- problem_data_t *problem_data = NULL;
- mw_result_t res = run_post_create_and_load_data(fullname, &problem_data);
- switch (res)
- {
- case MW_OK:
- log("New problem directory %s, processing", fullname);
- /* Fall through */
-
- case MW_OCCURRED: /* dup */
- {
- const char *first = NULL;
- if (res != MW_OK)
- {
- /* Fetch the name of older directory of which we are a dup */
- first = get_problem_item_content_or_NULL(problem_data, CD_DUMPDIR);
-
- log("Deleting problem directory %s (dup of %s)",
- strrchr(fullname, '/') + 1,
- strrchr(first, '/') + 1);
- delete_dump_dir(fullname);
- }
+ struct event_processing_state *state = new_event_processing_state();
+ state->dirname = concat_path_file(g_settings_dump_location, name);
- /* Run "notify[_dup]" event */
- FILE *fp;
- pid_t child = spawn_event_handler_child(
- (first ? first : fullname),
- (first ? "notify-dup" : "notify"),
- &fp
- );
- char *buf;
- while ((buf = xmalloc_fgetline(fp)) != NULL)
- {
- log("%s", buf);
- free(buf);
- }
- fclose(fp);
- /* Prevent having zombie child process */
- safe_waitpid(child, NULL, 0);
+ state->child_pid = spawn_event_handler_child(state->dirname, "post-create", &state->child_stdout_fd);
- break;
- }
- default: /* always MW_ERROR */
- log("Corrupted or bad directory %s, deleting", fullname);
- delete_dump_dir(fullname);
- break;
- }
- free(fullname);
- free_problem_data(problem_data);
+ GIOChannel *channel_event_output = my_io_channel_unix_new(state->child_stdout_fd);
+ g_io_channel_set_close_on_unref(channel_event_output, TRUE);
+ /*uint channel_id_event_output =*/ g_io_add_watch(channel_event_output,
+ G_IO_IN,
+ handle_event_output_cb,
+ state);
} /* while */
free(buf);
11 years, 10 months
[PATCH] trac#608 : dd_lock() function fals to infinite loop
by Jakub Filak
If a problem directory is locked by a process and the process tries to
lock directory again the function dd_lock() fals to infinite loop. In
this case the function get_and_set_lock() correctly recognizes that a
lock is owned by the process but the function returns an error value 0.
The correct returned value is 1 because a directory is locked by the
process thus lock was successful.
Signed-off-by: Jakub Filak <jfilak(a)redhat.com>
---
src/lib/dump_dir.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c
index a61fccf..5b759c6 100644
--- a/src/lib/dump_dir.c
+++ b/src/lib/dump_dir.c
@@ -147,7 +147,7 @@ static int get_and_set_lock(const char* lock_file, const char* pid)
if (strcmp(pid_buf, pid) == 0)
{
log("Lock file '%s' is already locked by us", lock_file);
- return 0;
+ return 1;
}
if (isdigit_str(pid_buf))
{
--
1.7.10.2
11 years, 10 months
[PATCH] trac#583: add NewProblem function to ABRT D-Bus API
by Jakub Filak
Signed-off-by: Jakub Filak <jfilak(a)redhat.com>
---
src/dbus/abrt-dbus.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/src/dbus/abrt-dbus.c b/src/dbus/abrt-dbus.c
index 0322e64..00feb1a 100644
--- a/src/dbus/abrt-dbus.c
+++ b/src/dbus/abrt-dbus.c
@@ -21,6 +21,10 @@ static GDBusNodeInfo *introspection_data = NULL;
static const gchar introspection_xml[] =
"<node>"
" <interface name='"ABRT_DBUS_IFACE"'>"
+ " <method name='NewProblem'>"
+ " <arg type='a{ss}' name='problem_data' direction='in'/>"
+ " <arg type='s' name='problem_id' direction='out'/>"
+ " </method>"
" <method name='GetProblems'>"
" <arg type='as' name='response' direction='out'/>"
" </method>"
@@ -358,12 +362,47 @@ static bool allowed_problem_dir(const char *dir_name)
return true;
}
+static bool handle_new_problem(GVariant *problem_info, char **problem_id, char **error)
+{
+ bool result = true;
+
+ problem_data_t *pd = problem_data_new();
+
+ GVariantIter *iter;
+ g_variant_get(problem_info, "a{ss}", &iter);
+ gchar *key, *value;
+ while (g_variant_iter_loop(iter, "{ss}", &key, &value))
+ {
+ if (problem_data_add_item(pd, key, value) != 0)
+ {
+ if (error)
+ *error = xasprintf("Cannot save item '%s'", key);
+
+ result = false;
+ }
+ }
+
+ /* based on short-circuit evaluation */
+ /* if something failed the result is false and the problem_data_save() is not called */
+ if (result && problem_data_save(pd, problem_id) != 0)
+ {
+ if (error)
+ *error = xasprintf("Cannot create a new problem");
+
+ result = false;
+ }
+
+ problem_data_destroy(pd);
+ return result;
+}
+
static void return_InvalidProblemDir_error(GDBusMethodInvocation *invocation, const char *dir_name)
{
char *msg = xasprintf(_("'%s' is not a valid problem directory"), dir_name);
g_dbus_method_invocation_return_dbus_error(invocation,
"org.freedesktop.problems.InvalidProblemDir",
msg);
+
free(msg);
}
@@ -388,6 +427,27 @@ static void handle_method_call(GDBusConnection *connection,
if (caller_uid == (uid_t) -1)
return;
+ if (g_strcmp0(method_name, "NewProblem") == 0)
+ {
+ char *problem_id = NULL;
+ char *error = NULL;
+ if (!handle_new_problem(g_variant_get_child_value(parameters, 0), &problem_id, &error))
+ {
+ g_dbus_method_invocation_return_dbus_error(invocation,
+ "org.freedesktop.problems.Failure",
+ error);
+ free(error);
+ }
+ else
+ {
+ response = g_variant_new("(s)", problem_id);
+ g_dbus_method_invocation_return_value(invocation, response);
+ free(problem_id);
+ }
+
+ return;
+ }
+
if (g_strcmp0(method_name, "GetProblems") == 0)
{
response = get_problem_dirs_for_uid(caller_uid, g_settings_dump_location);
--
1.7.10.2
11 years, 10 months
[PATCH] polkit: shorter version of polkit_check_authorization_dname
by Nikola Pajkovsky
I'm getting be lazy, so I did only rename of that loong function by
define
Signed-off-by: Nikola Pajkovsky <npajkovs(a)redhat.com>
---
src/dbus/abrt-dbus.c | 10 +++++-----
src/dbus/abrt-polkit.h | 3 +++
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/src/dbus/abrt-dbus.c b/src/dbus/abrt-dbus.c
index 0322e64..b02b3cc 100644
--- a/src/dbus/abrt-dbus.c
+++ b/src/dbus/abrt-dbus.c
@@ -407,7 +407,7 @@ static void handle_method_call(GDBusConnection *connection,
*/
if (caller_uid != 0)
{
- if (polkit_check_authorization_dname(caller, "org.freedesktop.problems.getall") == PolkitYes)
+ if (polkit_chk_auth_dname(caller, "getall") == PolkitYes)
caller_uid = 0;
}
@@ -436,7 +436,7 @@ static void handle_method_call(GDBusConnection *connection,
return;
}
- if (polkit_check_authorization_dname(caller, "org.freedesktop.problems.getall") != PolkitYes)
+ if (polkit_chk_auth_dname(caller, "getall") != PolkitYes)
{
VERB1 log("not authorized");
g_dbus_method_invocation_return_dbus_error(invocation,
@@ -509,7 +509,7 @@ static void handle_method_call(GDBusConnection *connection,
if (!dir_accessible_by_uid(problem_dir, caller_uid))
{
- if (polkit_check_authorization_dname(caller, "org.freedesktop.problems.getall") != PolkitYes)
+ if (polkit_chk_auth_dname(caller, "getall") != PolkitYes)
{
VERB1 log("not authorized");
g_dbus_method_invocation_return_dbus_error(invocation,
@@ -583,7 +583,7 @@ static void handle_method_call(GDBusConnection *connection,
const char *dir_name = (const char*)l->data;
if (!dir_accessible_by_uid(dir_name, caller_uid))
{
- if (polkit_check_authorization_dname(caller, "org.freedesktop.problems.getall") != PolkitYes)
+ if (polkit_chk_auth_dname(caller, "getall") != PolkitYes)
{ // if user didn't provide correct credentials, just move to the next dir
continue;
}
@@ -607,7 +607,7 @@ static void handle_method_call(GDBusConnection *connection,
g_variant_get(parameters, "(ssxxb)", &element, &value, ×tamp_from, ×tamp_to, &all);
- if (all && polkit_check_authorization_dname(caller, "org.freedesktop.problems.getall") == PolkitYes)
+ if (all && polkit_chk_auth_dname(caller, "getall") == PolkitYes)
caller_uid = 0;
response = get_problem_dirs_for_element_in_time(caller_uid, element, value, timestamp_from,
diff --git a/src/dbus/abrt-polkit.h b/src/dbus/abrt-polkit.h
index 0f6eb1e..f7d66a3 100644
--- a/src/dbus/abrt-polkit.h
+++ b/src/dbus/abrt-polkit.h
@@ -33,6 +33,9 @@ typedef enum {
PolkitChallenge = 0x03
} PolkitResult;
+#define polkit_chk_auth_dname(dbus_name, action) \
+ polkit_check_authorization_dname(dbus_name, ABRT_DBUS_NAME"." #action)
+
PolkitResult polkit_check_authorization_dname(const char *dbus_name, const char *action_id);
PolkitResult polkit_check_authorization_pid(pid_t pid, const char *action_id);
--
1.7.10.2
11 years, 10 months
Re: [PATCH] abrt-gui: spawn abrt-handle-event for opening problem dirs
by Denys Vlasenko
On 06/26/2012 04:28 PM, Jiri Moskovcak wrote:
> On 06/26/2012 01:16 PM, Denys Vlasenko wrote:
>> This makes sure we exec immediately after fork and thus eliminate
>> threading-related problems.
>>
>> Run-tested.
>> Stops https://bugzilla.redhat.com/show_bug.cgi?id=835112
>> from happening.
>
> The code looks ok, but I don't like the way we're polluting
> the bin,sbin. I understand it's easier for us to code, but
> /usr/libexec is here for a reason.. and according to RHEL policy
> we should provide a man page for everything which is in bin, sbin
> directories ...
I will revert to using libexec.
--
vda
11 years, 10 months