+Workaround Linux kernel last device block inaccessibility.
[captive.git] / src / libcaptive / storage / size.c
index 47948e9..cab7f76 100644 (file)
 #include "config.h"
 
 #include "captive/storage.h"   /* self */
+#include "../client/giochannel-blind.h"        /* for captive_giochannel_blind_get_size() */
+#include "../client/giochannel-subrange.h"     /* for captive_giochannel_subrange_get_size() */
+#include "../sandbox/client-CaptiveIOChannel.h"        /* for captive_io_channel_get_size() */
 #include <glib/gmessages.h>
 #include <glib/gtypes.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+/* Do not: #include <linux/fs.h>       * for 'BLKGETSIZE64' *
+ * as including any Linux kernel include files is too much incompatible.
+ */
+#include <sys/mount.h> /* for 'BLKGETSIZE' */
+#include "iounixchannel.h"
 
 
-guint64 captive_giochannel_size(GIOChannel *iochannel)
+static guint64 size_subrange(GIOChannel *iochannel)
+{
+guint64 r;
+
+       g_return_val_if_fail(iochannel!=NULL,0);
+
+       if (!captive_giochannel_subrange_get_size(iochannel,&r))
+               return 0;
+
+       return r;
+}
+
+
+static guint64 size_blind(GIOChannel *iochannel)
+{
+guint64 r;
+
+       g_return_val_if_fail(iochannel!=NULL,0);
+
+       if (!captive_giochannel_blind_get_size(iochannel,&r))
+               return 0;
+
+       return r;
+}
+
+
+static guint64 size_sandbox(GIOChannel *iochannel)
+{
+guint64 r;
+
+       g_return_val_if_fail(iochannel!=NULL,0);
+
+       if (!captive_io_channel_get_size(iochannel,&r))
+               return 0;
+
+       return r;
+}
+
+
+guint64 captive_giochannel_size_ioctl(GIOChannel *iochannel)
+{
+int fd;
+guint64 r;
+long r_long;
+
+       g_return_val_if_fail(iochannel!=NULL,0);
+
+       if (-1==(fd=captive_iounixchannel_get_fd(iochannel)))
+               return 0;
+
+#ifdef BLKGETSIZE64
+       if (!ioctl(fd,BLKGETSIZE64,&r))
+               return r;
+#endif
+       if (ioctl(fd,BLKGETSIZE,&r_long))
+               return 0;
+       if (r_long<0)
+               return 0;
+       r=((guint64)512)*r_long;
+
+       return r;
+}
+
+
+static guint64 size_seek(GIOChannel *iochannel)
+{
+int fd;
+off_t offset_orig,offset;
+
+       g_return_val_if_fail(iochannel!=NULL,0);
+
+       /* We may need '_FILE_OFFSET_BITS=64'.
+        * Setting '__USE_FILE_OFFSET64' did not help.
+        * Done by 'AC_SYS_LARGEFILE' of configure.in.
+        */
+       g_return_val_if_fail(sizeof(offset)==sizeof(guint64),0);
+
+       if (-1==(fd=captive_iounixchannel_get_fd(iochannel)))
+               return 0;
+
+       if (-1==(offset_orig=lseek(fd,0,SEEK_CUR)))
+               return 0;
+       g_return_val_if_fail(offset_orig>=0,0);
+       offset=lseek(fd,0,SEEK_END);
+       if (offset_orig!=lseek(fd,offset_orig,SEEK_SET))
+               g_assert_not_reached();
+
+       if (-1==offset)
+               return 0;
+       g_return_val_if_fail(offset>=0,0);
+
+       return offset;
+}
+
+
+
+static guint64 size_read(GIOChannel *iochannel)
 {
-gint fd;
 gint64 low,high,mid;
 GIOStatus erriostatus;
 gchar bufchar;
 gsize bufchargot;
 
        g_return_val_if_fail(iochannel!=NULL,0);
-
-       fd=g_io_channel_unix_get_fd(iochannel);
-       g_return_val_if_fail(fd!=-1,0);
-
-       /* FIXME: TODO: ioctl() detection */
+       /* Default "UTF-8" encoding is not much usable for us */
+       g_return_val_if_fail(g_io_channel_get_encoding(iochannel)==NULL,0);
 
        /* low  ==high: low (high)
         * low+1==high: mid==low; NORMAL: no change: high
@@ -45,23 +148,42 @@ gsize bufchargot;
         */
        for (low=0,
                        high=
-                                       G_MAXINT;       /* FIXME: 'G_MAXINT64' fails on g_io_channel_seek_position() */
+                                       G_MAXINT64;
                        low+1<high;) {
                mid=low+(high-low)/2;   /* beware of 'gint64' overflow! */
 
                erriostatus=g_io_channel_seek_position(iochannel,mid,G_SEEK_SET,
                                NULL);  /* error */
-               g_assert(erriostatus==G_IO_STATUS_NORMAL);
-               g_assert(sizeof(bufchar)==1);
-               erriostatus=g_io_channel_read_chars(iochannel,
-                               &bufchar, /* buf */
-                               sizeof(bufchar),        /* count */
-                               &bufchargot,    /* bytes_read */
-                               NULL);  /* error */
-               g_assert(erriostatus==G_IO_STATUS_NORMAL || erriostatus==G_IO_STATUS_EOF);
-               g_assert(0
-                               || (erriostatus==G_IO_STATUS_NORMAL && bufchargot==1)
-                               || (erriostatus==G_IO_STATUS_EOF    && bufchargot==0));
+               /* During seek in block device such as on URL file:///dev/hda1#captive-fastfat.sys-ro:/
+                * we will do llseek(2) on "/dev/hda1" device from captive_giochannel_size().
+                * Although we are allowed to seek behind EOF on regular files
+                * at least linux-kernel-2.4.19-ac4/fs/block_dev.c/block_llseek() will give
+                * EINVAL on seek behind EOF therefore it must be accepted without complaints by us.
+                */
+               if (erriostatus!=G_IO_STATUS_NORMAL) {
+                       erriostatus=G_IO_STATUS_EOF;
+                       bufchargot=0;
+                       }
+               else {
+                       g_assert(sizeof(bufchar)==1);
+                       erriostatus=g_io_channel_read_chars(iochannel,
+                                       &bufchar, /* buf */
+                                       sizeof(bufchar),        /* count */
+                                       &bufchargot,    /* bytes_read */
+                                       NULL);  /* error */
+                       /* During read on the end boundary of Linux kernel block device we will
+                        * get GNOME_VFS_ERROR_IO at least from linux-kernel-2.4.19-ac4
+                        * which will get mapped to G_IO_STATUS_ERROR by captive_gnomevfs_giognomevfs_io_read().
+                        */
+                       g_assert(0
+                                       || erriostatus==G_IO_STATUS_NORMAL
+                                       || erriostatus==G_IO_STATUS_EOF
+                                       || erriostatus==G_IO_STATUS_ERROR);
+                       g_assert(0
+                                       || (erriostatus==G_IO_STATUS_NORMAL && bufchargot==1)
+                                       || (erriostatus==G_IO_STATUS_EOF    && bufchargot==0)
+                                       || (erriostatus==G_IO_STATUS_ERROR  && bufchargot==0));
+                       }
 
                if (erriostatus==G_IO_STATUS_NORMAL)
                        low=mid;
@@ -72,3 +194,24 @@ gsize bufchargot;
        g_assert(high>=0);
        return high;
 }
+
+
+guint64 captive_giochannel_size(GIOChannel *iochannel)
+{
+guint64 r;
+
+       if ((r=size_subrange(iochannel)))
+               return r;
+       if ((r=size_blind(iochannel)))
+               return r;
+       if ((r=size_sandbox(iochannel)))
+               return r;
+       if ((r=captive_giochannel_size_ioctl(iochannel)))
+               return r;
+       if ((r=size_seek(iochannel)))
+               return r;
+       if ((r=size_read(iochannel)))
+               return r;
+
+       return r;
+}