Re: [Utopia] Multiuser HAL/gnome-volume-manager



Hi!

Jean-Christophe Haessig [2005-08-19  0:40 +0200]:
> It has come to my attention that gnome-volume-manager operates in a very
> mono-user way (only the user logged in Gnome may plug in devices e.g.
> USB keys). I was wondering how it is expected to work when there are
> multiple users simultaneously logged in. Since Unix/Linux is
> fundamentally multi-user oriented, I feel it would be essential to
> support such schemes.
> 
> I wont include any specific problematic use cases right now, since I
> have absolutely no information on what is supposed to happen (e.g. a
> race between two instances of gnome-volume-manager, nothing, etc).

In Ubuntu we also have an open bug report about this [1]. This is
indeed a fundamental physical issue; if several users are logged in to
the same computer at the same time, and somebody plugs in an USB
stick, there is no obvious "owner" of the device.

In Ubuntu we have a partial solution to this called "multiseat". It
was designed for the HP 441 multi-terminal server and allows you to
assign USB ports (and keyboards and screens) to a particular display.
Once this file is present, the Ubuntu version of g-v-m will only mount
USB devices from ports that match the $DISPLAY.

I attach the patch FYI, but it is probably not something that should
go upstream without a major redesign and at least a GUI to configure
it. However, Jeffrey, have you ever thought about this? What do you
think about a general facility to restrict g-v-m to certain USB ports?

Thanks,

Martin

[1] https://bugzilla.ubuntu.com/show_bug.cgi?id=4021
-- 
Martin Pitt        http://www.piware.de
Ubuntu Developer   http://www.ubuntu.com
Debian Developer   http://www.debian.org
diff -ruN gnome-volume-manager-1.3.2.orig/src/manager.c gnome-volume-manager-1.3.2/src/manager.c
--- gnome-volume-manager-1.3.2.orig/src/manager.c	2005-09-09 13:14:32.000000000 +0200
+++ gnome-volume-manager-1.3.2/src/manager.c	2005-09-09 13:15:15.000000000 +0200
@@ -409,6 +409,120 @@
 }
 
 /*
+ * multihead support
+ */
+
+#include <regex.h>
+
+static regex_t* usb_restrict_re = NULL;
+
+static void init_usb_restriction()
+{
+    regex_t re;
+    FILE *f;
+    regmatch_t m[3];
+    char restr[1024];
+    char line[4096];
+    char *busre, *portre;
+
+    /* get workstation */
+    char* display = getenv ("DISPLAY");
+    int workstation;
+
+    if (!display) {
+        dbg ("DISPLAY not set, not restricting USB ports\n");
+        return;
+    }
+    if (sscanf (display, ":%i.", &workstation) < 1 &&
+        sscanf (display, "%*[a-zA-Z0-9.-]:%i.", &workstation) < 1) {
+        dbg ("invalid DISPLAY variable, exiting\n");
+        exit (EXIT_SUCCESS);
+    }
+
+    f = fopen ("/etc/multiseat.conf", "r");
+    if (!f)
+        return;
+
+    /* generate key[index] = value regex */
+    snprintf (restr, sizeof(restr),
+            "^[[:space:]]*storage[[:space:]]*\\[[[:space:]]*%i[[:space:]]*\\][[:space:]]*=[[:space:]]*\"usb-([^-]*)-(.*)\"[[:space:]]*$",
+            workstation);
+    if (regcomp (&re, restr, REG_EXTENDED)) {
+        dbg ("init_usb_restriction: invalid regular expression\n");
+        exit (-99);
+    }
+
+    while (fgets (line, sizeof(line), f)) {
+        if (!regexec (&re, line, 3, m, 0)) {
+            line[m[2].rm_eo] = 0;
+            portre = (char*) malloc (m[2].rm_eo - m[2].rm_so + 1);
+            strcpy (portre, line+m[2].rm_so);
+            line[m[1].rm_eo] = 0;
+            busre = (char*) malloc (m[1].rm_eo - m[1].rm_so + 1);
+            strcpy (busre, line+m[1].rm_so);
+
+            snprintf (restr, sizeof (restr), "^/sys/.*/%s/.*-%s$", busre, portre);
+            dbg ("/etc/multiseat.conf: restricting to USB devices to bus /%s/ and port /%s/ -> sysfs path /%s/\n", 
+                    busre, portre, restr );
+
+            usb_restrict_re = (regex_t*) malloc (sizeof (regex_t));
+            if (regcomp (usb_restrict_re, restr, REG_EXTENDED)) {
+                dbg ("init_usb_restriction: invalid regular expression in /etc/multiseat.conf\n");
+                exit (-99);
+            }
+
+            free (busre);
+            free (portre);
+            break;
+        }
+    }
+
+    if (!usb_restrict_re) {
+        dbg ("init_usb_restriction: no matching storage configuration in /etc/multiseat.conf, exiting\n");
+        exit (EXIT_SUCCESS);
+    }
+
+    fclose (f);
+}
+
+static int
+pass_usb_restriction (const char* udi)
+{
+    char *dev, *parent, *value;
+    int match = 0;
+
+    if (!usb_restrict_re)
+        return 1;
+
+    dev = g_strdup (udi);
+
+    while (dev) {
+        if (libhal_device_property_exists (hal_ctx, dev, "usb_device.linux.sysfs_path", NULL)) {
+            value = libhal_device_get_property_string (hal_ctx, dev, "usb_device.linux.sysfs_path", NULL);
+            if (!value)
+                return 0;
+
+            dbg("Checking whether kernel device name %s matches USB restriction\n",
+                value);
+
+            match = !regexec (usb_restrict_re, value, 0, NULL, 0);
+            libhal_free_string (value);
+            break;
+        }
+        if (libhal_device_property_exists (hal_ctx, dev, "info.parent", NULL)) {
+            parent = libhal_device_get_property_string (hal_ctx, dev, "info.parent", NULL);
+            libhal_free_string (dev);
+            dev = parent;
+        } else
+            dev = NULL;
+    }
+
+    if (dev)
+        libhal_free_string (dev);
+    return match;
+}
+
+/*
  * gvm_load_config - synchronize gconf => config structure
  */
 static void
@@ -1303,6 +1417,12 @@
 		}
 		
 		if (mountable) {
+			/* Check multiseat configuration */
+			if (!pass_usb_restriction (udi)) {
+			    dbg ("%s is restricted by /etc/multiseat.conf, not mounting\n", udi);
+			    goto out;
+			}
+
 			/* only mount if the block device has a sensible filesystem */
 			fsusage = libhal_device_get_property_string (ctx, udi, "volume.fsusage", &error);
 			if (!fsusage || strcmp (fsusage, "filesystem") != 0) {
@@ -1758,6 +1878,8 @@
 		warn ("already running?");
 		return 1;
 	}
+
+	init_usb_restriction();
 	
 	g_signal_connect (client, "die", G_CALLBACK (gvm_die), NULL);
 	
diff -ruN gnome-volume-manager-1.3.2.orig/src/manager.c.orig gnome-volume-manager-1.3.2/src/manager.c.orig
--- gnome-volume-manager-1.3.2.orig/src/manager.c.orig	1970-01-01 01:00:00.000000000 +0100
+++ gnome-volume-manager-1.3.2/src/manager.c.orig	2005-09-09 13:15:15.000000000 +0200
@@ -0,0 +1,1792 @@
+/*
+ * src/manager.c - GNOME Volume Manager
+ *
+ * Robert Love <rml novell com>
+ *
+ * gnome-volume-manager is a simple policy engine that implements a state
+ * machine in response to events from HAL.  Responding to these events,
+ * gnome-volume-manager implements automount, autorun, autoplay, automatic
+ * photo management, and so on.
+ *
+ * Licensed under the GNU GPL v2.  See COPYING.
+ *
+ * (C) Copyright 2005 Novell, Inc.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <gnome.h>
+#include <glib/gi18n.h>
+#include <gconf/gconf-client.h>
+#include <gdk/gdkx.h>
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <libhal.h>
+
+#include "gvm.h"
+
+#define GVM_DEBUG
+#ifdef GVM_DEBUG
+# define dbg(fmt,arg...) fprintf(stderr, "%s/%d: " fmt,__FILE__,__LINE__,##arg)
+#else
+# define dbg(fmt,arg...) do { } while(0)
+#endif
+
+#define warn(fmt,arg...) g_warning("%s/%d: " fmt,__FILE__,__LINE__,##arg)
+
+#define NAUTILUS_COMMAND	 BIN_NAUTILUS" -n --no-desktop %m"
+
+static struct gvm_configuration config;
+static LibHalContext *hal_ctx;
+
+/** Table of UDI's for volumes being mounted by g-v-m that we need to apply policy to */
+static GHashTable *mount_table = NULL;
+
+/** List of UDI's of all volumes mounted during the lifetime of the program */
+static GSList *mounted_volumes = NULL;
+
+
+typedef enum {
+	TYPE_BOOL,
+	TYPE_STRING
+} type_t;
+
+enum {
+	AUTOBROWSE,
+	AUTOBURN,
+	AUTOBURN_AUDIO_CD_COMMAND,
+	AUTOBURN_PHOTO_CD_COMMAND,
+	AUTOBURN_DATA_CD_COMMAND,
+	AUTOIPOD,
+	AUTOIPOD_COMMAND,
+	AUTOMOUNT_DRIVES,
+	AUTOMOUNT_MEDIA,
+	AUTOPHOTO,
+	AUTOPHOTO_COMMAND,
+	AUTOPLAY_CDA,
+	AUTOPLAY_CDA_COMMAND,
+	AUTOPLAY_DVD,
+	AUTOPLAY_DVD_COMMAND,
+	AUTOPLAY_VCD,
+	AUTOPLAY_VCD_COMMAND,
+	AUTOPRINTER,
+	AUTOPRINTER_COMMAND,
+	AUTORUN,
+	AUTORUN_PATH,
+	EJECT_COMMAND,
+};
+
+static struct {
+	char *key;
+	type_t type;
+	void *var;
+} gvm_settings[] = {
+	{ GCONF_ROOT "autobrowse",                TYPE_BOOL,   &config.autobrowse                },
+	{ GCONF_ROOT "autoburn",                  TYPE_BOOL,   &config.autoburn                  },
+	{ GCONF_ROOT "autoburn_audio_cd_command", TYPE_STRING, &config.autoburn_audio_cd_command },
+	{ GCONF_ROOT "autoburn_photo_cd_command", TYPE_STRING, &config.autoburn_photo_cd_command },
+	{ GCONF_ROOT "autoburn_data_cd_command",  TYPE_STRING, &config.autoburn_data_cd_command  },
+	{ GCONF_ROOT "autoipod",                  TYPE_BOOL,   &config.autoipod                  },
+	{ GCONF_ROOT "autoipod_command",          TYPE_STRING, &config.autoipod_command          },
+	{ GCONF_ROOT "automount_drives",          TYPE_BOOL,   &config.automount_drives          },
+	{ GCONF_ROOT "automount_media",           TYPE_BOOL,   &config.automount_media           },
+	{ GCONF_ROOT "autophoto",                 TYPE_BOOL,   &config.autophoto                 },
+	{ GCONF_ROOT "autophoto_command",         TYPE_STRING, &config.autophoto_command         },
+	{ GCONF_ROOT "autoplay_cda",              TYPE_BOOL,   &config.autoplay_cda              },
+	{ GCONF_ROOT "autoplay_cda_command",      TYPE_STRING, &config.autoplay_cda_command      },
+	{ GCONF_ROOT "autoplay_dvd",              TYPE_BOOL,   &config.autoplay_dvd              },
+	{ GCONF_ROOT "autoplay_dvd_command",      TYPE_STRING, &config.autoplay_dvd_command      },
+	{ GCONF_ROOT "autoplay_vcd",              TYPE_BOOL,   &config.autoplay_vcd              },
+	{ GCONF_ROOT "autoplay_vcd_command",      TYPE_STRING, &config.autoplay_vcd_command      },
+	{ GCONF_ROOT "autoprinter",               TYPE_BOOL,   &config.autoprinter               },
+	{ GCONF_ROOT "autoprinter_command",       TYPE_STRING, &config.autoprinter_command       },
+	{ GCONF_ROOT "autorun",                   TYPE_BOOL,   &config.autorun                   },
+	{ GCONF_ROOT "autorun_path",              TYPE_STRING, &config.autorun_path              },
+	{ GCONF_ROOT "eject_command",             TYPE_STRING, &config.eject_command             },
+};
+
+static GHashTable *gvm_settings_hash = NULL;
+
+
+struct _GvmPromptButton {
+	const char *label;
+	const char *stock;
+	int response_id;
+};
+
+enum {
+	GVM_RESPONSE_NONE,
+	GVM_RESPONSE_RUN,
+	GVM_RESPONSE_PLAY,
+	GVM_RESPONSE_BROWSE,
+	GVM_RESPONSE_SYNC_MUSIC,
+	GVM_RESPONSE_IMPORT_PHOTOS,
+	GVM_RESPONSE_BURN_AUDIO_CD,
+	GVM_RESPONSE_BURN_PHOTO_CD,
+	GVM_RESPONSE_BURN_DATA_CD,
+};
+
+static struct _GvmPromptButton GVM_BUTTONS_RUN_CANCEL[] = {
+	{ NULL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL },
+	{ N_("_Run Command"), NULL, GVM_RESPONSE_RUN },
+};
+
+static struct _GvmPromptButton GVM_BUTTONS_IMPORT_CANCEL[] = {
+	{ NULL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL },
+	{ N_("_Import Photos"), NULL, GVM_RESPONSE_IMPORT_PHOTOS },
+};
+
+static struct _GvmPromptButton GVM_BUTTONS_IMPORT_SYNC_CANCEL[] = {
+	{ NULL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL },
+	{ N_("_Import Photos"), NULL, GVM_RESPONSE_IMPORT_PHOTOS },
+	{ N_("_Sync Music"), NULL, GVM_RESPONSE_SYNC_MUSIC },
+};
+
+static struct _GvmPromptButton GVM_BUTTONS_BROWSE_PLAY_CANCEL[] = {
+	{ NULL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL },
+	{ N_("_Browse Files"), NULL, GVM_RESPONSE_BROWSE },
+	{ N_("_Play Tracks"), NULL, GVM_RESPONSE_PLAY },
+};
+
+static struct _GvmPromptButton GVM_BUTTONS_BURN_CDR_AUDIO_PHOTO_DATA[] = {
+	{ NULL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL },
+	{ N_("Burn _Audio CD"), NULL, GVM_RESPONSE_BURN_AUDIO_CD },
+	{ N_("Burn _Photo CD"), NULL, GVM_RESPONSE_BURN_PHOTO_CD },
+	{ N_("Burn _Data CD"), NULL, GVM_RESPONSE_BURN_DATA_CD },
+};
+
+static struct _GvmPromptButton GVM_BUTTONS_BURN_DVD_PHOTO_DATA[] = {
+	{ NULL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL },
+	{ N_("Burn _Photo DVD"), NULL, GVM_RESPONSE_BURN_PHOTO_CD },
+	{ N_("Burn _Data DVD"), NULL, GVM_RESPONSE_BURN_DATA_CD },
+};
+
+typedef enum {
+	GVM_PROMPT_AUTORUN,
+	GVM_PROMPT_IMPORT_CAMERA,
+	GVM_PROMPT_IMPORT_PHOTOS,
+	GVM_PROMPT_IPOD_PHOTO,
+	GVM_PROMPT_CDA_EXTRA,
+	GVM_PROMPT_BURN_CDR,
+	GVM_PROMPT_BURN_DVD,
+} GvmPrompt;
+
+static struct {
+	GtkDialogFlags flags;
+	GtkMessageType type;
+	
+	const char *help_uri;
+	
+	struct _GvmPromptButton *buttons;
+	int n_buttons;
+	
+	int default_response;
+	
+	const char *title;
+	const char *primary;
+	const char *secondary;
+	int secondary_has_args;
+	
+	const char *dont_ask_again_key;
+	const char *dont_ask_again_label;
+} gvm_prompts[] = {
+	{ 0, GTK_MESSAGE_QUESTION, NULL, GVM_BUTTONS_RUN_CANCEL, 2, GVM_RESPONSE_RUN,
+	  N_("Run command from inserted media?"),
+	  N_("Run command from inserted media?"),
+	  N_("Do you want to run the file \"%s\"?"),
+	  TRUE, GCONF_ROOT "prompts/autorun", N_("Always take this action") },
+	{ 0, GTK_MESSAGE_QUESTION, NULL, GVM_BUTTONS_IMPORT_CANCEL, 2, GVM_RESPONSE_IMPORT_PHOTOS,
+	  N_("Import photos from camera?"),
+	  N_("Import photos from camera?"),
+	  N_("There are photos on the plugged-in camera. Would you like to import these photographs into your album?"),
+	  FALSE, GCONF_ROOT "prompts/camera_import_photos", N_("Always take this action") },
+	{ 0, GTK_MESSAGE_QUESTION, NULL, GVM_BUTTONS_IMPORT_CANCEL, 2, GVM_RESPONSE_IMPORT_PHOTOS,
+	  N_("Import photos from device?"),
+	  N_("Import photos from device?"),
+	  N_("There are photos on the inserted media. Would you like to import these photographs into your album?"),
+	  FALSE, GCONF_ROOT "prompts/device_import_photos", N_("Always take this action") },
+	{ 0, GTK_MESSAGE_QUESTION, NULL, GVM_BUTTONS_IMPORT_SYNC_CANCEL, 3, GVM_RESPONSE_SYNC_MUSIC,
+	  N_("Import photos or music?"),
+	  N_("Import photos or music?"),
+	  N_("There are both photos and music on the plugged-in iPod. "
+	     "Would you like to import the photos or would you prefer "
+	     "to sync your music?"),
+	  FALSE, GCONF_ROOT "prompts/ipod_photo", N_("Always take this action") },
+	{ 0, GTK_MESSAGE_QUESTION, NULL, GVM_BUTTONS_BROWSE_PLAY_CANCEL, 3, GVM_RESPONSE_PLAY,
+	  N_("Browse files or play tracks from disc?"),
+	  N_("Browse files or play tracks from disc?"),
+	  N_("This CD contains both music and data. What would you like to do?"),
+	  FALSE, GCONF_ROOT "prompts/cd_mixed", N_("Always take this action") },
+	{ 0, GTK_MESSAGE_QUESTION, NULL, GVM_BUTTONS_BURN_CDR_AUDIO_PHOTO_DATA, 4, GVM_RESPONSE_BURN_DATA_CD,
+	  N_("Burn audio, photo, or data CD?"),
+	  N_("Burn audio, photo, or data CD?"),
+	  N_("Would you like to burn an audio, photo, or data CD?"),
+	  FALSE, GCONF_ROOT "prompts/burn_cd", N_("Always take this action") },
+	{ 0, GTK_MESSAGE_QUESTION, NULL, GVM_BUTTONS_BURN_DVD_PHOTO_DATA, 3, GVM_RESPONSE_BURN_DATA_CD,
+	  N_("Burn photo or data DVD?"),
+	  N_("Burn photo or data DVD?"),
+	  N_("Would you like to burn a photo or data DVD?"),
+	  FALSE, GCONF_ROOT "prompts/burn_cdr", N_("Always take this action") },
+};
+
+static void
+prompt_response_cb (GtkWidget *dialog, int response, gpointer user_data)
+{
+	int prompt = GPOINTER_TO_INT (user_data);
+	GError *err = NULL;
+	
+	if (response != GTK_RESPONSE_HELP)
+		return;
+	
+	g_signal_stop_emission_by_name (dialog, "response");
+	gnome_url_show (gvm_prompts[prompt].help_uri, &err);
+	if (err) {
+		warn ("Unable to run help uri: %s", err->message);
+		g_error_free (err);
+	}
+}
+
+static int
+gvm_prompt (GvmPrompt prompt, ...)
+{
+	GtkWidget *dialog, *hbox, *image, *label, *check = NULL;
+	const char *stock_id = NULL;
+	GConfClient *gconf;
+	GError *err = NULL;
+	GtkStockItem item;
+	int response, i;
+	GString *str;
+	va_list args;
+	char *text;
+	
+	gconf = gconf_client_get_default ();
+	
+	/* don't prompt the user again if she's already chosen a default action and has asked to not be prompetd again */
+	if (gvm_prompts[prompt].dont_ask_again_key) {
+		response = gconf_client_get_int (gconf, gvm_prompts[prompt].dont_ask_again_key, &err);
+		if (response > GVM_RESPONSE_NONE && err == NULL)
+			return response;
+		if (err != NULL)
+			g_error_free (err);
+	}
+	
+	dialog = gtk_dialog_new ();
+	gtk_widget_ensure_style (dialog);
+	gtk_dialog_set_has_separator ((GtkDialog *) dialog, FALSE);
+	
+	gtk_container_set_border_width ((GtkContainer *) ((GtkDialog *) dialog)->vbox, 0);
+	gtk_container_set_border_width ((GtkContainer *) ((GtkDialog *) dialog)->action_area, 12);
+	
+	if (gvm_prompts[prompt].flags & GTK_DIALOG_MODAL)
+		gtk_window_set_modal ((GtkWindow *) dialog, TRUE);
+	
+	if (gvm_prompts[prompt].help_uri) {
+		gtk_dialog_add_button ((GtkDialog *) dialog, GTK_STOCK_HELP, GTK_RESPONSE_HELP);
+		g_signal_connect (dialog, "response", G_CALLBACK (prompt_response_cb), GINT_TO_POINTER (prompt));
+	}
+	
+	if (gvm_prompts[prompt].buttons) {
+		for (i = 0; i < gvm_prompts[prompt].n_buttons; i++) {
+			const char *name;
+			
+			name = gvm_prompts[prompt].buttons[i].stock ?
+				gvm_prompts[prompt].buttons[i].stock :
+				_(gvm_prompts[prompt].buttons[i].label);
+			
+			gtk_dialog_add_button ((GtkDialog *) dialog, name, gvm_prompts[prompt].buttons[i].response_id);
+		}
+		
+		if (gvm_prompts[prompt].default_response != GVM_RESPONSE_NONE)
+			gtk_dialog_set_default_response ((GtkDialog *) dialog, gvm_prompts[prompt].default_response);
+	} else {
+		gtk_dialog_add_button ((GtkDialog *) dialog, GTK_STOCK_OK, GTK_RESPONSE_OK);
+	}
+	
+	hbox = gtk_hbox_new (FALSE, 0);
+	gtk_container_set_border_width ((GtkContainer *) hbox, 12);
+	
+	switch (gvm_prompts[prompt].type) {
+	case GTK_MESSAGE_INFO:
+		stock_id = GTK_STOCK_DIALOG_INFO;
+		break;
+	case GTK_MESSAGE_QUESTION:
+		stock_id = GTK_STOCK_DIALOG_QUESTION;
+		break;
+	case GTK_MESSAGE_WARNING:
+		stock_id = GTK_STOCK_DIALOG_WARNING;
+		break;
+	case GTK_MESSAGE_ERROR:
+		stock_id = GTK_STOCK_DIALOG_ERROR;
+		break;
+	default:
+		stock_id = GTK_STOCK_DIALOG_INFO;
+		break;
+	}
+	
+	image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_DIALOG);
+	gtk_misc_set_alignment ((GtkMisc *) image, 0.0, 0.0);
+	gtk_box_pack_start ((GtkBox *) hbox, image, FALSE, FALSE, 12);
+	gtk_widget_show (image);
+	
+	if (gvm_prompts[prompt].title)
+		gtk_window_set_title ((GtkWindow *) dialog, _(gvm_prompts[prompt].title));
+	else if (gtk_stock_lookup (stock_id, &item))
+		gtk_window_set_title ((GtkWindow *) dialog, item.label);
+	
+	/* build the primary and secondary text */
+	str = g_string_new ("");
+	
+	g_string_append (str, "<span weight=\"bold\" size=\"larger\">");
+	g_string_append (str, _(gvm_prompts[prompt].primary));
+	g_string_append (str, "</span>\n\n");
+	
+	if (gvm_prompts[prompt].secondary_has_args) {
+		va_start (args, prompt);
+		text = g_strdup_vprintf (_(gvm_prompts[prompt].secondary), args);
+		va_end (args);
+		
+		g_string_append (str, text);
+		g_free (text);
+	} else {
+		g_string_append (str, _(gvm_prompts[prompt].secondary));
+	}
+	
+	label = gtk_label_new (NULL);
+	/*gtk_label_set_selectable ((GtkLabel *) label, TRUE);*/
+	gtk_label_set_line_wrap ((GtkLabel *) label, TRUE);
+	gtk_label_set_markup ((GtkLabel *) label, str->str);
+	
+	gtk_box_pack_start ((GtkBox *) hbox, label, FALSE, FALSE, 0);
+	gtk_widget_show (label);
+	
+	gtk_box_pack_start ((GtkBox *) ((GtkDialog *) dialog)->vbox, hbox, FALSE, FALSE, 0);
+	gtk_widget_show (hbox);
+	
+	/* conditionally add a checkbox to never bother the user again */
+	if (gvm_prompts[prompt].dont_ask_again_key && gvm_prompts[prompt].dont_ask_again_label) {
+		check = gtk_check_button_new_with_label (_(gvm_prompts[prompt].dont_ask_again_label));
+		gtk_container_set_border_width ((GtkContainer *) check, 12);
+		gtk_box_pack_start ((GtkBox *) ((GtkDialog *) dialog)->vbox, check, FALSE, FALSE, 0);
+		gtk_widget_show (check);
+	}
+	
+	response = gtk_dialog_run ((GtkDialog *) dialog);
+	
+	if (check != NULL && gtk_toggle_button_get_active ((GtkToggleButton *) check))
+		gconf_client_set_int (gconf, gvm_prompts[prompt].dont_ask_again_key, response, NULL);
+	
+	gtk_widget_destroy (dialog);
+	g_object_unref (gconf);
+	
+	return response;
+}
+
+static void
+to_be_or_not_to_be (void)
+{
+	/*
+	 * If all of the options that control our policy are disabled, then we
+	 * have no point in living.  Save the user some memory and exit.
+	 */
+	if (!(config.automount_drives || config.autobrowse || config.autorun 
+	      || config.autoplay_cda || config.autoplay_dvd 
+	      || config.autophoto || config.autoipod
+	      || config.autoprinter)) {
+		dbg ("daemon exit: no point living\n");
+		exit (EXIT_SUCCESS);
+	}
+}
+
+/*
+ * gvm_load_config - synchronize gconf => config structure
+ */
+static void
+gvm_load_config (void)
+{
+	unsigned int i;
+	
+	gvm_settings_hash = g_hash_table_new (g_str_hash, g_str_equal);
+	
+	for (i = 0; i < G_N_ELEMENTS (gvm_settings); i++) {
+		g_hash_table_insert (gvm_settings_hash, gvm_settings[i].key, GINT_TO_POINTER (i + 1));
+		if (gvm_settings[i].type == TYPE_STRING) {
+			*((char **) gvm_settings[i].var) =
+				gconf_client_get_string (config.client, gvm_settings[i].key, NULL);
+			dbg ("setting[%d]: string: %s = %s\n", i, strrchr (gvm_settings[i].key, '/') + 1,
+			     *((char **) gvm_settings[i].var));
+		} else if (gvm_settings[i].type == TYPE_BOOL) {
+			*((int *) gvm_settings[i].var) =
+				gconf_client_get_bool (config.client, gvm_settings[i].key, NULL);
+			dbg ("setting[%d]: bool: %s = %d\n", i, strrchr (gvm_settings[i].key, '/') + 1,
+			     *((int *) gvm_settings[i].var));
+		} else {
+			g_assert_not_reached ();
+		}
+	}
+	
+	to_be_or_not_to_be ();
+}
+
+/*
+ * gvm_config_changed - gconf_client_notify_add () call back to reload config
+ */
+static void
+gvm_config_changed (GConfClient *client __attribute__((__unused__)),
+		    guint id __attribute__((__unused__)),
+		    GConfEntry *entry,
+		    gpointer data __attribute__((__unused__)))
+{
+	GConfValue *value;
+	gpointer result;
+	int which;
+	
+	g_return_if_fail (gconf_entry_get_key (entry) != NULL);
+	
+	if (!(value = gconf_entry_get_value (entry)))
+		return;
+	
+	if (!(result = g_hash_table_lookup (gvm_settings_hash, entry->key)))
+		return;
+	
+	which = GPOINTER_TO_INT (result) - 1;
+	
+	if (gvm_settings[which].type == TYPE_STRING) {
+		g_free (*((char **) gvm_settings[which].var));
+		*((char **) gvm_settings[which].var) = g_strdup (gconf_value_get_string (value));
+		dbg ("setting changed: string: %s = %s\n", strrchr (gvm_settings[which].key, '/') + 1,
+		     *((char **) gvm_settings[which].var));
+	} else if (gvm_settings[which].type == TYPE_BOOL) {
+		*((int *) gvm_settings[which].var) = gconf_value_get_bool (value);
+		dbg ("setting changed: bool: %s = %d\n", strrchr (gvm_settings[which].key, '/') + 1,
+		     *((int *) gvm_settings[which].var));
+	} else {
+		g_assert_not_reached ();
+	}
+	
+	to_be_or_not_to_be ();
+}
+
+/*
+ * gvm_init_config - initialize gconf client and load config data
+ */
+static void
+gvm_init_config (void)
+{
+	config.client = gconf_client_get_default ();
+
+	gconf_client_add_dir (config.client, GCONF_ROOT_SANS_SLASH,
+			      GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
+
+	gvm_load_config ();
+
+	gconf_client_notify_add (config.client, GCONF_ROOT_SANS_SLASH,
+				 gvm_config_changed, NULL, NULL, NULL);
+}
+
+/*
+ * gvm_run_command - run the given command, replacing %d with the device node,
+ * %h with the HAL UDI and %m with the given path.
+ *
+ * Returns TRUE if successful or FALSE otherwise.
+ */
+static gboolean
+gvm_run_command (const char *command, const char *udi, const char *device, const char *mount_point)
+{
+	char *path, *mp = NULL, *dev = NULL;
+	const char *inptr, *start;
+	GError *error = NULL;
+	GString *exec;
+	char *argv[4];
+	
+	g_assert (udi != NULL);
+	
+	exec = g_string_new (NULL);
+	
+	/* perform s/%d/device/, s/%m/mount_point/ and s/%h/udi/ */
+	start = inptr = command;
+	while ((inptr = strchr (inptr, '%')) != NULL) {
+		g_string_append_len (exec, start, inptr - start);
+		inptr++;
+		switch (*inptr) {
+		case 'd':
+			g_string_append (exec, device ? device : "");
+			break;
+		case 'm':
+			if (mount_point == NULL && libhal_device_query_capability (hal_ctx, udi, "volume", NULL)) {
+				mp = libhal_device_get_property_string (hal_ctx, udi, "volume.mount_point", NULL);
+				mount_point = mp;
+			}
+			
+			if (mount_point) {
+				path = g_shell_quote (mount_point);
+				g_string_append (exec, path);
+				g_free (path);
+			} else {
+				g_string_append (exec, "\"\"");
+			}
+			break;
+		case 'h':
+			g_string_append (exec, udi);
+			break;
+		case '%':
+			g_string_append_c (exec, '%');
+			break;
+		default:
+			g_string_append_c (exec, '%');
+			if (*inptr)
+				g_string_append_c (exec, *inptr);
+			break;
+		}
+		
+		if (*inptr)
+			inptr++;
+		start = inptr;
+	}
+	g_string_append (exec, start);
+	
+	libhal_free_string (mp);
+	libhal_free_string (dev);
+	
+	argv[0] = "/bin/sh";
+	argv[1] = "-c";
+	argv[2] = exec->str;
+	argv[3] = NULL;
+	
+	dbg ("executing command: %s\n", exec->str);
+	if (!g_spawn_async (g_get_home_dir (), argv, NULL, 0, NULL, NULL, NULL, &error)) {
+		warn ("failed to exec %s: %s", exec->str, error->message);
+		g_string_free (exec, TRUE);
+		g_error_free (error);
+		return FALSE;
+	}
+	
+	g_string_free (exec, TRUE);
+	
+	return TRUE;
+}
+
+/*
+ * gvm_ask_autorun - ask the user if they want to autorun a specific file
+ *
+ * Returns TRUE if the user selected 'Run' and FALSE otherwise
+ */
+static gboolean
+gvm_ask_autorun (const char *path)
+{
+	return gvm_prompt (GVM_PROMPT_AUTORUN, path) == GVM_RESPONSE_RUN;
+}
+
+static gboolean
+gvm_check_dir (const char *dirname, const char *name)
+{
+	gboolean exists = FALSE;
+	struct dirent *dent;
+	struct stat st;
+	char *path;
+	DIR *dir;
+	
+	if (!(dir = opendir (dirname)))
+		return FALSE;
+	
+	while ((dent = readdir (dir))) {
+		if (!g_ascii_strcasecmp (dent->d_name, name)) {
+			path = g_build_filename (dirname, dent->d_name, NULL);
+			if (stat (path, &st) == 0 && S_ISDIR (st.st_mode))
+				exists = TRUE;
+			g_free (path);
+			break;
+		}
+	}
+	
+	closedir (dir);
+	
+	return exists;
+}
+
+/*
+ * gvm_check_dvd - is this a Video DVD?  If so, do something about it.
+ *
+ * Returns TRUE if this was a Video DVD and FALSE otherwise.
+ */
+static gboolean
+gvm_check_dvd (const char *udi, const char *device, const char *mount_point)
+{
+	gboolean is_dvd;
+	
+	is_dvd = gvm_check_dir (mount_point, "video_ts");
+	
+	if (is_dvd && config.autoplay_dvd)
+		gvm_run_command (config.autoplay_dvd_command, udi, device, mount_point);
+	
+	return is_dvd;
+}
+
+/*
+ * gvm_check_vcd - is this a Video CD?  If so, do something about it.
+ *
+ * Returns TRUE if this was a Video CD and FALSE otherwise.
+ */
+static gboolean
+gvm_check_vcd (const char *udi, const char *device, const char *mount_point)
+{
+	gboolean is_vcd;
+	
+	is_vcd = gvm_check_dir (mount_point, "vcd");
+	
+	if (is_vcd && config.autoplay_dvd)
+		gvm_run_command (config.autoplay_vcd_command, udi, device, mount_point);
+	
+	return is_vcd;
+}
+
+/*
+ * gvm_udi_is_camera - checks if the udi is a camera device
+ *
+ * Returns TRUE if the device is a camera or FALSE otherwise.
+ */
+static gboolean
+gvm_udi_is_camera (const char *udi)
+{
+	char *storage_device = NULL, *physical_device = NULL;
+	gboolean is_camera = FALSE;
+	DBusError error;
+	
+	dbus_error_init (&error);
+	if (!(storage_device = libhal_device_get_property_string (hal_ctx, udi, "block.storage_device", &error))) {
+		warn ("cannot get block.storage_device property: %s", error.message);
+		if (dbus_error_is_set (&error))
+			dbus_error_free (&error);
+		goto out;
+	}
+	
+	if (!(physical_device = libhal_device_get_property_string (hal_ctx, storage_device, "storage.physical_device", &error))) {
+		warn ("cannot get storage.physical_device property: %s", error.message);
+		if (dbus_error_is_set (&error))
+			dbus_error_free (&error);
+		goto out;
+	}
+	
+	if ((is_camera = libhal_device_query_capability (hal_ctx, physical_device, "camera", NULL)))
+		dbg ("Camera detected: %s\n", udi);
+	
+ out:
+	
+	libhal_free_string (storage_device);
+	libhal_free_string (physical_device);
+	
+	return is_camera;
+}
+
+/*
+ * gvm_udi_is_ptp_camera - checks if the udi is a PTP camera device
+ *
+ * Returns TRUE if the device is a PTP camera with libgphoto2 support or FALSE otherwise.
+ */
+static gboolean
+gvm_udi_is_ptp_camera (const char *udi)
+{
+	gboolean is_ptp_camera = FALSE;
+	char *access_method;
+	
+	if (!libhal_device_query_capability (hal_ctx, udi, "camera", NULL))
+		return FALSE;
+	
+	if (!(access_method = libhal_device_get_property_string (hal_ctx, udi, "camera.access_method", NULL)))
+		return FALSE;
+	
+	if (!strcmp (access_method, "ptp")) {
+		dbg ("PTP Camera detected: %s\n", udi);
+		is_ptp_camera = TRUE;
+	}
+	
+	libhal_free_string (access_method);
+	
+	return is_ptp_camera;
+}
+
+/*
+ * gvm_udi_is_ipod - checks if the udi is the mountable volume of an iPod
+ *
+ * Returns TRUE if the device is an iPod or FALSE otherwise.
+ */
+static gboolean
+gvm_udi_is_ipod (const char *udi)
+{
+	char *product = NULL, *storage_device = NULL;
+	gboolean is_ipod = FALSE;
+	DBusError error;
+	
+	dbus_error_init (&error);
+	if (!(storage_device = libhal_device_get_property_string (hal_ctx, udi, "block.storage_device", &error))) {
+		warn ("cannot get block.storage_device property: %s", error.message);
+		if (dbus_error_is_set (&error))
+			dbus_error_free (&error);
+		goto out;
+	}
+	
+	if (!(product = libhal_device_get_property_string (hal_ctx, storage_device, "info.product", &error))) {
+		warn ("cannot get info.product property: %s", error.message);
+		if (dbus_error_is_set (&error))
+			dbus_error_free (&error);
+		goto out;
+	}
+	
+	if ((is_ipod = !strcmp (product, "iPod")))
+		dbg ("iPod detected: %s\n", udi);
+	else
+		dbg ("not an iPod: %s\n", udi);
+	
+ out:
+	
+	libhal_free_string (storage_device);
+	libhal_free_string (product);
+	
+	return is_ipod;
+}
+
+/*
+ * gvm_run_camera - launch the camera application
+ */
+static void
+gvm_run_camera (const char *udi, const char *device, const char *mount_point)
+{
+	if (config.autophoto_command != NULL)
+		gvm_run_command (config.autophoto_command, udi, device, mount_point);
+}
+
+/*
+ * gvm_check_photos - check if this device has a dcim directory
+ *
+ * Returns TRUE if there were photos on this device, FALSE otherwise
+ */
+static gboolean
+gvm_check_photos (const char *udi, const char *device, const char *mount_point)
+{
+	DBusError error;
+	
+	if (!gvm_check_dir (mount_point, "dcim"))
+		return FALSE;
+	
+	dbg ("Photos detected: %s\n", mount_point);
+	
+	/* add the "content.photos" capability to this device */
+	dbus_error_init (&error);
+	if (!libhal_device_add_capability (hal_ctx, udi, "content.photos", &error)) {
+		warn ("failed to set content.photos on %s: %s", device, error.message);
+		dbus_error_free (&error);
+	}
+	
+	return TRUE;
+}
+
+/*
+ * gvm_run_ipod - launch the ipod application
+ */
+static void
+gvm_run_ipod (const char *udi, const char *device, const char *mount_point)
+{
+	if (config.autoipod_command != NULL)
+		gvm_run_command (config.autoipod_command, udi, device, mount_point);
+}
+
+/*
+ * gvm_run_printer - launch the printer application
+ */
+static void
+gvm_run_printer (const char *udi)
+{
+	DBusError error;
+	char *device;
+	
+	if (config.autoprinter_command == NULL)
+		return;
+	
+	dbus_error_init (&error);
+	if (!(device = libhal_device_get_property_string (hal_ctx, udi, "printer.device", &error))) {
+		warn ("cannot get printer.device property: %s", error.message);
+		if (dbus_error_is_set (&error))
+			dbus_error_free (&error);
+		return;
+	}
+	
+	gvm_run_command (config.autoprinter_command, udi, device, NULL);
+	libhal_free_string (device);
+}
+
+/*
+ * gvm_autorun - automatically execute stuff
+ *
+ * we currently autorun: autorun files, video DVD's, and digital photos
+ */
+static void
+gvm_autorun (const char *udi, const char *device, const char *mount_point)
+{
+	gboolean autorun_succeeded = FALSE;
+	
+	if (gvm_check_dvd (udi, device, mount_point))
+		return;
+	
+	if (gvm_check_vcd (udi, device, mount_point))
+		return;
+	
+	if (config.autophoto && gvm_check_photos (udi, device, mount_point)) {
+		if (gvm_prompt (GVM_PROMPT_IMPORT_PHOTOS) == GVM_RESPONSE_IMPORT_PHOTOS) {
+			gvm_run_camera (udi, device, mount_point);
+			return;
+		}
+	}
+	
+	if (config.autorun && config.autorun_path) {
+		char **autorun_fns;
+		int i;
+		
+		autorun_fns = g_strsplit (config.autorun_path, ":", -1);
+		
+		for (i = 0; autorun_fns[i]; i++) {
+			char *path, *argv[2];
+			
+			path = g_strdup_printf ("%s/%s", mount_point, autorun_fns[i]);
+			argv[0] = path;
+			argv[1] = NULL;
+			
+			if (access (path, X_OK)) {
+				g_free (path);
+				continue;
+			}
+			
+			if (gvm_ask_autorun (path)) {
+				GError *error = NULL;
+				
+				g_spawn_async (g_get_home_dir (), argv, NULL,
+					       0, NULL, NULL, NULL, &error);
+				if (error)
+					warn ("failed to exec %s: %s", path, error->message);
+				else
+					autorun_succeeded = TRUE;
+				
+				g_free (path);
+				break;
+			}
+			
+			g_free (path);
+		}
+		
+		g_strfreev (autorun_fns);
+	}
+	
+	if (config.autobrowse && !autorun_succeeded)
+		gvm_run_command (NAUTILUS_COMMAND, udi, device, mount_point);
+}
+
+static gboolean
+gvm_udi_is_subfs_mount (const char *udi)
+{
+	int subfs = FALSE;
+	char **callouts;
+	int i;
+	
+	if ((callouts = libhal_device_get_property_strlist (hal_ctx, udi, "info.callouts.add", NULL))) {
+		for (i = 0; callouts[i] != NULL; i++) {
+			if (!strcmp (callouts[i], "hald-block-subfs")) {
+				dbg ("subfs to handle mounting of %s; skipping\n", udi);
+				subfs = TRUE;
+				break;
+			}
+		}
+		
+		libhal_free_string_array (callouts);
+	}
+	
+	return subfs;
+}
+
+/*
+ * gvm_device_mounted - called once a device has been
+ * mounted. Launches any user-specified applications that require the
+ * device to be mounted first (Mass-Storage cameras, iPods, CDs, DVDs,
+ * etc)
+ *
+ */
+static void
+gvm_device_mounted (const char *udi)
+{
+	char *device = NULL, *mount_point = NULL;
+	DBusError error;
+	
+	dbus_error_init (&error);
+	if (!(device = libhal_device_get_property_string (hal_ctx, udi, "block.device", &error))) {
+		warn ("cannot get block.device: %s", error.message);
+		if (dbus_error_is_set (&error))
+			dbus_error_free (&error);
+		goto out;
+	}
+	
+	if (!(mount_point = libhal_device_get_property_string (hal_ctx, udi, "volume.mount_point", &error))) {
+		warn ("cannot get volume.mount_point: %s", error.message);
+		if (dbus_error_is_set (&error))
+			dbus_error_free (&error);
+		goto out;
+	}
+	
+	/* this is where the magic happens */
+	if (config.autophoto && gvm_udi_is_camera (udi)) {
+		if (gvm_prompt (GVM_PROMPT_IMPORT_CAMERA) == GVM_RESPONSE_IMPORT_PHOTOS)
+			gvm_run_camera (udi, device, mount_point);
+	} else if (config.autoipod && gvm_udi_is_ipod (udi)) {
+		int action = GVM_RESPONSE_SYNC_MUSIC;
+		char *ipod_control;
+		
+		ipod_control = g_build_filename (mount_point, "iPod_Control", NULL);
+		
+		if (gvm_check_photos (udi, device, ipod_control)) {
+			/* we have ourselves an iPod Photo - need to prompt what to do */
+			action = gvm_prompt (GVM_PROMPT_IPOD_PHOTO);
+		}
+		
+		switch (action) {
+		case GVM_RESPONSE_IMPORT_PHOTOS:
+			gvm_run_camera (udi, device, ipod_control);
+			break;
+		case GVM_RESPONSE_SYNC_MUSIC:
+			gvm_run_ipod (udi, device, mount_point);
+			break;
+		default:
+			break;
+		}
+		
+		g_free (ipod_control);
+	} else {
+		gvm_autorun (udi, device, mount_point);
+	}
+ out:
+	
+	libhal_free_string (mount_point);
+	libhal_free_string (device);
+}
+
+
+/*
+ * gvm_device_mount - mount the given device.
+ *
+ * @return TRUE iff the mount was succesful
+ */
+static gboolean
+gvm_device_mount (const char *udi, const char *device, const char *mount_point)
+{
+	char *key;
+	
+	key = g_strdup (udi);
+	g_hash_table_insert (mount_table, key, key);
+	
+	dbg ("mounting %s...\n", udi);
+	if (gvm_run_command (MOUNT_COMMAND, udi, device, mount_point))
+		return TRUE;
+	
+	dbg ("mount failed: %s\n", udi);
+	g_hash_table_remove (mount_table, key);
+	g_free (key);
+	
+	return FALSE;
+}
+
+/*
+ * gvm_device_unmount - unmount the given device.
+ *
+ * @return TRUE iff the unmount was succesful
+ */
+static gboolean
+gvm_device_unmount (const char *udi, const char *device, const char *mount_point)
+{
+	return gvm_run_command (UNMOUNT_COMMAND, udi, device, mount_point);
+}
+
+/*
+ * gvm_run_cdplay - if so configured, execute the user-specified CD player on
+ * the given device node
+ */
+static void
+gvm_run_cdplayer (const char *udi, const char *device, const char *mount_point)
+{
+	if (config.autoplay_cda_command != NULL)
+		gvm_run_command (config.autoplay_cda_command, udi, device, mount_point);
+}
+
+/*
+ * gvm_ask_mixed - if a mixed mode CD (CD Plus) is inserted, we can either
+ * mount the data tracks or play the audio tracks.  How we handle that depends
+ * on the user's configuration.  If the configuration allows either option,
+ * we ask.
+ */
+static void
+gvm_ask_mixed (const char *udi)
+{
+	char *device = NULL, *mount_point = NULL;
+	int action = GVM_RESPONSE_NONE;
+	DBusError error;
+	
+	dbus_error_init (&error);
+	device = libhal_device_get_property_string (hal_ctx, udi, "block.device", &error);
+	if (!device) {
+		warn ("cannot get block.device: %s", error.message);
+		if (dbus_error_is_set (&error))
+			dbus_error_free (&error);
+		
+		goto out;
+	}
+	
+	if (config.automount_media && config.autoplay_cda) {
+		action = gvm_prompt (GVM_PROMPT_CDA_EXTRA);
+	} else if (config.automount_media) {
+		action = GVM_RESPONSE_BROWSE;
+	} else if (config.autoplay_cda) {
+		action = GVM_RESPONSE_PLAY;
+	}
+	
+	switch (action) {
+	case GVM_RESPONSE_BROWSE:
+		if (!gvm_udi_is_subfs_mount (udi))
+			gvm_device_mount (udi, device, NULL);
+		break;
+	case GVM_RESPONSE_PLAY:
+		gvm_run_cdplayer (udi, device, NULL);
+		break;
+	default:
+		break;
+	}
+	
+ out:
+	libhal_free_string (device);
+	libhal_free_string (mount_point);
+}
+
+enum {
+	WRITER_TYPE_NONE,
+	WRITER_TYPE_CDR,
+	WRITER_TYPE_DVD
+};
+
+/*
+ * gvm_device_is_writer - is this device capable of writing CDs/DVDs?
+ */
+static int
+gvm_device_is_writer (const char *udi)
+{
+	static const char *writers[] = {
+		"storage.cdrom.cdr",
+		"storage.cdrom.cdrw",
+		"storage.cdrom.dvdr",
+		"storage.cdrom.dvdram",
+		"storage.cdrom.dvdplusr",
+		"storage.cdrom.dvdplusrw"
+	};
+	size_t i;
+	
+	for (i = 0; i < G_N_ELEMENTS (writers); i++) {
+		if (libhal_device_get_property_bool (hal_ctx, udi, writers[i], NULL)) {
+			const char *type;
+			
+			type = strrchr (writers[i], '.') + 1;
+			if (!strncmp (type, "dvd", 3))
+				return WRITER_TYPE_DVD;
+			else
+				return WRITER_TYPE_CDR;
+		}
+	}
+	
+	return WRITER_TYPE_NONE;
+}
+
+
+/*
+ * gvm_run_cdburner - execute the user-specified CD burner command on the
+ * given device node, if so configured
+ */
+static void
+gvm_run_cdburner (const char *udi, int type, const char *device, const char *mount_point)
+{
+	const char *command;
+	int action;
+	
+	if (!config.autoburn)
+		return;
+	
+	if (type == WRITER_TYPE_DVD)
+		action = gvm_prompt (GVM_PROMPT_BURN_DVD);
+	else
+		action = gvm_prompt (GVM_PROMPT_BURN_CDR);
+	
+	switch (action) {
+	case GVM_RESPONSE_BURN_AUDIO_CD:
+		command = config.autoburn_audio_cd_command;
+		break;
+	case GVM_RESPONSE_BURN_PHOTO_CD:
+		command = config.autoburn_photo_cd_command;
+		break;
+	case GVM_RESPONSE_BURN_DATA_CD:
+		command = config.autoburn_data_cd_command;
+		break;
+	default:
+		return;
+	}
+	
+	gvm_run_command (command, udi, device, mount_point);
+}
+
+
+/*
+ * gvm_cdrom_policy - There has been a media change event on the CD-ROM
+ * associated with the given UDI.  Enforce policy.
+ */
+static void
+gvm_cdrom_policy (const char *udi)
+{
+	char *device = NULL;
+	char *drive_udi = NULL;
+	dbus_bool_t has_audio;
+	dbus_bool_t has_data;
+	dbus_bool_t is_blank;
+	DBusError error;
+	int type;
+	
+	dbus_error_init (&error);
+	has_audio = libhal_device_get_property_bool (hal_ctx, udi, "volume.disc.has_audio", NULL);
+	has_data = libhal_device_get_property_bool (hal_ctx, udi, "volume.disc.has_data", NULL);
+	is_blank = libhal_device_get_property_bool (hal_ctx, udi, "volume.disc.is_blank", NULL);
+	drive_udi = libhal_device_get_property_string (hal_ctx, udi, "info.parent", NULL);
+	device = libhal_device_get_property_string (hal_ctx, udi, "block.device", &error);
+	
+	if (!device) {
+		warn ("cannot get block.device: %s", error.message);
+		if (dbus_error_is_set (&error))
+			dbus_error_free (&error);
+		goto out;
+	}
+	
+	if (has_audio && !has_data) {
+		if (config.autoplay_cda)
+			gvm_run_cdplayer (udi, device, NULL);
+	} else if (has_audio && has_data) {
+		gvm_ask_mixed (udi);
+	} else if (has_data) {
+		if (config.automount_media && !gvm_udi_is_subfs_mount (udi))
+			gvm_device_mount (udi, device, NULL);
+	} else if (is_blank) {
+		if ((type = gvm_device_is_writer (drive_udi)))
+			gvm_run_cdburner (udi, type, device, NULL);
+	}
+	
+	/** @todo enforce policy for all the new disc types now supported */
+	
+ out:
+	libhal_free_string (device);
+	libhal_free_string (drive_udi);
+}
+
+/*
+ * gvm_media_changed - generic media change handler.
+ *
+ * This is called on a UDI and the media's parent device in response to a media
+ * change event.  We have to decipher the storage media type to run the
+ * appropriate media-present check.  Then, if there is indeed media in the
+ * drive, we enforce the appropriate policy.
+ *
+ * At the moment, we only handle CD-ROM and DVD drives.
+ *
+ * Returns TRUE if the device was handled or FALSE otherwise
+ */
+static gboolean
+gvm_media_changed (const char *udi, const char *storage_device)
+{
+	gboolean handled = FALSE;
+	char *media_type;
+	DBusError error;
+	
+	/* Refuse to enforce policy on removable media if drive is locked */
+	dbus_error_init (&error);
+	if (libhal_device_property_exists (hal_ctx, storage_device, "info.locked", NULL)
+	    && libhal_device_get_property_bool (hal_ctx, storage_device, "info.locked", NULL)) {
+		dbg ("Drive with udi %s is locked through hal; skipping policy\n", storage_device);
+		/* we return TRUE here because the device is locked - we can pretend we handled it */
+		return TRUE;
+	}
+	
+	/*
+	 * Get HAL's interpretation of our media type.  Note that we must check
+	 * the storage device and not this UDI
+	 */
+	if (!(media_type = libhal_device_get_property_string (hal_ctx, storage_device, "storage.drive_type", &error))) {
+		warn ("cannot get storage.drive_type: %s", error.message);
+		if (dbus_error_is_set (&error))
+			dbus_error_free (&error);
+		
+		/* FIXME: should we really return TRUE here? not sure... need a test case */
+		
+		return TRUE;
+	}
+	
+	if (!strcmp (media_type, "cdrom")) {
+		gvm_cdrom_policy (udi);
+		handled = TRUE;
+	}
+	
+	libhal_free_string (media_type);
+	
+	return handled;
+}
+
+
+/* Return whether or not the device should be automounted based on the
+ * HAL automount hint. Default to returning TRUE if the property isn't
+ * set for the device or its parent storage device. */
+static gboolean
+gvm_automount_enabled (const char *udi)
+{
+	gboolean enabled = TRUE;
+	DBusError error;
+	char *drive;
+	
+	if (libhal_device_property_exists (hal_ctx, udi, "storage.automount_enabled_hint", NULL))
+		return libhal_device_get_property_bool (hal_ctx, udi, "storage.automount_enabled_hint", NULL);
+	
+	dbus_error_init (&error);
+	if (!(drive = libhal_device_get_property_string (hal_ctx, udi, "info.parent", &error))) {
+		warn ("couldn't get info.parent for %s: %s", udi, error.message);
+		dbus_error_free (&error);
+		return TRUE; /* sensible default? */
+	}
+	
+	if (libhal_device_property_exists (hal_ctx, drive, "storage.automount_enabled_hint", NULL))
+		enabled = libhal_device_get_property_bool (hal_ctx, drive, "storage.automount_enabled_hint", NULL);
+	
+	libhal_free_string (drive);
+	
+	return enabled;
+}
+
+/** Invoked when a device is added to the Global Device List. 
+ *
+ *  @param  ctx                 LibHal context
+ *  @param  udi                 Universal Device Id
+ */
+static void
+hal_device_added (LibHalContext *ctx __attribute__((__unused__)),
+		  const char *udi)
+{
+	char *fsusage = NULL, *device = NULL, *storage_device = NULL;
+	DBusError error;
+	int mountable;
+	
+	dbg ("New Device: %s\n", udi);
+	
+	dbus_error_init (&error);
+	if (libhal_device_query_capability (hal_ctx, udi, "block", NULL)) {
+		/* is this a mountable volume? */
+		if (!(mountable = libhal_device_get_property_bool (hal_ctx, udi, "block.is_volume", NULL))) {
+			dbg ("not a mountable volume: %s\n", udi);
+		}
+		
+		/* if it is a volume, it must have a device node */
+		if (!(device = libhal_device_get_property_string (hal_ctx, udi, "block.device", &error))) {
+			dbg ("cannot get block.device: %s\n", error.message);
+			goto out;
+		}
+		
+		if (mountable) {
+			/* only mount if the block device has a sensible filesystem */
+			fsusage = libhal_device_get_property_string (ctx, udi, "volume.fsusage", &error);
+			if (!fsusage || strcmp (fsusage, "filesystem") != 0) {
+				dbg ("no sensible filesystem for %s\n", udi);
+				mountable = FALSE;
+			}
+		}
+		
+		/* get the backing storage device */
+		if (!(storage_device = libhal_device_get_property_string (hal_ctx, udi, "block.storage_device", &error))) {
+			dbg ("cannot get block.storage_device: %s\n", error.message);
+			goto out;
+		}
+		
+		/*
+		 * Does this device support removable media?  Note that we
+		 * check storage_device and not our own UDI
+		 */
+		if (libhal_device_get_property_bool (hal_ctx, storage_device, "storage.removable", NULL)) {
+			/* we handle media change events separately */
+			dbg ("Changed: %s\n", device);
+			if (gvm_media_changed (udi, storage_device))
+				goto out;
+		}
+		
+		if (config.automount_drives && mountable) {
+			if (!gvm_udi_is_subfs_mount (udi)) {
+				if (gvm_automount_enabled (udi)) {
+					gvm_device_mount (udi, device, NULL);
+				} else {
+					dbg ("storage.automount_enabled_hint set to false on %s, not mounting\n", udi);
+				}
+			}
+		}
+	} else if (libhal_device_query_capability (hal_ctx, udi, "input", NULL)) {
+		/* input device (keyboard, mouse, wacom tablet, etc...) */
+		const char *command = NULL;
+		int autoexec = FALSE;
+		char *device;
+		
+		if (libhal_device_query_capability (hal_ctx, udi, "input.keyboard", NULL)) {
+			autoexec = config.autokeyboard;
+			command = config.autokeyboard_command;
+		} else if (libhal_device_query_capability (hal_ctx, udi, "input.mouse", NULL)) {
+			autoexec = config.automouse;
+			command = config.automouse_command;
+		} else if (libhal_device_query_capability (hal_ctx, udi, "input.tablet", NULL)) {
+			autoexec = config.autotablet;
+			command = config.autotablet_command;
+		}
+		
+		if (autoexec && command) {
+			if ((device = libhal_device_get_property_string (hal_ctx, udi, "input.device", &error))) {
+				gvm_run_command (command, udi, device, NULL);
+				libhal_free_string (device);
+			} else {
+				warn ("cannot get input.device property: %s", error.message);
+			}
+		}
+	} else if (libhal_device_query_capability (hal_ctx, udi, "printer", NULL)) {
+		if (config.autoprinter)
+			gvm_run_printer (udi);
+	} else if (gvm_udi_is_ptp_camera (udi)) {
+		/* if the device is a PTP camera with libgphoto2 support, launch the user-specified application */
+		if (config.autophoto && gvm_prompt (GVM_PROMPT_IMPORT_CAMERA) == GVM_RESPONSE_IMPORT_PHOTOS)
+			gvm_run_camera (udi, NULL, NULL);
+	}
+	
+ out:
+	if (dbus_error_is_set (&error))
+		dbus_error_free (&error);
+	
+	libhal_free_string (device);
+	libhal_free_string (fsusage);
+	libhal_free_string (storage_device);
+}
+
+/** Invoked when a device is removed from the Global Device List. 
+ *
+ *  @param  ctx                 LibHal context
+ *  @param  udi                 Universal Device Id
+ */
+static void
+hal_device_removed (LibHalContext *ctx __attribute__((__unused__)), 
+		    const char *udi)
+{
+	dbg ("Device removed: %s\n", udi);
+}
+
+/** Invoked when device in the Global Device List acquires a new capability.
+ *
+ *  @param  ctx                 LibHal context
+ *  @param  udi                 Universal Device Id
+ *  @param  capability          Name of capability
+ */
+static void
+hal_device_new_capability (LibHalContext *ctx __attribute__((__unused__)),
+			   const char *udi __attribute__((__unused__)), 
+			   const char *capability __attribute__((__unused__)))
+{
+}
+
+/** Invoked when device in the Global Device List loses a capability.
+ *
+ *  @param  ctx                 LibHal context
+ *  @param  udi                 Universal Device Id
+ *  @param  capability          Name of capability
+ */
+static void
+hal_device_lost_capability (LibHalContext *ctx __attribute__((__unused__)),
+			    const char *udi __attribute__((__unused__)), 
+			    const char *capability __attribute__((__unused__)))
+{
+}
+
+/** Invoked when a property of a device in the Global Device List is
+ *  changed, and we have subscribed to changes for that device.
+ *
+ *  @param  ctx                 LibHal context
+ *  @param  udi                 Univerisal Device Id
+ *  @param  key                 Key of property
+ */
+static void
+hal_property_modified (LibHalContext *ctx __attribute__((__unused__)),
+		       const char *udi, 
+		       const char *key,
+		       dbus_bool_t is_removed __attribute__((__unused__)), 
+		       dbus_bool_t is_added __attribute__((__unused__)))
+{
+	char *mounting_udi;
+	dbus_bool_t val;
+	GSList *l, *n;
+	
+	if (strcmp (key, "volume.is_mounted") != 0)
+		return;
+	
+	val = libhal_device_get_property_bool (hal_ctx, udi, key, NULL);
+	
+	if (val == TRUE) {
+		dbg ("Mounted: %s\n", udi);
+		
+		if ((mounting_udi = g_hash_table_lookup (mount_table, udi))) {
+			g_hash_table_remove (mount_table, udi);
+			g_free (mounting_udi);
+			
+			/* add to list of all volumes mounted during lifetime */
+			mounted_volumes = g_slist_append (mounted_volumes, g_strdup (udi));
+		} else {
+			dbg ("not in mount queue: %s\n", udi);
+		}
+		
+		gvm_device_mounted (udi);
+	} else {
+		dbg ("Unmounted: %s\n", udi);
+		
+		/* remove from list of all volumes mounted during lifetime */
+		
+		for (l = mounted_volumes; l != NULL; l = n) {
+			n = g_slist_next (l);
+			if (strcmp (udi, (const char *) l->data) == 0) {
+				g_free (l->data);
+				mounted_volumes = g_slist_delete_link (mounted_volumes, l);
+				break;
+			}
+		}
+	}
+}
+
+/** Invoked when a device in the GDL emits a condition that cannot be
+ *  expressed in a property (like when the processor is overheating)
+ *
+ *  @param  ctx                 LibHal context
+ *  @param  udi                 Univerisal Device Id
+ *  @param  condition_name      Name of condition
+ *  @param  message             D-BUS message with parameters
+ */
+static void
+hal_device_condition (LibHalContext *ctx __attribute__((__unused__)),
+		      const char *udi __attribute__((__unused__)), 
+		      const char *condition_name __attribute__((__unused__)),
+		      const char *condition_detail __attribute__((__unused__)))
+{
+}
+
+/** Integrate a dbus mainloop. 
+ *
+ *  @param  ctx                 LibHal context
+ *  @param  error     		pointer to a D-BUS error object
+ *
+ *  @return 			TRUE if we connected to the bus
+ */
+static dbus_bool_t 
+hal_mainloop_integration (LibHalContext *ctx, DBusError *error)
+{
+	DBusConnection *dbus_connection;
+
+	dbus_connection = dbus_bus_get (DBUS_BUS_SYSTEM, error);
+
+	if (dbus_error_is_set (error))
+		return FALSE;
+	
+        dbus_connection_setup_with_g_main (dbus_connection, NULL);
+
+	libhal_ctx_set_dbus_connection (ctx, dbus_connection);
+	
+	return TRUE;
+}
+
+/** Internal HAL initialization function
+ *
+ * @return			The LibHalContext of the HAL connection or
+ *				NULL on error.
+ */
+static LibHalContext *
+gvm_do_hal_init (void)
+{
+	LibHalContext *ctx;
+	DBusError error;
+	char **devices;
+	int nr;
+	
+	if (!(ctx = libhal_ctx_new ())) {
+		warn ("failed to initialize HAL!");
+		return NULL;
+	}
+	
+	dbus_error_init (&error);
+	if (!hal_mainloop_integration (ctx, &error)) {
+		warn ("hal_initialize failed: %s", error.message);
+		dbus_error_free (&error);
+		return NULL;
+	}
+	
+	libhal_ctx_set_device_added (ctx, hal_device_added);
+	libhal_ctx_set_device_removed (ctx, hal_device_removed);
+	libhal_ctx_set_device_new_capability (ctx, hal_device_new_capability);
+	libhal_ctx_set_device_lost_capability (ctx, hal_device_lost_capability);
+	libhal_ctx_set_device_property_modified (ctx, hal_property_modified);
+	libhal_ctx_set_device_condition (ctx, hal_device_condition);
+	
+	if (!libhal_device_property_watch_all (ctx, &error)) {
+		warn ("failed to watch all HAL properties!: %s", error.message);
+		dbus_error_free (&error);
+		libhal_ctx_free (ctx);
+		return NULL;
+	}
+
+	if (!libhal_ctx_init (ctx, &error)) {
+		warn ("hal_initialize failed: %s", error.message);
+		dbus_error_free (&error);
+		libhal_ctx_free (ctx);
+		return NULL;
+	}
+
+	/*
+	 * Do something to ping the HAL daemon - the above functions will
+	 * succeed even if hald is not running, so long as DBUS is.  But we
+	 * want to exit silently if hald is not running, to behave on
+	 * pre-2.6 systems.
+	 */
+	if (!(devices = libhal_get_all_devices (ctx, &nr, &error))) {
+		warn ("seems that HAL is not running: %s", error.message);
+		dbus_error_free (&error);
+		
+		libhal_ctx_shutdown (ctx, NULL);
+		libhal_ctx_free (ctx);
+		return NULL;
+	}
+	
+	libhal_free_string_array (devices);
+
+	return ctx;
+}
+
+/** Attempt to mount all volumes; should be called on startup.
+ *
+ *  @param  ctx                 LibHal context
+ */
+static void
+mount_all (LibHalContext *ctx)
+{
+	char *prop, *dev, *udi, *drive;
+	int num_volumes, mount;
+	char **volumes;
+	DBusError error;
+	int i;
+	
+	if (!config.automount_media)
+		return;
+	
+	dbus_error_init (&error);
+	volumes = libhal_find_device_by_capability (ctx, "volume", &num_volumes, &error);
+	if (dbus_error_is_set (&error)) {
+		warn ("mount_all: could not find volume devices: %s", error.message);
+		dbus_error_free (&error);
+		return;
+	}
+	
+	for (i = 0; i < num_volumes; i++) {
+		udi = volumes[i];
+		
+		if (gvm_udi_is_subfs_mount (udi))
+			continue;
+		
+		/* don't attempt to mount already mounted volumes */
+		if (!libhal_device_property_exists (ctx, udi, "volume.is_mounted", NULL)
+		    || libhal_device_get_property_bool (ctx, udi, "volume.is_mounted", NULL))
+			continue;
+		
+		/* only mount if the block device has a sensible filesystem */
+		if (!libhal_device_property_exists (ctx, udi, "volume.fsusage", NULL))
+			continue;
+		prop = libhal_device_get_property_string (ctx, udi, "volume.fsusage", NULL);
+		if (!prop || strcmp (prop, "filesystem") != 0) {
+			libhal_free_string (prop);
+			continue;
+		}
+		libhal_free_string (prop);
+		
+		/* check our mounting policy */
+		if (!(drive = libhal_device_get_property_string (ctx, udi, "info.parent", NULL)))
+			continue;
+		
+		if (libhal_device_property_exists (ctx, drive, "storage.hotpluggable", NULL)
+		    && libhal_device_get_property_bool (ctx, drive, "storage.hotpluggable", NULL))
+			mount = config.automount_drives;
+		else if (libhal_device_property_exists (ctx, drive, "storage.removable", NULL)
+			 && libhal_device_get_property_bool (ctx, drive, "storage.removable", NULL))
+			mount = config.automount_media;
+		else
+			mount = TRUE;
+		libhal_free_string (drive);
+		
+		if (!mount)
+			continue;
+		
+		/* mount the device */
+		if ((dev = libhal_device_get_property_string (ctx, udi, "block.device", &error))) {
+			dbg ("mount_all: mounting %s\n", dev);
+			gvm_device_mount (udi, dev, NULL);
+			libhal_free_string (dev);
+		} else {
+			warn ("mount_all: no device for udi=%s: %s", udi, error.message);
+			if (dbus_error_is_set (&error))
+				dbus_error_free (&error);
+		}
+	}
+	
+	libhal_free_string_array (volumes);
+}
+
+/** Unmount all volumes that were mounted during the lifetime of this
+ *  g-v-m instance
+ *
+ *  @param  ctx                 LibHal context
+ */
+static void
+unmount_all (LibHalContext *ctx)
+{
+	char *udi, *device;
+	DBusError error;
+	GSList *l;
+	
+	dbg ("unmounting all volumes that we saw mounted in our life\n");
+	dbus_error_init (&error);
+	for (l = mounted_volumes; l != NULL; l = g_slist_next (l)) {
+		udi = l->data;
+		
+		if ((device = libhal_device_get_property_string (ctx, udi, "block.device", &error))) {
+			dbg ("unmount_all: unmounting %s\n", device);
+			gvm_device_unmount (udi, device, NULL);
+			libhal_free_string (device);
+		} else {
+			warn ("unmount_all: no device for udi=%s: %s", udi, error.message);
+			if (dbus_error_is_set (&error))
+				dbus_error_free (&error);
+		}
+	}
+}
+
+
+static int sigterm_unix_signal_pipe_fds[2];
+static GIOChannel *sigterm_iochn;
+
+static void 
+handle_sigterm (int value __attribute__((__unused__)))
+{
+	static char marker[1] = {'S'};
+
+	/* write a 'S' character to the other end to tell about
+	 * the signal. Note that 'the other end' is a GIOChannel thingy
+	 * that is only called from the mainloop - thus this is how we
+	 * defer this since UNIX signal handlers are evil
+	 *
+	 * Oh, and write(2) is indeed reentrant */
+	write (sigterm_unix_signal_pipe_fds[1], marker, 1);
+}
+
+static gboolean
+sigterm_iochn_data (GIOChannel *source, 
+		    GIOCondition condition __attribute__((__unused__)), 
+		    gpointer user_data __attribute__((__unused__)))
+{
+	GError *err = NULL;
+	gchar data[1];
+	gsize bytes_read;
+
+	/* Empty the pipe */
+	if (G_IO_STATUS_NORMAL != g_io_channel_read_chars (source, data, 1, &bytes_read, &err)) {
+		warn ("Error emptying callout notify pipe: %s", err->message);
+		g_error_free (err);
+		goto out;
+	}
+	
+	dbg ("Received SIGTERM, initiating shutdown\n");
+	
+	unmount_all (hal_ctx);
+	
+	gtk_main_quit ();
+	
+ out:
+	return TRUE;
+}
+
+static void
+gvm_die (GnomeClient *client __attribute__((__unused__)),
+	 gpointer user_data __attribute__((__unused__)))
+{
+	dbg ("Received 'die', initiating shutdown\n");
+	
+	unmount_all (hal_ctx);
+	
+	gtk_main_quit ();
+}
+
+int
+main (int argc, char *argv[])
+{
+	GnomeClient *client;
+	
+	gnome_program_init (PACKAGE, VERSION, LIBGNOMEUI_MODULE,
+			    argc, argv, GNOME_PARAM_NONE);
+	
+	bindtextdomain (PACKAGE, GNOMELOCALEDIR);
+	bind_textdomain_codeset (PACKAGE, "UTF-8");
+	textdomain (PACKAGE);
+	
+	client = gnome_master_client ();
+	if (gvm_get_clipboard ()) {
+		gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY);
+	} else {
+		gnome_client_set_restart_style (client, GNOME_RESTART_NEVER);
+		warn ("already running?");
+		return 1;
+	}
+	
+	g_signal_connect (client, "die", G_CALLBACK (gvm_die), NULL);
+	
+	hal_ctx = gvm_do_hal_init ();
+	if (!hal_ctx)
+		return 1;
+	
+	gvm_init_config ();
+	
+	/* SIGTERM handling via pipes  */
+	if (pipe (sigterm_unix_signal_pipe_fds) != 0) {
+		warn ("Could not setup pipe, errno=%d", errno);
+		return 1;
+	}
+	
+	sigterm_iochn = g_io_channel_unix_new (sigterm_unix_signal_pipe_fds[0]);
+	if (sigterm_iochn == NULL) {
+		warn ("Could not create GIOChannel");
+		return 1;
+	}
+	
+	g_io_add_watch (sigterm_iochn, G_IO_IN, sigterm_iochn_data, NULL);
+	signal (SIGTERM, handle_sigterm);
+	
+	mount_table = g_hash_table_new (g_str_hash, g_str_equal);
+	
+	mount_all (hal_ctx);
+	
+	gtk_main ();
+	
+	return 0;
+}

Attachment: signature.asc
Description: Digital signature



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]