summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOndrej Holy <oholy@redhat.com>2015-09-25 08:24:50 +0200
committerOndrej Holy <oholy@redhat.com>2015-09-30 15:34:51 +0200
commit27430703cb7ac571acaea927bd2dd24f4c45397d (patch)
tree711702bbf662e29ad0e09bd852ae70a9cec1181b
parent2eaaac903fe54a9c111888c7446b9f5f4dfdc36a (diff)
downloadgvfs-wip/oholy/storaged.tar.gz
Add storaged volume monitorwip/oholy/storaged
It is just copy of udisks2 volume monitor using storaged api.
-rw-r--r--configure.ac21
-rw-r--r--monitor/Makefile.am6
-rw-r--r--monitor/storaged/.gitignore1
-rw-r--r--monitor/storaged/Makefile.am56
-rw-r--r--monitor/storaged/gvfsstorageddrive.c987
-rw-r--r--monitor/storaged/gvfsstorageddrive.h52
-rw-r--r--monitor/storaged/gvfsstoragedmount.c1378
-rw-r--r--monitor/storaged/gvfsstoragedmount.h61
-rw-r--r--monitor/storaged/gvfsstoragedutils.c828
-rw-r--r--monitor/storaged/gvfsstoragedutils.h64
-rw-r--r--monitor/storaged/gvfsstoragedvolume.c1786
-rw-r--r--monitor/storaged/gvfsstoragedvolume.h67
-rw-r--r--monitor/storaged/gvfsstoragedvolumemonitor.c1885
-rw-r--r--monitor/storaged/gvfsstoragedvolumemonitor.h55
-rw-r--r--monitor/storaged/org.gtk.vfs.StoragedVolumeMonitor.service.in3
-rw-r--r--monitor/storaged/storaged.monitor5
-rw-r--r--monitor/storaged/storagedvolumemonitordaemon.c46
17 files changed, 7300 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac
index e5b4849d..628fb513 100644
--- a/configure.ac
+++ b/configure.ac
@@ -260,6 +260,25 @@ fi
AM_CONDITIONAL(USE_UDISKS2, [test "$msg_udisks2" = "yes"])
+dnl **************************
+dnl *** Check for storaged ***
+dnl **************************
+
+AC_ARG_ENABLE([storaged], [AS_HELP_STRING([--disable-storaged],[build without libstoraged])])
+msg_storaged=no
+STORAGED_REQUIRED=1.97
+
+if test "x$enable_storaged" != "xno"; then
+ PKG_CHECK_EXISTS([storaged >= $STORAGED_REQUIRED], [msg_storaged=yes])
+
+ if test "x$msg_storaged" = "xyes"; then
+ PKG_CHECK_MODULES([STORAGED],[storaged >= $STORAGED_REQUIRED])
+ AC_DEFINE([HAVE_STORAGED], 1, [Define to 1 if libstoraged is available])
+ fi
+fi
+
+AM_CONDITIONAL(USE_STORAGED, [test "$msg_storaged" = "yes"])
+
dnl **********************************
dnl *** Check for libsystemd-login ***
dnl **********************************
@@ -912,6 +931,7 @@ monitor/gphoto2/Makefile
monitor/afc/Makefile
monitor/mtp/Makefile
monitor/goa/Makefile
+monitor/storaged/Makefile
programs/Makefile
programs/completion/Makefile
man/Makefile
@@ -943,6 +963,7 @@ echo "
Build HAL volume monitor: $msg_hal (with fast init path: $have_hal_fast_init)
Build GDU volume monitor: $msg_gdu
Build udisks2 volume monitor: $msg_udisks2
+ Build storaged volume monitor: $msg_storaged
Build GOA volume monitor: $msg_goa
Use libsystemd-login: $msg_libsystemd_login
Use GCR: $msg_gcr
diff --git a/monitor/Makefile.am b/monitor/Makefile.am
index f7cf7a21..014cbf2f 100644
--- a/monitor/Makefile.am
+++ b/monitor/Makefile.am
@@ -1,4 +1,4 @@
-DIST_SUBDIRS = proxy hal gdu gphoto2 afc udisks2 mtp goa
+DIST_SUBDIRS = proxy hal gdu gphoto2 afc udisks2 mtp goa storaged
SUBDIRS = proxy
if USE_HAL
@@ -28,3 +28,7 @@ endif
if USE_GOA
SUBDIRS += goa
endif
+
+if USE_STORAGED
+SUBDIRS += storaged
+endif
diff --git a/monitor/storaged/.gitignore b/monitor/storaged/.gitignore
new file mode 100644
index 00000000..ba968b00
--- /dev/null
+++ b/monitor/storaged/.gitignore
@@ -0,0 +1 @@
+gvfs-storaged-volume-monitor
diff --git a/monitor/storaged/Makefile.am b/monitor/storaged/Makefile.am
new file mode 100644
index 00000000..398e1d79
--- /dev/null
+++ b/monitor/storaged/Makefile.am
@@ -0,0 +1,56 @@
+
+NULL =
+
+libexec_PROGRAMS = gvfs-storaged-volume-monitor
+
+gvfs_storaged_volume_monitor_SOURCES = \
+ storagedvolumemonitordaemon.c \
+ gvfsstoragedvolumemonitor.c gvfsstoragedvolumemonitor.h \
+ gvfsstorageddrive.c gvfsstorageddrive.h \
+ gvfsstoragedvolume.c gvfsstoragedvolume.h \
+ gvfsstoragedmount.c gvfsstoragedmount.h \
+ gvfsstoragedutils.c gvfsstoragedutils.h \
+ $(NULL)
+
+gvfs_storaged_volume_monitor_CFLAGS = \
+ -DG_LOG_DOMAIN=\"GVFS-Storaged\" \
+ -I$(top_srcdir)/common \
+ -I$(top_srcdir)/monitor/proxy \
+ $(GLIB_CFLAGS) \
+ $(STORAGED_CFLAGS) \
+ $(GUDEV_CFLAGS) \
+ $(LIBSYSTEMD_LOGIN_CFLAGS) \
+ $(KEYRING_CFLAGS) \
+ -DGIO_MODULE_DIR=\"$(GIO_MODULE_DIR)\" \
+ -DGVFS_LOCALEDIR=\""$(localedir)"\" \
+ -DG_DISABLE_DEPRECATED \
+ $(NULL)
+
+gvfs_storaged_volume_monitor_LDFLAGS = \
+ $(NULL)
+
+gvfs_storaged_volume_monitor_LDADD = \
+ $(GLIB_LIBS) \
+ $(STORAGED_LIBS) \
+ $(GUDEV_LIBS) \
+ $(LIBSYSTEMD_LOGIN_LIBS) \
+ $(KEYRING_LIBS) \
+ $(top_builddir)/common/libgvfscommon.la \
+ $(top_builddir)/common/libgvfscommon-monitor.la \
+ $(top_builddir)/monitor/proxy/libgvfsproxyvolumemonitordaemon-noin.la \
+ $(NULL)
+
+remote_volume_monitorsdir = $(datadir)/gvfs/remote-volume-monitors
+remote_volume_monitors_DATA = storaged.monitor
+
+servicedir = $(datadir)/dbus-1/services
+service_in_files = org.gtk.vfs.StoragedVolumeMonitor.service.in
+service_DATA = $(service_in_files:.service.in=.service)
+
+$(service_DATA): $(service_in_files) Makefile
+ $(AM_V_GEN) $(SED) -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@
+
+clean-local:
+ rm -f *~ *.loT $(BUILT_SOURCES) $(service_DATA)
+
+EXTRA_DIST = $(service_in_files) storaged.monitor
diff --git a/monitor/storaged/gvfsstorageddrive.c b/monitor/storaged/gvfsstorageddrive.c
new file mode 100644
index 00000000..de3d7f0a
--- /dev/null
+++ b/monitor/storaged/gvfsstorageddrive.c
@@ -0,0 +1,987 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2006-2012 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+
+#include "gvfsstoragedvolumemonitor.h"
+#include "gvfsstorageddrive.h"
+#include "gvfsstoragedvolume.h"
+#include "gvfsstoragedutils.h"
+
+typedef struct _GVfsStoragedDriveClass GVfsStoragedDriveClass;
+
+struct _GVfsStoragedDriveClass
+{
+ GObjectClass parent_class;
+};
+
+struct _GVfsStoragedDrive
+{
+ GObject parent;
+
+ GVfsStoragedVolumeMonitor *monitor; /* owned by volume monitor */
+ GList *volumes; /* entries in list are owned by volume monitor */
+
+ /* If TRUE, the drive was discovered at coldplug time */
+ gboolean coldplug;
+
+ StoragedDrive *storaged_drive;
+
+ GIcon *icon;
+ GIcon *symbolic_icon;
+ gchar *name;
+ gchar *sort_key;
+ gchar *device_file;
+ dev_t dev;
+ gboolean is_media_removable;
+ gboolean has_media;
+ gboolean can_eject;
+ gboolean can_stop;
+};
+
+static void gvfs_storaged_drive_drive_iface_init (GDriveIface *iface);
+
+static void on_storaged_drive_notify (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data);
+
+G_DEFINE_TYPE_EXTENDED (GVfsStoragedDrive, gvfs_storaged_drive, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (G_TYPE_DRIVE, gvfs_storaged_drive_drive_iface_init))
+
+static void
+gvfs_storaged_drive_finalize (GObject *object)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (object);
+ GList *l;
+
+ for (l = drive->volumes; l != NULL; l = l->next)
+ {
+ GVfsStoragedVolume *volume = l->data;
+ gvfs_storaged_volume_unset_drive (volume, drive);
+ }
+
+ if (drive->storaged_drive != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (drive->storaged_drive, on_storaged_drive_notify, drive);
+ g_object_unref (drive->storaged_drive);
+ }
+
+ g_clear_object (&drive->icon);
+ g_clear_object (&drive->symbolic_icon);
+ g_free (drive->name);
+ g_free (drive->sort_key);
+ g_free (drive->device_file);
+
+ G_OBJECT_CLASS (gvfs_storaged_drive_parent_class)->finalize (object);
+}
+
+static void
+gvfs_storaged_drive_class_init (GVfsStoragedDriveClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = gvfs_storaged_drive_finalize;
+}
+
+static void
+gvfs_storaged_drive_init (GVfsStoragedDrive *gdu_drive)
+{
+}
+
+static void
+emit_changed (GVfsStoragedDrive *drive)
+{
+ g_signal_emit_by_name (drive, "changed");
+ g_signal_emit_by_name (drive->monitor, "drive-changed", drive);
+}
+
+
+#if STORAGED_CHECK_VERSION(2,0,90)
+static gpointer
+_g_object_ref0 (gpointer object)
+{
+ if (object != NULL)
+ return g_object_ref (G_OBJECT (object));
+ else
+ return NULL;
+}
+#endif
+
+static gboolean
+update_drive (GVfsStoragedDrive *drive)
+{
+ StoragedClient *storaged_client;
+ gboolean changed;
+ GIcon *old_icon;
+ GIcon *old_symbolic_icon;
+ gchar *old_name;
+ gchar *old_sort_key;
+ gchar *old_device_file;
+ dev_t old_dev;
+ gboolean old_is_media_removable;
+ gboolean old_has_media;
+ gboolean old_can_eject;
+ gboolean old_can_stop;
+ StoragedBlock *block;
+#if STORAGED_CHECK_VERSION(2,0,90)
+ StoragedObjectInfo *info = NULL;
+#endif
+
+ storaged_client = gvfs_storaged_volume_monitor_get_storaged_client (drive->monitor);
+
+ /* ---------------------------------------------------------------------------------------------------- */
+ /* save old values */
+
+ old_is_media_removable = drive->is_media_removable;
+ old_has_media = drive->has_media;
+ old_can_eject = drive->can_eject;
+ old_can_stop = drive->can_stop;
+
+ old_name = g_strdup (drive->name);
+ old_sort_key = g_strdup (drive->sort_key);
+ old_device_file = g_strdup (drive->device_file);
+ old_dev = drive->dev;
+ old_icon = drive->icon != NULL ? g_object_ref (drive->icon) : NULL;
+ old_symbolic_icon = drive->symbolic_icon != NULL ? g_object_ref (drive->symbolic_icon) : NULL;
+
+ /* ---------------------------------------------------------------------------------------------------- */
+ /* reset */
+
+ drive->is_media_removable = drive->has_media = drive->can_eject = drive->can_stop = FALSE;
+ g_free (drive->name); drive->name = NULL;
+ g_free (drive->sort_key); drive->sort_key = NULL;
+ g_free (drive->device_file); drive->device_file = NULL;
+ drive->dev = 0;
+ g_clear_object (&drive->icon);
+ g_clear_object (&drive->symbolic_icon);
+
+ /* ---------------------------------------------------------------------------------------------------- */
+ /* in with the new */
+
+ block = storaged_client_get_block_for_drive (storaged_client,
+ drive->storaged_drive,
+ FALSE);
+ if (block != NULL)
+ {
+ drive->device_file = storaged_block_dup_device (block);
+ drive->dev = storaged_block_get_device_number (block);
+
+ g_object_unref (block);
+ }
+
+ drive->sort_key = g_strdup (storaged_drive_get_sort_key (drive->storaged_drive));
+
+ drive->is_media_removable = storaged_drive_get_media_removable (drive->storaged_drive);
+ if (drive->is_media_removable)
+ {
+ drive->has_media = storaged_drive_get_media_available (drive->storaged_drive);
+ }
+ else
+ {
+ drive->has_media = TRUE;
+ }
+ drive->can_eject = storaged_drive_get_ejectable (drive->storaged_drive);
+
+#if STORAGED_CHECK_VERSION(2,0,90)
+ {
+ StoragedObject *object = (StoragedObject *) g_dbus_interface_get_object (G_DBUS_INTERFACE (drive->storaged_drive));
+ if (object != NULL)
+ {
+ info = storaged_client_get_object_info (storaged_client, object);
+ if (info != NULL)
+ {
+ drive->name = g_strdup (storaged_object_info_get_name (info));
+ drive->icon = _g_object_ref0 (storaged_object_info_get_icon (info));
+ drive->symbolic_icon = _g_object_ref0 (storaged_object_info_get_icon_symbolic (info));
+ }
+ }
+ }
+#else
+ storaged_client_get_drive_info (storaged_client,
+ drive->storaged_drive,
+ NULL, /* drive_name */
+ &drive->name,
+ &drive->icon,
+ NULL, /* media_desc */
+ NULL); /* media_icon */
+#endif
+
+#if STORAGED_CHECK_VERSION(2,0,90)
+ {
+ /* If can_stop is TRUE, then
+ *
+ * - the GUI (e.g. Files, Shell) will call GDrive.stop() whenever the
+ * user presses the Eject icon, which will result in:
+ *
+ * - us calling StoragedDrive.PowerOff() on GDrive.stop(), which
+ * will result in:
+ *
+ * - Storaged asking the kernel to power off the USB port the drive
+ * is connected to, which will result in
+ *
+ * - Most drives powering off (especially true for bus-powered
+ * drives such as 2.5" HDDs and USB sticks), which will result in
+ *
+ * - Users feeling warm and cozy when they see the LED on the
+ * device turn off (win)
+ *
+ * Obviously this is unwanted if
+ *
+ * - the drive is using removable media (e.g. optical discs,
+ * flash media etc); or
+ *
+ * - the device is internal
+ *
+ * So for the latter, only do this for drives we appear *during*
+ * the login session. Note that this heuristic has the nice
+ * side-effect that USB-attached hard disks that are plugged in
+ * when the computer starts up will not be powered off when the
+ * user clicks the "eject" icon.
+ */
+ if (!drive->is_media_removable && !drive->coldplug)
+ {
+ if (storaged_drive_get_can_power_off (drive->storaged_drive))
+ {
+ drive->can_stop = TRUE;
+ }
+ }
+ }
+#endif
+
+ /* ---------------------------------------------------------------------------------------------------- */
+ /* fallbacks */
+
+ /* Never use empty/blank names (#582772) */
+ if (drive->name == NULL || strlen (drive->name) == 0)
+ {
+ if (drive->device_file != NULL)
+ drive->name = g_strdup_printf (_("Unnamed Drive (%s)"), drive->device_file);
+ else
+ drive->name = g_strdup (_("Unnamed Drive"));
+ }
+ if (drive->icon == NULL)
+ drive->icon = g_themed_icon_new ("drive-removable-media");
+ if (drive->symbolic_icon == NULL)
+ drive->symbolic_icon = g_themed_icon_new ("drive-removable-media-symbolic");
+
+ /* ---------------------------------------------------------------------------------------------------- */
+ /* compute whether something changed */
+ changed = !((old_is_media_removable == drive->is_media_removable) &&
+ (old_has_media == drive->has_media) &&
+ (old_can_eject == drive->can_eject) &&
+ (old_can_stop == drive->can_stop) &&
+ (g_strcmp0 (old_name, drive->name) == 0) &&
+ (g_strcmp0 (old_sort_key, drive->sort_key) == 0) &&
+ (g_strcmp0 (old_device_file, drive->device_file) == 0) &&
+ (old_dev == drive->dev) &&
+ g_icon_equal (old_icon, drive->icon) &&
+ g_icon_equal (old_symbolic_icon, drive->symbolic_icon)
+ );
+
+ /* free old values */
+ g_free (old_name);
+ g_free (old_sort_key);
+ g_free (old_device_file);
+ g_clear_object (&old_icon);
+ g_clear_object (&old_symbolic_icon);
+
+ /*g_debug ("in update_drive(); has_media=%d changed=%d", drive->has_media, changed);*/
+
+#if STORAGED_CHECK_VERSION(2,0,90)
+ g_clear_object (&info);
+#endif
+
+ return changed;
+}
+
+static void
+on_storaged_drive_notify (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (user_data);
+ if (update_drive (drive))
+ emit_changed (drive);
+}
+
+GVfsStoragedDrive *
+gvfs_storaged_drive_new (GVfsStoragedVolumeMonitor *monitor,
+ StoragedDrive *storaged_drive,
+ gboolean coldplug)
+{
+ GVfsStoragedDrive *drive;
+
+ drive = g_object_new (GVFS_TYPE_STORAGED_DRIVE, NULL);
+ drive->monitor = monitor;
+ drive->coldplug = coldplug;
+
+ drive->storaged_drive = g_object_ref (storaged_drive);
+ g_signal_connect (drive->storaged_drive,
+ "notify",
+ G_CALLBACK (on_storaged_drive_notify),
+ drive);
+
+ update_drive (drive);
+
+ return drive;
+}
+
+void
+gvfs_storaged_drive_disconnected (GVfsStoragedDrive *drive)
+{
+ GList *l, *volumes;
+
+ volumes = drive->volumes;
+ drive->volumes = NULL;
+ for (l = volumes; l != NULL; l = l->next)
+ {
+ GVfsStoragedVolume *volume = l->data;
+ gvfs_storaged_volume_unset_drive (volume, drive);
+ }
+ g_list_free (volumes);
+}
+
+void
+gvfs_storaged_drive_set_volume (GVfsStoragedDrive *drive,
+ GVfsStoragedVolume *volume)
+{
+ if (g_list_find (drive->volumes, volume) == NULL)
+ {
+ drive->volumes = g_list_prepend (drive->volumes, volume);
+ emit_changed (drive);
+ }
+}
+
+void
+gvfs_storaged_drive_unset_volume (GVfsStoragedDrive *drive,
+ GVfsStoragedVolume *volume)
+{
+ GList *l;
+ l = g_list_find (drive->volumes, volume);
+ if (l != NULL)
+ {
+ drive->volumes = g_list_delete_link (drive->volumes, l);
+ emit_changed (drive);
+ }
+}
+
+static GIcon *
+gvfs_storaged_drive_get_icon (GDrive *_drive)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+ return drive->icon != NULL ? g_object_ref (drive->icon) : NULL;
+}
+
+static GIcon *
+gvfs_storaged_drive_get_symbolic_icon (GDrive *_drive)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+ return drive->symbolic_icon != NULL ? g_object_ref (drive->symbolic_icon) : NULL;
+}
+
+static char *
+gvfs_storaged_drive_get_name (GDrive *_drive)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+ return g_strdup (drive->name);
+}
+
+static GList *
+gvfs_storaged_drive_get_volumes (GDrive *_drive)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+ GList *l;
+ l = g_list_copy (drive->volumes);
+ g_list_foreach (l, (GFunc) g_object_ref, NULL);
+ return l;
+}
+
+static gboolean
+gvfs_storaged_drive_has_volumes (GDrive *_drive)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+ gboolean res;
+ res = drive->volumes != NULL;
+ return res;
+}
+
+static gboolean
+gvfs_storaged_drive_is_media_removable (GDrive *_drive)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+ return drive->is_media_removable;
+}
+
+static gboolean
+gvfs_storaged_drive_has_media (GDrive *_drive)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+ return drive->has_media;
+}
+
+static gboolean
+gvfs_storaged_drive_is_media_check_automatic (GDrive *_drive)
+{
+ return TRUE;
+}
+
+static gboolean
+gvfs_storaged_drive_can_eject (GDrive *_drive)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+ return drive->can_eject;
+}
+
+static gboolean
+gvfs_storaged_drive_can_poll_for_media (GDrive *_drive)
+{
+ return FALSE;
+}
+
+static gboolean
+gvfs_storaged_drive_can_start (GDrive *_drive)
+{
+ return FALSE;
+}
+
+static gboolean
+gvfs_storaged_drive_can_start_degraded (GDrive *_drive)
+{
+ return FALSE;
+}
+
+static gboolean
+gvfs_storaged_drive_can_stop (GDrive *_drive)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+ return drive->can_stop;
+}
+
+static GDriveStartStopType
+gvfs_storaged_drive_get_start_stop_type (GDrive *_drive)
+{
+ return G_DRIVE_START_STOP_TYPE_SHUTDOWN;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static char *
+gvfs_storaged_drive_get_identifier (GDrive *_drive,
+ const gchar *kind)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+ gchar *ret = NULL;
+
+ if (drive->device_file != NULL)
+ {
+ if (g_strcmp0 (kind, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE) == 0)
+ ret = g_strdup (drive->device_file);
+ }
+ return ret;
+}
+
+static gchar **
+gvfs_storaged_drive_enumerate_identifiers (GDrive *_drive)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+ GPtrArray *p;
+
+ p = g_ptr_array_new ();
+ if (drive->device_file != NULL)
+ g_ptr_array_add (p, g_strdup (G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE));
+ g_ptr_array_add (p, NULL);
+
+ return (gchar **) g_ptr_array_free (p, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef void (*UnmountsMountsFunc) (GDrive *drive,
+ GMountOperation *mount_operation,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data,
+ gpointer on_all_unmounted_data);
+
+typedef struct {
+ GDrive *drive;
+ GAsyncReadyCallback callback;
+ gpointer user_data;
+ GMountOperation *mount_operation;
+ GCancellable *cancellable;
+ GMountUnmountFlags flags;
+
+ GList *pending_mounts;
+
+ UnmountsMountsFunc on_all_unmounted;
+ gpointer on_all_unmounted_data;
+} UnmountMountsOp;
+
+static void
+free_unmount_mounts_op (UnmountMountsOp *data)
+{
+ g_list_free_full (data->pending_mounts, g_object_unref);
+
+ g_object_unref (data->drive);
+ g_free (data);
+}
+
+static void
+unmount_mounts_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data);
+
+static void
+unmount_mounts_do (UnmountMountsOp *data)
+{
+ if (data->pending_mounts == NULL)
+ {
+ data->on_all_unmounted (data->drive,
+ data->mount_operation,
+ data->cancellable,
+ data->callback,
+ data->user_data,
+ data->on_all_unmounted_data);
+
+ free_unmount_mounts_op (data);
+ }
+ else
+ {
+ GMount *mount;
+ mount = data->pending_mounts->data;
+ data->pending_mounts = g_list_remove (data->pending_mounts, mount);
+
+ g_mount_unmount_with_operation (mount,
+ data->flags,
+ data->mount_operation,
+ data->cancellable,
+ unmount_mounts_cb,
+ data);
+ }
+}
+
+static void
+unmount_mounts_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ UnmountMountsOp *data = user_data;
+ GMount *mount = G_MOUNT (source_object);
+ GSimpleAsyncResult *simple;
+ GError *error = NULL;
+
+ if (!g_mount_unmount_with_operation_finish (mount, res, &error))
+ {
+ /* make the error dialog more targeted to the drive.. unless the user has already seen a dialog */
+ if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_BUSY)
+ {
+ g_error_free (error);
+ error = g_error_new (G_IO_ERROR,
+ G_IO_ERROR_BUSY,
+ _("Failed to eject medium; one or more volumes on the medium are busy."));
+ }
+
+ if (data->mount_operation != NULL)
+ gvfs_storaged_unmount_notify_stop (data->mount_operation);
+
+ /* unmount failed; need to fail the whole eject operation */
+ simple = g_simple_async_result_new_from_error (G_OBJECT (data->drive),
+ data->callback,
+ data->user_data,
+ error);
+ g_error_free (error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+
+ free_unmount_mounts_op (data);
+ }
+ else
+ {
+ /* move on to the next mount.. */
+ unmount_mounts_do (data);
+ }
+ g_object_unref (mount);
+}
+
+static void
+unmount_mounts (GVfsStoragedDrive *drive,
+ GMountUnmountFlags flags,
+ GMountOperation *mount_operation,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data,
+ UnmountsMountsFunc on_all_unmounted,
+ gpointer on_all_unmounted_data)
+{
+ GMount *mount;
+ UnmountMountsOp *data;
+ GList *l;
+
+ data = g_new0 (UnmountMountsOp, 1);
+ data->drive = g_object_ref (drive);
+ data->mount_operation = mount_operation;
+ data->cancellable = cancellable;
+ data->callback = callback;
+ data->user_data = user_data;
+ data->flags = flags;
+ data->on_all_unmounted = on_all_unmounted;
+ data->on_all_unmounted_data = on_all_unmounted_data;
+
+ for (l = drive->volumes; l != NULL; l = l->next)
+ {
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (l->data);
+ mount = g_volume_get_mount (G_VOLUME (volume));
+ if (mount != NULL && g_mount_can_unmount (mount))
+ data->pending_mounts = g_list_prepend (data->pending_mounts, g_object_ref (mount));
+ }
+
+ unmount_mounts_do (data);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GSimpleAsyncResult *simple;
+
+ GVfsStoragedDrive *drive;
+ GMountOperation *mount_operation;
+} EjectData;
+
+static void
+eject_data_free (EjectData *data)
+{
+ g_object_unref (data->simple);
+ g_clear_object (&data->drive);
+ g_clear_object (&data->mount_operation);
+
+ g_free (data);
+}
+
+static void
+eject_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ EjectData *data = user_data;
+ GError *error;
+
+ error = NULL;
+ if (!storaged_drive_call_eject_finish (STORAGED_DRIVE (source_object), res, &error))
+ {
+ gvfs_storaged_utils_storaged_error_to_gio_error (error);
+ g_simple_async_result_take_error (data->simple, error);
+ }
+
+ if (data->mount_operation != NULL)
+ {
+ /* If we fail send an ::aborted signal to make any notification go away */
+ if (error != NULL)
+ g_signal_emit_by_name (data->mount_operation, "aborted");
+
+ gvfs_storaged_unmount_notify_stop (data->mount_operation);
+ }
+
+ g_simple_async_result_complete (data->simple);
+ eject_data_free (data);
+}
+
+static void
+gvfs_storaged_drive_eject_on_all_unmounted (GDrive *_drive,
+ GMountOperation *mount_operation,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data,
+ gpointer on_all_unmounted_data)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+ GVariantBuilder builder;
+ EjectData *data;
+
+ data = g_new0 (EjectData, 1);
+ data->simple = g_simple_async_result_new (G_OBJECT (drive),
+ callback,
+ user_data,
+ gvfs_storaged_drive_eject_on_all_unmounted);
+ data->drive = g_object_ref (drive);
+ if (mount_operation != NULL)
+ data->mount_operation = g_object_ref (mount_operation);
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+ if (mount_operation == NULL)
+ {
+ g_variant_builder_add (&builder,
+ "{sv}",
+ "auth.no_user_interaction", g_variant_new_boolean (TRUE));
+ }
+ storaged_drive_call_eject (drive->storaged_drive,
+ g_variant_builder_end (&builder),
+ cancellable,
+ eject_cb,
+ data);
+}
+
+static void
+gvfs_storaged_drive_eject_with_operation (GDrive *_drive,
+ GMountUnmountFlags flags,
+ GMountOperation *mount_operation,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+
+ /* This information is needed in GVfsDdisks2Volume when apps have
+ * open files on the device ... we need to know if the button should
+ * be "Unmount Anyway", "Eject Anyway" or "Power Off Anyway"
+ */
+ if (mount_operation != NULL)
+ {
+ g_object_set_data (G_OBJECT (mount_operation), "x-storaged-is-eject", GINT_TO_POINTER (1));
+ gvfs_storaged_unmount_notify_start (mount_operation, NULL, _drive, FALSE);
+ }
+
+ /* first we need to go through all the volumes and unmount their assoicated mounts (if any) */
+ unmount_mounts (drive,
+ flags,
+ mount_operation,
+ cancellable,
+ callback,
+ user_data,
+ gvfs_storaged_drive_eject_on_all_unmounted,
+ NULL);
+}
+
+static gboolean
+gvfs_storaged_drive_eject_with_operation_finish (GDrive *drive,
+ GAsyncResult *result,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+}
+
+static void
+gvfs_storaged_drive_eject (GDrive *drive,
+ GMountUnmountFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ gvfs_storaged_drive_eject_with_operation (drive, flags, NULL, cancellable, callback, user_data);
+}
+
+static gboolean
+gvfs_storaged_drive_eject_finish (GDrive *drive,
+ GAsyncResult *result,
+ GError **error)
+{
+ return gvfs_storaged_drive_eject_with_operation_finish (drive, result, error);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#if STORAGED_CHECK_VERSION(2,0,90)
+
+typedef struct
+{
+ GSimpleAsyncResult *simple;
+
+ GVfsStoragedDrive *drive;
+ GMountOperation *mount_operation;
+} StopData;
+
+static void
+stop_data_free (StopData *data)
+{
+ g_object_unref (data->simple);
+ g_clear_object (&data->drive);
+ g_clear_object (&data->mount_operation);
+
+ g_free (data);
+}
+
+static void
+power_off_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ StopData *data = user_data;
+ GError *error;
+
+ error = NULL;
+ if (!storaged_drive_call_power_off_finish (STORAGED_DRIVE (source_object), res, &error))
+ {
+ gvfs_storaged_utils_storaged_error_to_gio_error (error);
+ g_simple_async_result_take_error (data->simple, error);
+ }
+
+ if (data->mount_operation != NULL)
+ {
+ /* If we fail send an ::aborted signal to make any notification go away */
+ if (error != NULL)
+ g_signal_emit_by_name (data->mount_operation, "aborted");
+
+ gvfs_storaged_unmount_notify_stop (data->mount_operation);
+ }
+
+ g_simple_async_result_complete (data->simple);
+ stop_data_free (data);
+}
+
+static void
+gvfs_storaged_drive_stop_on_all_unmounted (GDrive *_drive,
+ GMountOperation *mount_operation,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data,
+ gpointer on_all_unmounted_data)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+ GVariantBuilder builder;
+ StopData *data;
+
+ data = g_new0 (StopData, 1);
+ data->simple = g_simple_async_result_new (G_OBJECT (drive),
+ callback,
+ user_data,
+ gvfs_storaged_drive_stop_on_all_unmounted);
+ data->drive = g_object_ref (drive);
+ if (mount_operation != NULL)
+ data->mount_operation = g_object_ref (mount_operation);
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+ if (mount_operation == NULL)
+ {
+ g_variant_builder_add (&builder,
+ "{sv}",
+ "auth.no_user_interaction", g_variant_new_boolean (TRUE));
+ }
+ storaged_drive_call_power_off (drive->storaged_drive,
+ g_variant_builder_end (&builder),
+ cancellable,
+ power_off_cb,
+ data);
+}
+
+static void
+gvfs_storaged_drive_stop (GDrive *_drive,
+ GMountUnmountFlags flags,
+ GMountOperation *mount_operation,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+
+ /* This information is needed in GVfsDdisks2Volume when apps have
+ * open files on the device ... we need to know if the button should
+ * be "Unmount Anyway", "Eject Anyway" or "Power Off Anyway"
+ */
+ if (mount_operation != NULL)
+ {
+ g_object_set_data (G_OBJECT (mount_operation), "x-storaged-is-stop", GINT_TO_POINTER (1));
+ gvfs_storaged_unmount_notify_start (mount_operation, NULL, _drive, FALSE);
+ }
+
+ /* first we need to go through all the volumes and unmount their assoicated mounts (if any) */
+ unmount_mounts (drive,
+ flags,
+ mount_operation,
+ cancellable,
+ callback,
+ user_data,
+ gvfs_storaged_drive_stop_on_all_unmounted,
+ NULL);
+}
+
+static gboolean
+gvfs_storaged_drive_stop_finish (GDrive *drive,
+ GAsyncResult *result,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+}
+
+#endif /* STORAGED_CHECK_VERSION(2,0,90) */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const gchar *
+gvfs_storaged_drive_get_sort_key (GDrive *_drive)
+{
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (_drive);
+ return drive->sort_key;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+gvfs_storaged_drive_drive_iface_init (GDriveIface *iface)
+{
+ iface->get_name = gvfs_storaged_drive_get_name;
+ iface->get_icon = gvfs_storaged_drive_get_icon;
+ iface->get_symbolic_icon = gvfs_storaged_drive_get_symbolic_icon;
+ iface->has_volumes = gvfs_storaged_drive_has_volumes;
+ iface->get_volumes = gvfs_storaged_drive_get_volumes;
+ iface->is_media_removable = gvfs_storaged_drive_is_media_removable;
+ iface->has_media = gvfs_storaged_drive_has_media;
+ iface->is_media_check_automatic = gvfs_storaged_drive_is_media_check_automatic;
+ iface->can_eject = gvfs_storaged_drive_can_eject;
+ iface->can_poll_for_media = gvfs_storaged_drive_can_poll_for_media;
+ iface->get_identifier = gvfs_storaged_drive_get_identifier;
+ iface->enumerate_identifiers = gvfs_storaged_drive_enumerate_identifiers;
+ iface->get_start_stop_type = gvfs_storaged_drive_get_start_stop_type;
+ iface->can_start = gvfs_storaged_drive_can_start;
+ iface->can_start_degraded = gvfs_storaged_drive_can_start_degraded;
+ iface->can_stop = gvfs_storaged_drive_can_stop;
+ iface->eject = gvfs_storaged_drive_eject;
+ iface->eject_finish = gvfs_storaged_drive_eject_finish;
+ iface->eject_with_operation = gvfs_storaged_drive_eject_with_operation;
+ iface->eject_with_operation_finish = gvfs_storaged_drive_eject_with_operation_finish;
+ iface->get_sort_key = gvfs_storaged_drive_get_sort_key;
+#if 0
+ iface->poll_for_media = gvfs_storaged_drive_poll_for_media;
+ iface->poll_for_media_finish = gvfs_storaged_drive_poll_for_media_finish;
+ iface->start = gvfs_storaged_drive_start;
+ iface->start_finish = gvfs_storaged_drive_start_finish;
+#endif
+
+#if STORAGED_CHECK_VERSION(2,0,90)
+ iface->stop = gvfs_storaged_drive_stop;
+ iface->stop_finish = gvfs_storaged_drive_stop_finish;
+#endif
+}
+
+StoragedDrive *
+gvfs_storaged_drive_get_storaged_drive (GVfsStoragedDrive *drive)
+{
+ return drive->storaged_drive;
+}
diff --git a/monitor/storaged/gvfsstorageddrive.h b/monitor/storaged/gvfsstorageddrive.h
new file mode 100644
index 00000000..1b765296
--- /dev/null
+++ b/monitor/storaged/gvfsstorageddrive.h
@@ -0,0 +1,52 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2006-2012 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __GVFS_STORAGED_DRIVE_H__
+#define __GVFS_STORAGED_DRIVE_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "gvfsstoragedvolumemonitor.h"
+
+G_BEGIN_DECLS
+
+#define GVFS_TYPE_STORAGED_DRIVE (gvfs_storaged_drive_get_type ())
+#define GVFS_STORAGED_DRIVE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVFS_TYPE_STORAGED_DRIVE, GVfsStoragedDrive))
+#define GVFS_IS_STORAGED_DRIVE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVFS_TYPE_STORAGED_DRIVE))
+
+GType gvfs_storaged_drive_get_type (void) G_GNUC_CONST;
+GVfsStoragedDrive *gvfs_storaged_drive_new (GVfsStoragedVolumeMonitor *monitor,
+ StoragedDrive *storaged_drive,
+ gboolean coldplug);
+void gvfs_storaged_drive_disconnected (GVfsStoragedDrive *drive);
+
+void gvfs_storaged_drive_set_volume (GVfsStoragedDrive *drive,
+ GVfsStoragedVolume *volume);
+void gvfs_storaged_drive_unset_volume (GVfsStoragedDrive *drive,
+ GVfsStoragedVolume *volume);
+StoragedDrive *gvfs_storaged_drive_get_storaged_drive (GVfsStoragedDrive *drive);
+
+G_END_DECLS
+
+#endif /* __GVFS_STORAGED_DRIVE_H__ */
diff --git a/monitor/storaged/gvfsstoragedmount.c b/monitor/storaged/gvfsstoragedmount.c
new file mode 100644
index 00000000..f15048cd
--- /dev/null
+++ b/monitor/storaged/gvfsstoragedmount.c
@@ -0,0 +1,1378 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2006-2012 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+
+#include <gvfsmountinfo.h>
+
+#include <gudev/gudev.h>
+
+#include "gvfsstoragedvolumemonitor.h"
+#include "gvfsstoragedmount.h"
+#include "gvfsstoragedvolume.h"
+#include "gvfsstorageddrive.h"
+#include "gvfsstoragedutils.h"
+
+typedef struct _GVfsStoragedMountClass GVfsStoragedMountClass;
+struct _GVfsStoragedMountClass
+{
+ GObjectClass parent_class;
+};
+
+struct _GVfsStoragedMount
+{
+ GObject parent;
+
+ GVfsStoragedVolumeMonitor *monitor; /* owned by volume monitor */
+
+ /* may be NULL */
+ GVfsStoragedVolume *volume; /* owned by volume monitor */
+
+ /* may be NULL */
+ GUnixMountEntry *mount_entry;
+
+ /* the following members are set in update_mount() */
+ GFile *root;
+ GIcon *icon;
+ GIcon *symbolic_icon;
+ gchar *name;
+ gchar *sort_key;
+ gchar *uuid;
+ gchar *device_file;
+ gchar *mount_path;
+ gboolean can_unmount;
+ gchar *mount_entry_name;
+ gchar *mount_entry_fs_type;
+
+ gboolean is_burn_mount;
+
+ GIcon *autorun_icon;
+ gboolean searched_for_autorun;
+
+ gchar *xdg_volume_info_name;
+ GIcon *xdg_volume_info_icon;
+ gboolean searched_for_xdg_volume_info;
+
+ gchar *bdmv_volume_info_name;
+ GIcon *bdmv_volume_info_icon;
+ gboolean searched_for_bdmv_volume_info;
+};
+
+static gboolean update_mount (GVfsStoragedMount *mount);
+
+static void gvfs_storaged_mount_mount_iface_init (GMountIface *iface);
+
+G_DEFINE_TYPE_EXTENDED (GVfsStoragedMount, gvfs_storaged_mount, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (G_TYPE_MOUNT,
+ gvfs_storaged_mount_mount_iface_init))
+
+static void on_volume_changed (GVolume *volume, gpointer user_data);
+
+static void
+gvfs_storaged_mount_finalize (GObject *object)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (object);
+
+ if (mount->volume != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (mount->volume, on_volume_changed, mount);
+ gvfs_storaged_volume_unset_mount (mount->volume, mount);
+ }
+
+ g_clear_object (&mount->root);
+ g_clear_object (&mount->icon);
+ g_clear_object (&mount->symbolic_icon);
+ g_free (mount->name);
+ g_free (mount->sort_key);
+ g_free (mount->uuid);
+ g_free (mount->device_file);
+ g_free (mount->mount_path);
+
+ g_free (mount->mount_entry_name);
+
+ if (mount->autorun_icon != NULL)
+ g_object_unref (mount->autorun_icon);
+
+ g_free (mount->xdg_volume_info_name);
+ if (mount->xdg_volume_info_icon != NULL)
+ g_object_unref (mount->xdg_volume_info_icon);
+
+ G_OBJECT_CLASS (gvfs_storaged_mount_parent_class)->finalize (object);
+}
+
+static void
+gvfs_storaged_mount_class_init (GVfsStoragedMountClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = gvfs_storaged_mount_finalize;
+}
+
+static void
+gvfs_storaged_mount_init (GVfsStoragedMount *mount)
+{
+}
+
+static void
+emit_changed (GVfsStoragedMount *mount)
+{
+ g_signal_emit_by_name (mount, "changed");
+ g_signal_emit_by_name (mount->monitor, "mount-changed", mount);
+}
+
+static void
+got_autorun_info_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (user_data);
+ mount->autorun_icon = g_vfs_mount_info_query_autorun_info_finish (G_FILE (source_object), res, NULL);
+ if (update_mount (mount))
+ emit_changed (mount);
+ g_object_unref (mount);
+}
+
+static void
+got_xdg_volume_info_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (user_data);
+ mount->xdg_volume_info_icon = g_vfs_mount_info_query_xdg_volume_info_finish (G_FILE (source_object),
+ res,
+ &(mount->xdg_volume_info_name),
+ NULL);
+ if (update_mount (mount))
+ emit_changed (mount);
+ g_object_unref (mount);
+}
+
+static void
+got_bdmv_volume_info_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (user_data);
+ mount->bdmv_volume_info_icon = g_vfs_mount_info_query_bdmv_volume_info_finish (G_FILE (source_object),
+ res,
+ &(mount->bdmv_volume_info_name),
+ NULL);
+ if (update_mount (mount))
+ emit_changed (mount);
+ g_object_unref (mount);
+}
+
+static gboolean
+update_mount (GVfsStoragedMount *mount)
+{
+ gboolean changed;
+ gboolean old_can_unmount;
+ gchar *old_name;
+ GIcon *old_icon;
+ GIcon *old_symbolic_icon;
+
+ /* save old values */
+ old_can_unmount = mount->can_unmount;
+ old_name = g_strdup (mount->name);
+ old_icon = mount->icon != NULL ? g_object_ref (mount->icon) : NULL;
+ old_symbolic_icon = mount->symbolic_icon != NULL ? g_object_ref (mount->symbolic_icon) : NULL;
+
+ /* reset */
+ mount->can_unmount = FALSE;
+ g_clear_object (&mount->icon);
+ g_clear_object (&mount->symbolic_icon);
+ g_free (mount->name); mount->name = NULL;
+
+ /* in with the new */
+ if (mount->volume != NULL)
+ {
+ mount->can_unmount = TRUE;
+
+ /* icon order of preference: bdmv, xdg, autorun, probed */
+ if (mount->bdmv_volume_info_icon != NULL)
+ mount->icon = g_object_ref (mount->bdmv_volume_info_icon);
+ else if (mount->xdg_volume_info_icon != NULL)
+ mount->icon = g_object_ref (mount->xdg_volume_info_icon);
+ else if (mount->autorun_icon != NULL)
+ mount->icon = g_object_ref (mount->autorun_icon);
+ else
+ mount->icon = g_volume_get_icon (G_VOLUME (mount->volume));
+
+ /* name order of preference : bdmv, xdg, probed */
+ if (mount->bdmv_volume_info_name != NULL)
+ mount->name = g_strdup (mount->bdmv_volume_info_name);
+ else if (mount->xdg_volume_info_name != NULL)
+ mount->name = g_strdup (mount->xdg_volume_info_name);
+ else
+ mount->name = g_volume_get_name (G_VOLUME (mount->volume));
+
+ mount->symbolic_icon = g_volume_get_symbolic_icon (G_VOLUME (mount->volume));
+ }
+ else
+ {
+ mount->can_unmount = TRUE;
+
+ if (mount->icon != NULL)
+ g_object_unref (mount->icon);
+
+ /* icon order of preference: bdmv, xdg, autorun, probed */
+ if (mount->bdmv_volume_info_icon != NULL)
+ mount->icon = g_object_ref (mount->bdmv_volume_info_icon);
+ else if (mount->xdg_volume_info_icon != NULL)
+ mount->icon = g_object_ref (mount->xdg_volume_info_icon);
+ else if (mount->autorun_icon != NULL)
+ mount->icon = g_object_ref (mount->autorun_icon);
+ else
+ {
+ mount->icon = gvfs_storaged_utils_icon_from_fs_type (g_unix_mount_get_fs_type (mount->mount_entry));
+ }
+
+ g_free (mount->name);
+
+ /* name order of preference: bdmv, xdg, probed */
+ if (mount->bdmv_volume_info_name != NULL)
+ mount->name = g_strdup (mount->bdmv_volume_info_name);
+ else if (mount->xdg_volume_info_name != NULL)
+ mount->name = g_strdup (mount->xdg_volume_info_name);
+ else
+ mount->name = g_strdup (mount->mount_entry_name);
+
+ mount->symbolic_icon = gvfs_storaged_utils_symbolic_icon_from_fs_type (g_unix_mount_get_fs_type (mount->mount_entry));
+ }
+
+ /* compute whether something changed */
+ changed = !((old_can_unmount == mount->can_unmount) &&
+ (g_strcmp0 (old_name, mount->name) == 0) &&
+ g_icon_equal (old_icon, mount->icon) &&
+ g_icon_equal (old_symbolic_icon, mount->symbolic_icon));
+
+ /* free old values */
+ g_free (old_name);
+ g_clear_object (&old_icon);
+ g_clear_object (&old_symbolic_icon);
+
+ /*g_debug ("in update_mount(), changed=%d", changed);*/
+
+ /* search for BDMV */
+ if (!mount->searched_for_bdmv_volume_info)
+ {
+ mount->searched_for_bdmv_volume_info = TRUE;
+ g_vfs_mount_info_query_bdmv_volume_info (mount->root,
+ NULL,
+ got_bdmv_volume_info_cb,
+ g_object_ref (mount));
+ }
+
+ /* search for .xdg-volume-info */
+ if (!mount->searched_for_xdg_volume_info)
+ {
+ mount->searched_for_xdg_volume_info = TRUE;
+ g_vfs_mount_info_query_xdg_volume_info (mount->root,
+ NULL,
+ got_xdg_volume_info_cb,
+ g_object_ref (mount));
+ }
+
+ /* search for autorun.inf */
+ if (!mount->searched_for_autorun)
+ {
+ mount->searched_for_autorun = TRUE;
+ g_vfs_mount_info_query_autorun_info (mount->root,
+ NULL,
+ got_autorun_info_cb,
+ g_object_ref (mount));
+ }
+
+ return changed;
+}
+
+static void
+on_volume_changed (GVolume *volume,
+ gpointer user_data)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (user_data);
+ if (update_mount (mount))
+ emit_changed (mount);
+}
+
+GVfsStoragedMount *
+gvfs_storaged_mount_new (GVfsStoragedVolumeMonitor *monitor,
+ GUnixMountEntry *mount_entry, /* takes ownership */
+ GVfsStoragedVolume *volume)
+{
+ GVfsStoragedMount *mount = NULL;
+
+ /* Ignore internal mounts unless there's a volume */
+ if (volume == NULL && (mount_entry != NULL && !g_unix_mount_guess_should_display (mount_entry)))
+ goto out;
+
+ mount = g_object_new (GVFS_TYPE_STORAGED_MOUNT, NULL);
+ mount->monitor = monitor;
+ mount->sort_key = g_strdup_printf ("gvfs.time_detected_usec.%" G_GINT64_FORMAT, g_get_real_time ());
+
+ if (mount_entry != NULL)
+ {
+ mount->mount_entry = mount_entry; /* takes ownership */
+ mount->mount_entry_name = g_unix_mount_guess_name (mount_entry);
+ mount->device_file = g_strdup (g_unix_mount_get_device_path (mount_entry));
+ mount->mount_path = g_strdup (g_unix_mount_get_mount_path (mount_entry));
+ mount->root = g_file_new_for_path (mount->mount_path);
+ }
+ else
+ {
+ /* burn:/// mount (the only mounts we support with mount_entry == NULL) */
+ mount->device_file = NULL;
+ mount->mount_path = NULL;
+ mount->root = g_file_new_for_uri ("burn:///");
+ mount->is_burn_mount = TRUE;
+ }
+
+ /* need to set the volume only when the mount is fully constructed */
+ mount->volume = volume;
+ if (mount->volume != NULL)
+ {
+ gvfs_storaged_volume_set_mount (volume, mount);
+ /* this is for piggy backing on the name and icon of the associated volume */
+ g_signal_connect (mount->volume, "changed", G_CALLBACK (on_volume_changed), mount);
+ }
+
+ update_mount (mount);
+
+ out:
+
+ return mount;
+}
+
+void
+gvfs_storaged_mount_unmounted (GVfsStoragedMount *mount)
+{
+ if (mount->volume != NULL)
+ {
+ gvfs_storaged_volume_unset_mount (mount->volume, mount);
+ g_signal_handlers_disconnect_by_func (mount->volume, on_volume_changed, mount);
+ mount->volume = NULL;
+ emit_changed (mount);
+ }
+}
+
+void
+gvfs_storaged_mount_unset_volume (GVfsStoragedMount *mount,
+ GVfsStoragedVolume *volume)
+{
+ if (mount->volume == volume)
+ {
+ g_signal_handlers_disconnect_by_func (mount->volume, on_volume_changed, mount);
+ mount->volume = NULL;
+ emit_changed (mount);
+ }
+}
+
+void
+gvfs_storaged_mount_set_volume (GVfsStoragedMount *mount,
+ GVfsStoragedVolume *volume)
+{
+ if (mount->volume != volume)
+ {
+ if (mount->volume != NULL)
+ gvfs_storaged_mount_unset_volume (mount, mount->volume);
+ mount->volume = volume;
+ if (mount->volume != NULL)
+ {
+ gvfs_storaged_volume_set_mount (volume, mount);
+ /* this is for piggy backing on the name and icon of the associated volume */
+ g_signal_connect (mount->volume, "changed", G_CALLBACK (on_volume_changed), mount);
+ }
+ update_mount (mount);
+ emit_changed (mount);
+ }
+}
+
+static GFile *
+gvfs_storaged_mount_get_root (GMount *_mount)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ return mount->root != NULL ? g_object_ref (mount->root) : NULL;
+}
+
+static GIcon *
+gvfs_storaged_mount_get_icon (GMount *_mount)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ return mount->icon != NULL ? g_object_ref (mount->icon) : NULL;
+}
+
+static GIcon *
+gvfs_storaged_mount_get_symbolic_icon (GMount *_mount)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ return mount->symbolic_icon != NULL ? g_object_ref (mount->symbolic_icon) : NULL;
+}
+
+static gchar *
+gvfs_storaged_mount_get_uuid (GMount *_mount)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ return g_strdup (mount->uuid);
+}
+
+static gchar *
+gvfs_storaged_mount_get_name (GMount *_mount)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ return g_strdup (mount->name);
+}
+
+gboolean
+gvfs_storaged_mount_has_uuid (GVfsStoragedMount *_mount,
+ const gchar *uuid)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ return g_strcmp0 (mount->uuid, uuid) == 0;
+}
+
+const gchar *
+gvfs_storaged_mount_get_mount_path (GVfsStoragedMount *mount)
+{
+ return mount->mount_path;
+}
+
+GUnixMountEntry *
+gvfs_storaged_mount_get_mount_entry (GVfsStoragedMount *_mount)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ return mount->mount_entry;
+}
+
+static GDrive *
+gvfs_storaged_mount_get_drive (GMount *_mount)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ GDrive *drive = NULL;
+
+ if (mount->volume != NULL)
+ drive = g_volume_get_drive (G_VOLUME (mount->volume));
+ return drive;
+}
+
+static GVolume *
+gvfs_storaged_mount_get_volume_ (GMount *_mount)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ GVolume *volume = NULL;
+
+ if (mount->volume != NULL)
+ volume = G_VOLUME (g_object_ref (mount->volume));
+ return volume;
+}
+
+static gboolean
+gvfs_storaged_mount_can_unmount (GMount *_mount)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ return mount->can_unmount;
+}
+
+static gboolean
+gvfs_storaged_mount_can_eject (GMount *_mount)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ GDrive *drive;
+ gboolean can_eject;
+
+ can_eject = FALSE;
+ if (mount->volume != NULL)
+ {
+ drive = g_volume_get_drive (G_VOLUME (mount->volume));
+ if (drive != NULL)
+ can_eject = g_drive_can_eject (drive);
+ }
+
+ return can_eject;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ volatile gint ref_count;
+ GSimpleAsyncResult *simple;
+ gboolean in_progress;
+ gboolean completed;
+
+ GVfsStoragedMount *mount;
+
+ StoragedEncrypted *encrypted;
+ StoragedFilesystem *filesystem;
+
+ GCancellable *cancellable;
+
+ GMountOperation *mount_operation;
+ GMountUnmountFlags flags;
+
+ gulong mount_op_reply_handler_id;
+ guint retry_unmount_timer_id;
+
+ GMountOperationResult reply_result;
+ gint reply_choice;
+ gboolean reply_set;
+} UnmountData;
+
+static UnmountData *
+unmount_data_ref (UnmountData *data)
+{
+ g_atomic_int_inc (&data->ref_count);
+ return data;
+}
+
+static void
+unmount_data_unref (UnmountData *data)
+{
+ if (g_atomic_int_dec_and_test (&data->ref_count))
+ {
+ g_object_unref (data->simple);
+
+ if (data->mount_op_reply_handler_id > 0)
+ {
+ /* make the operation dialog go away */
+ g_signal_emit_by_name (data->mount_operation, "aborted");
+ g_signal_handler_disconnect (data->mount_operation, data->mount_op_reply_handler_id);
+ }
+ if (data->retry_unmount_timer_id > 0)
+ {
+ g_source_remove (data->retry_unmount_timer_id);
+ data->retry_unmount_timer_id = 0;
+ }
+
+ g_clear_object (&data->mount);
+ g_clear_object (&data->cancellable);
+ g_clear_object (&data->mount_operation);
+ g_clear_object (&data->encrypted);
+ g_clear_object (&data->filesystem);
+ g_free (data);
+ }
+}
+
+static gboolean
+unmount_operation_is_eject (GMountOperation *op)
+{
+ return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (op), "x-storaged-is-eject"));
+}
+
+static gboolean
+unmount_operation_is_stop (GMountOperation *op)
+{
+ return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (op), "x-storaged-is-stop"));
+}
+
+static void
+unmount_data_complete (UnmountData *data,
+ gboolean complete_idle)
+{
+ if (data->mount_operation &&
+ !unmount_operation_is_eject (data->mount_operation))
+ gvfs_storaged_unmount_notify_stop (data->mount_operation);
+
+ if (complete_idle)
+ g_simple_async_result_complete_in_idle (data->simple);
+ else
+ g_simple_async_result_complete (data->simple);
+
+ data->in_progress = FALSE;
+ data->completed = TRUE;
+ unmount_data_unref (data);
+}
+
+static void unmount_do (UnmountData *data, gboolean force);
+
+static gboolean
+on_retry_timer_cb (gpointer user_data)
+{
+ UnmountData *data = user_data;
+
+ if (data->retry_unmount_timer_id == 0)
+ goto out;
+
+ /* we're removing the timeout */
+ data->retry_unmount_timer_id = 0;
+
+ if (data->completed || data->in_progress)
+ goto out;
+
+ /* timeout expired => try again */
+ unmount_do (data, FALSE);
+
+ out:
+ return FALSE; /* remove timeout */
+}
+
+static void
+mount_op_reply_handle (UnmountData *data)
+{
+ data->reply_set = FALSE;
+
+ if (data->reply_result == G_MOUNT_OPERATION_ABORTED ||
+ (data->reply_result == G_MOUNT_OPERATION_HANDLED &&
+ data->reply_choice == 1))
+ {
+ /* don't show an error dialog here */
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED_HANDLED,
+ "GMountOperation aborted (user should never see this "
+ "error since it is G_IO_ERROR_FAILED_HANDLED)");
+ unmount_data_complete (data, TRUE);
+ }
+ else if (data->reply_result == G_MOUNT_OPERATION_HANDLED)
+ {
+ /* user chose force unmount => try again with force_unmount==TRUE */
+ unmount_do (data, TRUE);
+ }
+ else
+ {
+ /* result == G_MOUNT_OPERATION_UNHANDLED => GMountOperation instance doesn't
+ * support :show-processes signal
+ */
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_BUSY,
+ _("One or more programs are preventing the unmount operation."));
+ unmount_data_complete (data, TRUE);
+ }
+}
+
+static void
+on_mount_op_reply (GMountOperation *mount_operation,
+ GMountOperationResult result,
+ gpointer user_data)
+{
+ UnmountData *data = user_data;
+ gint choice;
+
+ /* disconnect the signal handler */
+ g_warn_if_fail (data->mount_op_reply_handler_id != 0);
+ g_signal_handler_disconnect (data->mount_operation,
+ data->mount_op_reply_handler_id);
+ data->mount_op_reply_handler_id = 0;
+
+ choice = g_mount_operation_get_choice (mount_operation);
+ data->reply_result = result;
+ data->reply_choice = choice;
+ data->reply_set = TRUE;
+ if (!data->completed || !data->in_progress)
+ mount_op_reply_handle (data);
+}
+
+static void
+lsof_command_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ UnmountData *data = user_data;
+ GError *error;
+ gint exit_status;
+ GArray *processes;
+ const gchar *choices[3] = {NULL, NULL, NULL};
+ const gchar *message;
+ gchar *standard_output = NULL;
+ const gchar *p;
+
+ processes = g_array_new (FALSE, FALSE, sizeof (GPid));
+
+ error = NULL;
+ if (!gvfs_storaged_utils_spawn_finish (res,
+ &exit_status,
+ &standard_output,
+ NULL, /* gchar **out_standard_error */
+ &error))
+ {
+ g_printerr ("Error launching lsof(1): %s (%s, %d)\n",
+ error->message, g_quark_to_string (error->domain), error->code);
+ g_error_free (error);
+ goto out;
+ }
+
+ if (!(WIFEXITED (exit_status) && WEXITSTATUS (exit_status) == 0))
+ {
+ g_printerr ("lsof(1) did not exit normally\n");
+ goto out;
+ }
+
+ p = standard_output;
+ while (TRUE)
+ {
+ GPid pid;
+ gchar *endp;
+
+ if (*p == '\0')
+ break;
+
+ pid = strtol (p, &endp, 10);
+ if (pid == 0 && p == endp)
+ break;
+
+ g_array_append_val (processes, pid);
+
+ p = endp;
+ }
+
+ out:
+ if (!data->completed)
+ {
+ gboolean is_eject;
+ gboolean is_stop;
+
+ is_eject = unmount_operation_is_eject (data->mount_operation);
+ is_stop = unmount_operation_is_stop (data->mount_operation);
+
+ /* We want to emit the 'show-processes' signal even if launching
+ * lsof(1) failed or if it didn't return any PIDs. This is because
+ * it won't show e.g. root-owned processes operating on files
+ * on the mount point.
+ *
+ * (unfortunately there's no way to convey that it failed)
+ */
+ if (data->mount_op_reply_handler_id == 0)
+ {
+ data->mount_op_reply_handler_id = g_signal_connect (data->mount_operation,
+ "reply",
+ G_CALLBACK (on_mount_op_reply),
+ data);
+ }
+ if (is_eject || is_stop)
+ {
+ /* Note that the GUI (Shell, Files) currently use the term
+ * "Eject" for both GDrive.stop() and GDrive.eject().
+ */
+ choices[0] = _("Eject Anyway");
+ }
+ else
+ {
+ choices[0] = _("Unmount Anyway");
+ }
+ choices[1] = _("Cancel");
+ message = _("Volume is busy\n"
+ "One or more applications are keeping the volume busy.");
+ g_signal_emit_by_name (data->mount_operation,
+ "show-processes",
+ message,
+ processes,
+ choices);
+ /* set up a timer to try unmounting every two seconds - this will also
+ * update the list of busy processes
+ */
+ if (data->retry_unmount_timer_id == 0)
+ {
+ data->retry_unmount_timer_id = g_timeout_add_seconds (2,
+ on_retry_timer_cb,
+ data);
+ }
+ g_array_free (processes, TRUE);
+ g_free (standard_output);
+ }
+ unmount_data_unref (data); /* return ref */
+}
+
+
+static void
+unmount_show_busy (UnmountData *data,
+ const gchar *mount_point)
+{
+ gchar *escaped_mount_point;
+
+ data->in_progress = FALSE;
+
+ /* We received an reply during an unmount operation which could not complete.
+ * Handle the reply now. */
+ if (data->reply_set)
+ {
+ mount_op_reply_handle (data);
+ return;
+ }
+
+ escaped_mount_point = g_strescape (mount_point, NULL);
+ gvfs_storaged_utils_spawn (10, /* timeout in seconds */
+ data->cancellable,
+ lsof_command_cb,
+ unmount_data_ref (data),
+ "lsof -t \"%s\"",
+ escaped_mount_point);
+ g_free (escaped_mount_point);
+}
+
+static void
+lock_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ StoragedEncrypted *encrypted = STORAGED_ENCRYPTED (source_object);
+ UnmountData *data = user_data;
+ GError *error;
+
+ error = NULL;
+ if (!storaged_encrypted_call_lock_finish (encrypted,
+ res,
+ &error))
+ g_simple_async_result_take_error (data->simple, error);
+ unmount_data_complete (data, FALSE);
+}
+
+static void
+unmount_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ StoragedFilesystem *filesystem = STORAGED_FILESYSTEM (source_object);
+ UnmountData *data = user_data;
+ GError *error;
+
+ error = NULL;
+ if (!storaged_filesystem_call_unmount_finish (filesystem,
+ res,
+ &error))
+ {
+ gvfs_storaged_utils_storaged_error_to_gio_error (error);
+
+ /* if the user passed in a GMountOperation, then do the GMountOperation::show-processes dance ... */
+ if (error->code == G_IO_ERROR_BUSY && data->mount_operation != NULL)
+ {
+ unmount_show_busy (data, storaged_filesystem_get_mount_points (filesystem)[0]);
+ goto out;
+ }
+ g_simple_async_result_take_error (data->simple, error);
+ }
+ else
+ {
+ gvfs_storaged_volume_monitor_update (data->mount->monitor);
+ if (data->encrypted != NULL)
+ {
+ storaged_encrypted_call_lock (data->encrypted,
+ g_variant_new ("a{sv}", NULL), /* options */
+ data->cancellable,
+ lock_cb,
+ data);
+ goto out;
+ }
+ }
+
+ unmount_data_complete (data, FALSE);
+ out:
+ ;
+}
+
+
+/* ------------------------------ */
+
+static void
+umount_command_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ UnmountData *data = user_data;
+ GError *error;
+ gint exit_status;
+ gchar *standard_error = NULL;
+
+ error = NULL;
+ if (!gvfs_storaged_utils_spawn_finish (res,
+ &exit_status,
+ NULL, /* gchar **out_standard_output */
+ &standard_error,
+ &error))
+ {
+ g_simple_async_result_take_error (data->simple, error);
+ unmount_data_complete (data, FALSE);
+ goto out;
+ }
+
+ if (WIFEXITED (exit_status) && WEXITSTATUS (exit_status) == 0)
+ {
+ gvfs_storaged_volume_monitor_update (data->mount->monitor);
+ unmount_data_complete (data, FALSE);
+ goto out;
+ }
+
+ if (standard_error != NULL &&
+ (strstr (standard_error, "device is busy") != NULL ||
+ strstr (standard_error, "target is busy") != NULL))
+ {
+ unmount_show_busy (data, data->mount->mount_path);
+ goto out;
+ }
+
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "%s", standard_error);
+ unmount_data_complete (data, FALSE);
+
+ out:
+ g_free (standard_error);
+}
+
+static void
+unmount_do (UnmountData *data,
+ gboolean force)
+{
+ GVariantBuilder builder;
+
+ data->in_progress = TRUE;
+
+ if (data->mount_operation != NULL)
+ gvfs_storaged_unmount_notify_start (data->mount_operation,
+ G_MOUNT (data->mount), NULL,
+ (data->filesystem == NULL));
+
+ /* Use the umount(8) command if there is no block device / filesystem */
+ if (data->filesystem == NULL)
+ {
+ gchar *escaped_mount_path;
+ escaped_mount_path = g_strescape (data->mount->mount_path, NULL);
+ gvfs_storaged_utils_spawn (10, /* timeout in seconds */
+ data->cancellable,
+ umount_command_cb,
+ data,
+ "umount %s \"%s\"",
+ force ? "-l " : "",
+ escaped_mount_path);
+ g_free (escaped_mount_path);
+ goto out;
+ }
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+ if (data->mount_operation == NULL)
+ {
+ g_variant_builder_add (&builder,
+ "{sv}",
+ "auth.no_user_interaction", g_variant_new_boolean (TRUE));
+ }
+ if (force || data->flags & G_MOUNT_UNMOUNT_FORCE)
+ {
+ g_variant_builder_add (&builder,
+ "{sv}",
+ "force", g_variant_new_boolean (TRUE));
+ }
+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (data->filesystem), G_MAXINT);
+ storaged_filesystem_call_unmount (data->filesystem,
+ g_variant_builder_end (&builder),
+ data->cancellable,
+ unmount_cb,
+ data);
+
+ out:
+ ;
+}
+
+static void
+gvfs_storaged_mount_unmount_with_operation (GMount *_mount,
+ GMountUnmountFlags flags,
+ GMountOperation *mount_operation,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ UnmountData *data;
+ StoragedBlock *block;
+
+ /* first emit the ::mount-pre-unmount signal */
+ g_signal_emit_by_name (mount->monitor, "mount-pre-unmount", mount);
+
+ data = g_new0 (UnmountData, 1);
+ data->ref_count = 1;
+ data->simple = g_simple_async_result_new (G_OBJECT (mount),
+ callback,
+ user_data,
+ gvfs_storaged_mount_unmount_with_operation);
+ data->mount = g_object_ref (mount);
+ data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL;
+ data->mount_operation = mount_operation != NULL ? g_object_ref (mount_operation) : NULL;
+ data->flags = flags;
+
+ if (mount->is_burn_mount)
+ {
+ /* burn mounts are really never mounted so complete successfully immediately */
+ unmount_data_complete (data, TRUE);
+ goto out;
+ }
+
+ block = NULL;
+ if (mount->volume != NULL)
+ block = gvfs_storaged_volume_get_block (data->mount->volume);
+ if (block != NULL)
+ {
+ GDBusObject *object;
+ object = g_dbus_interface_get_object (G_DBUS_INTERFACE (block));
+ if (object == NULL)
+ {
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "No object for D-Bus interface");
+
+ unmount_data_complete (data, FALSE);
+ goto out;
+ }
+ data->filesystem = storaged_object_get_filesystem (STORAGED_OBJECT (object));
+ if (data->filesystem == NULL)
+ {
+ StoragedBlock *cleartext_block;
+
+ data->encrypted = storaged_object_get_encrypted (STORAGED_OBJECT (object));
+ if (data->encrypted == NULL)
+ {
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "No filesystem or encrypted interface on D-Bus object");
+ unmount_data_complete (data, FALSE);
+ goto out;
+ }
+
+ cleartext_block = storaged_client_get_cleartext_block (gvfs_storaged_volume_monitor_get_storaged_client (mount->monitor),
+ block);
+ if (cleartext_block != NULL)
+ {
+ data->filesystem = storaged_object_get_filesystem (STORAGED_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (cleartext_block))));
+ g_object_unref (cleartext_block);
+ if (data->filesystem == NULL)
+ {
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "No filesystem interface on D-Bus object for cleartext device");
+ unmount_data_complete (data, FALSE);
+ goto out;
+ }
+ }
+ }
+ g_assert (data->filesystem != NULL);
+ }
+ unmount_do (data, FALSE /* force */);
+
+ out:
+ ;
+}
+
+static gboolean
+gvfs_storaged_mount_unmount_with_operation_finish (GMount *mount,
+ GAsyncResult *result,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+gvfs_storaged_mount_unmount (GMount *mount,
+ GMountUnmountFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ gvfs_storaged_mount_unmount_with_operation (mount, flags, NULL, cancellable, callback, user_data);
+}
+
+static gboolean
+gvfs_storaged_mount_unmount_finish (GMount *mount,
+ GAsyncResult *result,
+ GError **error)
+{
+ return gvfs_storaged_mount_unmount_with_operation_finish (mount, result, error);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GObject *object;
+ GAsyncReadyCallback callback;
+ gpointer user_data;
+} EjectWrapperOp;
+
+static void
+eject_wrapper_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ EjectWrapperOp *data = user_data;
+ data->callback (data->object, res, data->user_data);
+ g_object_unref (data->object);
+ g_free (data);
+}
+
+static void
+gvfs_storaged_mount_eject_with_operation (GMount *_mount,
+ GMountUnmountFlags flags,
+ GMountOperation *mount_operation,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ GDrive *drive;
+
+ drive = NULL;
+ if (mount->volume != NULL)
+ drive = g_volume_get_drive (G_VOLUME (mount->volume));
+
+ if (drive != NULL)
+ {
+ EjectWrapperOp *data;
+ data = g_new0 (EjectWrapperOp, 1);
+ data->object = g_object_ref (mount);
+ data->callback = callback;
+ data->user_data = user_data;
+ g_drive_eject_with_operation (drive, flags, mount_operation, cancellable, eject_wrapper_callback, data);
+ g_object_unref (drive);
+ }
+ else
+ {
+ GSimpleAsyncResult *simple;
+ simple = g_simple_async_result_new_error (G_OBJECT (mount),
+ callback,
+ user_data,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Operation not supported by backend"));
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ }
+}
+
+static gboolean
+gvfs_storaged_mount_eject_with_operation_finish (GMount *_mount,
+ GAsyncResult *result,
+ GError **error)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ gboolean ret = TRUE;
+ GDrive *drive;
+
+ drive = NULL;
+ if (mount->volume != NULL)
+ drive = g_volume_get_drive (G_VOLUME (mount->volume));
+
+ if (drive != NULL)
+ {
+ ret = g_drive_eject_with_operation_finish (drive, result, error);
+ g_object_unref (drive);
+ }
+ else
+ {
+ g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+ ret = FALSE;
+ }
+ return ret;
+}
+
+static void
+gvfs_storaged_mount_eject (GMount *mount,
+ GMountUnmountFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ gvfs_storaged_mount_eject_with_operation (mount, flags, NULL, cancellable, callback, user_data);
+}
+
+static gboolean
+gvfs_storaged_mount_eject_finish (GMount *mount,
+ GAsyncResult *result,
+ GError **error)
+{
+ return gvfs_storaged_mount_eject_with_operation_finish (mount, result, error);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar **
+gvfs_storaged_mount_guess_content_type_sync (GMount *_mount,
+ gboolean force_rescan,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ gchar **x_content_types;
+ GPtrArray *p;
+ gchar **ret;
+ guint n;
+
+ p = g_ptr_array_new ();
+
+ /* doesn't make sense to probe blank discs - look at the disc type instead */
+ if (mount->is_burn_mount)
+ {
+ GDrive *drive;
+ drive = gvfs_storaged_mount_get_drive (_mount);
+ if (drive != NULL)
+ {
+ StoragedDrive *storaged_drive = gvfs_storaged_drive_get_storaged_drive (GVFS_STORAGED_DRIVE (drive));;
+ const gchar *media = storaged_drive_get_media (storaged_drive);
+ if (media != NULL)
+ {
+ if (g_str_has_prefix (media, "optical_dvd"))
+ g_ptr_array_add (p, g_strdup ("x-content/blank-dvd"));
+ else if (g_str_has_prefix (media, "optical_hddvd"))
+ g_ptr_array_add (p, g_strdup ("x-content/blank-hddvd"));
+ else if (g_str_has_prefix (media, "optical_bd"))
+ g_ptr_array_add (p, g_strdup ("x-content/blank-bd"));
+ else
+ g_ptr_array_add (p, g_strdup ("x-content/blank-cd")); /* assume CD */
+ }
+ g_object_unref (drive);
+ }
+ }
+ else
+ {
+ /* sniff content type */
+ x_content_types = g_content_type_guess_for_tree (mount->root);
+ if (x_content_types != NULL)
+ {
+ for (n = 0; x_content_types[n] != NULL; n++)
+ g_ptr_array_add (p, g_strdup (x_content_types[n]));
+ g_strfreev (x_content_types);
+ }
+ }
+
+ if (mount->device_file != NULL)
+ {
+ GUdevDevice *gudev_device;
+ gudev_device = g_udev_client_query_by_device_file (gvfs_storaged_volume_monitor_get_gudev_client (mount->monitor),
+ mount->device_file);
+ if (gudev_device != NULL)
+ {
+ /* Check if its bootable */
+ const gchar *boot_sys_id;
+
+ boot_sys_id = g_udev_device_get_property (gudev_device,
+ "ID_FS_BOOT_SYSTEM_ID");
+ if (boot_sys_id != NULL ||
+ g_udev_device_get_property_as_boolean (gudev_device, "OSINFO_BOOTABLE"))
+ g_ptr_array_add (p, g_strdup ("x-content/bootable-media"));
+
+ /* Check for media player */
+ if (g_udev_device_has_property (gudev_device, "ID_MEDIA_PLAYER"))
+ g_ptr_array_add (p, g_strdup ("x-content/audio-player"));
+
+ g_object_unref (gudev_device);
+ }
+ }
+
+ if (p->len == 0)
+ {
+ ret = NULL;
+ g_ptr_array_free (p, TRUE);
+ }
+ else
+ {
+ g_ptr_array_add (p, NULL);
+ ret = (char **) g_ptr_array_free (p, FALSE);
+ }
+ return ret;
+}
+
+/* since we're an out-of-process volume monitor we'll just do this sync */
+static void
+gvfs_storaged_mount_guess_content_type (GMount *mount,
+ gboolean force_rescan,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ simple = g_simple_async_result_new (G_OBJECT (mount),
+ callback,
+ user_data,
+ NULL);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static gchar **
+gvfs_storaged_mount_guess_content_type_finish (GMount *mount,
+ GAsyncResult *result,
+ GError **error)
+{
+ return gvfs_storaged_mount_guess_content_type_sync (mount, FALSE, NULL, error);
+}
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const gchar *
+gvfs_storaged_mount_get_sort_key (GMount *_mount)
+{
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (_mount);
+ return mount->sort_key;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+gvfs_storaged_mount_mount_iface_init (GMountIface *iface)
+{
+ iface->get_root = gvfs_storaged_mount_get_root;
+ iface->get_name = gvfs_storaged_mount_get_name;
+ iface->get_icon = gvfs_storaged_mount_get_icon;
+ iface->get_symbolic_icon = gvfs_storaged_mount_get_symbolic_icon;
+ iface->get_uuid = gvfs_storaged_mount_get_uuid;
+ iface->get_drive = gvfs_storaged_mount_get_drive;
+ iface->get_volume = gvfs_storaged_mount_get_volume_;
+ iface->can_unmount = gvfs_storaged_mount_can_unmount;
+ iface->can_eject = gvfs_storaged_mount_can_eject;
+ iface->unmount = gvfs_storaged_mount_unmount;
+ iface->unmount_finish = gvfs_storaged_mount_unmount_finish;
+ iface->unmount_with_operation = gvfs_storaged_mount_unmount_with_operation;
+ iface->unmount_with_operation_finish = gvfs_storaged_mount_unmount_with_operation_finish;
+ iface->eject = gvfs_storaged_mount_eject;
+ iface->eject_finish = gvfs_storaged_mount_eject_finish;
+ iface->eject_with_operation = gvfs_storaged_mount_eject_with_operation;
+ iface->eject_with_operation_finish = gvfs_storaged_mount_eject_with_operation_finish;
+ iface->guess_content_type = gvfs_storaged_mount_guess_content_type;
+ iface->guess_content_type_finish = gvfs_storaged_mount_guess_content_type_finish;
+ iface->guess_content_type_sync = gvfs_storaged_mount_guess_content_type_sync;
+ iface->get_sort_key = gvfs_storaged_mount_get_sort_key;
+}
+
+gboolean
+gvfs_storaged_mount_has_volume (GVfsStoragedMount *mount,
+ GVfsStoragedVolume *volume)
+{
+ return mount->volume == volume;
+}
+
+GVfsStoragedVolume *
+gvfs_storaged_mount_get_volume (GVfsStoragedMount *mount)
+{
+ return mount->volume;
+}
diff --git a/monitor/storaged/gvfsstoragedmount.h b/monitor/storaged/gvfsstoragedmount.h
new file mode 100644
index 00000000..d3472211
--- /dev/null
+++ b/monitor/storaged/gvfsstoragedmount.h
@@ -0,0 +1,61 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2006-2012 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __GVFS_STORAGED_MOUNT_H__
+#define __GVFS_STORAGED_MOUNT_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "gvfsstoragedvolumemonitor.h"
+
+G_BEGIN_DECLS
+
+#define GVFS_TYPE_STORAGED_MOUNT (gvfs_storaged_mount_get_type ())
+#define GVFS_STORAGED_MOUNT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVFS_TYPE_STORAGED_MOUNT, GVfsStoragedMount))
+#define GVFS_IS_STORAGED_MOUNT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVFS_TYPE_STORAGED_MOUNT))
+
+
+GType gvfs_storaged_mount_get_type (void) G_GNUC_CONST;
+GVfsStoragedMount *gvfs_storaged_mount_new (GVfsStoragedVolumeMonitor *monitor,
+ GUnixMountEntry *mount_entry,
+ GVfsStoragedVolume *volume);
+void gvfs_storaged_mount_unmounted (GVfsStoragedMount *mount);
+
+gboolean gvfs_storaged_mount_has_uuid (GVfsStoragedMount *mount,
+ const gchar *uuid);
+
+void gvfs_storaged_mount_set_volume (GVfsStoragedMount *mount,
+ GVfsStoragedVolume *volume);
+void gvfs_storaged_mount_unset_volume (GVfsStoragedMount *mount,
+ GVfsStoragedVolume *volume);
+gboolean gvfs_storaged_mount_has_volume (GVfsStoragedMount *mount,
+ GVfsStoragedVolume *volume);
+GVfsStoragedVolume *gvfs_storaged_mount_get_volume (GVfsStoragedMount *mount);
+
+const gchar *gvfs_storaged_mount_get_mount_path (GVfsStoragedMount *mount);
+GUnixMountEntry *gvfs_storaged_mount_get_mount_entry (GVfsStoragedMount *mount);
+
+G_END_DECLS
+
+#endif /* __GVFS_STORAGED_MOUNT_H__ */
diff --git a/monitor/storaged/gvfsstoragedutils.c b/monitor/storaged/gvfsstoragedutils.c
new file mode 100644
index 00000000..c7d7793c
--- /dev/null
+++ b/monitor/storaged/gvfsstoragedutils.c
@@ -0,0 +1,828 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2006-2012 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+
+#include "gvfsstoragedutils.h"
+
+void
+gvfs_storaged_utils_storaged_error_to_gio_error (GError *error)
+{
+ g_return_if_fail (error != NULL);
+
+ if (error->domain == STORAGED_ERROR)
+ {
+ switch (error->code)
+ {
+ case STORAGED_ERROR_DEVICE_BUSY:
+ error->code = G_IO_ERROR_BUSY;
+ break;
+ case STORAGED_ERROR_NOT_AUTHORIZED_DISMISSED:
+ error->code = G_IO_ERROR_FAILED_HANDLED;
+ break;
+ default:
+ error->code = G_IO_ERROR_FAILED;
+ break;
+ }
+ }
+ else
+ {
+ error->code = G_IO_ERROR_FAILED;
+ }
+
+ error->domain = G_IO_ERROR;
+ g_dbus_error_strip_remote_error (error);
+}
+
+
+GIcon *
+gvfs_storaged_utils_icon_from_fs_type (const gchar *fs_type)
+{
+ const gchar *icon_name;
+ if (g_strcmp0 (fs_type, "nfs") == 0 ||
+ g_strcmp0 (fs_type, "nfs4") == 0 ||
+ g_strcmp0 (fs_type, "cifs") == 0)
+ {
+ icon_name = "folder-remote";
+ }
+ else
+ {
+ icon_name = "drive-removable-media";
+ }
+ return g_themed_icon_new_with_default_fallbacks (icon_name);
+}
+
+GIcon *
+gvfs_storaged_utils_symbolic_icon_from_fs_type (const gchar *fs_type)
+{
+ const gchar *icon_name;
+ if (g_strcmp0 (fs_type, "nfs") == 0 ||
+ g_strcmp0 (fs_type, "nfs4") == 0 ||
+ g_strcmp0 (fs_type, "cifs") == 0)
+ {
+ icon_name = "folder-remote-symbolic";
+ }
+ else
+ {
+ icon_name = "drive-removable-media-symbolic";
+ }
+ return g_themed_icon_new_with_default_fallbacks (icon_name);
+}
+
+gchar *
+gvfs_storaged_utils_lookup_fstab_options_value (const gchar *fstab_options,
+ const gchar *key)
+{
+ gchar *ret = NULL;
+
+ if (fstab_options != NULL)
+ {
+ const gchar *start;
+ guint n;
+
+ start = strstr (fstab_options, key);
+ if (start != NULL)
+ {
+ start += strlen (key);
+ for (n = 0; start[n] != ',' && start[n] != '\0'; n++)
+ ;
+ if (n == 0)
+ ret = g_strdup ("");
+ else if (n >= 1)
+ ret = g_uri_unescape_segment (start, start + n, NULL);
+ }
+ }
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GSimpleAsyncResult *simple; /* borrowed reference */
+ GMainContext *main_context; /* may be NULL */
+
+ gchar *command_line;
+
+ GCancellable *cancellable; /* may be NULL */
+ gulong cancellable_handler_id;
+
+ GPid child_pid;
+ gint child_stdout_fd;
+ gint child_stderr_fd;
+
+ GIOChannel *child_stdout_channel;
+ GIOChannel *child_stderr_channel;
+
+ GSource *child_watch_source;
+ GSource *child_stdout_source;
+ GSource *child_stderr_source;
+
+ gboolean timed_out;
+ GSource *timeout_source;
+
+ GString *child_stdout;
+ GString *child_stderr;
+
+ gint exit_status;
+} SpawnData;
+
+static void
+child_watch_from_release_cb (GPid pid,
+ gint status,
+ gpointer user_data)
+{
+}
+
+static void
+spawn_data_free (SpawnData *data)
+{
+ if (data->timeout_source != NULL)
+ {
+ g_source_destroy (data->timeout_source);
+ data->timeout_source = NULL;
+ }
+
+ /* Nuke the child, if necessary */
+ if (data->child_watch_source != NULL)
+ {
+ g_source_destroy (data->child_watch_source);
+ data->child_watch_source = NULL;
+ }
+
+ if (data->child_pid != 0)
+ {
+ GSource *source;
+ kill (data->child_pid, SIGTERM);
+ /* OK, we need to reap for the child ourselves - we don't want
+ * to use waitpid() because that might block the calling
+ * thread (the child might handle SIGTERM and use several
+ * seconds for cleanup/rollback).
+ *
+ * So we use GChildWatch instead.
+ *
+ * Avoid taking a references to ourselves. but note that we need
+ * to pass the GSource so we can nuke it once handled.
+ */
+ source = g_child_watch_source_new (data->child_pid);
+ g_source_set_callback (source,
+ (GSourceFunc) child_watch_from_release_cb,
+ source,
+ (GDestroyNotify) g_source_destroy);
+ g_source_attach (source, data->main_context);
+ g_source_unref (source);
+ data->child_pid = 0;
+ }
+
+ if (data->child_stdout != NULL)
+ {
+ g_string_free (data->child_stdout, TRUE);
+ data->child_stdout = NULL;
+ }
+
+ if (data->child_stderr != NULL)
+ {
+ g_string_free (data->child_stderr, TRUE);
+ data->child_stderr = NULL;
+ }
+
+ if (data->child_stdout_channel != NULL)
+ {
+ g_io_channel_unref (data->child_stdout_channel);
+ data->child_stdout_channel = NULL;
+ }
+ if (data->child_stderr_channel != NULL)
+ {
+ g_io_channel_unref (data->child_stderr_channel);
+ data->child_stderr_channel = NULL;
+ }
+
+ if (data->child_stdout_source != NULL)
+ {
+ g_source_destroy (data->child_stdout_source);
+ data->child_stdout_source = NULL;
+ }
+ if (data->child_stderr_source != NULL)
+ {
+ g_source_destroy (data->child_stderr_source);
+ data->child_stderr_source = NULL;
+ }
+
+ if (data->child_stdout_fd != -1)
+ {
+ g_warn_if_fail (close (data->child_stdout_fd) == 0);
+ data->child_stdout_fd = -1;
+ }
+ if (data->child_stderr_fd != -1)
+ {
+ g_warn_if_fail (close (data->child_stderr_fd) == 0);
+ data->child_stderr_fd = -1;
+ }
+
+ if (data->cancellable_handler_id > 0)
+ {
+ g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id);
+ data->cancellable_handler_id = 0;
+ }
+
+ if (data->main_context != NULL)
+ g_main_context_unref (data->main_context);
+
+ if (data->cancellable != NULL)
+ g_object_unref (data->cancellable);
+
+ g_free (data->command_line);
+
+ g_slice_free (SpawnData, data);
+}
+
+/* called in the thread where @cancellable was cancelled */
+static void
+on_cancelled (GCancellable *cancellable,
+ gpointer user_data)
+{
+ SpawnData *data = user_data;
+ GError *error;
+
+ error = NULL;
+ g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error));
+ g_simple_async_result_take_error (data->simple, error);
+ g_simple_async_result_complete_in_idle (data->simple);
+ g_object_unref (data->simple);
+}
+
+static gboolean
+read_child_stderr (GIOChannel *channel,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ SpawnData *data = user_data;
+ gchar buf[1024];
+ gsize bytes_read;
+
+ g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
+ g_string_append_len (data->child_stderr, buf, bytes_read);
+ return TRUE;
+}
+
+static gboolean
+read_child_stdout (GIOChannel *channel,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ SpawnData *data = user_data;
+ gchar buf[1024];
+ gsize bytes_read;
+
+ g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL);
+ g_string_append_len (data->child_stdout, buf, bytes_read);
+ return TRUE;
+}
+
+static void
+child_watch_cb (GPid pid,
+ gint status,
+ gpointer user_data)
+{
+ SpawnData *data = user_data;
+ gchar *buf;
+ gsize buf_size;
+
+ if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
+ {
+ g_string_append_len (data->child_stdout, buf, buf_size);
+ g_free (buf);
+ }
+ if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL)
+ {
+ g_string_append_len (data->child_stderr, buf, buf_size);
+ g_free (buf);
+ }
+
+ data->exit_status = status;
+
+ /* ok, child watch is history, make sure we don't free it in spawn_data_free() */
+ data->child_pid = 0;
+ data->child_watch_source = NULL;
+
+ /* we're done */
+ g_simple_async_result_complete_in_idle (data->simple);
+ g_object_unref (data->simple);
+}
+
+static gboolean
+timeout_cb (gpointer user_data)
+{
+ SpawnData *data = user_data;
+
+ data->timed_out = TRUE;
+
+ /* ok, timeout is history, make sure we don't free it in spawn_data_free() */
+ data->timeout_source = NULL;
+
+ /* we're done */
+ g_simple_async_result_complete_in_idle (data->simple);
+ g_object_unref (data->simple);
+
+ return FALSE; /* remove source */
+}
+
+void
+gvfs_storaged_utils_spawn (guint timeout_seconds,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data,
+ const gchar *command_line_format,
+ ...)
+{
+ va_list var_args;
+ SpawnData *data;
+ GError *error;
+ gint child_argc;
+ gchar **child_argv = NULL;
+
+ data = g_slice_new0 (SpawnData);
+ data->simple = g_simple_async_result_new (NULL,
+ callback,
+ user_data,
+ gvfs_storaged_utils_spawn);
+ data->main_context = g_main_context_get_thread_default ();
+ if (data->main_context != NULL)
+ g_main_context_ref (data->main_context);
+
+ data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL;
+
+ va_start (var_args, command_line_format);
+ data->command_line = g_strdup_vprintf (command_line_format, var_args);
+ va_end (var_args);
+
+ data->child_stdout = g_string_new (NULL);
+ data->child_stderr = g_string_new (NULL);
+ data->child_stdout_fd = -1;
+ data->child_stderr_fd = -1;
+
+ /* the life-cycle of SpawnData is tied to its GSimpleAsyncResult */
+ g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) spawn_data_free);
+
+ error = NULL;
+ if (data->cancellable != NULL)
+ {
+ /* could already be cancelled */
+ error = NULL;
+ if (g_cancellable_set_error_if_cancelled (data->cancellable, &error))
+ {
+ g_simple_async_result_take_error (data->simple, error);
+ g_simple_async_result_complete_in_idle (data->simple);
+ g_object_unref (data->simple);
+ goto out;
+ }
+
+ data->cancellable_handler_id = g_cancellable_connect (data->cancellable,
+ G_CALLBACK (on_cancelled),
+ data,
+ NULL);
+ }
+
+ error = NULL;
+ if (!g_shell_parse_argv (data->command_line,
+ &child_argc,
+ &child_argv,
+ &error))
+ {
+ g_prefix_error (&error,
+ "Error parsing command-line `%s': ",
+ data->command_line);
+ g_simple_async_result_take_error (data->simple, error);
+ g_simple_async_result_complete_in_idle (data->simple);
+ g_object_unref (data->simple);
+ goto out;
+ }
+
+ error = NULL;
+ if (!g_spawn_async_with_pipes (NULL, /* working directory */
+ child_argv,
+ NULL, /* envp */
+ G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL, /* child_setup */
+ NULL, /* child_setup's user_data */
+ &(data->child_pid),
+ NULL, /* gint *stdin_fd */
+ &(data->child_stdout_fd),
+ &(data->child_stderr_fd),
+ &error))
+ {
+ g_prefix_error (&error,
+ "Error spawning command-line `%s': ",
+ data->command_line);
+ g_simple_async_result_take_error (data->simple, error);
+ g_simple_async_result_complete_in_idle (data->simple);
+ g_object_unref (data->simple);
+ goto out;
+ }
+
+ if (timeout_seconds > 0)
+ {
+ data->timeout_source = g_timeout_source_new_seconds (timeout_seconds);
+ g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT);
+ g_source_set_callback (data->timeout_source, timeout_cb, data, NULL);
+ g_source_attach (data->timeout_source, data->main_context);
+ g_source_unref (data->timeout_source);
+ }
+
+ data->child_watch_source = g_child_watch_source_new (data->child_pid);
+ g_source_set_callback (data->child_watch_source, (GSourceFunc) child_watch_cb, data, NULL);
+ g_source_attach (data->child_watch_source, data->main_context);
+ g_source_unref (data->child_watch_source);
+
+ data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd);
+ g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL);
+ data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN);
+ g_source_set_callback (data->child_stdout_source, (GSourceFunc) read_child_stdout, data, NULL);
+ g_source_attach (data->child_stdout_source, data->main_context);
+ g_source_unref (data->child_stdout_source);
+
+ data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd);
+ g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL);
+ data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN);
+ g_source_set_callback (data->child_stderr_source, (GSourceFunc) read_child_stderr, data, NULL);
+ g_source_attach (data->child_stderr_source, data->main_context);
+ g_source_unref (data->child_stderr_source);
+
+ out:
+ g_strfreev (child_argv);
+}
+
+gboolean
+gvfs_storaged_utils_spawn_finish (GAsyncResult *res,
+ gint *out_exit_status,
+ gchar **out_standard_output,
+ gchar **out_standard_error,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ SpawnData *data;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == gvfs_storaged_utils_spawn);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ goto out;
+
+ data = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (data->timed_out)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_TIMED_OUT,
+ _("Timed out running command-line `%s'"),
+ data->command_line);
+ goto out;
+ }
+
+ if (out_exit_status != NULL)
+ *out_exit_status = data->exit_status;
+
+ if (out_standard_output != NULL)
+ *out_standard_output = g_strdup (data->child_stdout->str);
+
+ if (out_standard_error != NULL)
+ *out_standard_error = g_strdup (data->child_stderr->str);
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#if defined(HAVE_LIBSYSTEMD_LOGIN)
+#include <systemd/sd-login.h>
+
+static const gchar *
+get_seat (void)
+{
+ static gsize once = 0;
+ static char *seat = NULL;
+
+ if (g_once_init_enter (&once))
+ {
+ char *session = NULL;
+ if (sd_pid_get_session (getpid (), &session) == 0)
+ {
+ sd_session_get_seat (session, &seat);
+ free (session);
+ /* we intentionally leak seat here... */
+ }
+ g_once_init_leave (&once, (gsize) 1);
+ }
+ return seat;
+}
+
+#else
+
+static const gchar *
+get_seat (void)
+{
+ return NULL;
+}
+
+#endif
+
+gboolean
+gvfs_storaged_utils_is_drive_on_our_seat (StoragedDrive *drive)
+{
+ gboolean ret = FALSE;
+ const gchar *seat;
+ const gchar *drive_seat = NULL;
+
+ /* assume our own seat if we don't have seat-support or it doesn't work */
+ seat = get_seat ();
+ if (seat == NULL)
+ {
+ ret = TRUE;
+ goto out;
+ }
+
+ /* If the device is not tagged, assume that udisks does not have
+ * working seat-support... so just assume it's available at our
+ * seat.
+ *
+ * Note that seat support was added in udisks 1.95.0 (and so was the
+ * STORAGED_CHECK_VERSION macro).
+ */
+ drive_seat = storaged_drive_get_seat (drive);
+
+ if (drive_seat == NULL || strlen (drive_seat) == 0)
+ {
+ ret = TRUE;
+ goto out;
+ }
+
+ /* Otherwise, check if it's on our seat */
+ if (g_strcmp0 (seat, drive_seat) == 0)
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+/* unmount progress notification utilities */
+typedef struct {
+ GMount *mount;
+ GDrive *drive;
+
+ GMountOperation *op;
+ gboolean op_aborted;
+ gboolean generic_text;
+ gboolean show_processes_up;
+
+ guint unmount_timer_id;
+ gboolean unmount_fired;
+} UnmountNotifyData;
+
+static gboolean
+unmount_notify_should_show (UnmountNotifyData *data)
+{
+ GVolume *volume;
+ gchar *identifier = NULL;
+ gboolean retval = TRUE;
+
+ if (data->mount)
+ {
+ volume = g_mount_get_volume (data->mount);
+
+ if (volume)
+ {
+ identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+ g_object_unref (volume);
+ }
+ }
+ else if (data->drive)
+ {
+ identifier = g_drive_get_identifier (data->drive, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
+ }
+
+ if (identifier && g_str_has_prefix (identifier, "/dev/sr"))
+ retval = FALSE;
+
+ g_free (identifier);
+
+ return retval;
+}
+
+static gchar *
+unmount_notify_get_name (UnmountNotifyData *data)
+{
+ if (data->mount)
+ return g_mount_get_name (data->mount);
+ else
+ return g_drive_get_name (data->drive);
+}
+
+static gboolean
+unmount_notify_timer_cb (gpointer user_data)
+{
+ UnmountNotifyData *data = user_data;
+ gchar *message, *name;
+ const gchar *format;
+
+ data->unmount_timer_id = 0;
+
+ if (data->unmount_fired)
+ goto out;
+
+ /* TODO: it would be nice to include and update the time left and
+ * bytes left fields.
+ */
+ data->unmount_fired = TRUE;
+
+ name = unmount_notify_get_name (data);
+ format = data->generic_text ?
+ _("Unmounting %s\nPlease wait") :
+ _("Writing data to %s\nDon't unplug until finished");
+
+ message = g_strdup_printf (format, name);
+ g_signal_emit_by_name (data->op, "show-unmount-progress",
+ message, -1, -1);
+ g_free (message);
+ g_free (name);
+
+ out:
+ return FALSE;
+}
+
+static void
+unmount_notify_ensure_timer (UnmountNotifyData *data)
+{
+ if (data->unmount_timer_id > 0)
+ return;
+
+ if (!unmount_notify_should_show (data))
+ return;
+
+ data->unmount_timer_id =
+ g_timeout_add (1500, unmount_notify_timer_cb, data);
+}
+
+static void
+unmount_notify_stop_timer (UnmountNotifyData *data)
+{
+ if (data->unmount_timer_id > 0)
+ {
+ g_source_remove (data->unmount_timer_id);
+ data->unmount_timer_id = 0;
+ }
+}
+
+static void
+unmount_notify_op_show_processes (UnmountNotifyData *data)
+{
+ unmount_notify_stop_timer (data);
+ data->show_processes_up = TRUE;
+}
+
+static void
+unmount_notify_op_aborted (UnmountNotifyData *data)
+{
+ unmount_notify_stop_timer (data);
+ data->op_aborted = TRUE;
+}
+
+static void
+unmount_notify_op_reply (UnmountNotifyData *data,
+ GMountOperationResult result)
+{
+ gint choice;
+
+ choice = g_mount_operation_get_choice (data->op);
+
+ if ((result == G_MOUNT_OPERATION_HANDLED && data->show_processes_up && choice == 1) ||
+ result == G_MOUNT_OPERATION_ABORTED)
+ unmount_notify_op_aborted (data);
+ else if (result == G_MOUNT_OPERATION_HANDLED)
+ unmount_notify_ensure_timer (data);
+
+ data->show_processes_up = FALSE;
+}
+
+static void
+unmount_notify_data_free (gpointer user_data)
+{
+ UnmountNotifyData *data = user_data;
+
+ unmount_notify_stop_timer (data);
+
+ g_clear_object (&data->mount);
+ g_clear_object (&data->drive);
+
+ g_slice_free (UnmountNotifyData, data);
+}
+
+static UnmountNotifyData *
+unmount_notify_data_for_operation (GMountOperation *op,
+ GMount *mount,
+ GDrive *drive,
+ gboolean generic_text)
+{
+ UnmountNotifyData *data;
+
+ data = g_object_get_data (G_OBJECT (op), "x-storaged-notify-data");
+ if (data != NULL)
+ return data;
+
+ data = g_slice_new0 (UnmountNotifyData);
+ data->op = op;
+ data->generic_text = generic_text;
+
+ if (mount)
+ data->mount = g_object_ref (mount);
+ if (drive)
+ data->drive = g_object_ref (drive);
+
+ g_object_set_data_full (G_OBJECT (data->op),
+ "x-storaged-notify-data", data,
+ unmount_notify_data_free);
+
+ g_signal_connect_swapped (data->op, "aborted",
+ G_CALLBACK (unmount_notify_op_aborted), data);
+ g_signal_connect_swapped (data->op, "show-processes",
+ G_CALLBACK (unmount_notify_op_show_processes), data);
+ g_signal_connect_swapped (data->op, "reply",
+ G_CALLBACK (unmount_notify_op_reply), data);
+
+ return data;
+}
+
+void
+gvfs_storaged_unmount_notify_start (GMountOperation *op,
+ GMount *mount,
+ GDrive *drive,
+ gboolean generic_text)
+{
+ UnmountNotifyData *data;
+
+ data = unmount_notify_data_for_operation (op, mount, drive, generic_text);
+ unmount_notify_ensure_timer (data);
+}
+
+void
+gvfs_storaged_unmount_notify_stop (GMountOperation *op)
+{
+ gchar *message, *name;
+ const gchar *format;
+ UnmountNotifyData *data = g_object_get_data (G_OBJECT (op), "x-storaged-notify-data");
+
+ if (data == NULL)
+ return;
+
+ unmount_notify_stop_timer (data);
+
+ if (data->op_aborted)
+ return;
+
+ name = unmount_notify_get_name (data);
+ format = data->generic_text ?
+ _("%s has been unmounted\n") : _("You can now unplug %s\n");
+
+ message = g_strdup_printf (format, name);
+ g_signal_emit_by_name (data->op, "show-unmount-progress",
+ message, 0, 0);
+
+ g_free (message);
+ g_free (name);
+}
diff --git a/monitor/storaged/gvfsstoragedutils.h b/monitor/storaged/gvfsstoragedutils.h
new file mode 100644
index 00000000..fdd0d405
--- /dev/null
+++ b/monitor/storaged/gvfsstoragedutils.h
@@ -0,0 +1,64 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2006-2012 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __GVFS_STORAGED_UTILS_H__
+#define __GVFS_STORAGED_UTILS_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "gvfsstoragedvolumemonitor.h"
+
+G_BEGIN_DECLS
+
+void gvfs_storaged_utils_storaged_error_to_gio_error (GError *error);
+GIcon *gvfs_storaged_utils_icon_from_fs_type (const gchar *fs_type);
+GIcon *gvfs_storaged_utils_symbolic_icon_from_fs_type (const gchar *fs_type);
+
+gchar *gvfs_storaged_utils_lookup_fstab_options_value (const gchar *fstab_options,
+ const gchar *key);
+
+void gvfs_storaged_utils_spawn (guint timeout_seconds,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data,
+ const gchar *command_line_format,
+ ...);
+
+gboolean gvfs_storaged_utils_spawn_finish (GAsyncResult *res,
+ gint *out_exit_status,
+ gchar **out_standard_output,
+ gchar **out_standard_error,
+ GError **error);
+
+gboolean gvfs_storaged_utils_is_drive_on_our_seat (StoragedDrive *drive);
+
+void gvfs_storaged_unmount_notify_start (GMountOperation *op,
+ GMount *mount,
+ GDrive *drive,
+ gboolean generic_text);
+void gvfs_storaged_unmount_notify_stop (GMountOperation *op);
+
+G_END_DECLS
+
+#endif /* __GVFS_STORAGED_UTILS_H__ */
diff --git a/monitor/storaged/gvfsstoragedvolume.c b/monitor/storaged/gvfsstoragedvolume.c
new file mode 100644
index 00000000..d29a5b51
--- /dev/null
+++ b/monitor/storaged/gvfsstoragedvolume.c
@@ -0,0 +1,1786 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2006-2012 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+
+#ifdef HAVE_KEYRING
+#define SECRET_API_SUBJECT_TO_CHANGE
+#include <libsecret/secret.h>
+#endif
+
+#include "gvfsstorageddrive.h"
+#include "gvfsstoragedvolume.h"
+#include "gvfsstoragedmount.h"
+#include "gvfsstoragedutils.h"
+
+typedef struct _GVfsStoragedVolumeClass GVfsStoragedVolumeClass;
+
+struct _GVfsStoragedVolumeClass
+{
+ GObjectClass parent_class;
+};
+
+struct MountData;
+typedef struct MountData MountData;
+
+static void mount_cancel_pending_op (MountData *data);
+
+struct _GVfsStoragedVolume
+{
+ GObject parent;
+
+ GVfsStoragedVolumeMonitor *monitor; /* owned by volume monitor */
+ GVfsStoragedMount *mount; /* owned by volume monitor */
+ GVfsStoragedDrive *drive; /* owned by volume monitor */
+
+ /* If TRUE, the volume was discovered at coldplug time */
+ gboolean coldplug;
+
+ /* exactly one of these are set */
+ StoragedBlock *block;
+ GUnixMountPoint *mount_point;
+
+ /* set in update_volume() */
+ GIcon *icon;
+ GIcon *symbolic_icon;
+ GFile *activation_root;
+ gchar *name;
+ gchar *sort_key;
+ gchar *device_file;
+ dev_t dev;
+ gchar *uuid;
+ gboolean can_mount;
+ gboolean should_automount;
+
+ /* If a mount operation is in progress, then pending_mount_op is != NULL. This
+ * is used to cancel the operation to make possible authentication dialogs go
+ * away.
+ */
+ MountData *mount_pending_op;
+};
+
+static void gvfs_storaged_volume_volume_iface_init (GVolumeIface *iface);
+
+static void on_block_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data);
+
+static void on_storaged_client_changed (StoragedClient *client,
+ gpointer user_data);
+
+G_DEFINE_TYPE_EXTENDED (GVfsStoragedVolume, gvfs_storaged_volume, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (G_TYPE_VOLUME, gvfs_storaged_volume_volume_iface_init))
+
+static void
+gvfs_storaged_volume_finalize (GObject *object)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (object);
+
+ g_signal_handlers_disconnect_by_func (gvfs_storaged_volume_monitor_get_storaged_client (volume->monitor),
+ G_CALLBACK (on_storaged_client_changed),
+ volume);
+
+ if (volume->mount != NULL)
+ {
+ gvfs_storaged_mount_unset_volume (volume->mount, volume);
+ }
+
+ if (volume->drive != NULL)
+ {
+ gvfs_storaged_drive_unset_volume (volume->drive, volume);
+ }
+
+ if (volume->block != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (volume->block, G_CALLBACK (on_block_changed), volume);
+ g_object_unref (volume->block);
+ }
+
+ if (volume->mount_point != NULL)
+ g_unix_mount_point_free (volume->mount_point);
+
+ g_clear_object (&volume->icon);
+ g_clear_object (&volume->symbolic_icon);
+ g_clear_object (&volume->activation_root);
+
+ g_free (volume->name);
+ g_free (volume->sort_key);
+ g_free (volume->device_file);
+ g_free (volume->uuid);
+
+ G_OBJECT_CLASS (gvfs_storaged_volume_parent_class)->finalize (object);
+}
+
+static void
+gvfs_storaged_volume_class_init (GVfsStoragedVolumeClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = gvfs_storaged_volume_finalize;
+}
+
+static void
+gvfs_storaged_volume_init (GVfsStoragedVolume *volume)
+{
+}
+
+static void
+emit_changed (GVfsStoragedVolume *volume)
+{
+ g_signal_emit_by_name (volume, "changed");
+ g_signal_emit_by_name (volume->monitor, "volume-changed", volume);
+}
+
+static void
+apply_options_from_fstab (GVfsStoragedVolume *volume,
+ const gchar *fstab_options)
+{
+ gchar *name;
+ gchar *icon_name;
+ gchar *symbolic_icon_name;
+
+ name = gvfs_storaged_utils_lookup_fstab_options_value (fstab_options, "x-gvfs-name=");
+ if (name != NULL)
+ {
+ g_free (volume->name);
+ volume->name = name;
+ }
+
+ icon_name = gvfs_storaged_utils_lookup_fstab_options_value (fstab_options, "x-gvfs-icon=");
+ if (icon_name != NULL)
+ {
+ g_clear_object (&volume->icon);
+ volume->icon = g_themed_icon_new_with_default_fallbacks (icon_name);
+ g_free (icon_name);
+ }
+
+ symbolic_icon_name = gvfs_storaged_utils_lookup_fstab_options_value (fstab_options, "x-gvfs-symbolic-icon=");
+ if (symbolic_icon_name != NULL)
+ {
+ g_clear_object (&volume->symbolic_icon);
+ volume->symbolic_icon = g_themed_icon_new_with_default_fallbacks (symbolic_icon_name);
+ g_free (symbolic_icon_name);
+ }
+}
+
+
+#if STORAGED_CHECK_VERSION(2,0,90)
+static gpointer
+_g_object_ref0 (gpointer object)
+{
+ if (object != NULL)
+ return g_object_ref (G_OBJECT (object));
+ else
+ return NULL;
+}
+#endif
+
+static gboolean
+update_volume (GVfsStoragedVolume *volume)
+{
+ StoragedClient *storaged_client;
+ gboolean changed;
+ gboolean old_can_mount;
+ gboolean old_should_automount;
+ gchar *old_name;
+ gchar *old_device_file;
+ dev_t old_dev;
+ GIcon *old_icon;
+ StoragedDrive *storaged_drive;
+ gchar *s;
+
+ storaged_client = gvfs_storaged_volume_monitor_get_storaged_client (volume->monitor);
+
+ /* ---------------------------------------------------------------------------------------------------- */
+ /* save old values */
+
+ old_can_mount = volume->can_mount;
+ old_should_automount = volume->should_automount;
+ old_name = g_strdup (volume->name);
+ old_device_file = g_strdup (volume->device_file);
+ old_dev = volume->dev;
+ old_icon = volume->icon != NULL ? g_object_ref (volume->icon) : NULL;
+
+ /* ---------------------------------------------------------------------------------------------------- */
+ /* reset */
+
+ volume->can_mount = volume->should_automount = FALSE;
+ g_free (volume->name); volume->name = NULL;
+ g_free (volume->device_file); volume->device_file = NULL;
+ volume->dev = 0;
+ g_clear_object (&volume->icon);
+ g_clear_object (&volume->symbolic_icon);
+
+ /* ---------------------------------------------------------------------------------------------------- */
+ /* in with the new */
+
+ if (volume->block != NULL)
+ {
+ const gchar *hint;
+ StoragedBlock *block;
+ StoragedBlock *cleartext_block;
+ GVariantIter iter;
+ const gchar *configuration_type;
+ GVariant *configuration_value;
+ StoragedLoop *loop = NULL;
+
+ loop = storaged_client_get_loop_for_block (storaged_client,
+ volume->block);
+
+ /* If unlocked, use the values from the unlocked block device for presentation */
+ cleartext_block = storaged_client_get_cleartext_block (storaged_client, volume->block);
+ if (cleartext_block != NULL)
+ block = cleartext_block;
+ else
+ block = volume->block;
+
+ volume->dev = storaged_block_get_device_number (block);
+ volume->device_file = storaged_block_dup_device (block);
+
+ if (strlen (storaged_block_get_id_label (block)) > 0)
+ {
+ volume->name = g_strdup (storaged_block_get_id_label (block));
+ }
+ else if (g_strcmp0 (storaged_block_get_id_type (block), "crypto_LUKS") == 0)
+ {
+ s = storaged_client_get_size_for_display (storaged_client, storaged_block_get_size (volume->block), FALSE, FALSE);
+ /* Translators: This is used for encrypted volumes.
+ * The first %s is the formatted size (e.g. "42.0 MB").
+ */
+ volume->name = g_strdup_printf (_("%s Encrypted"), s);
+ g_free (s);
+ }
+ else
+ {
+ guint64 size = storaged_block_get_size (block);
+ if (size > 0)
+ {
+ s = storaged_client_get_size_for_display (storaged_client, size, FALSE, FALSE);
+ /* Translators: This is used for volume with no filesystem label.
+ * The first %s is the formatted size (e.g. "42.0 MB").
+ */
+ volume->name = g_strdup_printf (_("%s Volume"), s);
+ g_free (s);
+ }
+ }
+
+ storaged_drive = storaged_client_get_drive_for_block (storaged_client, volume->block);
+ if (storaged_drive != NULL)
+ {
+ gchar *drive_desc = NULL;
+ GIcon *drive_icon = NULL;
+ GIcon *drive_symbolic_icon = NULL;
+ gchar *media_desc = NULL;
+ GIcon *media_icon = NULL;
+ GIcon *media_symbolic_icon = NULL;
+
+#if STORAGED_CHECK_VERSION(2,0,90)
+ {
+ StoragedObject *object = (StoragedObject *) g_dbus_interface_get_object (G_DBUS_INTERFACE (storaged_drive));
+ if (object != NULL)
+ {
+ StoragedObjectInfo *info = storaged_client_get_object_info (storaged_client, object);
+ if (info != NULL)
+ {
+ drive_desc = g_strdup (storaged_object_info_get_description (info));
+ drive_icon = _g_object_ref0 (storaged_object_info_get_icon (info));
+ drive_symbolic_icon = _g_object_ref0 (storaged_object_info_get_icon_symbolic (info));
+ media_desc = g_strdup (storaged_object_info_get_media_description (info));
+ media_icon = _g_object_ref0 (storaged_object_info_get_media_icon (info));
+ media_symbolic_icon = _g_object_ref0 (storaged_object_info_get_media_icon_symbolic (info));
+ g_object_unref (info);
+ }
+ }
+ }
+#else
+ storaged_client_get_drive_info (storaged_client,
+ storaged_drive,
+ NULL, /* drive_name */
+ &drive_desc,
+ &drive_icon,
+ &media_desc,
+ &media_icon);
+#endif
+
+ if (media_desc == NULL)
+ {
+ media_desc = drive_desc;
+ drive_desc = NULL;
+ }
+ if (media_icon == NULL)
+ {
+ media_icon = drive_icon;
+ drive_icon = NULL;
+ }
+ if (media_symbolic_icon == NULL)
+ {
+ media_symbolic_icon = drive_symbolic_icon;
+ drive_symbolic_icon = NULL;
+ }
+
+ /* Override name for blank and audio discs */
+ if (storaged_drive_get_optical_blank (storaged_drive))
+ {
+ g_free (volume->name);
+ volume->name = g_strdup (media_desc);
+ }
+ else if (volume->activation_root != NULL && g_file_has_uri_scheme (volume->activation_root, "cdda"))
+ {
+ g_free (volume->name);
+ volume->name = g_strdup (_("Audio Disc"));
+ }
+
+ volume->icon = media_icon != NULL ? g_object_ref (media_icon) : NULL;
+ volume->symbolic_icon = media_symbolic_icon != NULL ? g_object_ref (media_symbolic_icon) : NULL;
+
+ /* use media_desc if we haven't figured out a name yet (applies to e.g.
+ * /dev/fd0 since its size is 0)
+ */
+ if (volume->name == NULL)
+ {
+ volume->name = media_desc;
+ media_desc = NULL;
+ }
+
+ g_free (media_desc);
+ g_clear_object (&media_icon);
+ g_clear_object (&media_symbolic_icon);
+
+ /* Only automount drives attached to the same seat as we're running on
+ */
+ if (gvfs_storaged_utils_is_drive_on_our_seat (storaged_drive))
+ {
+ /* Only automount filesystems from drives of known types/interconnects:
+ *
+ * - USB
+ * - Firewire
+ * - sdio
+ * - optical discs
+ *
+ * The mantra here is "be careful" - we really don't want to
+ * automount filesystems from all devices in a SAN etc - We
+ * REALLY need to be CAREFUL here.
+ *
+ * Fortunately udisks provides a property just for this.
+ */
+ if (storaged_block_get_hint_auto (volume->block))
+ {
+ gboolean just_plugged_in = FALSE;
+ /* Also, if a volume (partition) appear _much later_ than when media was inserted it
+ * can only be because the media was repartitioned. We don't want to automount
+ * such volumes. So only mark volumes appearing just after their drive.
+ *
+ * There's a catch here - if the volume was discovered at coldplug-time (typically
+ * when the user desktop session started), we can't use this heuristic
+ */
+ if (g_get_real_time () - storaged_drive_get_time_media_detected (storaged_drive) < 5 * G_USEC_PER_SEC)
+ just_plugged_in = TRUE;
+ if (volume->coldplug || just_plugged_in)
+ volume->should_automount = TRUE;
+ }
+ }
+ g_object_unref (storaged_drive);
+ }
+ else
+ {
+ /* No StoragedDrive, but we do have a StoragedBlock (example: /dev/loop0). Use
+ * that to get the icons via StoragedObjectInfo
+ */
+#if STORAGED_CHECK_VERSION(2,0,90)
+ {
+ StoragedObject *object = (StoragedObject *) g_dbus_interface_get_object (G_DBUS_INTERFACE (volume->block));
+ if (object != NULL)
+ {
+ StoragedObjectInfo *info = storaged_client_get_object_info (storaged_client, object);
+ if (info != NULL)
+ {
+ volume->icon = _g_object_ref0 (storaged_object_info_get_icon (info));
+ volume->symbolic_icon = _g_object_ref0 (storaged_object_info_get_icon_symbolic (info));
+ g_object_unref (info);
+ }
+ }
+ }
+#endif
+ }
+
+ /* Also automount loop devices set up by the user himself - e.g. via the
+ * udisks interfaces or the gnome-disk-image-mounter(1) command
+ */
+ if (loop != NULL)
+ {
+ if (storaged_loop_get_setup_by_uid (loop) == getuid ())
+ {
+ volume->should_automount = TRUE;
+ }
+ }
+
+ /* Use hints, if available */
+ hint = storaged_block_get_hint_name (volume->block);
+ if (hint != NULL && strlen (hint) > 0)
+ {
+ g_free (volume->name);
+ volume->name = g_strdup (hint);
+ }
+ hint = storaged_block_get_hint_icon_name (volume->block);
+ if (hint != NULL && strlen (hint) > 0)
+ {
+ g_clear_object (&volume->icon);
+ volume->icon = g_themed_icon_new_with_default_fallbacks (hint);
+ }
+#if STORAGED_CHECK_VERSION(2,0,90)
+ hint = storaged_block_get_hint_symbolic_icon_name (volume->block);
+ if (hint != NULL && strlen (hint) > 0)
+ {
+ g_clear_object (&volume->symbolic_icon);
+ volume->symbolic_icon = g_themed_icon_new_with_default_fallbacks (hint);
+ }
+#endif
+
+ /* Use x-gvfs-name=The%20Name, x-gvfs-icon=foo-name, x-gvfs-icon=foo-name-symbolic, if available */
+ g_variant_iter_init (&iter, storaged_block_get_configuration (block));
+ while (g_variant_iter_next (&iter, "(&s@a{sv})", &configuration_type, &configuration_value))
+ {
+ if (g_strcmp0 (configuration_type, "fstab") == 0)
+ {
+ const gchar *fstab_options;
+ if (g_variant_lookup (configuration_value, "opts", "^&ay", &fstab_options))
+ apply_options_from_fstab (volume, fstab_options);
+ }
+ g_variant_unref (configuration_value);
+ }
+
+ /* Add an emblem, depending on whether the encrypted volume is locked or unlocked */
+ if (g_strcmp0 (storaged_block_get_id_type (volume->block), "crypto_LUKS") == 0)
+ {
+ GEmblem *emblem;
+ GIcon *padlock;
+ GIcon *emblemed_icon;
+
+ if (volume->icon == NULL)
+ volume->icon = g_themed_icon_new ("drive-removable-media");
+
+ if (cleartext_block != NULL)
+ padlock = g_themed_icon_new ("changes-allow");
+ else
+ padlock = g_themed_icon_new ("changes-prevent");
+ emblem = g_emblem_new_with_origin (padlock, G_EMBLEM_ORIGIN_DEVICE);
+ emblemed_icon = g_emblemed_icon_new (volume->icon, emblem);
+ g_object_unref (padlock);
+ g_object_unref (emblem);
+
+ g_object_unref (volume->icon);
+ volume->icon = emblemed_icon;
+ }
+
+ g_clear_object (&cleartext_block);
+ g_clear_object (&loop);
+ }
+ else
+ {
+ apply_options_from_fstab (volume, g_unix_mount_point_get_options (volume->mount_point));
+ if (volume->name == NULL)
+ volume->name = g_unix_mount_point_guess_name (volume->mount_point);
+ if (volume->icon == NULL)
+ volume->icon = gvfs_storaged_utils_icon_from_fs_type (g_unix_mount_point_get_fs_type (volume->mount_point));
+ if (volume->symbolic_icon == NULL)
+ volume->symbolic_icon = gvfs_storaged_utils_symbolic_icon_from_fs_type (g_unix_mount_point_get_fs_type (volume->mount_point));
+ }
+
+ if (volume->mount == NULL)
+ volume->can_mount = TRUE;
+
+ /* ---------------------------------------------------------------------------------------------------- */
+ /* fallbacks */
+
+ if (volume->name == NULL)
+ {
+ /* Translators: Name used for volume */
+ volume->name = g_strdup (_("Volume"));
+ }
+ if (volume->icon == NULL)
+ volume->icon = g_themed_icon_new ("drive-removable-media");
+ if (volume->symbolic_icon == NULL)
+ volume->symbolic_icon = g_themed_icon_new ("drive-removable-media-symbolic");
+
+ /* ---------------------------------------------------------------------------------------------------- */
+ /* compute whether something changed */
+
+ changed = !((old_can_mount == volume->can_mount) &&
+ (old_should_automount == volume->should_automount) &&
+ (g_strcmp0 (old_name, volume->name) == 0) &&
+ (g_strcmp0 (old_device_file, volume->device_file) == 0) &&
+ (old_dev == volume->dev) &&
+ g_icon_equal (old_icon, volume->icon)
+ );
+
+ /* ---------------------------------------------------------------------------------------------------- */
+ /* free old values */
+
+ g_free (old_name);
+ g_free (old_device_file);
+ if (old_icon != NULL)
+ g_object_unref (old_icon);
+
+ return changed;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+update_volume_on_event (GVfsStoragedVolume *volume)
+{
+ if (update_volume (volume))
+ {
+ emit_changed (volume);
+ /* It could be that volume->dev changed (cryptotext volume morphing into a cleartext
+ * volume)... since this is used to associated mounts with volumes (see the loop over
+ * @unchanged in gvfsstoragedvolumemonitor.c:update_mounts()) we need to over
+ * the volume monitor
+ */
+ gvfs_storaged_volume_monitor_update (volume->monitor);
+ }
+}
+
+static void
+on_block_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (user_data);
+ update_volume_on_event (volume);
+}
+
+static void
+on_storaged_client_changed (StoragedClient *client,
+ gpointer user_data)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (user_data);
+ update_volume_on_event (volume);
+}
+
+/* takes ownership of @mount_point if not NULL */
+GVfsStoragedVolume *
+gvfs_storaged_volume_new (GVfsStoragedVolumeMonitor *monitor,
+ StoragedBlock *block,
+ GUnixMountPoint *mount_point,
+ GVfsStoragedDrive *drive,
+ GFile *activation_root,
+ gboolean coldplug)
+{
+ GVfsStoragedVolume *volume;
+
+ volume = g_object_new (GVFS_TYPE_STORAGED_VOLUME, NULL);
+ volume->monitor = monitor;
+ volume->coldplug = coldplug;
+
+ volume->sort_key = g_strdup_printf ("gvfs.time_detected_usec.%" G_GINT64_FORMAT, g_get_real_time ());
+
+ if (block != NULL)
+ {
+ volume->block = g_object_ref (block);
+ g_signal_connect (volume->block, "notify", G_CALLBACK (on_block_changed), volume);
+ }
+ else if (mount_point != NULL)
+ {
+ volume->mount_point = mount_point;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ volume->activation_root = activation_root != NULL ? g_object_ref (activation_root) : NULL;
+
+ volume->drive = drive;
+ if (drive != NULL)
+ gvfs_storaged_drive_set_volume (drive, volume);
+
+ update_volume (volume);
+
+ /* For LUKS devices, we also need to listen for changes on any possible cleartext device */
+ if (volume->block != NULL && g_strcmp0 (storaged_block_get_id_type (volume->block), "crypto_LUKS") == 0)
+ {
+ g_signal_connect (gvfs_storaged_volume_monitor_get_storaged_client (volume->monitor),
+ "changed",
+ G_CALLBACK (on_storaged_client_changed),
+ volume);
+ }
+
+ return volume;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void
+gvfs_storaged_volume_removed (GVfsStoragedVolume *volume)
+{
+ if (volume->mount_pending_op != NULL)
+ mount_cancel_pending_op (volume->mount_pending_op);
+
+ if (volume->mount != NULL)
+ {
+ gvfs_storaged_mount_unset_volume (volume->mount, volume);
+ volume->mount = NULL;
+ }
+
+ if (volume->drive != NULL)
+ {
+ gvfs_storaged_drive_unset_volume (volume->drive, volume);
+ volume->drive = NULL;
+ }
+}
+
+void
+gvfs_storaged_volume_set_mount (GVfsStoragedVolume *volume,
+ GVfsStoragedMount *mount)
+{
+ if (volume->mount != mount)
+ {
+ if (volume->mount != NULL)
+ gvfs_storaged_mount_unset_volume (volume->mount, volume);
+
+ volume->mount = mount;
+
+ emit_changed (volume);
+ }
+}
+
+void
+gvfs_storaged_volume_unset_mount (GVfsStoragedVolume *volume,
+ GVfsStoragedMount *mount)
+{
+ if (volume->mount == mount)
+ {
+ volume->mount = NULL;
+ emit_changed (volume);
+ }
+}
+
+void
+gvfs_storaged_volume_set_drive (GVfsStoragedVolume *volume,
+ GVfsStoragedDrive *drive)
+{
+ if (volume->drive != drive)
+ {
+ if (volume->drive != NULL)
+ gvfs_storaged_drive_unset_volume (volume->drive, volume);
+ volume->drive = drive;
+ emit_changed (volume);
+ }
+}
+
+void
+gvfs_storaged_volume_unset_drive (GVfsStoragedVolume *volume,
+ GVfsStoragedDrive *drive)
+{
+ if (volume->drive == drive)
+ {
+ volume->drive = NULL;
+ emit_changed (volume);
+ }
+}
+
+static GIcon *
+gvfs_storaged_volume_get_icon (GVolume *_volume)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (_volume);
+ return volume->icon != NULL ? g_object_ref (volume->icon) : NULL;
+}
+
+static GIcon *
+gvfs_storaged_volume_get_symbolic_icon (GVolume *_volume)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (_volume);
+ return volume->symbolic_icon != NULL ? g_object_ref (volume->symbolic_icon) : NULL;
+}
+
+static char *
+gvfs_storaged_volume_get_name (GVolume *_volume)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (_volume);
+ return g_strdup (volume->name);
+}
+
+static char *
+gvfs_storaged_volume_get_uuid (GVolume *_volume)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (_volume);
+ return g_strdup (volume->uuid);
+}
+
+static gboolean
+gvfs_storaged_volume_can_mount (GVolume *_volume)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (_volume);
+ return volume->can_mount;
+}
+
+static gboolean
+gvfs_storaged_volume_can_eject (GVolume *_volume)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (_volume);
+ gboolean can_eject = FALSE;
+
+ if (volume->drive != NULL)
+ can_eject = g_drive_can_eject (G_DRIVE (volume->drive));
+ return can_eject;
+}
+
+static gboolean
+gvfs_storaged_volume_should_automount (GVolume *_volume)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (_volume);
+ return volume->should_automount;
+}
+
+static GDrive *
+gvfs_storaged_volume_get_drive (GVolume *volume)
+{
+ GVfsStoragedVolume *gdu_volume = GVFS_STORAGED_VOLUME (volume);
+ GDrive *drive = NULL;
+
+ if (gdu_volume->drive != NULL)
+ drive = g_object_ref (gdu_volume->drive);
+ return drive;
+}
+
+static GMount *
+gvfs_storaged_volume_get_mount (GVolume *volume)
+{
+ GVfsStoragedVolume *gdu_volume = GVFS_STORAGED_VOLUME (volume);
+ GMount *mount = NULL;
+
+ if (gdu_volume->mount != NULL)
+ mount = g_object_ref (gdu_volume->mount);
+ return mount;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+gvfs_storaged_volume_is_network_class (GVfsStoragedVolume *volume)
+{
+ gboolean ret = FALSE;
+ if (volume->mount_point != NULL)
+ {
+ const gchar *fstype = g_unix_mount_point_get_fs_type (volume->mount_point);
+ if (g_strcmp0 (fstype, "nfs") == 0 ||
+ g_strcmp0 (fstype, "nfs4") == 0 ||
+ g_strcmp0 (fstype, "cifs") == 0 ||
+ g_strcmp0 (fstype, "smbfs") == 0 ||
+ g_strcmp0 (fstype, "ncpfs") == 0)
+ ret = TRUE;
+ }
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* can remove this once we depend on gio >= 2.31.19 */
+#ifndef G_VOLUME_IDENTIFIER_KIND_CLASS
+#define G_VOLUME_IDENTIFIER_KIND_CLASS "class"
+#endif
+
+static gchar *
+gvfs_storaged_volume_get_identifier (GVolume *_volume,
+ const gchar *kind)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (_volume);
+ const gchar *label;
+ const gchar *uuid;
+ gchar *ret = NULL;
+
+ if (volume->block != NULL)
+ {
+ label = storaged_block_get_id_label (volume->block);
+ uuid = storaged_block_get_id_uuid (volume->block);
+
+ if (strcmp (kind, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE) == 0)
+ ret = g_strdup (volume->device_file);
+ else if (strcmp (kind, G_VOLUME_IDENTIFIER_KIND_LABEL) == 0)
+ ret = strlen (label) > 0 ? g_strdup (label) : NULL;
+ else if (strcmp (kind, G_VOLUME_IDENTIFIER_KIND_UUID) == 0)
+ ret = strlen (uuid) > 0 ? g_strdup (uuid) : NULL;
+ }
+ if (strcmp (kind, G_VOLUME_IDENTIFIER_KIND_CLASS) == 0)
+ ret = g_strdup (gvfs_storaged_volume_is_network_class (volume) ? "network" : "device");
+
+ return ret;
+}
+
+static gchar **
+gvfs_storaged_volume_enumerate_identifiers (GVolume *_volume)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (_volume);
+ GPtrArray *p;
+
+ p = g_ptr_array_new ();
+ g_ptr_array_add (p, g_strdup (G_VOLUME_IDENTIFIER_KIND_CLASS));
+ if (volume->block != NULL)
+ {
+ const gchar *label;
+ const gchar *uuid;
+ label = storaged_block_get_id_label (volume->block);
+ uuid = storaged_block_get_id_uuid (volume->block);
+ g_ptr_array_add (p, g_strdup (G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE));
+ if (strlen (label) > 0)
+ g_ptr_array_add (p, g_strdup (G_VOLUME_IDENTIFIER_KIND_LABEL));
+ if (strlen (uuid) > 0)
+ g_ptr_array_add (p, g_strdup (G_VOLUME_IDENTIFIER_KIND_UUID));
+ }
+ g_ptr_array_add (p, NULL);
+ return (gchar **) g_ptr_array_free (p, FALSE);
+}
+
+static GFile *
+gvfs_storaged_volume_get_activation_root (GVolume *_volume)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (_volume);
+ return volume->activation_root != NULL ? g_object_ref (volume->activation_root) : NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_KEYRING
+static SecretSchema luks_passphrase_schema =
+{
+ "org.gnome.GVfs.Luks.Password",
+ SECRET_SCHEMA_DONT_MATCH_NAME,
+ {
+ { "gvfs-luks-uuid", SECRET_SCHEMA_ATTRIBUTE_STRING },
+ { NULL, 0 },
+ }
+};
+#endif
+
+struct MountData
+{
+ GSimpleAsyncResult *simple;
+
+ GVfsStoragedVolume *volume;
+ GCancellable *cancellable;
+
+ gulong mount_operation_reply_handler_id;
+ gulong mount_operation_aborted_handler_id;
+ GMountOperation *mount_operation;
+
+ gchar *passphrase;
+
+ gchar *passphrase_from_keyring;
+ GPasswordSave password_save;
+
+ gchar *uuid_of_encrypted_to_unlock;
+ gchar *desc_of_encrypted_to_unlock;
+ StoragedEncrypted *encrypted_to_unlock;
+ StoragedFilesystem *filesystem_to_mount;
+
+ gboolean checked_keyring;
+};
+
+static void
+mount_data_free (MountData *data)
+{
+ if (data->volume->mount_pending_op == data)
+ data->volume->mount_pending_op = NULL;
+
+ g_object_unref (data->simple);
+
+ g_clear_object (&data->volume);
+ g_clear_object (&data->cancellable);
+
+ if (data->mount_operation != NULL)
+ {
+ if (data->mount_operation_reply_handler_id != 0)
+ g_signal_handler_disconnect (data->mount_operation, data->mount_operation_reply_handler_id);
+ if (data->mount_operation_aborted_handler_id != 0)
+ g_signal_handler_disconnect (data->mount_operation, data->mount_operation_aborted_handler_id);
+ g_object_unref (data->mount_operation);
+ }
+
+#ifdef HAVE_KEYRING
+ secret_password_free (data->passphrase);
+ secret_password_free (data->passphrase_from_keyring);
+#else
+ g_free (data->passphrase);
+ g_free (data->passphrase_from_keyring);
+#endif
+
+ g_free (data->uuid_of_encrypted_to_unlock);
+ g_free (data->desc_of_encrypted_to_unlock);
+ g_clear_object (&data->encrypted_to_unlock);
+ g_clear_object (&data->filesystem_to_mount);
+ g_free (data);
+}
+
+static void
+mount_cancel_pending_op (MountData *data)
+{
+ g_cancellable_cancel (data->cancellable);
+ /* send an ::aborted signal to make the dialog go away */
+ if (data->mount_operation != NULL)
+ g_signal_emit_by_name (data->mount_operation, "aborted");
+}
+
+/* ------------------------------ */
+
+static void
+mount_command_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ MountData *data = user_data;
+ GError *error;
+ gint exit_status;
+ gchar *standard_error = NULL;
+
+ /* NOTE: for e.g. NFS and CIFS mounts we could do GMountOperation stuff and pipe a
+ * password to mount(8)'s stdin channel
+ *
+ * NOTE: if this fails because the user is not authorized (e.g. EPERM), we could
+ * run it through a polkit-ified setuid root helper
+ */
+
+ error = NULL;
+ if (!gvfs_storaged_utils_spawn_finish (res,
+ &exit_status,
+ NULL, /* gchar **out_standard_output */
+ &standard_error,
+ &error))
+ {
+ g_simple_async_result_take_error (data->simple, error);
+ g_simple_async_result_complete (data->simple);
+ mount_data_free (data);
+ goto out;
+ }
+
+ if (WIFEXITED (exit_status) && WEXITSTATUS (exit_status) == 0)
+ {
+ gvfs_storaged_volume_monitor_update (data->volume->monitor);
+ g_simple_async_result_complete (data->simple);
+ mount_data_free (data);
+ goto out;
+ }
+
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "%s", standard_error);
+ g_simple_async_result_complete (data->simple);
+ mount_data_free (data);
+
+ out:
+ g_free (standard_error);
+}
+
+/* ------------------------------ */
+
+static void
+ensure_autoclear (MountData *data)
+{
+ StoragedLoop *loop;
+ loop = storaged_client_get_loop_for_block (gvfs_storaged_volume_monitor_get_storaged_client (data->volume->monitor),
+ data->volume->block);
+ if (loop != NULL)
+ {
+ if (!storaged_loop_get_autoclear (loop) && storaged_loop_get_setup_by_uid (loop) == getuid ())
+ {
+ /* we don't care about the result */
+ storaged_loop_call_set_autoclear (loop, TRUE,
+ g_variant_new ("a{sv}", NULL), /* options */
+ NULL, NULL, NULL);
+ }
+ g_object_unref (loop);
+ }
+}
+
+/* ------------------------------ */
+
+
+static void
+mount_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ MountData *data = user_data;
+ gchar *mount_path;
+ GError *error;
+
+ error = NULL;
+ if (!storaged_filesystem_call_mount_finish (STORAGED_FILESYSTEM (source_object),
+ &mount_path,
+ res,
+ &error))
+ {
+ gvfs_storaged_utils_storaged_error_to_gio_error (error);
+ g_simple_async_result_take_error (data->simple, error);
+ g_simple_async_result_complete (data->simple);
+ }
+ else
+ {
+ /* if mounting worked, ensure that the loop device goes away when unmounted */
+ ensure_autoclear (data);
+
+ gvfs_storaged_volume_monitor_update (data->volume->monitor);
+ g_simple_async_result_complete (data->simple);
+ g_free (mount_path);
+ }
+ mount_data_free (data);
+}
+
+static void
+do_mount (MountData *data)
+{
+ GVariantBuilder builder;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+ if (data->mount_operation == NULL)
+ {
+ g_variant_builder_add (&builder,
+ "{sv}",
+ "auth.no_user_interaction", g_variant_new_boolean (TRUE));
+ }
+ storaged_filesystem_call_mount (data->filesystem_to_mount,
+ g_variant_builder_end (&builder),
+ data->cancellable,
+ mount_cb,
+ data);
+}
+
+/* ------------------------------ */
+
+#ifdef HAVE_KEYRING
+static void
+luks_store_passphrase_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ MountData *data = user_data;
+ GError *error = NULL;
+
+ if (secret_password_store_finish (result, &error))
+ {
+ /* everything is good */
+ do_mount (data);
+ }
+ else
+ {
+ /* report failure */
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Error storing passphrase in keyring (%s)"),
+ error->message);
+ g_simple_async_result_complete (data->simple);
+ g_error_free (error);
+ mount_data_free (data);
+ }
+}
+#endif
+
+
+static void do_unlock (MountData *data);
+
+
+#ifdef HAVE_KEYRING
+static void
+luks_delete_passphrase_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ MountData *data = user_data;
+ GError *error = NULL;
+
+ secret_password_clear_finish (result, &error);
+ if (!error)
+ {
+ /* with the bad passphrase out of the way, try again */
+ g_free (data->passphrase);
+ data->passphrase = NULL;
+ do_unlock (data);
+ }
+ else
+ {
+ /* report failure */
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Error deleting invalid passphrase from keyring (%s)"),
+ error->message);
+ g_simple_async_result_complete (data->simple);
+ g_error_free (error);
+ mount_data_free (data);
+ }
+}
+#endif
+
+static void
+unlock_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ MountData *data = user_data;
+ gchar *cleartext_device = NULL;
+ GError *error;
+
+ error = NULL;
+ if (!storaged_encrypted_call_unlock_finish (STORAGED_ENCRYPTED (source_object),
+ &cleartext_device,
+ res,
+ &error))
+ {
+#ifdef HAVE_KEYRING
+ /* If this failed with a passphrase read from the keyring, try again
+ * this time prompting the user...
+ *
+ * TODO: ideally check against something like STORAGED_ERROR_PASSPHRASE_INVALID
+ * when such a thing is available in udisks
+ */
+ if (data->passphrase_from_keyring != NULL &&
+ g_strcmp0 (data->passphrase, data->passphrase_from_keyring) == 0)
+ {
+ /* nuke the invalid passphrase from keyring... */
+ secret_password_clear (&luks_passphrase_schema, data->cancellable,
+ luks_delete_passphrase_cb, data,
+ "gvfs-luks-uuid", data->uuid_of_encrypted_to_unlock,
+ NULL); /* sentinel */
+ goto out;
+ }
+#endif
+ gvfs_storaged_utils_storaged_error_to_gio_error (error);
+ g_simple_async_result_take_error (data->simple, error);
+ g_simple_async_result_complete (data->simple);
+ mount_data_free (data);
+ goto out;
+ }
+ else
+ {
+ StoragedObject *object;
+
+ /* if unlocking worked, ensure that the loop device goes away when locked */
+ ensure_autoclear (data);
+
+ gvfs_storaged_volume_monitor_update (data->volume->monitor);
+
+ object = storaged_client_peek_object (gvfs_storaged_volume_monitor_get_storaged_client (data->volume->monitor),
+ cleartext_device);
+ data->filesystem_to_mount = object != NULL ? storaged_object_get_filesystem (object) : NULL;
+ if (data->filesystem_to_mount == NULL)
+ {
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("The unlocked device does not have a recognizable file system on it"));
+ g_simple_async_result_complete (data->simple);
+ mount_data_free (data);
+ goto out;
+ }
+
+#ifdef HAVE_KEYRING
+ /* passphrase worked - save it in the keyring if requested */
+ if (data->password_save != G_PASSWORD_SAVE_NEVER)
+ {
+ const gchar *keyring;
+ gchar *display_name;
+
+ switch (data->password_save)
+ {
+ case G_PASSWORD_SAVE_NEVER:
+ g_assert_not_reached ();
+ break;
+ case G_PASSWORD_SAVE_FOR_SESSION:
+ keyring = SECRET_COLLECTION_SESSION;
+ break;
+ case G_PASSWORD_SAVE_PERMANENTLY:
+ keyring = SECRET_COLLECTION_DEFAULT;
+ break;
+ default:
+ keyring = SECRET_COLLECTION_DEFAULT;
+ break;
+ }
+
+ display_name = g_strdup_printf (_("Encryption passphrase for %s"),
+ data->desc_of_encrypted_to_unlock);
+
+ secret_password_store (&luks_passphrase_schema,
+ keyring, display_name, data->passphrase,
+ data->cancellable,
+ luks_store_passphrase_cb, data,
+ "gvfs-luks-uuid", data->uuid_of_encrypted_to_unlock,
+ NULL); /* sentinel */
+ goto out;
+ }
+#endif
+
+ /* OK, ready to rock */
+ do_mount (data);
+ }
+
+ out:
+ g_free (cleartext_device);
+}
+
+static void
+on_mount_operation_reply (GMountOperation *mount_operation,
+ GMountOperationResult result,
+ gpointer user_data)
+{
+ MountData *data = user_data;
+
+ /* we got what we wanted; don't listen to any other signals from the mount operation */
+ if (data->mount_operation_reply_handler_id != 0)
+ {
+ g_signal_handler_disconnect (data->mount_operation, data->mount_operation_reply_handler_id);
+ data->mount_operation_reply_handler_id = 0;
+ }
+ if (data->mount_operation_aborted_handler_id != 0)
+ {
+ g_signal_handler_disconnect (data->mount_operation, data->mount_operation_aborted_handler_id);
+ data->mount_operation_aborted_handler_id = 0;
+ }
+
+ if (result != G_MOUNT_OPERATION_HANDLED)
+ {
+ if (result == G_MOUNT_OPERATION_ABORTED)
+ {
+ /* The user aborted the operation so consider it "handled" */
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED_HANDLED,
+ "Password dialog aborted (user should never see this error since it is G_IO_ERROR_FAILED_HANDLED)");
+ }
+ else
+ {
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_PERMISSION_DENIED,
+ "Expected G_MOUNT_OPERATION_HANDLED but got %d", result);
+ }
+ g_simple_async_result_complete (data->simple);
+ mount_data_free (data);
+ goto out;
+ }
+
+ data->passphrase = g_strdup (g_mount_operation_get_password (mount_operation));
+ data->password_save = g_mount_operation_get_password_save (mount_operation);
+
+ /* Don't save password in keyring just yet - check if it works first */
+
+ do_unlock (data);
+
+ out:
+ ;
+}
+
+static void
+on_mount_operation_aborted (GMountOperation *mount_operation,
+ gpointer user_data)
+{
+ on_mount_operation_reply (mount_operation, G_MOUNT_OPERATION_ABORTED, user_data);
+}
+
+static gboolean
+has_crypttab_passphrase (MountData *data)
+{
+ gboolean ret = FALSE;
+ GVariantIter iter;
+ GVariant *configuration_value;
+ const gchar *configuration_type;
+
+ g_variant_iter_init (&iter, storaged_block_get_configuration (data->volume->block));
+ while (g_variant_iter_next (&iter, "(&s@a{sv})", &configuration_type, &configuration_value))
+ {
+ if (g_strcmp0 (configuration_type, "crypttab") == 0)
+ {
+ const gchar *passphrase_path;
+ if (g_variant_lookup (configuration_value, "passphrase-path", "^&ay", &passphrase_path))
+ {
+ if (passphrase_path != NULL && strlen (passphrase_path) > 0)
+ {
+ ret = TRUE;
+ g_variant_unref (configuration_value);
+ goto out;
+ }
+ }
+ }
+ g_variant_unref (configuration_value);
+ }
+ out:
+ return ret;
+}
+
+#ifdef HAVE_KEYRING
+static void
+luks_find_passphrase_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ MountData *data = user_data;
+ gchar *password;
+
+ password = secret_password_lookup_finish (result, NULL);
+
+ /* Don't fail if a keyring error occured - just continue and request
+ * the passphrase from the user...
+ */
+ if (password)
+ {
+ data->passphrase = password;
+ data->passphrase_from_keyring = g_strdup (password);
+ }
+ /* try again */
+ do_unlock (data);
+}
+#endif
+
+static void
+do_unlock (MountData *data)
+{
+ GVariantBuilder builder;
+
+ if (data->passphrase == NULL)
+ {
+ /* If the passphrase is in the crypttab file, no need to ask the user, just use a blank passphrase */
+ if (has_crypttab_passphrase (data))
+ {
+ data->passphrase = g_strdup ("");
+ }
+ else
+ {
+ gchar *message;
+
+#ifdef HAVE_KEYRING
+ /* check if the passphrase is in the user's keyring */
+ if (!data->checked_keyring)
+ {
+ data->checked_keyring = TRUE;
+ secret_password_lookup (&luks_passphrase_schema, data->cancellable,
+ luks_find_passphrase_cb, data,
+ "gvfs-luks-uuid", data->uuid_of_encrypted_to_unlock,
+ NULL); /* sentinel */
+ goto out;
+ }
+#endif
+
+ if (data->mount_operation == NULL)
+ {
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("A passphrase is required to access the volume"));
+ g_simple_async_result_complete (data->simple);
+ mount_data_free (data);
+ goto out;
+ }
+
+ data->mount_operation_reply_handler_id = g_signal_connect (data->mount_operation,
+ "reply",
+ G_CALLBACK (on_mount_operation_reply),
+ data);
+ data->mount_operation_aborted_handler_id = g_signal_connect (data->mount_operation,
+ "aborted",
+ G_CALLBACK (on_mount_operation_aborted),
+ data);
+ /* Translators: This is the message shown to users */
+ message = g_strdup_printf (_("Enter a passphrase to unlock the volume\n"
+ "The passphrase is needed to access encrypted data on %s."),
+ data->desc_of_encrypted_to_unlock);
+
+ /* NOTE: We (currently) don't offer the user to save the
+ * passphrase in the keyring or /etc/crypttab - compared to
+ * the gdu volume monitor (that used the keyring for this)
+ * this is a "regression" but it's done this way on purpose
+ * because
+ *
+ * - if the device is encrypted, it was probably the intent
+ * that the passphrase is to be used every time it's used
+ *
+ * - supporting both /etc/crypttab and the keyring is confusing
+ * and leaves the user to wonder where the key is stored.
+ *
+ * - the user can add an /etc/crypttab entry himself either
+ * manually or through palimpsest
+ */
+ g_signal_emit_by_name (data->mount_operation,
+ "ask-password",
+ message,
+ NULL,
+ NULL,
+ G_ASK_PASSWORD_NEED_PASSWORD |
+ G_ASK_PASSWORD_SAVING_SUPPORTED);
+ g_free (message);
+ goto out;
+ }
+ }
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+ if (data->mount_operation == NULL)
+ {
+ g_variant_builder_add (&builder,
+ "{sv}",
+ "auth.no_user_interaction", g_variant_new_boolean (TRUE));
+ }
+ storaged_encrypted_call_unlock (data->encrypted_to_unlock,
+ data->passphrase,
+ g_variant_builder_end (&builder),
+ data->cancellable,
+ unlock_cb,
+ data);
+ out:
+ ;
+}
+
+
+static void
+gvfs_storaged_volume_mount (GVolume *_volume,
+ GMountMountFlags flags,
+ GMountOperation *mount_operation,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (_volume);
+ GDBusObject *object;
+ StoragedBlock *block;
+ StoragedFilesystem *filesystem;
+ MountData *data;
+
+ data = g_new0 (MountData, 1);
+ data->simple = g_simple_async_result_new (G_OBJECT (volume),
+ callback,
+ user_data,
+ gvfs_storaged_volume_mount);
+ data->volume = g_object_ref (volume);
+ data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL;
+ data->mount_operation = mount_operation != NULL ? g_object_ref (mount_operation) : NULL;
+
+ if (volume->mount_pending_op != NULL)
+ {
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "A mount operation is already pending");
+ g_simple_async_result_complete (data->simple);
+ mount_data_free (data);
+ goto out;
+ }
+ volume->mount_pending_op = data;
+
+ /* Use the mount(8) command if there is no block device */
+ if (volume->block == NULL)
+ {
+ gchar *escaped_mount_path;
+ escaped_mount_path = g_strescape (g_unix_mount_point_get_mount_path (data->volume->mount_point), NULL);
+ gvfs_storaged_utils_spawn (10, /* timeout in seconds */
+ data->cancellable,
+ mount_command_cb,
+ data,
+ "mount \"%s\"",
+ escaped_mount_path);
+ g_free (escaped_mount_path);
+ goto out;
+ }
+
+ /* if encrypted and already unlocked, just mount the cleartext block device */
+ block = storaged_client_get_cleartext_block (gvfs_storaged_volume_monitor_get_storaged_client (volume->monitor),
+ volume->block);
+ if (block != NULL)
+ g_object_unref (block);
+ else
+ block = volume->block;
+
+ object = g_dbus_interface_get_object (G_DBUS_INTERFACE (block));
+ if (object == NULL)
+ {
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "No object for D-Bus interface");
+ g_simple_async_result_complete (data->simple);
+ mount_data_free (data);
+ goto out;
+ }
+
+ filesystem = storaged_object_peek_filesystem (STORAGED_OBJECT (object));
+ if (filesystem == NULL)
+ {
+ data->encrypted_to_unlock = storaged_object_get_encrypted (STORAGED_OBJECT (object));
+ if (data->encrypted_to_unlock != NULL)
+ {
+ StoragedDrive *storaged_drive;
+
+ /* This description is used in both the prompt and the display-name of
+ * the key stored in the user's keyring ...
+ *
+ * NOTE: we want a little bit more detail than what g_drive_get_name()
+ * gives us, since this is going to be used to refer to the device even
+ * when not plugged in
+ */
+ storaged_drive = storaged_client_get_drive_for_block (gvfs_storaged_volume_monitor_get_storaged_client (volume->monitor),
+ block);
+ if (storaged_drive != NULL)
+ {
+ gchar *drive_name = NULL;
+ gchar *drive_desc = NULL;
+
+#if STORAGED_CHECK_VERSION(2,0,90)
+ {
+ StoragedObject *object = (StoragedObject *) g_dbus_interface_get_object (G_DBUS_INTERFACE (storaged_drive));
+ if (object != NULL)
+ {
+ StoragedObjectInfo *info = storaged_client_get_object_info (gvfs_storaged_volume_monitor_get_storaged_client (volume->monitor),
+ object);
+ if (info != NULL)
+ {
+ drive_name = g_strdup (storaged_object_info_get_name (info));
+ drive_desc = g_strdup (storaged_object_info_get_description (info));
+ g_object_unref (info);
+ }
+ }
+ }
+#else
+ storaged_client_get_drive_info (gvfs_storaged_volume_monitor_get_storaged_client (volume->monitor),
+ storaged_drive,
+ &drive_name,
+ &drive_desc,
+ NULL, /* drive_icon */
+ NULL, /* media_desc */
+ NULL); /* media_icon */
+#endif
+ /* Translators: this is used to describe the drive the encrypted media
+ * is on - the first %s is the name (such as 'WD 2500JB External'), the
+ * second %s is the description ('250 GB Hard Disk').
+ */
+ data->desc_of_encrypted_to_unlock = g_strdup_printf (_("%s (%s)"),
+ drive_name,
+ drive_desc);
+ g_free (drive_desc);
+ g_free (drive_name);
+ g_object_unref (storaged_drive);
+ }
+ else
+ {
+ data->desc_of_encrypted_to_unlock = storaged_block_dup_preferred_device (block);
+ }
+ data->uuid_of_encrypted_to_unlock = storaged_block_dup_id_uuid (block);
+
+ do_unlock (data);
+ goto out;
+ }
+
+ g_simple_async_result_set_error (data->simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "No .Filesystem or .Encrypted interface on D-Bus object");
+ g_simple_async_result_complete (data->simple);
+ mount_data_free (data);
+ goto out;
+ }
+
+ data->filesystem_to_mount = g_object_ref (filesystem);
+ do_mount (data);
+
+ out:
+ ;
+}
+
+static gboolean
+gvfs_storaged_volume_mount_finish (GVolume *volume,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+ return !g_simple_async_result_propagate_error (simple, error);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GObject *object;
+ GAsyncReadyCallback callback;
+ gpointer user_data;
+} EjectWrapperOp;
+
+static void
+eject_wrapper_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ EjectWrapperOp *data = user_data;
+ data->callback (data->object, res, data->user_data);
+ g_object_unref (data->object);
+ g_free (data);
+}
+
+static void
+gvfs_storaged_volume_eject_with_operation (GVolume *_volume,
+ GMountUnmountFlags flags,
+ GMountOperation *mount_operation,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (_volume);
+ GVfsStoragedDrive *drive;
+
+ drive = NULL;
+ if (volume->drive != NULL)
+ drive = g_object_ref (volume->drive);
+
+ if (drive != NULL)
+ {
+ EjectWrapperOp *data;
+ data = g_new0 (EjectWrapperOp, 1);
+ data->object = g_object_ref (volume);
+ data->callback = callback;
+ data->user_data = user_data;
+ g_drive_eject_with_operation (G_DRIVE (drive), flags, mount_operation, cancellable, eject_wrapper_callback, data);
+ g_object_unref (drive);
+ }
+ else
+ {
+ GSimpleAsyncResult *simple;
+ simple = g_simple_async_result_new_error (G_OBJECT (volume),
+ callback,
+ user_data,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ _("Operation not supported by backend"));
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ }
+}
+
+static gboolean
+gvfs_storaged_volume_eject_with_operation_finish (GVolume *_volume,
+ GAsyncResult *result,
+ GError **error)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (_volume);
+ gboolean ret = TRUE;
+
+ if (volume->drive != NULL)
+ {
+ ret = g_drive_eject_with_operation_finish (G_DRIVE (volume->drive), result, error);
+ }
+ else
+ {
+ g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+ ret = FALSE;
+ }
+ return ret;
+}
+
+static void
+gvfs_storaged_volume_eject (GVolume *volume,
+ GMountUnmountFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ gvfs_storaged_volume_eject_with_operation (volume, flags, NULL, cancellable, callback, user_data);
+}
+
+static gboolean
+gvfs_storaged_volume_eject_finish (GVolume *volume,
+ GAsyncResult *result,
+ GError **error)
+{
+ return gvfs_storaged_volume_eject_with_operation_finish (volume, result, error);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const gchar *
+gvfs_storaged_volume_get_sort_key (GVolume *_volume)
+{
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (_volume);
+ return volume->sort_key;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+gvfs_storaged_volume_volume_iface_init (GVolumeIface *iface)
+{
+ iface->get_name = gvfs_storaged_volume_get_name;
+ iface->get_icon = gvfs_storaged_volume_get_icon;
+ iface->get_symbolic_icon = gvfs_storaged_volume_get_symbolic_icon;
+ iface->get_uuid = gvfs_storaged_volume_get_uuid;
+ iface->get_drive = gvfs_storaged_volume_get_drive;
+ iface->get_mount = gvfs_storaged_volume_get_mount;
+ iface->can_mount = gvfs_storaged_volume_can_mount;
+ iface->can_eject = gvfs_storaged_volume_can_eject;
+ iface->should_automount = gvfs_storaged_volume_should_automount;
+ iface->get_activation_root = gvfs_storaged_volume_get_activation_root;
+ iface->enumerate_identifiers = gvfs_storaged_volume_enumerate_identifiers;
+ iface->get_identifier = gvfs_storaged_volume_get_identifier;
+
+ iface->mount_fn = gvfs_storaged_volume_mount;
+ iface->mount_finish = gvfs_storaged_volume_mount_finish;
+ iface->eject = gvfs_storaged_volume_eject;
+ iface->eject_finish = gvfs_storaged_volume_eject_finish;
+ iface->eject_with_operation = gvfs_storaged_volume_eject_with_operation;
+ iface->eject_with_operation_finish = gvfs_storaged_volume_eject_with_operation_finish;
+ iface->get_sort_key = gvfs_storaged_volume_get_sort_key;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+StoragedBlock *
+gvfs_storaged_volume_get_block (GVfsStoragedVolume *volume)
+{
+ g_return_val_if_fail (GVFS_IS_STORAGED_VOLUME (volume), NULL);
+ return volume->block;
+}
+
+GUnixMountPoint *
+gvfs_storaged_volume_get_mount_point (GVfsStoragedVolume *volume)
+{
+ g_return_val_if_fail (GVFS_IS_STORAGED_VOLUME (volume), NULL);
+ return volume->mount_point;
+}
+
+dev_t
+gvfs_storaged_volume_get_dev (GVfsStoragedVolume *volume)
+{
+ g_return_val_if_fail (GVFS_IS_STORAGED_VOLUME (volume), 0);
+ return volume->dev;
+}
+
+gboolean
+gvfs_storaged_volume_has_uuid (GVfsStoragedVolume *volume,
+ const gchar *uuid)
+{
+ g_return_val_if_fail (GVFS_IS_STORAGED_VOLUME (volume), FALSE);
+ return g_strcmp0 (volume->uuid, uuid) == 0;
+}
diff --git a/monitor/storaged/gvfsstoragedvolume.h b/monitor/storaged/gvfsstoragedvolume.h
new file mode 100644
index 00000000..2c396c97
--- /dev/null
+++ b/monitor/storaged/gvfsstoragedvolume.h
@@ -0,0 +1,67 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2006-2012 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __GVFS_STORAGED_VOLUME_H__
+#define __GVFS_STORAGED_VOLUME_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "gvfsstoragedvolumemonitor.h"
+
+G_BEGIN_DECLS
+
+#define GVFS_TYPE_STORAGED_VOLUME (gvfs_storaged_volume_get_type ())
+#define GVFS_STORAGED_VOLUME(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVFS_TYPE_STORAGED_VOLUME, GVfsStoragedVolume))
+#define GVFS_IS_STORAGED_VOLUME(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVFS_TYPE_STORAGED_VOLUME))
+
+GType gvfs_storaged_volume_get_type (void) G_GNUC_CONST;
+
+GVfsStoragedVolume *gvfs_storaged_volume_new (GVfsStoragedVolumeMonitor *monitor,
+ StoragedBlock *block,
+ GUnixMountPoint *mount_point,
+ GVfsStoragedDrive *drive,
+ GFile *activation_root,
+ gboolean coldplug);
+void gvfs_storaged_volume_removed (GVfsStoragedVolume *volume);
+
+StoragedBlock *gvfs_storaged_volume_get_block (GVfsStoragedVolume *volume);
+GUnixMountPoint *gvfs_storaged_volume_get_mount_point (GVfsStoragedVolume *volume);
+dev_t gvfs_storaged_volume_get_dev (GVfsStoragedVolume *volume);
+
+void gvfs_storaged_volume_set_mount (GVfsStoragedVolume *volume,
+ GVfsStoragedMount *mount);
+void gvfs_storaged_volume_unset_mount (GVfsStoragedVolume *volume,
+ GVfsStoragedMount *mount);
+
+void gvfs_storaged_volume_set_drive (GVfsStoragedVolume *volume,
+ GVfsStoragedDrive *drive);
+void gvfs_storaged_volume_unset_drive (GVfsStoragedVolume *volume,
+ GVfsStoragedDrive *drive);
+
+gboolean gvfs_storaged_volume_has_uuid (GVfsStoragedVolume *volume,
+ const gchar *uuid);
+
+G_END_DECLS
+
+#endif /* __GVFS_STORAGED_VOLUME_H__ */
diff --git a/monitor/storaged/gvfsstoragedvolumemonitor.c b/monitor/storaged/gvfsstoragedvolumemonitor.c
new file mode 100644
index 00000000..8afaec3c
--- /dev/null
+++ b/monitor/storaged/gvfsstoragedvolumemonitor.c
@@ -0,0 +1,1885 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2011-2012 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <config.h>
+
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+
+#include "gvfsstoragedvolumemonitor.h"
+#include "gvfsstorageddrive.h"
+#include "gvfsstoragedvolume.h"
+#include "gvfsstoragedmount.h"
+#include "gvfsstoragedutils.h"
+
+static GVfsStoragedVolumeMonitor *the_volume_monitor = NULL;
+
+typedef struct _GVfsStoragedVolumeMonitorClass GVfsStoragedVolumeMonitorClass;
+
+struct _GVfsStoragedVolumeMonitorClass
+{
+ GNativeVolumeMonitorClass parent_class;
+};
+
+struct _GVfsStoragedVolumeMonitor
+{
+ GNativeVolumeMonitor parent;
+
+ StoragedClient *client;
+ GUdevClient *gudev_client;
+ GUnixMountMonitor *mount_monitor;
+
+ GList *drives;
+ GList *volumes;
+ GList *fstab_volumes;
+ GList *mounts;
+ /* we keep volumes/mounts for blank and audio discs separate to handle e.g. mixed discs properly */
+ GList *disc_volumes;
+ GList *disc_mounts;
+};
+
+static StoragedClient *get_storaged_client_sync (GError **error);
+
+static void update_all (GVfsStoragedVolumeMonitor *monitor,
+ gboolean emit_changes,
+ gboolean coldplug);
+static void update_drives (GVfsStoragedVolumeMonitor *monitor,
+ GList **added_drives,
+ GList **removed_drives,
+ gboolean coldplug);
+static void update_volumes (GVfsStoragedVolumeMonitor *monitor,
+ GList **added_volumes,
+ GList **removed_volumes,
+ gboolean coldplug);
+static void update_fstab_volumes (GVfsStoragedVolumeMonitor *monitor,
+ GList **added_volumes,
+ GList **removed_volumes,
+ gboolean coldplug);
+static void update_mounts (GVfsStoragedVolumeMonitor *monitor,
+ GList **added_mounts,
+ GList **removed_mounts,
+ gboolean coldplug);
+static void update_discs (GVfsStoragedVolumeMonitor *monitor,
+ GList **added_volumes,
+ GList **removed_volumes,
+ GList **added_mounts,
+ GList **removed_mounts,
+ gboolean coldplug);
+
+
+static void on_client_changed (StoragedClient *client,
+ gpointer user_data);
+
+static void mountpoints_changed (GUnixMountMonitor *mount_monitor,
+ gpointer user_data);
+
+static void mounts_changed (GUnixMountMonitor *mount_monitor,
+ gpointer user_data);
+
+G_DEFINE_TYPE (GVfsStoragedVolumeMonitor, gvfs_storaged_volume_monitor, G_TYPE_NATIVE_VOLUME_MONITOR)
+
+static void
+gvfs_storaged_volume_monitor_dispose (GObject *object)
+{
+ the_volume_monitor = NULL;
+
+ if (G_OBJECT_CLASS (gvfs_storaged_volume_monitor_parent_class)->dispose != NULL)
+ G_OBJECT_CLASS (gvfs_storaged_volume_monitor_parent_class)->dispose (object);
+}
+
+static void
+gvfs_storaged_volume_monitor_finalize (GObject *object)
+{
+ GVfsStoragedVolumeMonitor *monitor = GVFS_STORAGED_VOLUME_MONITOR (object);
+
+ g_signal_handlers_disconnect_by_func (monitor->mount_monitor, mountpoints_changed, monitor);
+ g_signal_handlers_disconnect_by_func (monitor->mount_monitor, mounts_changed, monitor);
+ g_clear_object (&monitor->mount_monitor);
+
+ g_signal_handlers_disconnect_by_func (monitor->client,
+ G_CALLBACK (on_client_changed),
+ monitor);
+
+ g_clear_object (&monitor->client);
+ g_clear_object (&monitor->gudev_client);
+
+ g_list_free_full (monitor->drives, g_object_unref);
+ g_list_free_full (monitor->volumes, g_object_unref);
+ g_list_free_full (monitor->fstab_volumes, g_object_unref);
+ g_list_free_full (monitor->mounts, g_object_unref);
+
+ g_list_free_full (monitor->disc_volumes, g_object_unref);
+ g_list_free_full (monitor->disc_mounts, g_object_unref);
+
+ G_OBJECT_CLASS (gvfs_storaged_volume_monitor_parent_class)->finalize (object);
+}
+
+static GList *
+get_mounts (GVolumeMonitor *_monitor)
+{
+ GVfsStoragedVolumeMonitor *monitor = GVFS_STORAGED_VOLUME_MONITOR (_monitor);
+ GList *ret;
+
+ ret = g_list_copy (monitor->mounts);
+ ret = g_list_concat (ret, g_list_copy (monitor->disc_mounts));
+ g_list_foreach (ret, (GFunc) g_object_ref, NULL);
+ return ret;
+}
+
+static GList *
+get_volumes (GVolumeMonitor *_monitor)
+{
+ GVfsStoragedVolumeMonitor *monitor = GVFS_STORAGED_VOLUME_MONITOR (_monitor);
+ GList *ret;
+
+ ret = g_list_copy (monitor->volumes);
+ ret = g_list_concat (ret, g_list_copy (monitor->fstab_volumes));
+ ret = g_list_concat (ret, g_list_copy (monitor->disc_volumes));
+ g_list_foreach (ret, (GFunc) g_object_ref, NULL);
+ return ret;
+}
+
+static GList *
+get_connected_drives (GVolumeMonitor *_monitor)
+{
+ GVfsStoragedVolumeMonitor *monitor = GVFS_STORAGED_VOLUME_MONITOR (_monitor);
+ GList *ret;
+
+ ret = g_list_copy (monitor->drives);
+ g_list_foreach (ret, (GFunc) g_object_ref, NULL);
+ return ret;
+}
+
+static GVolume *
+get_volume_for_uuid (GVolumeMonitor *_monitor,
+ const gchar *uuid)
+{
+ GVfsStoragedVolumeMonitor *monitor = GVFS_STORAGED_VOLUME_MONITOR (_monitor);
+ GVfsStoragedVolume *volume;
+ GList *l;
+
+ for (l = monitor->volumes; l != NULL; l = l->next)
+ {
+ volume = l->data;
+ if (gvfs_storaged_volume_has_uuid (l->data, uuid))
+ goto found;
+ }
+ for (l = monitor->fstab_volumes; l != NULL; l = l->next)
+ {
+ volume = l->data;
+ if (gvfs_storaged_volume_has_uuid (l->data, uuid))
+ goto found;
+ }
+ for (l = monitor->disc_volumes; l != NULL; l = l->next)
+ {
+ volume = l->data;
+ if (gvfs_storaged_volume_has_uuid (volume, uuid))
+ goto found;
+ }
+
+ return NULL;
+
+ found:
+ return G_VOLUME (g_object_ref (volume));
+}
+
+static GMount *
+get_mount_for_uuid (GVolumeMonitor *_monitor,
+ const gchar *uuid)
+{
+ GVfsStoragedVolumeMonitor *monitor = GVFS_STORAGED_VOLUME_MONITOR (_monitor);
+ GVfsStoragedMount *mount;
+ GList *l;
+
+ for (l = monitor->mounts; l != NULL; l = l->next)
+ {
+ mount = l->data;
+ if (gvfs_storaged_mount_has_uuid (l->data, uuid))
+ goto found;
+ }
+ for (l = monitor->disc_mounts; l != NULL; l = l->next)
+ {
+ mount = l->data;
+ if (gvfs_storaged_mount_has_uuid (mount, uuid))
+ goto found;
+ }
+
+ return NULL;
+
+ found:
+ return G_MOUNT (g_object_ref (mount));
+}
+
+static GMount *
+get_mount_for_mount_path (const gchar *mount_path,
+ GCancellable *cancellable)
+{
+ GVfsStoragedVolumeMonitor *monitor = NULL;
+ GMount *ret = NULL;
+
+ if (the_volume_monitor == NULL)
+ {
+ /* Bah, no monitor is set up.. so we have to create one, find
+ * what the user asks for and throw it away again.
+ */
+ monitor = GVFS_STORAGED_VOLUME_MONITOR (gvfs_storaged_volume_monitor_new ());
+ }
+ else
+ {
+ monitor = g_object_ref (the_volume_monitor);
+ }
+
+ /* creation of the volume monitor could fail */
+ if (monitor != NULL)
+ {
+ GList *l;
+ for (l = monitor->mounts; l != NULL; l = l->next)
+ {
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (l->data);
+ if (g_strcmp0 (gvfs_storaged_mount_get_mount_path (mount), mount_path) == 0)
+ {
+ ret = g_object_ref (mount);
+ goto out;
+ }
+ }
+ }
+
+ out:
+ if (monitor != NULL)
+ g_object_unref (monitor);
+ return ret;
+}
+
+static GObject *
+gvfs_storaged_volume_monitor_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GObject *ret = NULL;
+ GObjectClass *parent_class;
+
+ if (the_volume_monitor != NULL)
+ {
+ ret = g_object_ref (the_volume_monitor);
+ goto out;
+ }
+
+ parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (g_type_class_peek (type)));
+ ret = parent_class->constructor (type,
+ n_construct_properties,
+ construct_properties);
+
+ the_volume_monitor = GVFS_STORAGED_VOLUME_MONITOR (ret);
+
+ out:
+ return ret;
+}
+
+static void
+gvfs_storaged_volume_monitor_init (GVfsStoragedVolumeMonitor *monitor)
+{
+ monitor->gudev_client = g_udev_client_new (NULL); /* don't listen to any changes */
+
+ monitor->client = get_storaged_client_sync (NULL);
+ g_signal_connect (monitor->client,
+ "changed",
+ G_CALLBACK (on_client_changed),
+ monitor);
+
+ monitor->mount_monitor = g_unix_mount_monitor_get ();
+ g_signal_connect (monitor->mount_monitor,
+ "mounts-changed",
+ G_CALLBACK (mounts_changed),
+ monitor);
+ g_signal_connect (monitor->mount_monitor,
+ "mountpoints-changed",
+ G_CALLBACK (mountpoints_changed),
+ monitor);
+
+ update_all (monitor, FALSE, TRUE);
+}
+
+static gboolean
+is_supported (void)
+{
+ if (get_storaged_client_sync (NULL) != NULL)
+ return TRUE;
+ return FALSE;
+}
+
+static void
+gvfs_storaged_volume_monitor_class_init (GVfsStoragedVolumeMonitorClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GVolumeMonitorClass *monitor_class = G_VOLUME_MONITOR_CLASS (klass);
+ GNativeVolumeMonitorClass *native_class = G_NATIVE_VOLUME_MONITOR_CLASS (klass);
+
+ gobject_class->constructor = gvfs_storaged_volume_monitor_constructor;
+ gobject_class->finalize = gvfs_storaged_volume_monitor_finalize;
+ gobject_class->dispose = gvfs_storaged_volume_monitor_dispose;
+
+ monitor_class->get_mounts = get_mounts;
+ monitor_class->get_volumes = get_volumes;
+ monitor_class->get_connected_drives = get_connected_drives;
+ monitor_class->get_volume_for_uuid = get_volume_for_uuid;
+ monitor_class->get_mount_for_uuid = get_mount_for_uuid;
+ monitor_class->is_supported = is_supported;
+
+ native_class->get_mount_for_mount_path = get_mount_for_mount_path;
+}
+
+/**
+ * gvfs_storaged_volume_monitor_new:
+ *
+ * Returns: a new #GVolumeMonitor.
+ **/
+GVolumeMonitor *
+gvfs_storaged_volume_monitor_new (void)
+{
+ return G_VOLUME_MONITOR (g_object_new (GVFS_TYPE_STORAGED_VOLUME_MONITOR, NULL));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+StoragedClient *
+gvfs_storaged_volume_monitor_get_storaged_client (GVfsStoragedVolumeMonitor *monitor)
+{
+ g_return_val_if_fail (GVFS_IS_STORAGED_VOLUME_MONITOR (monitor), NULL);
+ return monitor->client;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+GUdevClient *
+gvfs_storaged_volume_monitor_get_gudev_client (GVfsStoragedVolumeMonitor *monitor)
+{
+ g_return_val_if_fail (GVFS_IS_STORAGED_VOLUME_MONITOR (monitor), NULL);
+ return monitor->gudev_client;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void
+gvfs_storaged_volume_monitor_update (GVfsStoragedVolumeMonitor *monitor)
+{
+ g_return_if_fail (GVFS_IS_STORAGED_VOLUME_MONITOR (monitor));
+ storaged_client_settle (monitor->client);
+ update_all (monitor, TRUE, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static StoragedClient *
+get_storaged_client_sync (GError **error)
+{
+ static StoragedClient *_client = NULL;
+ static GError *_error = NULL;
+ static volatile gsize initialized = 0;
+
+ if (g_once_init_enter (&initialized))
+ {
+ _client = storaged_client_new_sync (NULL, &_error);
+ g_once_init_leave (&initialized, 1);
+ }
+
+ if (_error != NULL && error != NULL)
+ *error = g_error_copy (_error);
+
+ return _client;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+diff_sorted_lists (GList *list1,
+ GList *list2,
+ GCompareFunc compare,
+ GList **added,
+ GList **removed,
+ GList **unchanged)
+{
+ int order;
+
+ *added = *removed = NULL;
+ if (unchanged != NULL)
+ *unchanged = NULL;
+
+ while (list1 != NULL &&
+ list2 != NULL)
+ {
+ order = (*compare) (list1->data, list2->data);
+ if (order < 0)
+ {
+ *removed = g_list_prepend (*removed, list1->data);
+ list1 = list1->next;
+ }
+ else if (order > 0)
+ {
+ *added = g_list_prepend (*added, list2->data);
+ list2 = list2->next;
+ }
+ else
+ { /* same item */
+ if (unchanged != NULL)
+ *unchanged = g_list_prepend (*unchanged, list1->data);
+ list1 = list1->next;
+ list2 = list2->next;
+ }
+ }
+
+ while (list1 != NULL)
+ {
+ *removed = g_list_prepend (*removed, list1->data);
+ list1 = list1->next;
+ }
+ while (list2 != NULL)
+ {
+ *added = g_list_prepend (*added, list2->data);
+ list2 = list2->next;
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+object_list_emit (GVfsStoragedVolumeMonitor *monitor,
+ const gchar *monitor_signal,
+ const gchar *object_signal,
+ GList *objects)
+{
+ GList *l;
+ for (l = objects; l != NULL; l = l->next)
+ {
+ g_signal_emit_by_name (monitor, monitor_signal, l->data);
+ if (object_signal)
+ g_signal_emit_by_name (l->data, object_signal);
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_client_changed (StoragedClient *client,
+ gpointer user_data)
+{
+ GVfsStoragedVolumeMonitor *monitor = GVFS_STORAGED_VOLUME_MONITOR (user_data);
+ update_all (monitor, TRUE, FALSE);
+}
+
+static void
+mountpoints_changed (GUnixMountMonitor *mount_monitor,
+ gpointer user_data)
+{
+ GVfsStoragedVolumeMonitor *monitor = GVFS_STORAGED_VOLUME_MONITOR (user_data);
+ update_all (monitor, TRUE, FALSE);
+}
+
+static void
+mounts_changed (GUnixMountMonitor *mount_monitor,
+ gpointer user_data)
+{
+ GVfsStoragedVolumeMonitor *monitor = GVFS_STORAGED_VOLUME_MONITOR (user_data);
+ update_all (monitor, TRUE, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+update_all (GVfsStoragedVolumeMonitor *monitor,
+ gboolean emit_changes,
+ gboolean coldplug)
+{
+ GList *added_drives, *removed_drives;
+ GList *added_volumes, *removed_volumes;
+ GList *added_mounts, *removed_mounts;
+
+ added_drives = NULL;
+ removed_drives = NULL;
+ added_volumes = NULL;
+ removed_volumes = NULL;
+ added_mounts = NULL;
+ removed_mounts = NULL;
+
+ update_drives (monitor, &added_drives, &removed_drives, coldplug);
+ update_volumes (monitor, &added_volumes, &removed_volumes, coldplug);
+ update_fstab_volumes (monitor, &added_volumes, &removed_volumes, coldplug);
+ update_mounts (monitor, &added_mounts, &removed_mounts, coldplug);
+ update_discs (monitor,
+ &added_volumes, &removed_volumes,
+ &added_mounts, &removed_mounts,
+ coldplug);
+
+ if (emit_changes)
+ {
+ object_list_emit (monitor,
+ "drive-disconnected", NULL,
+ removed_drives);
+ object_list_emit (monitor,
+ "drive-connected", NULL,
+ added_drives);
+
+ object_list_emit (monitor,
+ "volume-removed", "removed",
+ removed_volumes);
+ object_list_emit (monitor,
+ "volume-added", NULL,
+ added_volumes);
+
+ object_list_emit (monitor,
+ "mount-removed", "unmounted",
+ removed_mounts);
+ object_list_emit (monitor,
+ "mount-added", NULL,
+ added_mounts);
+ }
+
+ g_list_free_full (removed_drives, g_object_unref);
+ g_list_free_full (added_drives, g_object_unref);
+ g_list_free_full (removed_volumes, g_object_unref);
+ g_list_free_full (added_volumes, g_object_unref);
+ g_list_free_full (removed_mounts, g_object_unref);
+ g_list_free_full (added_mounts, g_object_unref);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GUnixMountPoint *
+get_mount_point_for_mount (GUnixMountEntry *mount_entry)
+{
+ GUnixMountPoint *ret = NULL;
+ GList *mount_points, *l;
+
+ mount_points = g_unix_mount_points_get (NULL);
+ for (l = mount_points; l != NULL; l = l->next)
+ {
+ GUnixMountPoint *mount_point = l->data;
+ if (g_strcmp0 (g_unix_mount_get_mount_path (mount_entry),
+ g_unix_mount_point_get_mount_path (mount_point)) == 0)
+ {
+ ret = mount_point;
+ goto out;
+ }
+ }
+
+ out:
+ for (l = mount_points; l != NULL; l = l->next)
+ {
+ GUnixMountPoint *mount_point = l->data;
+ if (G_LIKELY (mount_point != ret))
+ g_unix_mount_point_free (mount_point);
+ }
+ g_list_free (mount_points);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+should_include (const gchar *mount_path,
+ const gchar *options)
+{
+ gboolean ret = FALSE;
+ const gchar *home_dir = NULL;
+ const gchar *user_name;
+ gsize user_name_len;
+
+ g_return_val_if_fail (mount_path != NULL, FALSE);
+
+ /* The x-gvfs-show option trumps everything else */
+ if (options != NULL)
+ {
+ gchar *value;
+ value = gvfs_storaged_utils_lookup_fstab_options_value (options, "x-gvfs-show");
+ if (value != NULL)
+ {
+ ret = TRUE;
+ g_free (value);
+ goto out;
+ }
+ value = gvfs_storaged_utils_lookup_fstab_options_value (options, "x-gvfs-hide");
+ if (value != NULL)
+ {
+ ret = FALSE;
+ g_free (value);
+ goto out;
+ }
+ }
+
+ /* Never display internal mountpoints */
+ if (g_unix_is_mount_path_system_internal (mount_path))
+ goto out;
+
+ /* Only display things in
+ * - /media; and
+ * - $HOME; and
+ * - /run/media/$USER
+ */
+
+ /* Hide mounts within a subdirectory starting with a "." - suppose it was a purpose to hide this mount */
+ if (g_strstr_len (mount_path, -1, "/.") != NULL)
+ goto out;
+
+ /* Check /media */
+ if (g_str_has_prefix (mount_path, "/media/"))
+ {
+ ret = TRUE;
+ goto out;
+ }
+
+ /* Check home dir */
+ home_dir = g_get_home_dir ();
+ if (home_dir != NULL)
+ {
+ if (g_str_has_prefix (mount_path, home_dir) && mount_path[strlen (home_dir)] == G_DIR_SEPARATOR)
+ {
+ ret = TRUE;
+ goto out;
+ }
+ }
+
+ /* Check /run/media/$USER/ */
+ user_name = g_get_user_name ();
+ user_name_len = strlen (user_name);
+ if (strncmp (mount_path, "/run/media/", sizeof ("/run/media/") - 1) == 0 &&
+ strncmp (mount_path + sizeof ("/run/media/") - 1, user_name, user_name_len) == 0 &&
+ mount_path[sizeof ("/run/media/") - 1 + user_name_len] == '/')
+ {
+ ret = TRUE;
+ goto out;
+ }
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+should_include_mount_point (GVfsStoragedVolumeMonitor *monitor,
+ GUnixMountPoint *mount_point)
+{
+ return should_include (g_unix_mount_point_get_mount_path (mount_point),
+ g_unix_mount_point_get_options (mount_point));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+should_include_mount (GVfsStoragedVolumeMonitor *monitor,
+ GUnixMountEntry *mount_entry)
+{
+ GUnixMountPoint *mount_point;
+ gboolean ret;
+
+ /* if mounted at the designated mount point, use that info to decide */
+ mount_point = get_mount_point_for_mount (mount_entry);
+ if (mount_point != NULL)
+ {
+ ret = should_include_mount_point (monitor, mount_point);
+ g_unix_mount_point_free (mount_point);
+ goto out;
+ }
+
+ /* otherwise, use the mount's info */
+ ret = should_include (g_unix_mount_get_mount_path (mount_entry),
+ NULL); /* no mount options yet - see bug 668132 */
+
+ out:
+ return ret;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+should_include_volume_check_mount_points (GVfsStoragedVolumeMonitor *monitor,
+ StoragedBlock *block)
+{
+ gboolean ret = TRUE;
+ GDBusObject *obj;
+ StoragedFilesystem *fs;
+ const gchar* const *mount_points;
+ guint n;
+
+ obj = g_dbus_interface_get_object (G_DBUS_INTERFACE (block));
+ if (obj == NULL)
+ goto out;
+
+ fs = storaged_object_peek_filesystem (STORAGED_OBJECT (obj));
+ if (fs == NULL)
+ goto out;
+
+ mount_points = storaged_filesystem_get_mount_points (fs);
+ for (n = 0; mount_points != NULL && mount_points[n] != NULL; n++)
+ {
+ const gchar *mount_point = mount_points[n];
+ GUnixMountEntry *mount_entry;
+
+ mount_entry = g_unix_mount_at (mount_point, NULL);
+ if (mount_entry != NULL)
+ {
+ if (!should_include_mount (monitor, mount_entry))
+ {
+ g_unix_mount_free (mount_entry);
+ ret = FALSE;
+ goto out;
+ }
+ g_unix_mount_free (mount_entry);
+ }
+ }
+
+ out:
+ return ret;
+}
+
+static gboolean
+should_include_volume_check_configuration (GVfsStoragedVolumeMonitor *monitor,
+ StoragedBlock *block)
+{
+ gboolean ret = TRUE;
+ GVariantIter iter;
+ const gchar *configuration_type;
+ GVariant *configuration_value;
+
+ g_variant_iter_init (&iter, storaged_block_get_configuration (block));
+ while (g_variant_iter_next (&iter, "(&s@a{sv})", &configuration_type, &configuration_value))
+ {
+ if (g_strcmp0 (configuration_type, "fstab") == 0)
+ {
+ const gchar *fstab_dir;
+ const gchar *fstab_options;
+ if (g_variant_lookup (configuration_value, "dir", "^&ay", &fstab_dir) &&
+ g_variant_lookup (configuration_value, "opts", "^&ay", &fstab_options))
+ {
+ if (!should_include (fstab_dir, fstab_options))
+ {
+ ret = FALSE;
+ g_variant_unref (configuration_value);
+ goto out;
+ }
+ }
+ }
+ g_variant_unref (configuration_value);
+ }
+
+ out:
+ return ret;
+}
+
+static gboolean should_include_drive (GVfsStoragedVolumeMonitor *monitor,
+ StoragedDrive *drive);
+
+static gboolean
+should_include_volume (GVfsStoragedVolumeMonitor *monitor,
+ StoragedBlock *block,
+ gboolean allow_encrypted_cleartext)
+{
+ gboolean ret = FALSE;
+ GDBusObject *object;
+ StoragedFilesystem *filesystem;
+ StoragedDrive *storaged_drive = NULL;
+ const gchar* const *mount_points;
+ StoragedLoop *loop = NULL;
+
+ /* Block:Ignore trumps everything */
+ if (storaged_block_get_hint_ignore (block))
+ goto out;
+
+ /* If the device (or if a partition, its containing device) is a
+ * loop device, check the SetupByUid property - we don't want to
+ * show loop devices set up by other users
+ */
+ loop = storaged_client_get_loop_for_block (monitor->client, block);
+ if (loop != NULL)
+ {
+ GDBusObject *loop_object;
+ StoragedBlock *block_for_loop;
+ guint setup_by_uid;
+
+ setup_by_uid = storaged_loop_get_setup_by_uid (loop);
+ if (setup_by_uid != 0 && setup_by_uid != getuid ())
+ goto out;
+
+ /* Work-around bug in Linux where partitions of a loop
+ * device (e.g. /dev/loop0p1) are lingering even when the
+ * parent loop device (e.g. /dev/loop0) has been cleared
+ */
+ loop_object = g_dbus_interface_get_object (G_DBUS_INTERFACE (loop));
+ if (loop_object == NULL)
+ goto out;
+ block_for_loop = storaged_object_peek_block (STORAGED_OBJECT (loop_object));
+ if (block_for_loop == NULL)
+ goto out;
+ if (storaged_block_get_size (block_for_loop) == 0)
+ goto out;
+ }
+
+ /* ignore the volume if the drive is ignored */
+ storaged_drive = storaged_client_get_drive_for_block (monitor->client, block);
+ if (storaged_drive != NULL)
+ {
+ if (!should_include_drive (monitor, storaged_drive))
+ {
+ goto out;
+ }
+ }
+
+ /* show encrypted volumes... */
+ if (g_strcmp0 (storaged_block_get_id_type (block), "crypto_LUKS") == 0)
+ {
+ StoragedBlock *cleartext_block;
+ /* ... unless the volume is unlocked and we don't want to show the cleartext volume */
+ cleartext_block = storaged_client_get_cleartext_block (monitor->client, block);
+ if (cleartext_block != NULL)
+ {
+ ret = should_include_volume (monitor, cleartext_block, TRUE);
+ g_object_unref (cleartext_block);
+ }
+ else
+ {
+ ret = TRUE;
+ }
+ goto out;
+ }
+
+ if (!allow_encrypted_cleartext)
+ {
+ /* ... but not unlocked volumes (because the volume for the encrypted part morphs
+ * into the cleartext part when unlocked)
+ */
+ if (g_strcmp0 (storaged_block_get_crypto_backing_device (block), "/") != 0)
+ {
+ goto out;
+ }
+ }
+
+ /* Check should_include_mount() for all mount points, if any - e.g. if a volume
+ * is mounted in a place where the mount is to be ignored, we ignore the volume
+ * as well
+ */
+ if (!should_include_volume_check_mount_points (monitor, block))
+ goto out;
+
+ object = g_dbus_interface_get_object (G_DBUS_INTERFACE (block));
+ if (object == NULL)
+ goto out;
+
+ filesystem = storaged_object_peek_filesystem (STORAGED_OBJECT (object));
+ if (filesystem == NULL)
+ goto out;
+
+ /* If not mounted but the volume is referenced in /etc/fstab and
+ * that configuration indicates the volume should be ignored, then
+ * do so
+ */
+ mount_points = storaged_filesystem_get_mount_points (filesystem);
+ if (mount_points == NULL || g_strv_length ((gchar **) mount_points) == 0)
+ {
+ if (!should_include_volume_check_configuration (monitor, block))
+ goto out;
+ }
+
+ /* otherwise, we're good to go */
+ ret = TRUE;
+
+ out:
+ g_clear_object (&storaged_drive);
+ g_clear_object (&loop);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+should_include_drive (GVfsStoragedVolumeMonitor *monitor,
+ StoragedDrive *drive)
+{
+ gboolean ret = TRUE;
+
+ /* Don't include drives on other seats */
+ if (!gvfs_storaged_utils_is_drive_on_our_seat (drive))
+ {
+ ret = FALSE;
+ goto out;
+ }
+
+ /* NOTE: For now, we just include a drive no matter its
+ * content. This may be wrong ... for example non-removable drives
+ * without anything visible (such RAID components) should probably
+ * not be shown. Then again, the GNOME 3 user interface doesn't
+ * really show GDrive instances except for in the computer:///
+ * location in Nautilus....
+ */
+
+ out:
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gint
+storaged_drive_compare (StoragedDrive *a, StoragedDrive *b)
+{
+ GDBusObject *oa = g_dbus_interface_get_object (G_DBUS_INTERFACE (a));
+ GDBusObject *ob = g_dbus_interface_get_object (G_DBUS_INTERFACE (b));
+ /* Either or both of oa, ob can be NULL for the case where a drive
+ * is removed but we still hold a reference to the drive interface
+ */
+ if (oa != NULL && ob != NULL)
+ return g_strcmp0 (g_dbus_object_get_object_path (oa), g_dbus_object_get_object_path (ob));
+ else
+ return (const gchar*) ob - (const gchar*) oa;
+}
+
+static gint
+block_compare (StoragedBlock *a, StoragedBlock *b)
+{
+ return g_strcmp0 (storaged_block_get_device (a), storaged_block_get_device (b));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GVfsStoragedVolume *
+find_disc_volume_for_storaged_drive (GVfsStoragedVolumeMonitor *monitor,
+ StoragedDrive *storaged_drive)
+{
+ GVfsStoragedVolume *ret = NULL;
+ GList *l;
+
+ for (l = monitor->disc_volumes; l != NULL; l = l->next)
+ {
+ GVolume *volume = G_VOLUME (l->data);
+ GDrive *drive = g_volume_get_drive (volume);
+ if (drive != NULL)
+ {
+ if (gvfs_storaged_drive_get_storaged_drive (GVFS_STORAGED_DRIVE (drive)) == storaged_drive)
+ {
+ ret = GVFS_STORAGED_VOLUME (volume);
+ g_object_unref (drive);
+ goto out;
+ }
+ g_object_unref (drive);
+ }
+ }
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GVfsStoragedMount *
+find_disc_mount_for_storaged_drive (GVfsStoragedVolumeMonitor *monitor,
+ StoragedDrive *storaged_drive)
+{
+ GVfsStoragedMount *ret = NULL;
+ GList *l;
+
+ for (l = monitor->disc_mounts; l != NULL; l = l->next)
+ {
+ GMount *mount = G_MOUNT (l->data);
+ GVolume *volume;
+
+ volume = g_mount_get_volume (mount);
+ if (volume != NULL)
+ {
+ GDrive *drive = g_volume_get_drive (volume);
+ if (drive != NULL)
+ {
+ if (gvfs_storaged_drive_get_storaged_drive (GVFS_STORAGED_DRIVE (drive)) == storaged_drive)
+ {
+ ret = GVFS_STORAGED_MOUNT (mount);
+ g_object_unref (volume);
+ g_object_unref (drive);
+ goto out;
+ }
+ g_object_unref (drive);
+ }
+ g_object_unref (volume);
+ }
+ }
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GVfsStoragedDrive *
+find_drive_for_storaged_drive (GVfsStoragedVolumeMonitor *monitor,
+ StoragedDrive *storaged_drive)
+{
+ GVfsStoragedDrive *ret = NULL;
+ GList *l;
+
+ for (l = monitor->drives; l != NULL; l = l->next)
+ {
+ GVfsStoragedDrive *drive = GVFS_STORAGED_DRIVE (l->data);
+ if (gvfs_storaged_drive_get_storaged_drive (drive) == storaged_drive)
+ {
+ ret = drive;
+ goto out;
+ }
+ }
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GVfsStoragedVolume *
+find_volume_for_block (GVfsStoragedVolumeMonitor *monitor,
+ StoragedBlock *block)
+{
+ GVfsStoragedVolume *ret = NULL;
+ GList *l;
+
+ for (l = monitor->volumes; l != NULL; l = l->next)
+ {
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (l->data);
+ if (gvfs_storaged_volume_get_block (volume) == block)
+ {
+ ret = volume;
+ goto out;
+ }
+ }
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GVfsStoragedVolume *
+find_fstab_volume_for_mount_point (GVfsStoragedVolumeMonitor *monitor,
+ GUnixMountPoint *mount_point)
+{
+ GVfsStoragedVolume *ret = NULL;
+ GList *l;
+
+ for (l = monitor->fstab_volumes; l != NULL; l = l->next)
+ {
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (l->data);
+ if (g_unix_mount_point_compare (gvfs_storaged_volume_get_mount_point (volume), mount_point) == 0)
+ {
+ ret = volume;
+ goto out;
+ }
+ }
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+mount_point_matches_mount_entry (GUnixMountPoint *mount_point,
+ GUnixMountEntry *mount_entry)
+{
+ gboolean ret = FALSE;
+ gchar *mp_path = NULL;
+ gchar *mp_entry = NULL;
+
+ mp_path = g_strdup (g_unix_mount_point_get_mount_path (mount_point));
+ mp_entry = g_strdup (g_unix_mount_get_mount_path (mount_entry));
+
+ if (g_str_has_suffix (mp_path, "/"))
+ mp_path[strlen(mp_path) - 1] = '\0';
+ if (g_str_has_suffix (mp_entry, "/"))
+ mp_entry[strlen(mp_entry) - 1] = '\0';
+
+ if (g_strcmp0 (mp_path, mp_entry) != 0)
+ goto out;
+
+ ret = TRUE;
+
+ out:
+ g_free (mp_path);
+ g_free (mp_entry);
+ return ret;
+}
+
+static GVfsStoragedVolume *
+find_fstab_volume_for_mount_entry (GVfsStoragedVolumeMonitor *monitor,
+ GUnixMountEntry *mount_entry)
+{
+ GVfsStoragedVolume *ret = NULL;
+ GList *l;
+
+ for (l = monitor->fstab_volumes; l != NULL; l = l->next)
+ {
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (l->data);
+ if (mount_point_matches_mount_entry (gvfs_storaged_volume_get_mount_point (volume), mount_entry))
+ {
+ ret = volume;
+ goto out;
+ }
+ }
+
+ out:
+ return ret;
+}
+
+static GVfsStoragedMount *
+find_lonely_mount_for_mount_point (GVfsStoragedVolumeMonitor *monitor,
+ GUnixMountPoint *mount_point)
+{
+ GVfsStoragedMount *ret = NULL;
+ GList *l;
+
+ for (l = monitor->mounts; l != NULL; l = l->next)
+ {
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (l->data);
+ if (mount_point != NULL &&
+ mount_point_matches_mount_entry (mount_point, gvfs_storaged_mount_get_mount_entry (mount)))
+ {
+ GVolume *volume = g_mount_get_volume (G_MOUNT (mount));
+ if (volume != NULL)
+ {
+ g_object_unref (volume);
+ }
+ else
+ {
+ ret = mount;
+ goto out;
+ }
+ }
+ }
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GVfsStoragedVolume *
+find_volume_for_device (GVfsStoragedVolumeMonitor *monitor,
+ const gchar *device)
+{
+ GVfsStoragedVolume *ret = NULL;
+ GList *blocks = NULL;
+ GList *l;
+ struct stat statbuf;
+
+ /* don't consider e.g. network mounts */
+ if (g_str_has_prefix (device, "LABEL="))
+ {
+ blocks = storaged_client_get_block_for_label (monitor->client, device + 6);
+ if (blocks != NULL)
+ device = storaged_block_get_device (STORAGED_BLOCK (blocks->data));
+ else
+ goto out;
+ }
+ else if (g_str_has_prefix (device, "UUID="))
+ {
+ blocks = storaged_client_get_block_for_uuid (monitor->client, device + 6);
+ if (blocks != NULL)
+ device = storaged_block_get_device (STORAGED_BLOCK (blocks->data));
+ else
+ goto out;
+ }
+ else if (!g_str_has_prefix (device, "/dev/"))
+ {
+ goto out;
+ }
+
+ if (stat (device, &statbuf) != 0)
+ goto out;
+
+ for (l = monitor->volumes; l != NULL; l = l->next)
+ {
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (l->data);
+ if (gvfs_storaged_volume_get_dev (volume) == statbuf.st_rdev)
+ {
+ ret = volume;
+ goto out;
+ }
+ }
+
+ for (l = monitor->disc_volumes; l != NULL; l = l->next)
+ {
+ GVfsStoragedVolume *volume = GVFS_STORAGED_VOLUME (l->data);
+ if (gvfs_storaged_volume_get_dev (volume) == statbuf.st_rdev)
+ {
+ ret = volume;
+ goto out;
+ }
+ }
+
+ out:
+ g_list_free_full (blocks, g_object_unref);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GVfsStoragedMount *
+find_mount_by_mount_path (GVfsStoragedVolumeMonitor *monitor,
+ const gchar *mount_path)
+{
+ GVfsStoragedMount *ret = NULL;
+ GList *l;
+
+ for (l = monitor->mounts; l != NULL; l = l->next)
+ {
+ GVfsStoragedMount *mount = GVFS_STORAGED_MOUNT (l->data);
+ if (g_strcmp0 (gvfs_storaged_mount_get_mount_path (mount), mount_path) == 0)
+ {
+ ret = mount;
+ goto out;
+ }
+ }
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+update_drives (GVfsStoragedVolumeMonitor *monitor,
+ GList **added_drives,
+ GList **removed_drives,
+ gboolean coldplug)
+{
+ GList *cur_storaged_drives;
+ GList *new_storaged_drives;
+ GList *removed, *added;
+ GList *l;
+ GVfsStoragedDrive *drive;
+ GList *objects;
+
+ objects = g_dbus_object_manager_get_objects (storaged_client_get_object_manager (monitor->client));
+
+ cur_storaged_drives = NULL;
+ for (l = monitor->drives; l != NULL; l = l->next)
+ {
+ cur_storaged_drives = g_list_prepend (cur_storaged_drives,
+ gvfs_storaged_drive_get_storaged_drive (GVFS_STORAGED_DRIVE (l->data)));
+ }
+
+ /* remove devices we want to ignore - we do it here so we get to reevaluate
+ * on the next update whether they should still be ignored
+ */
+ new_storaged_drives = NULL;
+ for (l = objects; l != NULL; l = l->next)
+ {
+ StoragedDrive *storaged_drive = storaged_object_peek_drive (STORAGED_OBJECT (l->data));
+ if (storaged_drive == NULL)
+ continue;
+ if (should_include_drive (monitor, storaged_drive))
+ new_storaged_drives = g_list_prepend (new_storaged_drives, storaged_drive);
+ }
+
+ cur_storaged_drives = g_list_sort (cur_storaged_drives, (GCompareFunc) storaged_drive_compare);
+ new_storaged_drives = g_list_sort (new_storaged_drives, (GCompareFunc) storaged_drive_compare);
+ diff_sorted_lists (cur_storaged_drives,
+ new_storaged_drives, (GCompareFunc) storaged_drive_compare,
+ &added, &removed, NULL);
+
+ for (l = removed; l != NULL; l = l->next)
+ {
+ StoragedDrive *storaged_drive = STORAGED_DRIVE (l->data);
+
+ drive = find_drive_for_storaged_drive (monitor, storaged_drive);
+ if (drive != NULL)
+ {
+ /*g_debug ("removing drive %s", gdu_presentable_get_id (p));*/
+ gvfs_storaged_drive_disconnected (drive);
+ monitor->drives = g_list_remove (monitor->drives, drive);
+ *removed_drives = g_list_prepend (*removed_drives, g_object_ref (drive));
+ g_object_unref (drive);
+ }
+ }
+
+ for (l = added; l != NULL; l = l->next)
+ {
+ StoragedDrive *storaged_drive = STORAGED_DRIVE (l->data);
+
+ drive = find_drive_for_storaged_drive (monitor, storaged_drive);
+ if (drive == NULL)
+ {
+ /*g_debug ("adding drive %s", gdu_presentable_get_id (p));*/
+ drive = gvfs_storaged_drive_new (monitor, storaged_drive, coldplug);
+ if (storaged_drive != NULL)
+ {
+ monitor->drives = g_list_prepend (monitor->drives, drive);
+ *added_drives = g_list_prepend (*added_drives, g_object_ref (drive));
+ }
+ }
+ }
+
+ g_list_free (added);
+ g_list_free (removed);
+
+ g_list_free (cur_storaged_drives);
+ g_list_free (new_storaged_drives);
+
+ g_list_free_full (objects, g_object_unref);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+update_volumes (GVfsStoragedVolumeMonitor *monitor,
+ GList **added_volumes,
+ GList **removed_volumes,
+ gboolean coldplug)
+{
+ GList *cur_block_volumes;
+ GList *new_block_volumes;
+ GList *removed, *added;
+ GList *l;
+ GVfsStoragedVolume *volume;
+ GList *objects;
+
+ objects = g_dbus_object_manager_get_objects (storaged_client_get_object_manager (monitor->client));
+
+ cur_block_volumes = NULL;
+ for (l = monitor->volumes; l != NULL; l = l->next)
+ {
+ cur_block_volumes = g_list_prepend (cur_block_volumes,
+ gvfs_storaged_volume_get_block (GVFS_STORAGED_VOLUME (l->data)));
+ }
+
+ new_block_volumes = NULL;
+ for (l = objects; l != NULL; l = l->next)
+ {
+ StoragedBlock *block = storaged_object_peek_block (STORAGED_OBJECT (l->data));
+ if (block == NULL)
+ continue;
+ if (should_include_volume (monitor, block, FALSE))
+ new_block_volumes = g_list_prepend (new_block_volumes, block);
+ }
+
+ cur_block_volumes = g_list_sort (cur_block_volumes, (GCompareFunc) block_compare);
+ new_block_volumes = g_list_sort (new_block_volumes, (GCompareFunc) block_compare);
+ diff_sorted_lists (cur_block_volumes,
+ new_block_volumes, (GCompareFunc) block_compare,
+ &added, &removed, NULL);
+
+ for (l = removed; l != NULL; l = l->next)
+ {
+ StoragedBlock *block = STORAGED_BLOCK (l->data);
+ volume = find_volume_for_block (monitor, block);
+ if (volume != NULL)
+ {
+ gvfs_storaged_volume_removed (volume);
+ monitor->volumes = g_list_remove (monitor->volumes, volume);
+ *removed_volumes = g_list_prepend (*removed_volumes, g_object_ref (volume));
+ g_object_unref (volume);
+ }
+ }
+
+ for (l = added; l != NULL; l = l->next)
+ {
+ StoragedBlock *block = STORAGED_BLOCK (l->data);
+ volume = find_volume_for_block (monitor, block);
+ if (volume == NULL)
+ {
+ GVfsStoragedDrive *drive = NULL;
+ StoragedDrive *storaged_drive;
+
+ storaged_drive = storaged_client_get_drive_for_block (monitor->client, block);
+ if (storaged_drive != NULL)
+ {
+ drive = find_drive_for_storaged_drive (monitor, storaged_drive);
+ g_object_unref (storaged_drive);
+ }
+ volume = gvfs_storaged_volume_new (monitor,
+ block,
+ NULL, /* mount_point */
+ drive,
+ NULL, /* activation_root */
+ coldplug);
+ if (volume != NULL)
+ {
+ monitor->volumes = g_list_prepend (monitor->volumes, volume);
+ *added_volumes = g_list_prepend (*added_volumes, g_object_ref (volume));
+ }
+ }
+ }
+
+ g_list_free (added);
+ g_list_free (removed);
+ g_list_free (new_block_volumes);
+ g_list_free (cur_block_volumes);
+
+ g_list_free_full (objects, g_object_unref);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+have_storaged_volume_for_mount_point (GVfsStoragedVolumeMonitor *monitor,
+ GUnixMountPoint *mount_point)
+{
+ gboolean ret = FALSE;
+
+ if (find_volume_for_device (monitor, g_unix_mount_point_get_device_path (mount_point)) == NULL)
+ goto out;
+
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+static gboolean
+mount_point_has_device (GVfsStoragedVolumeMonitor *monitor,
+ GUnixMountPoint *mount_point)
+{
+ gboolean ret = FALSE;
+ const gchar *device;
+ struct stat statbuf;
+ StoragedBlock *block;
+ GList *blocks = NULL;
+
+ device = g_unix_mount_point_get_device_path (mount_point);
+ if (g_str_has_prefix (device, "LABEL="))
+ {
+ blocks = storaged_client_get_block_for_label (monitor->client, device + 6);
+ if (blocks != NULL)
+ device = storaged_block_get_device (STORAGED_BLOCK (blocks->data));
+ else
+ goto out;
+ }
+ else if (g_str_has_prefix (device, "UUID="))
+ {
+ blocks = storaged_client_get_block_for_uuid (monitor->client, device + 6);
+ if (blocks != NULL)
+ device = storaged_block_get_device (STORAGED_BLOCK (blocks->data));
+ else
+ goto out;
+ }
+ else if (!g_str_has_prefix (device, "/dev/"))
+ {
+ /* NFS, CIFS and other non-device mounts always have a device */
+ ret = TRUE;
+ goto out;
+ }
+
+ if (stat (device, &statbuf) != 0)
+ goto out;
+
+ if (statbuf.st_rdev == 0)
+ goto out;
+
+ /* assume non-existant if media is not available */
+ block = storaged_client_get_block_for_dev (monitor->client, statbuf.st_rdev);
+ if (block != NULL)
+ {
+ StoragedDrive *drive;
+ drive = storaged_client_get_drive_for_block (monitor->client, block);
+ if (drive != NULL)
+ {
+ if (!storaged_drive_get_media_available (drive))
+ {
+ g_object_unref (drive);
+ g_object_unref (block);
+ goto out;
+ }
+ g_object_unref (drive);
+ }
+ g_object_unref (block);
+ }
+ else
+ {
+ /* not known by udisks, assume media is available */
+ }
+
+ ret = TRUE;
+
+ out:
+ g_list_free_full (blocks, g_object_unref);
+ return ret;
+}
+
+static void
+update_fstab_volumes (GVfsStoragedVolumeMonitor *monitor,
+ GList **added_volumes,
+ GList **removed_volumes,
+ gboolean coldplug)
+{
+ GList *cur_mount_points;
+ GList *new_mount_points;
+ GList *added;
+ GList *removed;
+ GList *l, *ll;
+ GVfsStoragedVolume *volume;
+
+ cur_mount_points = NULL;
+ for (l = monitor->fstab_volumes; l != NULL; l = l->next)
+ {
+ GUnixMountPoint *mount_point = gvfs_storaged_volume_get_mount_point (GVFS_STORAGED_VOLUME (l->data));
+ if (mount_point != NULL)
+ cur_mount_points = g_list_prepend (cur_mount_points, mount_point);
+ }
+
+ new_mount_points = g_unix_mount_points_get (NULL);
+ /* filter the mount points that we don't want to include */
+ for (l = new_mount_points; l != NULL; l = ll)
+ {
+ GUnixMountPoint *mount_point = l->data;
+
+ ll = l->next;
+
+ if (!should_include_mount_point (monitor, mount_point) ||
+ have_storaged_volume_for_mount_point (monitor, mount_point) ||
+ !mount_point_has_device (monitor, mount_point))
+ {
+ new_mount_points = g_list_remove_link (new_mount_points, l);
+ g_unix_mount_point_free (mount_point);
+ }
+ }
+
+ cur_mount_points = g_list_sort (cur_mount_points, (GCompareFunc) g_unix_mount_point_compare);
+ new_mount_points = g_list_sort (new_mount_points, (GCompareFunc) g_unix_mount_point_compare);
+ diff_sorted_lists (cur_mount_points,
+ new_mount_points, (GCompareFunc) g_unix_mount_point_compare,
+ &added, &removed, NULL);
+
+ for (l = removed; l != NULL; l = l->next)
+ {
+ GUnixMountPoint *mount_point = l->data;
+ volume = find_fstab_volume_for_mount_point (monitor, mount_point);
+ if (volume != NULL)
+ {
+ gvfs_storaged_volume_removed (volume);
+ monitor->fstab_volumes = g_list_remove (monitor->fstab_volumes, volume);
+ *removed_volumes = g_list_prepend (*removed_volumes, g_object_ref (volume));
+ g_object_unref (volume);
+ }
+ }
+
+ for (l = added; l != NULL; l = l->next)
+ {
+ GUnixMountPoint *mount_point = l->data;
+
+ volume = find_fstab_volume_for_mount_point (monitor, mount_point);
+ if (volume != NULL)
+ continue;
+
+ volume = gvfs_storaged_volume_new (monitor,
+ NULL, /* block */
+ mount_point,
+ NULL, /* drive */
+ NULL, /* activation_root */
+ coldplug);
+ if (volume != NULL)
+ {
+ GVfsStoragedMount *mount;
+
+ monitor->fstab_volumes = g_list_prepend (monitor->fstab_volumes, volume);
+ *added_volumes = g_list_prepend (*added_volumes, g_object_ref (volume));
+ /* since @volume takes ownership of @mount_point, don't free it below */
+ new_mount_points = g_list_remove (new_mount_points, mount_point);
+
+ /* Could be there's already a mount for this volume - for example, the
+ * user could just have added it to the /etc/fstab file
+ */
+ mount = find_lonely_mount_for_mount_point (monitor, mount_point);
+ if (mount != NULL)
+ gvfs_storaged_mount_set_volume (mount, volume);
+ }
+ }
+
+ g_list_free_full (new_mount_points, (GDestroyNotify) g_unix_mount_point_free);
+
+ g_list_free (cur_mount_points);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+update_mounts (GVfsStoragedVolumeMonitor *monitor,
+ GList **added_mounts,
+ GList **removed_mounts,
+ gboolean coldplug)
+{
+ GList *cur_mounts;
+ GList *new_mounts;
+ GList *removed, *added, *unchanged;
+ GList *l, *ll;
+ GVfsStoragedMount *mount;
+ GVfsStoragedVolume *volume;
+
+ cur_mounts = NULL;
+ for (l = monitor->mounts; l != NULL; l = l->next)
+ {
+ GUnixMountEntry *mount_entry;
+
+ mount = GVFS_STORAGED_MOUNT (l->data);
+ mount_entry = gvfs_storaged_mount_get_mount_entry (mount);
+ if (mount_entry != NULL)
+ cur_mounts = g_list_prepend (cur_mounts, mount_entry);
+ }
+
+ new_mounts = g_unix_mounts_get (NULL);
+ /* remove mounts we want to ignore - we do it here so we get to reevaluate
+ * on the next update whether they should still be ignored
+ */
+ for (l = new_mounts; l != NULL; l = ll)
+ {
+ GUnixMountEntry *mount_entry = l->data;
+ ll = l->next;
+ if (!should_include_mount (monitor, mount_entry))
+ {
+ g_unix_mount_free (mount_entry);
+ new_mounts = g_list_delete_link (new_mounts, l);
+ }
+ }
+
+ cur_mounts = g_list_sort (cur_mounts, (GCompareFunc) g_unix_mount_compare);
+ new_mounts = g_list_sort (new_mounts, (GCompareFunc) g_unix_mount_compare);
+ diff_sorted_lists (cur_mounts,
+ new_mounts, (GCompareFunc) g_unix_mount_compare,
+ &added, &removed, &unchanged);
+
+ for (l = removed; l != NULL; l = l->next)
+ {
+ GUnixMountEntry *mount_entry = l->data;
+ mount = find_mount_by_mount_path (monitor, g_unix_mount_get_mount_path (mount_entry));
+ if (mount != NULL)
+ {
+ gvfs_storaged_mount_unmounted (mount);
+ monitor->mounts = g_list_remove (monitor->mounts, mount);
+ *removed_mounts = g_list_prepend (*removed_mounts, g_object_ref (mount));
+ /*g_debug ("removed mount at %s", gvfs_storaged_mount_get_mount_path (mount));*/
+ g_object_unref (mount);
+ }
+ }
+
+ for (l = added; l != NULL; l = l->next)
+ {
+ GUnixMountEntry *mount_entry = l->data;
+ volume = find_volume_for_device (monitor, g_unix_mount_get_device_path (mount_entry));
+ if (volume == NULL)
+ volume = find_fstab_volume_for_mount_entry (monitor, mount_entry);
+ mount = gvfs_storaged_mount_new (monitor, mount_entry, volume); /* adopts mount_entry */
+ if (mount != NULL)
+ {
+ monitor->mounts = g_list_prepend (monitor->mounts, mount);
+ *added_mounts = g_list_prepend (*added_mounts, g_object_ref (mount));
+ /*g_debug ("added mount at %s for %p", gvfs_storaged_mount_get_mount_path (mount), volume);*/
+ /* since @mount takes ownership of @mount_entry, don't free it below */
+ new_mounts = g_list_remove (new_mounts, mount_entry);
+ }
+ }
+
+ /* Handle the case where the volume containing the mount appears *after*
+ * the mount.
+ *
+ * This can happen when unlocking+mounting a LUKS device and the two
+ * operations are *right* after each other. In that case we get the
+ * event from GUnixMountMonitor (which monitors /proc/mounts) before
+ * the event from udisks.
+ */
+ for (l = unchanged; l != NULL; l = l->next)
+ {
+ GUnixMountEntry *mount_entry = l->data;
+ mount = find_mount_by_mount_path (monitor, g_unix_mount_get_mount_path (mount_entry));
+ if (mount == NULL)
+ {
+ g_warning ("No mount object for path %s", g_unix_mount_get_mount_path (mount_entry));
+ continue;
+ }
+ if (gvfs_storaged_mount_get_volume (mount) == NULL)
+ {
+ volume = find_volume_for_device (monitor, g_unix_mount_get_device_path (mount_entry));
+ if (volume == NULL)
+ volume = find_fstab_volume_for_mount_entry (monitor, mount_entry);
+ if (volume != NULL)
+ gvfs_storaged_mount_set_volume (mount, volume);
+ }
+ }
+
+ g_list_free (added);
+ g_list_free (removed);
+ g_list_free (unchanged);
+
+ g_list_free_full (new_mounts, (GDestroyNotify) g_unix_mount_free);
+
+ g_list_free (cur_mounts);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+update_discs (GVfsStoragedVolumeMonitor *monitor,
+ GList **added_volumes,
+ GList **removed_volumes,
+ GList **added_mounts,
+ GList **removed_mounts,
+ gboolean coldplug)
+{
+ GList *objects;
+ GList *cur_disc_drives;
+ GList *new_disc_drives;
+ GList *removed, *added;
+ GList *l;
+ GVfsStoragedDrive *drive;
+ GVfsStoragedVolume *volume;
+ GVfsStoragedMount *mount;
+
+ /* we also need to generate GVolume + GMount objects for
+ *
+ * - optical discs with audio
+ * - optical discs that are blank
+ *
+ */
+
+ objects = g_dbus_object_manager_get_objects (storaged_client_get_object_manager (monitor->client));
+
+ cur_disc_drives = NULL;
+ for (l = monitor->disc_volumes; l != NULL; l = l->next)
+ {
+ volume = GVFS_STORAGED_VOLUME (l->data);
+ drive = GVFS_STORAGED_DRIVE (g_volume_get_drive (G_VOLUME (volume)));
+ if (drive != NULL)
+ {
+ cur_disc_drives = g_list_prepend (cur_disc_drives, gvfs_storaged_drive_get_storaged_drive (drive));
+ g_object_unref (drive);
+ }
+ }
+
+ new_disc_drives = NULL;
+ for (l = objects; l != NULL; l = l->next)
+ {
+ StoragedDrive *storaged_drive = storaged_object_peek_drive (STORAGED_OBJECT (l->data));
+ if (storaged_drive == NULL)
+ continue;
+
+ if (!should_include_drive (monitor, storaged_drive))
+ continue;
+
+ /* only consider blank and audio discs */
+ if (!(storaged_drive_get_optical_blank (storaged_drive) ||
+ storaged_drive_get_optical_num_audio_tracks (storaged_drive) > 0))
+ continue;
+
+ new_disc_drives = g_list_prepend (new_disc_drives, storaged_drive);
+ }
+
+ cur_disc_drives = g_list_sort (cur_disc_drives, (GCompareFunc) storaged_drive_compare);
+ new_disc_drives = g_list_sort (new_disc_drives, (GCompareFunc) storaged_drive_compare);
+ diff_sorted_lists (cur_disc_drives, new_disc_drives, (GCompareFunc) storaged_drive_compare,
+ &added, &removed, NULL);
+
+ for (l = removed; l != NULL; l = l->next)
+ {
+ StoragedDrive *storaged_drive = STORAGED_DRIVE (l->data);
+
+ volume = find_disc_volume_for_storaged_drive (monitor, storaged_drive);
+ mount = find_disc_mount_for_storaged_drive (monitor, storaged_drive);
+
+ if (mount != NULL)
+ {
+ gvfs_storaged_mount_unmounted (mount);
+ monitor->disc_mounts = g_list_remove (monitor->disc_mounts, mount);
+ *removed_mounts = g_list_prepend (*removed_mounts, mount);
+ }
+ if (volume != NULL)
+ {
+ gvfs_storaged_volume_removed (volume);
+ monitor->disc_volumes = g_list_remove (monitor->disc_volumes, volume);
+ *removed_volumes = g_list_prepend (*removed_volumes, volume);
+ }
+ }
+
+ for (l = added; l != NULL; l = l->next)
+ {
+ StoragedDrive *storaged_drive = STORAGED_DRIVE (l->data);
+ StoragedBlock *block;
+
+ block = storaged_client_get_block_for_drive (monitor->client, storaged_drive, FALSE);
+ if (block != NULL)
+ {
+ volume = find_disc_volume_for_storaged_drive (monitor, storaged_drive);
+ if (volume == NULL)
+ {
+ gchar *uri;
+ GFile *activation_root;
+ if (storaged_drive_get_optical_blank (storaged_drive))
+ {
+ uri = g_strdup ("burn://");
+ }
+ else
+ {
+ gchar *basename = g_path_get_basename (storaged_block_get_device (block));
+ uri = g_strdup_printf ("cdda://%s", basename);
+ g_free (basename);
+ }
+ activation_root = g_file_new_for_uri (uri);
+ volume = gvfs_storaged_volume_new (monitor,
+ block,
+ NULL, /* mount_point */
+ find_drive_for_storaged_drive (monitor, storaged_drive),
+ activation_root,
+ coldplug);
+ if (volume != NULL)
+ {
+ monitor->disc_volumes = g_list_prepend (monitor->disc_volumes, volume);
+ *added_volumes = g_list_prepend (*added_volumes, g_object_ref (volume));
+
+ if (storaged_drive_get_optical_blank (storaged_drive))
+ {
+ mount = gvfs_storaged_mount_new (monitor,
+ NULL, /* GUnixMountEntry */
+ volume);
+ if (mount != NULL)
+ {
+ monitor->disc_mounts = g_list_prepend (monitor->disc_mounts, mount);
+ *added_mounts = g_list_prepend (*added_mounts, g_object_ref (mount));
+ }
+ }
+ }
+
+ g_object_unref (activation_root);
+ g_free (uri);
+ }
+ g_object_unref (block);
+ }
+ }
+
+ g_list_free (added);
+ g_list_free (removed);
+
+ g_list_free (new_disc_drives);
+ g_list_free (cur_disc_drives);
+
+ g_list_free_full (objects, g_object_unref);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/monitor/storaged/gvfsstoragedvolumemonitor.h b/monitor/storaged/gvfsstoragedvolumemonitor.h
new file mode 100644
index 00000000..0e05505a
--- /dev/null
+++ b/monitor/storaged/gvfsstoragedvolumemonitor.h
@@ -0,0 +1,55 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2006-2012 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#ifndef __GVFS_STORAGED_VOLUME_MONITOR_H__
+#define __GVFS_STORAGED_VOLUME_MONITOR_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gio/gunixmounts.h>
+
+#include <storaged/storaged.h>
+#include <gudev/gudev.h>
+
+G_BEGIN_DECLS
+
+#define GVFS_TYPE_STORAGED_VOLUME_MONITOR (gvfs_storaged_volume_monitor_get_type ())
+#define GVFS_STORAGED_VOLUME_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVFS_TYPE_STORAGED_VOLUME_MONITOR, GVfsStoragedVolumeMonitor))
+#define GVFS_IS_STORAGED_VOLUME_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVFS_TYPE_STORAGED_VOLUME_MONITOR))
+
+typedef struct _GVfsStoragedVolumeMonitor GVfsStoragedVolumeMonitor;
+
+/* Forward definitions */
+typedef struct _GVfsStoragedDrive GVfsStoragedDrive;
+typedef struct _GVfsStoragedVolume GVfsStoragedVolume;
+typedef struct _GVfsStoragedMount GVfsStoragedMount;
+
+GType gvfs_storaged_volume_monitor_get_type (void) G_GNUC_CONST;
+GVolumeMonitor *gvfs_storaged_volume_monitor_new (void);
+StoragedClient *gvfs_storaged_volume_monitor_get_storaged_client (GVfsStoragedVolumeMonitor *monitor);
+void gvfs_storaged_volume_monitor_update (GVfsStoragedVolumeMonitor *monitor);
+GUdevClient *gvfs_storaged_volume_monitor_get_gudev_client (GVfsStoragedVolumeMonitor *monitor);
+
+G_END_DECLS
+
+#endif /* __GVFS_STORAGED_VOLUME_MONITOR_H__ */
diff --git a/monitor/storaged/org.gtk.vfs.StoragedVolumeMonitor.service.in b/monitor/storaged/org.gtk.vfs.StoragedVolumeMonitor.service.in
new file mode 100644
index 00000000..53617796
--- /dev/null
+++ b/monitor/storaged/org.gtk.vfs.StoragedVolumeMonitor.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.gtk.vfs.StoragedVolumeMonitor
+Exec=@libexecdir@/gvfs-storaged-volume-monitor
diff --git a/monitor/storaged/storaged.monitor b/monitor/storaged/storaged.monitor
new file mode 100644
index 00000000..67cd536f
--- /dev/null
+++ b/monitor/storaged/storaged.monitor
@@ -0,0 +1,5 @@
+[RemoteVolumeMonitor]
+Name=GProxyVolumeMonitorStoraged
+DBusName=org.gtk.vfs.StoragedVolumeMonitor
+IsNative=true
+NativePriority=5
diff --git a/monitor/storaged/storagedvolumemonitordaemon.c b/monitor/storaged/storagedvolumemonitordaemon.c
new file mode 100644
index 00000000..d0c41529
--- /dev/null
+++ b/monitor/storaged/storagedvolumemonitordaemon.c
@@ -0,0 +1,46 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2006-2012 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include <config.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+#include <gio/gio.h>
+
+#include <gvfsproxyvolumemonitordaemon.h>
+
+#include "gvfsstoragedvolumemonitor.h"
+
+int
+main (int argc, char *argv[])
+{
+ g_vfs_proxy_volume_monitor_daemon_init ();
+
+ g_set_application_name (_("GVfs Storaged Volume Monitor"));
+
+ return g_vfs_proxy_volume_monitor_daemon_main (argc,
+ argv,
+ "org.gtk.vfs.StoragedVolumeMonitor",
+ GVFS_TYPE_STORAGED_VOLUME_MONITOR);
+}