Implemented quick (non-seek&read) file size detection methods
[captive.git] / src / libcaptive / storage / size.c
index 07effc4..f1a80c2 100644 (file)
  */
 
 
+#define _FILE_OFFSET_BITS 64   /* for 64-bit 'off_t' */
+
 #include "config.h"
 
 #include "captive/storage.h"   /* self */
+#include "../client/giochannel-blind.h"        /* for captive_giochannel_blind_get_size() */
 #include <glib/gmessages.h>
 #include <glib/gtypes.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <linux/types.h>       /* for __u64 for u64 for BLKGETSIZE64 */
+#define u64 __u64
+#include <linux/fs.h>  /* for BLKGETSIZE64 */
 
 
-guint64 captive_giochannel_size(GIOChannel *iochannel)
+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 GIOChannel *iochannel_null;
+
+static int iounixchannel_get_fd(GIOChannel *iochannel)
+{
+int r;
+
+       g_return_val_if_fail(iochannel!=NULL,-1);
+
+       if (!iochannel_null) {
+int fd;
+
+               fd=open("/dev/null",O_RDONLY);
+               g_return_val_if_fail(fd!=-1,-1);
+               iochannel_null=g_io_channel_unix_new(fd);
+               g_return_val_if_fail(iochannel_null!=NULL,-1);
+               }
+
+       if (iochannel->funcs!=iochannel_null->funcs) {
+               /* Not a UNIX file descriptor */
+               return -1;
+               }
+
+       /* It is forbidden to callg_io_channel_unix_get_fd()
+        * if you are not sure it is a 'GIOUnixChannel'.
+        */
+       r=g_io_channel_unix_get_fd(iochannel);
+       g_return_val_if_fail(r!=-1,-1);
+
+       return r;
+}
+
+
+static guint64 size_ioctl(GIOChannel *iochannel)
+{
+int fd,err;
+guint64 r;
+
+       g_return_val_if_fail(iochannel!=NULL,0);
+
+       if (-1==(fd=iounixchannel_get_fd(iochannel)))
+               return 0;
+
+       if ((err=ioctl(fd,BLKGETSIZE64,&r)))
+               return 0;
+
+       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.
+        */
+       g_return_val_if_fail(sizeof(offset)==sizeof(guint64),0);
+
+       if (-1==(fd=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)
 {
 gint64 low,high,mid;
 GIOStatus erriostatus;
@@ -35,11 +137,6 @@ gsize bufchargot;
        /* Default "UTF-8" encoding is not much usable for us */
        g_return_val_if_fail(g_io_channel_get_encoding(iochannel)==NULL,0);
 
-       /* FIXME: TODO: ioctl() detection.
-        * WARNING: g_io_channel_unix_get_fd() may return bogus 'fd' w/o any error
-        * if GIOChannel is not the 'unix' type one.
-        */
-
        /* low  ==high: low (high)
         * low+1==high: mid==low; NORMAL: no change: high
         * low+1==high: mid==low; EOF   : high=mid => 'low==high' case
@@ -92,3 +189,20 @@ gsize bufchargot;
        g_assert(high>=0);
        return high;
 }
+
+
+guint64 captive_giochannel_size(GIOChannel *iochannel)
+{
+guint64 r;
+
+       if ((r=size_blind(iochannel)))
+               return r;
+       if ((r=size_ioctl(iochannel)))
+               return r;
+       if ((r=size_seek(iochannel)))
+               return r;
+       if ((r=size_read(iochannel)))
+               return r;
+
+       return r;
+}