[gnome-disk-utility] Use libdvdcss for creating disk images of DVDs, if available



commit cb3afaea9c2f61a736871a4881945fb746c8c1ff
Author: David Zeuthen <zeuthen gmail com>
Date:   Fri Oct 19 21:36:48 2012 -0400

    Use libdvdcss for creating disk images of DVDs, if available
    
    Without this change, the archive process may fail for some DVD video
    discs with "Add. Sense: Read of scrambled sector without
    authentication".
    
    This commit does NOT add a hard dependency on libdvdcss (which would
    be bad as this library isn't even shipped in most OSes) since it's
    accessed via dlopen(). If libdvdcss is not available on the system, we
    simply fall back to reading data using the standard libc interfaces
    (e.g. read(2)).
    
    License-wise there is no problem with this approach: libdvdcss is
    licensed under the GPLv2+ which is exactly the same license as Disks
    itself.
    
    This dlopen() approach is nothing new - it's also used in Brasero,
    VLC, mplayer, Totem and many other applications.
    
    Signed-off-by: David Zeuthen <zeuthen gmail com>

 configure.ac                         |    4 +-
 src/disks/Makefile.am                |    3 +
 src/disks/gducreatediskimagedialog.c |   90 +++++--
 src/disks/gdudvdsupport.c            |  454 ++++++++++++++++++++++++++++++++++
 src/disks/gdudvdsupport.h            |   31 +++
 src/disks/gdutypes.h                 |    3 +
 6 files changed, 561 insertions(+), 24 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 662c384..a2ccf68 100644
--- a/configure.ac
+++ b/configure.ac
@@ -87,13 +87,15 @@ GTK3_REQUIRED=3.5.8
 LIBSECRET1_REQUIRED=0.7
 PWQUALITY_REQUIRED=1.0.0
 CANBERRA_REQUIRED=0.1
+LIBDVDREAD_REQUIRED=4.2.0
 
 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])
 PKG_CHECK_MODULES(PWQUALITY, [pwquality >= $PWQUALITY_REQUIRED])
-PKG_CHECK_MODULES(CANBERRA, [libcanberra-gtk3 >= CANBERRA_REQUIRED])
+PKG_CHECK_MODULES(CANBERRA, [libcanberra-gtk3 >= $CANBERRA_REQUIRED])
+PKG_CHECK_MODULES(LIBDVDREAD, [dvdread >= $LIBDVDREAD_REQUIRED])
 
 GLIB_GSETTINGS
 
diff --git a/src/disks/Makefile.am b/src/disks/Makefile.am
index 94d7e4b..6f6efda 100644
--- a/src/disks/Makefile.am
+++ b/src/disks/Makefile.am
@@ -51,6 +51,7 @@ gnome_disks_SOURCES = 							\
 	gducreateraidarraydialog.h	gducreateraidarraydialog.c	\
 	gduselectdiskdialog.h		gduselectdiskdialog.c		\
 	gduerasemultipledisksdialog.h	gduerasemultipledisksdialog.c	\
+	gdudvdsupport.h			gdudvdsupport.c			\
 	$(enum_built_sources)						\
 	$(NULL)
 
@@ -70,6 +71,7 @@ gnome_disks_CFLAGS = 					\
 	$(LIBSYSTEMD_LOGIN_CFLAGS)			\
 	$(PWQUALITY_CFLAGS)				\
 	$(CANBERRA_CFLAGS)				\
+	$(LIBDVDREAD_CFLAGS)				\
 	$(WARN_CFLAGS)					\
 	-lm						\
 	$(NULL)
@@ -82,6 +84,7 @@ gnome_disks_LDADD = 					\
 	$(LIBSYSTEMD_LOGIN_LIBS)			\
 	$(PWQUALITY_LIBS)				\
 	$(CANBERRA_LIBS)				\
+	$(LIBDVDREAD_LIBS)				\
         $(top_builddir)/src/libgdu/libgdu.la        	\
 	$(NULL)
 
diff --git a/src/disks/gducreatediskimagedialog.c b/src/disks/gducreatediskimagedialog.c
index 86433d4..0196427 100644
--- a/src/disks/gducreatediskimagedialog.c
+++ b/src/disks/gducreatediskimagedialog.c
@@ -13,7 +13,6 @@
 #include <gio/gunixfdlist.h>
 #include <gio/gunixinputstream.h>
 
-#include <gmodule.h>
 #include <glib-unix.h>
 #include <sys/ioctl.h>
 #include <linux/fs.h>
@@ -27,6 +26,8 @@
 #include "gducreatefilesystemwidget.h"
 #include "gduestimator.h"
 
+#include "gdudvdsupport.h"
+
 /* TODOs / ideas for Disk Image creation
  *
  * - Be tolerant of I/O errors like dd_rescue(1), see http://www.gnu.org/s/ddrescue/ddrescue.html
@@ -78,6 +79,7 @@ typedef struct
   GMutex copy_lock;
   GduEstimator *estimator;
 
+  gboolean retrieving_dvd_keys;
   guint64 num_error_bytes;
   gint64 start_time_usec;
   gint64 end_time_usec;
@@ -316,6 +318,10 @@ update_gui (DialogData *data,
       g_free (s3);
       g_free (s2);
     }
+  else if (data->retrieving_dvd_keys)
+    {
+      s = g_strdup (_("Retrieving DVD keys"));
+    }
   else if (bytes_per_sec > 0 && usec_remaining > 0)
     {
       s2 = g_format_size (bytes_completed);
@@ -464,6 +470,7 @@ copy_span (int              fd,
            guint64          size,
            guchar          *buffer,
            gboolean         pad_with_zeroes,
+           GduDVDSupport   *dvd_support,
            GCancellable    *cancellable,
            GError         **error)
 {
@@ -477,35 +484,45 @@ copy_span (int              fd,
   g_return_val_if_fail (-1, cancellable == NULL || G_IS_CANCELLABLE (cancellable));
   g_return_val_if_fail (-1, error == NULL || *error == NULL);
 
-  if (lseek (fd, offset, SEEK_SET) == (off_t) -1)
-    {
-      g_set_error (error,
-                   G_IO_ERROR, g_io_error_from_errno (errno),
-                   "Error seeking to offset %" G_GUINT64_FORMAT ": %s",
-                   offset, strerror (errno));
-      goto out;
-    }
-
- copy_read_again:
-  num_bytes_read = read (fd, buffer, size);
-  if (num_bytes_read < 0)
+  if (dvd_support != NULL)
     {
-      if (errno == EAGAIN || errno == EINTR)
-        goto copy_read_again;
-      /* do not consider this an error - treat as zero bytes read */
-      num_bytes_read = 0;
+      num_bytes_read = gdu_dvd_support_read (dvd_support, fd, buffer, offset, size);
     }
   else
     {
-      /* EOF */
-      if (num_bytes_read == 0)
+      if (lseek (fd, offset, SEEK_SET) == (off_t) -1)
         {
           g_set_error (error,
-                       G_IO_ERROR, G_IO_ERROR_FAILED,
-                       "Reading from offset %" G_GUINT64_FORMAT " returned zero bytes",
-                       offset);
+                       G_IO_ERROR, g_io_error_from_errno (errno),
+                       "Error seeking to offset %" G_GUINT64_FORMAT ": %s",
+                       offset, strerror (errno));
           goto out;
         }
+    read_again:
+      num_bytes_read = read (fd, buffer, size);
+      if (num_bytes_read < 0)
+        {
+          if (errno == EAGAIN || errno == EINTR)
+            goto read_again;
+        }
+      else
+        {
+          /* EOF */
+          if (num_bytes_read == 0)
+            {
+              g_set_error (error,
+                           G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Reading from offset %" G_GUINT64_FORMAT " returned zero bytes",
+                           offset);
+              goto out;
+            }
+        }
+    }
+
+  if (num_bytes_read < 0)
+    {
+      /* do not consider this an error - treat as zero bytes read */
+      num_bytes_read = 0;
     }
 
   num_bytes_to_write = num_bytes_read;
@@ -554,6 +571,7 @@ static gpointer
 copy_thread_func (gpointer user_data)
 {
   DialogData *data = user_data;
+  GduDVDSupport *dvd_support = NULL;
   guchar *buffer_unaligned = NULL;
   guchar *buffer = NULL;
   guint64 block_device_size = 0;
@@ -578,7 +596,29 @@ copy_thread_func (gpointer user_data)
    */
   if (g_str_has_prefix (udisks_block_get_device (data->block), "/dev/sr"))
     {
-      fd = open (udisks_block_get_device (data->block), O_RDONLY);
+      const gchar *device_file = udisks_block_get_device (data->block);
+      fd = open (device_file, O_RDONLY);
+
+      /* Use libdvdcss (if available on the system) on DVDs with UDF
+       * filesystems - otherwise the backup process may fail because
+       * of unreadable/scrambled sectors
+       */
+      if (g_strcmp0 (udisks_block_get_id_usage (data->block), "filesystem") == 0 &&
+          g_strcmp0 (udisks_block_get_id_type (data->block), "udf") == 0 &&
+          g_str_has_prefix (udisks_drive_get_media (data->drive), "optical_dvd"))
+        {
+          g_mutex_lock (&data->copy_lock);
+          data->retrieving_dvd_keys = TRUE;
+          g_mutex_unlock (&data->copy_lock);
+          g_idle_add (on_update_ui, dialog_data_ref (data));
+
+          dvd_support = gdu_dvd_support_new (device_file, udisks_block_get_size (data->block));
+
+          g_mutex_lock (&data->copy_lock);
+          data->retrieving_dvd_keys = FALSE;
+          g_mutex_unlock (&data->copy_lock);
+          g_idle_add (on_update_ui, dialog_data_ref (data));
+        }
     }
 
   /* Otherwise, request the fd from udisks */
@@ -673,6 +713,7 @@ copy_thread_func (gpointer user_data)
                                   num_bytes_to_read,
                                   buffer,
                                   TRUE, /* pad_with_zeroes */
+                                  dvd_support,
                                   data->cancellable,
                                   &error);
       if (num_bytes_read < 0)
@@ -694,6 +735,9 @@ copy_thread_func (gpointer user_data)
     }
 
  out:
+  if (dvd_support != NULL)
+    gdu_dvd_support_free (dvd_support);
+
   data->end_time_usec = g_get_real_time ();
 
   /* in either case, close the stream */
diff --git a/src/disks/gdudvdsupport.c b/src/disks/gdudvdsupport.c
new file mode 100644
index 0000000..711953d
--- /dev/null
+++ b/src/disks/gdudvdsupport.c
@@ -0,0 +1,454 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008-2012 Red Hat, Inc.
+ *
+ * Licensed under GPL version 2 or later.
+ *
+ * Author: David Zeuthen <zeuthen gmail com>
+ */
+
+#include "config.h"
+
+#include <gmodule.h>
+#include <glib-unix.h>
+#include <sys/ioctl.h>
+#include <linux/fs.h>
+
+#include <dvdread/dvd_reader.h>
+#include <dvdread/dvd_udf.h>
+
+#include "gdudvdsupport.h"
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* libdvdcss support - see http://www.videolan.org/developers/libdvdcss.html */
+
+#define DVDCSS_BLOCK_SIZE     2048
+#define DVDCSS_READ_DECRYPT   (1 << 0)
+#define DVDCSS_SEEK_KEY       (1 << 1)
+
+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;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct Range
+{
+  guint64 start;
+  guint64 end;
+  gboolean scrambled;
+} Range;
+
+static gint
+range_compare_func (Range *a,
+                    Range *b)
+{
+  if (a->start > b->start)
+    return 1;
+  else if (a->start < b->start)
+    return -1;
+  return 0;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct GduDVDSupport
+{
+  dvd_reader_t *dvd;
+  dvdcss_t dvdcss;
+
+  gboolean debug;
+
+  Range *ranges;
+  guint num_ranges;
+
+  Range *last_read_range;
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+GduDVDSupport *
+gdu_dvd_support_new  (const gchar *device_file,
+                      guint64      device_size)
+{
+  GduDVDSupport *support = NULL;
+  guint title;
+  GList *scrambled_ranges = NULL;
+  GList *l;
+  guint64 pos;
+  GArray *a;
+
+  /* We use dlopen() to access libdvdcss since it's normally not
+   * shipped (so we can't hard-depend on it) but it may be installed
+   * on the user's system anyway
+   */
+  if (!have_dvdcss ())
+    goto out;
+
+  support = g_new0 (GduDVDSupport, 1);
+
+  if (g_getenv ("GDU_DEBUG") != NULL)
+    support->debug = TRUE;
+
+  support->dvd = DVDOpen (device_file);
+  if (support->dvd == NULL)
+    goto fail;
+
+  support->dvdcss = dvdcss_open (device_file);
+  if (support->dvdcss == NULL)
+    goto fail;
+
+  /* It follows from "6.9.1 Constraints imposed on UDF by DVD-Video"
+   * of the OSTA UDF 2.60 spec (March 1, 2005) that
+   *
+   *  o  Video DVDs are using UDF 1.0.2
+   *  o  Only VOB files are encrypted
+   *  o  All VOB files of interest are in the VIDEO_TS/ directory
+   *  o  VOB files are at most 2^30 bytes = 1.0 GB
+   *  o  VOB files are a single extent.
+   *  o  The same key is used everywhere in a VOB files
+   *
+   * This means we can simply go through all VOB files in the
+   * VIDEO_TS/ directory and get their on-disc offset. Then for each
+   * file, we retrieve the CSS key at said offset. We then build a
+   * simple array of ranges
+   *
+   *  {range_start, range_end, range_is_scrambled}
+   *
+   * that covers the entire disc. Then when we're reading we can
+   * consult this array to figure out when to change the key. Since
+   * keys are cached, no slowdown will happen.
+   *
+   * This approach was inspired by Brasero's dvdcss plug-in, see
+   *
+   *  http://git.gnome.org/browse/brasero/tree/plugins/dvdcss/burn-dvdcss.c?id=BRASERO_3_6_0
+   *
+   * For the 'ls -l VIDEO_TSâ*.VOB' part, we take advantage of the
+   * fact that VOB files are in a known format, e.g. title 0 is always
+   * VIDEO_TS.VOB and title 1 through 99 are always of the form
+   * VTS_NN_M.VOB where 01 <= N <= 99 and 0 <= M <= 9. This way we can
+   * simply use libdvdread's UDFFindFile() function on all 991
+   * possible filenames...
+   *
+   * See http://en.wikipedia.org/wiki/VOB for how VOB files work.
+   */
+  for (title = 0; title <= 99; title++)
+    {
+      gint part;
+      Range *range;
+
+      for (part = 0; part <= 9; part++)
+        {
+          gchar vob_filename[64];
+          uint32_t vob_sector_offset;
+          uint32_t vob_size;
+          guint64 rounded_vob_size;
+
+          if (title == 0)
+            {
+              if (part > 0)
+                break;
+              snprintf (vob_filename, sizeof vob_filename, "/VIDEO_TS/VIDEO_TS.VOB");
+            }
+          else
+            {
+              snprintf (vob_filename, sizeof vob_filename, "/VIDEO_TS/VTS_%02d_%d.VOB", title, part);
+            }
+
+          vob_sector_offset = UDFFindFile (support->dvd, vob_filename, &vob_size);
+          if (vob_sector_offset == 0)
+            continue;
+
+          if (dvdcss_seek (support->dvdcss, vob_sector_offset, DVDCSS_SEEK_KEY) != (int) vob_sector_offset)
+            goto fail;
+
+          /* round up VOB size to nearest 2048-byte sector */
+          rounded_vob_size = vob_size + 0x7ff;
+          rounded_vob_size &= ~0x7ff;
+
+          range = g_new0 (Range, 1);
+          range->start = vob_sector_offset * 2048ULL;
+          range->end = range->start + rounded_vob_size;
+          range->scrambled = TRUE;
+
+          /*g_print ("%s: %10" G_GUINT64_FORMAT " -> %10" G_GUINT64_FORMAT ": scrambled=%d\n",
+            vob_filename, range->start, range->end, range->scrambled);*/
+
+          scrambled_ranges = g_list_prepend (scrambled_ranges, range);
+        }
+    }
+
+  /* If there are no VOB files on the disc, we don't need to decrypt - just bail */
+  if (scrambled_ranges == NULL)
+    goto fail;
+
+  /* Otherwise, build an array of ranges */
+  scrambled_ranges = g_list_sort (scrambled_ranges, (GCompareFunc) range_compare_func);
+  a = g_array_new (FALSE, /* zero-terminated */
+                   FALSE, /* clear */
+                   sizeof (Range));
+  pos = 0;
+  for (l = scrambled_ranges; l != NULL; l = l->next)
+    {
+      Range *range = l->data;
+      if (pos < range->start)
+        {
+          Range unscrambled_range = {0};
+          unscrambled_range.start = pos;
+          unscrambled_range.end = range->start;
+          g_array_append_val (a, unscrambled_range);
+        }
+      g_array_append_val (a, *range);
+      pos = range->end;
+    }
+  if (pos < device_size)
+    {
+      Range unscrambled_range = {0};
+      unscrambled_range.start = pos;
+      unscrambled_range.end = device_size;
+      g_array_append_val (a, unscrambled_range);
+    }
+  support->num_ranges = a->len;
+  support->ranges = (Range*) g_array_free (a, FALSE);
+
+  if (G_UNLIKELY (support->debug))
+    {
+      guint n;
+      for (n = 0; n < support->num_ranges; n++)
+        {
+          Range *range = support->ranges + n;
+          g_print ("range %02d: %10" G_GUINT64_FORMAT " -> %10" G_GUINT64_FORMAT ": scrambled=%d\n",
+                   n, range->start, range->end, range->scrambled);
+        }
+    }
+
+ out:
+  g_list_free_full (scrambled_ranges, g_free);
+  return support;
+
+ fail:
+  gdu_dvd_support_free (support);
+  support = NULL;
+  goto out;
+}
+
+void
+gdu_dvd_support_free (GduDVDSupport *support)
+{
+  g_free (support->ranges);
+  if (support->dvdcss != NULL)
+    dvdcss_close (support->dvdcss);
+  if (support->dvd != NULL)
+    DVDClose (support->dvd);
+  g_free (support);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+gssize
+gdu_dvd_support_read (GduDVDSupport *support,
+                      int            fd,
+                      guchar        *buffer,
+                      guint64        offset,
+                      guint64        size)
+{
+  guint n;
+  gssize ret = -1;
+  guint64 cur_offset = offset;
+  guint64 num_left = size;
+  guchar *cur_buffer = buffer;
+
+  g_assert ((offset & 0x7ff) == 0);
+
+  /* First find the range we're in
+   *
+   * Since callers are typically looping sequentially over the entire
+   * data surface, first check if last used range still works
+   */
+  if (support->last_read_range != NULL &&
+      offset >= support->last_read_range->start &&
+      offset < support->last_read_range->end)
+    {
+      n = support->last_read_range - support->ranges;
+    }
+  else
+    {
+      /* Otherwise look through all ranges, starting form the first */
+      for (n = 0; n < support->num_ranges; n++)
+        {
+          if (offset >= support->ranges[n].start &&
+              offset < support->ranges[n].end)
+            break;
+        }
+    }
+
+  /* Break the read request into multiple requests not crossing any of
+   * the ranges... we only want to use dvdcss_read() for the encrypted
+   * bits
+   */
+  while (num_left > 0)
+    {
+      Range *r;
+      guint64 num_left_in_range;
+      guint64 num_to_read_in_range;
+      ssize_t num_bytes_read;
+
+      if (G_UNLIKELY (n == support->num_ranges))
+        {
+          g_warning ("Requested offset %" G_GUINT64_FORMAT " is out of range", offset);
+          ret = -1;
+          goto out;
+        }
+
+      r = support->ranges + n;
+
+      g_assert (cur_offset >= r->start && cur_offset < r->end);
+      num_left_in_range = r->end - cur_offset;
+
+      num_to_read_in_range = MIN (num_left_in_range, num_left);
+
+      if (G_UNLIKELY (support->debug))
+        {
+          g_print ("reading %" G_GUINT64_FORMAT " from %" G_GUINT64_FORMAT " (scrambled=%d)\n",
+                   num_to_read_in_range, cur_offset, r->scrambled);
+        }
+
+      /* now read @num_to_read_in_range from @cur_offset into @cur_buffer */
+      if (r->scrambled)
+        {
+          int flags = 0;
+          int block_offset = cur_offset / 2048;
+          int num_blocks_to_request = num_to_read_in_range / 2048;
+          int num_blocks_read;
+
+          g_assert ((cur_offset & 0x7ff) == 0);
+          g_assert ((num_to_read_in_range & 0x7ff) == 0);
+
+          /* see if we need to change the key? */
+          if (support->last_read_range != r)
+            {
+              if (G_UNLIKELY (support->debug))
+                {
+                  g_print ("setting CSS key at offset %" G_GUINT64_FORMAT "\n", cur_offset);
+                }
+              flags |= DVDCSS_SEEK_KEY;
+              support->last_read_range = r;
+            }
+
+          if (dvdcss_seek (support->dvdcss, block_offset, flags) != block_offset)
+            goto out;
+
+        dvdcss_read_again:
+          num_blocks_read = dvdcss_read (support->dvdcss,
+                                         cur_buffer,
+                                         num_blocks_to_request,
+                                         DVDCSS_READ_DECRYPT);
+          if (num_blocks_read < 0)
+            {
+              if (errno == EAGAIN || errno == EINTR)
+                goto dvdcss_read_again;
+              /* treat as partial read */
+              ret = size - num_left;
+              goto out;
+            }
+          if (num_blocks_read == 0)
+            {
+              /* treat as partial read */
+              ret = size - num_left;
+              goto out;
+            }
+          g_assert (num_blocks_read <= num_blocks_to_request);
+          num_bytes_read = num_blocks_read * 2048;
+        }
+      else
+        {
+          if (lseek (fd, cur_offset, SEEK_SET) == (off_t) -1)
+            goto out;
+        read_again:
+          num_bytes_read = read (fd, cur_buffer, num_to_read_in_range);
+          if (num_bytes_read < 0)
+            {
+              if (errno == EAGAIN || errno == EINTR)
+                goto read_again;
+              /* treat as partial read */
+              ret = size - num_left;
+              goto out;
+            }
+          if (num_bytes_read == 0)
+            {
+              /* treat as partial read */
+              ret = size - num_left;
+              goto out;
+            }
+        }
+
+      cur_offset += num_bytes_read;
+      cur_buffer += num_bytes_read;
+      num_left -= num_bytes_read;
+
+      /* the read could have been partial, in which case we're still in the same range */
+      if (cur_offset >= r->end)
+        n++;
+    }
+
+  ret = size - num_left;
+
+ out:
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/src/disks/gdudvdsupport.h b/src/disks/gdudvdsupport.h
new file mode 100644
index 0000000..fa2c237
--- /dev/null
+++ b/src/disks/gdudvdsupport.h
@@ -0,0 +1,31 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008-2012 Red Hat, Inc.
+ *
+ * Licensed under GPL version 2 or later.
+ *
+ * Author: David Zeuthen <zeuthen gmail com>
+ */
+
+#ifndef __GDU_DVD_SUPPORT_H__
+#define __GDU_DVD_SUPPORT_H__
+
+#include <gtk/gtk.h>
+#include "gdutypes.h"
+
+G_BEGIN_DECLS
+
+GduDVDSupport *gdu_dvd_support_new  (const gchar *device_file,
+                                     guint64      device_size);
+
+void           gdu_dvd_support_free (GduDVDSupport *support);
+
+gssize gdu_dvd_support_read (GduDVDSupport *support,
+                             int            fd,
+                             guchar        *buffer,
+                             guint64        offset,
+                             guint64        size);
+
+G_END_DECLS
+
+#endif /* __GDU_DVD_SUPPORT_H__ */
diff --git a/src/disks/gdutypes.h b/src/disks/gdutypes.h
index 8a7f5cc..ec62f36 100644
--- a/src/disks/gdutypes.h
+++ b/src/disks/gdutypes.h
@@ -40,6 +40,9 @@ typedef struct _GduPasswordStrengthWidget GduPasswordStrengthWidget;
 struct _GduEstimator;
 typedef struct _GduEstimator GduEstimator;
 
+struct GduDVDSupport;
+typedef struct GduDVDSupport GduDVDSupport;
+
 G_END_DECLS
 
 #endif /* __GDU_TYPES_H__ */



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