diff options
author | Ondrej Holy <oholy@redhat.com> | 2015-09-25 08:24:50 +0200 |
---|---|---|
committer | Ondrej Holy <oholy@redhat.com> | 2015-09-30 15:34:51 +0200 |
commit | 27430703cb7ac571acaea927bd2dd24f4c45397d (patch) | |
tree | 711702bbf662e29ad0e09bd852ae70a9cec1181b | |
parent | 2eaaac903fe54a9c111888c7446b9f5f4dfdc36a (diff) | |
download | gvfs-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.ac | 21 | ||||
-rw-r--r-- | monitor/Makefile.am | 6 | ||||
-rw-r--r-- | monitor/storaged/.gitignore | 1 | ||||
-rw-r--r-- | monitor/storaged/Makefile.am | 56 | ||||
-rw-r--r-- | monitor/storaged/gvfsstorageddrive.c | 987 | ||||
-rw-r--r-- | monitor/storaged/gvfsstorageddrive.h | 52 | ||||
-rw-r--r-- | monitor/storaged/gvfsstoragedmount.c | 1378 | ||||
-rw-r--r-- | monitor/storaged/gvfsstoragedmount.h | 61 | ||||
-rw-r--r-- | monitor/storaged/gvfsstoragedutils.c | 828 | ||||
-rw-r--r-- | monitor/storaged/gvfsstoragedutils.h | 64 | ||||
-rw-r--r-- | monitor/storaged/gvfsstoragedvolume.c | 1786 | ||||
-rw-r--r-- | monitor/storaged/gvfsstoragedvolume.h | 67 | ||||
-rw-r--r-- | monitor/storaged/gvfsstoragedvolumemonitor.c | 1885 | ||||
-rw-r--r-- | monitor/storaged/gvfsstoragedvolumemonitor.h | 55 | ||||
-rw-r--r-- | monitor/storaged/org.gtk.vfs.StoragedVolumeMonitor.service.in | 3 | ||||
-rw-r--r-- | monitor/storaged/storaged.monitor | 5 | ||||
-rw-r--r-- | monitor/storaged/storagedvolumemonitordaemon.c | 46 |
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); +} |