[gnome-disk-utility] Try opening the device file directly when creating images of CD/DVD discs
- From: David Zeuthen <davidz src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-disk-utility] Try opening the device file directly when creating images of CD/DVD discs
- Date: Mon, 30 Jul 2012 17:00:07 +0000 (UTC)
commit 87f06333397b5cc6eee7ac1f12172f35cd7fa278
Author: David Zeuthen <zeuthen gmail com>
Date: Mon Jul 30 12:54:35 2012 -0400
Try opening the device file directly when creating images of CD/DVD discs
This avoids a polkit dialog for the common case where the user already
has an ACL on e.g. /dev/sr0. Also attempt to dynamically load the
libdvdcss library and use its interfaces if it's available (many other
apps include Brasero and Totem does the same thing). See
http://www.videolan.org/developers/libdvdcss.html for more information
about this library and what it does.
Signed-off-by: David Zeuthen <zeuthen gmail com>
configure.ac | 2 +-
src/disks/gducreatediskimagedialog.c | 347 ++++++++++++++++++++++++++++++----
2 files changed, 308 insertions(+), 41 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 33cdc57..50dd013 100644
--- a/configure.ac
+++ b/configure.ac
@@ -87,7 +87,7 @@ GTK3_REQUIRED=3.5.8
LIBSECRET1_REQUIRED=0.7
PWQUALITY_REQUIRED=1.0.0
-PKG_CHECK_MODULES(GLIB2, [gio-unix-2.0 >= $GLIB2_REQUIRED])
+PKG_CHECK_MODULES(GLIB2, [gmodule-2.0 gio-unix-2.0 >= $GLIB2_REQUIRED])
PKG_CHECK_MODULES(UDISKS2, [udisks2 >= $UDISKS2_REQUIRED])
PKG_CHECK_MODULES(GTK3, [gtk+-3.0 >= $GTK3_REQUIRED])
PKG_CHECK_MODULES(LIBSECRET1, [libsecret-1 >= $LIBSECRET1_REQUIRED])
diff --git a/src/disks/gducreatediskimagedialog.c b/src/disks/gducreatediskimagedialog.c
index d66bcff..c2153ce 100644
--- a/src/disks/gducreatediskimagedialog.c
+++ b/src/disks/gducreatediskimagedialog.c
@@ -26,6 +26,7 @@
#include <gio/gunixfdlist.h>
#include <gio/gunixinputstream.h>
+#include <gmodule.h>
#include <glib-unix.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
@@ -50,6 +51,166 @@
*/
/* ---------------------------------------------------------------------------------------------------- */
+/* libdvdcss support - see http://www.videolan.org/developers/libdvdcss.html */
+
+#define DVDCSS_BLOCK_SIZE 2048
+#define DVDCSS_READ_DECRYPT (1 << 0)
+
+struct dvdcss_s;
+typedef struct dvdcss_s * dvdcss_t;
+static dvdcss_t (*dvdcss_open) (const char *psz_target) = NULL;
+static int (*dvdcss_close) (dvdcss_t ctx) = NULL;
+static int (*dvdcss_seek) (dvdcss_t ctx,
+ int i_blocks,
+ int i_flags ) = NULL;
+static int (*dvdcss_read) (dvdcss_t ctx,
+ void *p_buffer,
+ int i_blocks,
+ int i_flags ) = NULL;
+static int (*dvdcss_readv) (dvdcss_t ctx,
+ void *p_iovec,
+ int i_blocks,
+ int i_flags ) = NULL;
+static char * (*dvdcss_error) (dvdcss_t ctx) = NULL;
+
+static gboolean
+have_dvdcss (void)
+{
+ static gsize once = 0;
+ static gboolean available = FALSE;
+
+ if (g_once_init_enter (&once))
+ {
+ GModule *module = NULL;
+
+ module = g_module_open ("libdvdcss.so.2", G_MODULE_BIND_LOCAL);
+ if (module == NULL)
+ goto out;
+ if (!g_module_symbol (module, "dvdcss_open", (gpointer*) &dvdcss_open) || dvdcss_open == NULL)
+ goto out;
+ if (!g_module_symbol (module, "dvdcss_close", (gpointer*) &dvdcss_close) || dvdcss_close == NULL)
+ goto out;
+ if (!g_module_symbol (module, "dvdcss_seek", (gpointer*) &dvdcss_seek) || dvdcss_seek == NULL)
+ goto out;
+ if (!g_module_symbol (module, "dvdcss_read", (gpointer*) &dvdcss_read) || dvdcss_read == NULL)
+ goto out;
+ if (!g_module_symbol (module, "dvdcss_readv", (gpointer*) &dvdcss_readv) || dvdcss_readv == NULL)
+ goto out;
+ if (!g_module_symbol (module, "dvdcss_error", (gpointer*) &dvdcss_error) || dvdcss_error == NULL)
+ goto out;
+
+ available = TRUE;
+
+ out:
+ if (!available)
+ {
+ if (module != NULL)
+ g_module_close (module);
+ }
+ g_once_init_leave (&once, (gsize) 1);
+ }
+ return available;
+}
+
+/* async dvdcss_read(): */
+
+typedef struct
+{
+ dvdcss_t ctx;
+ guchar *buffer;
+ gsize num_bytes;
+} ReadDVDCSSData;
+
+static void
+read_dvdcss_data_free (ReadDVDCSSData *data)
+{
+ g_slice_free (ReadDVDCSSData, data);
+}
+
+static void
+read_dvdcss_in_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ ReadDVDCSSData *data;
+ int num_blocks_read;
+
+ data = g_object_get_data (G_OBJECT (simple), "x-data");
+ g_assert (data != NULL);
+
+ num_blocks_read = dvdcss_read (data->ctx,
+ data->buffer,
+ data->num_bytes / DVDCSS_BLOCK_SIZE,
+ DVDCSS_READ_DECRYPT);
+ if (num_blocks_read < 0)
+ {
+ g_simple_async_result_set_error (simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "%s", dvdcss_error (data->ctx));
+ }
+ else
+ {
+ g_simple_async_result_set_op_res_gssize (simple, num_blocks_read * DVDCSS_BLOCK_SIZE);
+ }
+}
+
+static void
+read_dvdcss_async (dvdcss_t ctx,
+ guchar *buffer,
+ gsize num_bytes,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ ReadDVDCSSData *data;
+
+ g_return_if_fail (buffer != NULL);
+ g_return_if_fail ((num_bytes % DVDCSS_BLOCK_SIZE) == 0);
+
+ simple = g_simple_async_result_new (NULL,
+ callback,
+ user_data,
+ read_dvdcss_async);
+
+ data = g_slice_new0 (ReadDVDCSSData);
+ data->ctx = ctx;
+ data->buffer = buffer;
+ data->num_bytes = num_bytes;
+ g_object_set_data_full (G_OBJECT (simple), "x-data", data, (GDestroyNotify) read_dvdcss_data_free);
+
+ g_simple_async_result_set_check_cancellable (simple, cancellable);
+ g_simple_async_result_run_in_thread (simple,
+ read_dvdcss_in_thread,
+ io_priority,
+ cancellable);
+ g_object_unref (simple);
+}
+
+static gssize
+read_dvdcss_finish (GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+ gssize ret = -1;
+
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (res), -1);
+ g_return_val_if_fail (error == NULL || *error == NULL, -1);
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == read_dvdcss_async);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ goto out;
+
+ ret = g_simple_async_result_get_op_res_gssize (simple);
+
+ out:
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
/* TODO: make dynamic? */
#define BUFFER_SIZE (1*1024*1024)
@@ -76,12 +237,15 @@ typedef struct
GtkWidget *copying_progress_label;
GCancellable *cancellable;
+ int fd;
GInputStream *block_stream;
GFile *output_file;
GFileOutputStream *output_file_stream;
guint64 block_size;
gboolean delete_on_free;
+ dvdcss_t dvdcss_ctx;
+
guchar *buffer;
guint64 total_bytes_read;
guint64 buffer_bytes_written;
@@ -93,6 +257,8 @@ typedef struct
gboolean completed;
} CreateDiskImageData;
+/* ---------------------------------------------------------------------------------------------------- */
+
static CreateDiskImageData *
create_disk_image_data_ref (CreateDiskImageData *data)
{
@@ -137,6 +303,10 @@ create_disk_image_data_unref (CreateDiskImageData *data)
g_object_unref (data->builder);
g_free (data->buffer);
g_clear_object (&data->estimator);
+ if (data->dvdcss_ctx != NULL)
+ {
+ dvdcss_close (data->dvdcss_ctx);
+ }
g_free (data);
}
}
@@ -332,6 +502,44 @@ write_more (CreateDiskImageData *data)
}
}
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+read_dvdcss_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CreateDiskImageData *data = user_data;
+ GError *error;
+ gssize bytes_read;
+
+ error = NULL;
+ bytes_read = read_dvdcss_finish (res, &error);
+ if (error != NULL)
+ {
+ gchar *s;
+ s = g_strdup_printf (_("Error reading with libdvdcss from offset %" G_GUINT64_FORMAT " of device %s"),
+ (guint64) data->total_bytes_read,
+ udisks_block_get_preferred_device (data->block));
+ if (!(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED))
+ gdu_utils_show_error (GTK_WINDOW (data->dialog), s, error);
+ g_free (s);
+ g_error_free (error);
+ create_disk_image_data_complete (data);
+ goto out;
+ }
+
+ /* TODO: handle zero bytes being read */
+
+ data->total_bytes_read += bytes_read;
+
+ data->buffer_bytes_written = 0;
+ data->buffer_bytes_to_write = bytes_read;
+ write_more (data);
+
+ out:
+ create_disk_image_data_unref (data);
+}
static void
read_cb (GInputStream *input_stream,
@@ -358,6 +566,8 @@ read_cb (GInputStream *input_stream,
goto out;
}
+ /* TODO: handle zero bytes being read */
+
data->total_bytes_read += bytes_read;
data->buffer_bytes_written = 0;
@@ -383,13 +593,26 @@ copy_more (CreateDiskImageData *data)
if (bytes_to_read > BUFFER_SIZE)
bytes_to_read = BUFFER_SIZE;
- g_input_stream_read_async (data->block_stream,
- data->buffer,
- bytes_to_read,
- G_PRIORITY_DEFAULT,
- data->cancellable,
- (GAsyncReadyCallback) read_cb,
- create_disk_image_data_ref (data));
+ if (data->dvdcss_ctx != NULL)
+ {
+ read_dvdcss_async (data->dvdcss_ctx,
+ data->buffer,
+ bytes_to_read,
+ G_PRIORITY_DEFAULT,
+ data->cancellable,
+ (GAsyncReadyCallback) read_dvdcss_cb,
+ create_disk_image_data_ref (data));
+ }
+ else
+ {
+ g_input_stream_read_async (data->block_stream,
+ data->buffer,
+ bytes_to_read,
+ G_PRIORITY_DEFAULT,
+ data->cancellable,
+ (GAsyncReadyCallback) read_cb,
+ create_disk_image_data_ref (data));
+ }
out:
;
}
@@ -397,6 +620,38 @@ copy_more (CreateDiskImageData *data)
/* ---------------------------------------------------------------------------------------------------- */
static void
+start_copying (CreateDiskImageData *data)
+{
+ g_assert (data->fd != 0);
+
+ /* We can't use udisks_block_get_size() because the media may have
+ * changed and udisks may not have noticed. TODO: maybe have a
+ * Block.GetSize() method instead...
+ */
+ if (ioctl (data->fd, BLKGETSIZE64, &data->block_size) != 0)
+ {
+ GError *error = g_error_new (G_IO_ERROR, g_io_error_from_errno (errno), "%s", strerror (errno));
+ gdu_utils_show_error (GTK_WINDOW (data->dialog), _("Error determining size of device"), error);
+ g_error_free (error);
+ create_disk_image_data_complete (data);
+ goto out;
+ }
+
+ /* now that we know the user picked a folder, update file chooser settings */
+ gdu_utils_file_chooser_for_disk_images_update_settings (GTK_FILE_CHOOSER (data->destination_name_fcbutton));
+
+ data->cancellable = g_cancellable_new ();
+ data->buffer = g_new0 (guchar, BUFFER_SIZE);
+ data->estimator = gdu_estimator_new (data->block_size);
+ data->block_stream = g_unix_input_stream_new (data->fd, TRUE);
+
+ /* Alright, time to start copying! */
+ copy_more (data);
+ out:
+ ;
+}
+
+static void
open_cb (UDisksBlock *block,
GAsyncResult *res,
gpointer user_data)
@@ -405,7 +660,6 @@ open_cb (UDisksBlock *block,
GError *error;
GUnixFDList *fd_list = NULL;
GVariant *fd_index = NULL;
- int fd;
error = NULL;
if (!udisks_block_call_open_for_backup_finish (block,
@@ -421,31 +675,9 @@ open_cb (UDisksBlock *block,
goto out;
}
- fd = g_unix_fd_list_get (fd_list, g_variant_get_handle (fd_index), NULL);
-
- /* We can't use udisks_block_get_size() because the media may have
- * changed and udisks may not have noticed. TODO: maybe have a
- * Block.GetSize() method instead...
- */
- if (ioctl (fd, BLKGETSIZE64, &data->block_size) != 0)
- {
- error = g_error_new (G_IO_ERROR, g_io_error_from_errno (errno), "%s", strerror (errno));
- gdu_utils_show_error (GTK_WINDOW (data->dialog), _("Error determining size of device"), error);
- g_error_free (error);
- create_disk_image_data_complete (data);
- goto out;
- }
-
- /* now that we know the user picked a folder, update file chooser settings */
- gdu_utils_file_chooser_for_disk_images_update_settings (GTK_FILE_CHOOSER (data->destination_name_fcbutton));
-
- data->block_stream = g_unix_input_stream_new (fd, TRUE);
+ data->fd = g_unix_fd_list_get (fd_list, g_variant_get_handle (fd_index), NULL);
- /* Alright, time to start copying! */
- data->cancellable = g_cancellable_new ();
- data->buffer = g_new0 (guchar, BUFFER_SIZE);
- data->estimator = gdu_estimator_new (data->block_size);
- copy_more (data);
+ start_copying (data);
out:
if (fd_index != NULL)
@@ -511,7 +743,7 @@ check_overwrite (CreateDiskImageData *data)
}
static gboolean
-start_copying (CreateDiskImageData *data)
+open_device (CreateDiskImageData *data)
{
gboolean ret = TRUE;
const gchar *name;
@@ -522,6 +754,7 @@ start_copying (CreateDiskImageData *data)
folder = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (data->destination_name_fcbutton));
error = NULL;
+ data->fd = -1;
data->output_file = g_file_get_child (folder, name);
data->output_file_stream = g_file_replace (data->output_file,
NULL, /* etag */
@@ -541,12 +774,45 @@ start_copying (CreateDiskImageData *data)
}
data->delete_on_free = TRUE;
- udisks_block_call_open_for_backup (data->block,
- g_variant_new ("a{sv}", NULL), /* options */
- NULL, /* fd_list */
- NULL, /* cancellable */
- (GAsyncReadyCallback) open_cb,
- data);
+ /* Most OSes put ACLs for logged-in users on /dev/sr* nodes (this is
+ * so CD burning tools etc. works) so see if we can open the device
+ * file ourselves. If so, great, since this avoids a polkit dialog.
+ *
+ * As opposed to udisks' OpenForBackup() we also avoid O_EXCL since
+ * the disc is read-only by its very nature. As a side-effect this
+ * allows creating a disk image of a mounted disc.
+ */
+ if (g_str_has_prefix (udisks_block_get_device (data->block), "/dev/sr"))
+ {
+ data->fd = open (udisks_block_get_device (data->block), O_RDONLY);
+ if (data->fd != -1)
+ {
+ /* Great, that worked! Also use libdvdcss (if available) */
+ if (have_dvdcss ())
+ {
+ data->dvdcss_ctx = dvdcss_open (udisks_block_get_device (data->block));
+ if (data->dvdcss_ctx == NULL)
+ {
+ g_printerr ("Error opening %s with dvdcss_open(). Falling back\n",
+ udisks_block_get_device (data->block));
+ }
+ }
+ }
+ }
+
+ if (data->fd != -1)
+ {
+ start_copying (data);
+ }
+ else
+ {
+ udisks_block_call_open_for_backup (data->block,
+ g_variant_new ("a{sv}", NULL), /* options */
+ NULL, /* fd_list */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback) open_cb,
+ data);
+ }
out:
g_clear_object (&folder);
@@ -569,7 +835,7 @@ on_dialog_response (GtkDialog *dialog,
gtk_notebook_set_current_page (GTK_NOTEBOOK (data->notebook), 1);
gtk_widget_hide (data->start_copying_button);
- start_copying (data);
+ open_device (data);
}
}
else
@@ -586,6 +852,7 @@ gdu_create_disk_image_dialog_show (GduWindow *window,
gchar *s;
data = g_new0 (CreateDiskImageData, 1);
+ data->fd = -1;
data->ref_count = 1;
data->window = g_object_ref (window);
data->object = g_object_ref (object);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]