+Workaround Linux kernel last device block inaccessibility.
[captive.git] / src / libcaptive / client / giochannel-blind.c
index a5c02cd..5590eb4 100644 (file)
 #include <glib/gmessages.h>
 #include "captive/macros.h"
 #include "captive/storage.h"
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <libxml/tree.h>
+#include "captive/libxml.h"
+#include <ctype.h>
+#ifdef HAVE_LIBXML_BUFFERING
+#include <libxml/xmlreader.h>
+#endif
+#include "lib.h"       /* for captive_giochannel_setup(); FIXME: pathname */
+
+
+/* CONFIG: */
+/* It should be the divisor of all offsets/sizes written by W32 filesystems.
+ */
+#define GIOCHANNEL_BLIND_BLOCK_SIZE 512
 
 
-#define GIOCHANNEL_BLIND_BLOCK_SIZE (PAGE_SIZE)
-
 /* FIXME: fill 'err' */
 
 struct captive_giochannel_blind {
        GIOChannel iochannel;
-       GIOChannel *giochannel_ro;
+       GIOChannel *giochannel_orig;    /* reffed by us */
        guint64 offset; /* gint64 range */
        guint64 size;
-       GHashTable *buffer_hash;        /* (guint64 *) -> (guint8[GIOCHANNEL_BLIND_BLOCK_SIZE]) */
+       GHashTable *buffer_hash;        /* (guint64 *) -> (struct blind_block *)  (guint8[GIOCHANNEL_BLIND_BLOCK_SIZE]) */
+       };
+
+struct blind_block {
+       guint64 offset;
+       gboolean was_read,was_written;
+       guint8 *data_written;   /* [GIOCHANNEL_BLIND_BLOCK_SIZE] */
        };
 
 
@@ -44,11 +63,11 @@ G_LOCK_DEFINE_STATIC(giochannel_blind_funcs);
 static GIOFuncs giochannel_blind_funcs;
 
 
-static guint captive_giochannel_blind_hash_func(const guint64 *key)
+static guint captive_giochannel_blind_hash_func(const guint64 *keyp)
 {
-       g_return_val_if_fail(key!=NULL,0);
+       g_return_val_if_fail(keyp!=NULL,0);
 
-       return (*key)^((*key)>>23);
+       return (*keyp)^((*keyp)>>23);
 }
 
 static gboolean captive_giochannel_blind_equal_func(const guint64 *ap,const guint64 *bp)
@@ -59,25 +78,27 @@ static gboolean captive_giochannel_blind_equal_func(const guint64 *ap,const guin
        return (*ap)==(*bp);
 }
 
-static void captive_giochannel_blind_key_destroy_func(guint64 *key)
+static void captive_giochannel_blind_key_destroy_func(guint64 *keyp)
 {
-       g_return_if_fail(key!=NULL);
+       g_return_if_fail(keyp!=NULL);
 
-       g_free(key);
+       g_free(keyp);
 }
 
-static void captive_giochannel_blind_value_destroy_func(void *data)
+static void captive_giochannel_blind_value_destroy_func(struct blind_block *blind_block)
 {
-       g_return_if_fail(data!=NULL);
+       g_return_if_fail(blind_block!=NULL);
 
-       g_free(data);
+       g_free(blind_block->data_written);
+       g_free(blind_block);
 }
 
 
 static gboolean validate_giochannel_blind(struct captive_giochannel_blind *giochannel_blind)
 {
+       g_return_val_if_fail(giochannel_blind->iochannel.funcs==&giochannel_blind_funcs,FALSE);
        g_return_val_if_fail(giochannel_blind!=NULL,FALSE);
-       g_return_val_if_fail(giochannel_blind->giochannel_ro!=NULL,FALSE);
+       /* 'giochannel_blind->giochannel_orig' may be NULL. */
        g_return_val_if_fail((gint64)giochannel_blind->offset>=0,FALSE);        /* gint64 overflow stored in guint64 */
        g_return_val_if_fail(giochannel_blind->buffer_hash!=NULL,FALSE);
 
@@ -89,11 +110,11 @@ static GIOStatus captive_giochannel_blind_io_read
                (GIOChannel *channel,gchar *buf,gsize count,gsize *bytes_read,GError **err)
 {
 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
-gpointer data;
 guint64 window_bottom,window_top,window_now;
 guint64 transfer_bottom,transfer_top;
 GIOStatus errgiostatus;
 guint64 maxread;       /* maximum offset of end of data we successfuly read */
+struct blind_block *blind_block;
 
        g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
        g_return_val_if_fail(buf!=NULL,G_IO_STATUS_ERROR);
@@ -102,8 +123,8 @@ guint64 maxread;    /* maximum offset of end of data we successfuly read */
        g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: read(offset=0x%llX,count=0x%lX)",G_STRLOC,
                        giochannel_blind->offset,(gulong)count);
 
-       window_bottom=CAPTIVE_ROUND_DOWN(giochannel_blind->offset,GIOCHANNEL_BLIND_BLOCK_SIZE);
-       window_top=CAPTIVE_ROUND_UP(giochannel_blind->offset+count,GIOCHANNEL_BLIND_BLOCK_SIZE);
+       window_bottom=CAPTIVE_ROUND_DOWN64(giochannel_blind->offset,GIOCHANNEL_BLIND_BLOCK_SIZE);
+       window_top=CAPTIVE_ROUND_UP64(giochannel_blind->offset+count,GIOCHANNEL_BLIND_BLOCK_SIZE);
        maxread=giochannel_blind->offset;
 
        for (window_now=window_bottom;window_now<window_top;window_now+=GIOCHANNEL_BLIND_BLOCK_SIZE) {
@@ -111,23 +132,28 @@ gsize bytes_read;
 
                transfer_bottom=MAX(window_now,giochannel_blind->offset);
                transfer_top=MIN(window_now+GIOCHANNEL_BLIND_BLOCK_SIZE,giochannel_blind->offset+count);
-               if ((data=g_hash_table_lookup(giochannel_blind->buffer_hash,&window_now))) {
+               if ((blind_block=g_hash_table_lookup(giochannel_blind->buffer_hash,&window_now)) && blind_block->data_written) {
                        g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: read-from-memcpy(window_now=0x%llX,dest=buf+0x%lX,src=data+0x%lX,n=0x%lX)",
                                        G_STRLOC,
                                        (guint64)window_now,(gulong)(transfer_bottom-giochannel_blind->offset),(gulong)(transfer_bottom-window_now),
                                        (gulong)(transfer_top-transfer_bottom));
                        memcpy(
                                        buf+transfer_bottom-giochannel_blind->offset,   /* dest */
-                                       data+transfer_bottom-window_now,        /* src */
+                                       blind_block->data_written+transfer_bottom-window_now,   /* src */
                                        transfer_top-transfer_bottom);  /* n */
+                       blind_block->was_read=TRUE;
                        maxread=transfer_top;
                        continue;
                        }
                g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: read-from-io(window_now=0x%llX,buf=buf+0x%lX,seek=0x%llX,count=0x%lX)",G_STRLOC,
                                (guint64)window_now,(gulong)(transfer_bottom-giochannel_blind->offset),(guint64)transfer_bottom,
                                (gulong)(transfer_top-transfer_bottom));
+               if (!giochannel_blind->giochannel_orig) {
+                       g_error("%s: Missing block at offset 0x%llX",G_STRLOC,(unsigned long long)window_now);
+                       g_return_val_if_reached(G_IO_STATUS_ERROR);
+                       }
                errgiostatus=g_io_channel_seek_position(
-                               giochannel_blind->giochannel_ro,        /* channel */
+                               giochannel_blind->giochannel_orig,      /* channel */
                                transfer_bottom,        /* offset */
                                G_SEEK_SET,     /* type */
                                err);   /* error */
@@ -143,7 +169,7 @@ gsize bytes_read;
                        }
                else {
                        errgiostatus=g_io_channel_read_chars(
-                                       giochannel_blind->giochannel_ro,        /* channel */
+                                       giochannel_blind->giochannel_orig,      /* channel */
                                        buf+transfer_bottom-giochannel_blind->offset,   /* buf */
                                        transfer_top-transfer_bottom,   /* count */
                                        &bytes_read,    /* bytes_read */
@@ -152,6 +178,24 @@ gsize bytes_read;
                g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL || errgiostatus==G_IO_STATUS_EOF,errgiostatus);
                g_return_val_if_fail(bytes_read<=(transfer_top-transfer_bottom),G_IO_STATUS_ERROR);
                g_return_val_if_fail((errgiostatus==G_IO_STATUS_EOF)==(bytes_read==0),G_IO_STATUS_ERROR);
+               if (bytes_read) {
+                       if (!blind_block) {
+guint64 *keyp;
+
+                               captive_new(blind_block);
+                               blind_block->offset=window_now;
+                               blind_block->was_read=FALSE;
+                               blind_block->was_written=FALSE;
+                               blind_block->data_written=NULL;
+                               captive_new(keyp);
+                               *keyp=window_now;
+                               g_hash_table_insert(
+                                               giochannel_blind->buffer_hash,  /* hash_table */
+                                               keyp,   /* key */
+                                               blind_block);   /* value */
+                               }
+                       blind_block->was_read=TRUE;
+                       }
                maxread=transfer_bottom+bytes_read;
                if (bytes_read==transfer_top-transfer_bottom)
                        g_return_val_if_fail(transfer_bottom+bytes_read<=giochannel_blind->size,G_IO_STATUS_ERROR);
@@ -169,10 +213,10 @@ static GIOStatus captive_giochannel_blind_io_write
                (GIOChannel *channel,const gchar *buf,gsize count,gsize *bytes_written,GError **err)
 {
 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
-gpointer data;
 guint64 window_bottom,window_top,window_now;
 guint64 transfer_bottom,transfer_top;
 GIOStatus errgiostatus;
+struct blind_block *blind_block;
 
        g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
        g_return_val_if_fail(buf!=NULL,G_IO_STATUS_ERROR);
@@ -183,24 +227,32 @@ GIOStatus errgiostatus;
 
        g_return_val_if_fail(giochannel_blind->offset+count<=giochannel_blind->size,G_IO_STATUS_ERROR);
 
-       window_bottom=CAPTIVE_ROUND_DOWN(giochannel_blind->offset,GIOCHANNEL_BLIND_BLOCK_SIZE);
-       window_top=CAPTIVE_ROUND_UP(giochannel_blind->offset+count,GIOCHANNEL_BLIND_BLOCK_SIZE);
+       g_return_val_if_fail(giochannel_blind->iochannel.is_writeable==TRUE,G_IO_STATUS_ERROR);
+
+       window_bottom=CAPTIVE_ROUND_DOWN64(giochannel_blind->offset,GIOCHANNEL_BLIND_BLOCK_SIZE);
+       window_top=CAPTIVE_ROUND_UP64(giochannel_blind->offset+count,GIOCHANNEL_BLIND_BLOCK_SIZE);
 
        for (window_now=window_bottom;window_now<window_top;window_now+=GIOCHANNEL_BLIND_BLOCK_SIZE) {
 gsize bytes_read;
 
                transfer_bottom=MAX(window_now,giochannel_blind->offset);
                transfer_top=MIN(window_now+GIOCHANNEL_BLIND_BLOCK_SIZE,giochannel_blind->offset+count);
-               if (!(data=g_hash_table_lookup(giochannel_blind->buffer_hash,&window_now))) {
+               if (!(blind_block=g_hash_table_lookup(giochannel_blind->buffer_hash,&window_now)) || !blind_block->data_written) {
+                       if (!blind_block) {
 guint64 *keyp;
 
-                       data=g_malloc(GIOCHANNEL_BLIND_BLOCK_SIZE);
-                       captive_new(keyp);
-                       *keyp=window_now;
-                       g_hash_table_insert(
-                                       giochannel_blind->buffer_hash,  /* hash_table */
-                                       keyp,   /* key */
-                                       data);  /* value */
+                               captive_new(blind_block);
+                               blind_block->offset=window_now;
+                               blind_block->was_read=FALSE;
+                               blind_block->was_written=FALSE;
+                               captive_new(keyp);
+                               *keyp=window_now;
+                               g_hash_table_insert(
+                                               giochannel_blind->buffer_hash,  /* hash_table */
+                                               keyp,   /* key */
+                                               blind_block);   /* value */
+                               }
+                       blind_block->data_written=g_malloc(GIOCHANNEL_BLIND_BLOCK_SIZE);
                        g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: write-new-mem(window_now=0x%llX)",G_STRLOC,
                                        (guint64)window_now);
 
@@ -208,20 +260,25 @@ guint64 *keyp;
                        if (transfer_bottom>window_now) {
                                g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: write-mem-read-lower(seek=0x%llX,count=0x%lX)",G_STRLOC,
                                                (guint64)window_now,(gulong)(transfer_bottom-window_now));
+                               if (!giochannel_blind->giochannel_orig) {
+                                       g_error("Missing block for partial read at offset 0x%llX",(unsigned long long)window_now);
+                                       g_assert_not_reached();
+                                       }
                                errgiostatus=g_io_channel_seek_position(
-                                               giochannel_blind->giochannel_ro,        /* channel */
+                                               giochannel_blind->giochannel_orig,      /* channel */
                                                window_now,     /* offset */
                                                G_SEEK_SET,     /* type */
                                                err);   /* error */
                                g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,errgiostatus);
                                errgiostatus=g_io_channel_read_chars(
-                                               giochannel_blind->giochannel_ro,        /* channel */
-                                               data,   /* buf */
+                                               giochannel_blind->giochannel_orig,      /* channel */
+                                               blind_block->data_written,      /* buf */
                                                transfer_bottom-window_now,     /* count */
                                                &bytes_read,    /* bytes_read */
                                                err);   /* error */
                                g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,errgiostatus);
                                g_return_val_if_fail(bytes_read==(transfer_bottom-window_now),G_IO_STATUS_ERROR);
+                               blind_block->was_read=TRUE;     /* FIXME: Support non-block-aligned buffers. */
                                }
 
                        /* Missing upper part of buffer? */
@@ -229,15 +286,19 @@ guint64 *keyp;
                                g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: write-mem-read-upper(buf=buf+0x%lX,seek=0x%llX,count=0x%lX)",G_STRLOC,
                                                (gulong)(transfer_top-window_now),(guint64)transfer_top,
                                                (gulong)(window_now+GIOCHANNEL_BLIND_BLOCK_SIZE-transfer_top));
+                               if (!giochannel_blind->giochannel_orig) {
+                                       g_error("Missing block for partial read at offset 0x%llX",(unsigned long long)window_now);
+                                       g_assert_not_reached();
+                                       }
                                errgiostatus=g_io_channel_seek_position(
-                                               giochannel_blind->giochannel_ro,        /* channel */
+                                               giochannel_blind->giochannel_orig,      /* channel */
                                                transfer_top,   /* offset */
                                                G_SEEK_SET,     /* type */
                                                err);   /* error */
                                g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,errgiostatus);
                                errgiostatus=g_io_channel_read_chars(
-                                               giochannel_blind->giochannel_ro,        /* channel */
-                                               data+transfer_top-window_now,   /* buf */
+                                               giochannel_blind->giochannel_orig,      /* channel */
+                                               blind_block->data_written+transfer_top-window_now,      /* buf */
                                                window_now+GIOCHANNEL_BLIND_BLOCK_SIZE-transfer_top,    /* count */
                                                &bytes_read,    /* bytes_read */
                                                err);   /* error */
@@ -248,6 +309,7 @@ guint64 *keyp;
                                        g_return_val_if_fail(transfer_top+bytes_read<=giochannel_blind->size,G_IO_STATUS_ERROR);
                                else
                                        g_return_val_if_fail(transfer_top+bytes_read==giochannel_blind->size,G_IO_STATUS_ERROR);        /* EOF hit */
+                               blind_block->was_read=TRUE;     /* FIXME: Support non-block-aligned buffers. */
                                }
 
                        }
@@ -255,10 +317,12 @@ guint64 *keyp;
                                G_STRLOC,
                                (guint64)window_now,(gulong)(transfer_bottom-window_now),(gulong)(transfer_bottom-giochannel_blind->offset),
                                (gulong)(transfer_top-transfer_bottom));
+               g_assert(blind_block); g_assert(blind_block->data_written);
                memcpy(
-                               ((char *)data)+transfer_bottom-window_now,      /* dest */
+                               ((char *)blind_block->data_written)+transfer_bottom-window_now, /* dest */
                                buf+transfer_bottom-giochannel_blind->offset,   /* src */
                                transfer_top-transfer_bottom);  /* n */
+               blind_block->was_written=TRUE;
                }
 
        *bytes_written=count;
@@ -292,12 +356,16 @@ GIOStatus erriostatus;
 
        g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
 
-       /* We are not authorized to destroy 'giochannel_blind->giochannel_ro'. */
-       erriostatus=g_io_channel_flush(
-                       giochannel_blind->giochannel_ro,        /* channel */
-                       NULL);  /* error */
-       g_assert(erriostatus==G_IO_STATUS_NORMAL);
-       giochannel_blind->giochannel_ro=NULL;
+       if (giochannel_blind->giochannel_orig) {
+               /* Just a sanity if 'giochannel_orig' is already falsely reffed a bit more... */
+               erriostatus=g_io_channel_flush(
+                               giochannel_blind->giochannel_orig,      /* channel */
+                               NULL);  /* error */
+               g_assert(erriostatus==G_IO_STATUS_NORMAL);
+
+               g_io_channel_unref(giochannel_blind->giochannel_orig);
+               giochannel_blind->giochannel_orig=NULL;
+               }
 
        g_hash_table_destroy(giochannel_blind->buffer_hash);
        giochannel_blind->buffer_hash=NULL;
@@ -325,7 +393,7 @@ struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_bli
         */
        g_return_if_fail(giochannel_blind!=NULL);
 
-       g_assert(giochannel_blind->giochannel_ro==NULL);
+       g_assert(giochannel_blind->giochannel_orig==NULL);
        g_assert(giochannel_blind->buffer_hash==NULL);
 
        g_free(giochannel_blind);
@@ -338,7 +406,10 @@ struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_bli
 
        g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
 
-       return g_io_channel_set_flags(giochannel_blind->giochannel_ro,(flags&~G_IO_FLAG_IS_WRITEABLE),err);
+       if (!giochannel_blind->giochannel_orig)
+               return G_IO_STATUS_NORMAL;
+
+       return g_io_channel_set_flags(giochannel_blind->giochannel_orig,(flags&~G_IO_FLAG_IS_WRITEABLE),err);
 }
 
 
@@ -348,16 +419,18 @@ struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_bli
 
        g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),0);
 
-       return g_io_channel_get_flags(giochannel_blind->giochannel_ro) | G_IO_FLAG_IS_WRITEABLE;
+       if (!giochannel_blind->giochannel_orig)
+               return G_IO_FLAG_IS_READABLE | G_IO_FLAG_IS_WRITEABLE | G_IO_FLAG_IS_SEEKABLE;
+
+       return g_io_channel_get_flags(giochannel_blind->giochannel_orig) | G_IO_FLAG_IS_WRITEABLE;
 }
 
 
-struct captive_giochannel_blind *captive_giochannel_blind_new(GIOChannel *giochannel_ro)
+struct captive_giochannel_blind *captive_giochannel_blind_new(GIOChannel *giochannel_orig,gboolean writeable)
 {
 struct captive_giochannel_blind *giochannel_blind;
-GIOStatus erriostatus;
 
-       g_return_val_if_fail(giochannel_ro!=NULL,NULL);
+       /* 'giochannel_orig' may be NULL if no fallback capability exists. */
 
        G_LOCK(giochannel_blind_funcs);
        giochannel_blind_funcs.io_read        =captive_giochannel_blind_io_read;
@@ -370,10 +443,10 @@ GIOStatus erriostatus;
        giochannel_blind_funcs.io_get_flags   =captive_giochannel_blind_io_get_flags;
        G_UNLOCK(giochannel_blind_funcs);
 
-       erriostatus=g_io_channel_set_encoding(giochannel_ro,
-                       NULL,   /* encoding; force binary data */
-                       NULL);  /* error */
-       g_assert(erriostatus==G_IO_STATUS_NORMAL);
+       if (giochannel_orig)
+               captive_giochannel_setup(giochannel_orig);
+
+       g_io_channel_ref(giochannel_orig);
 
        captive_new(giochannel_blind);
        g_assert(G_STRUCT_OFFSET(struct captive_giochannel_blind,iochannel)==0);        /* safely re-type-able */
@@ -381,16 +454,345 @@ GIOStatus erriostatus;
        giochannel_blind->iochannel.funcs=&giochannel_blind_funcs;
        giochannel_blind->iochannel.is_seekable=TRUE;
        giochannel_blind->iochannel.is_readable=TRUE;
-       giochannel_blind->iochannel.is_writeable=TRUE;
+       /* readonly captive_giochannel_blind can be used to track read access. */
+       giochannel_blind->iochannel.is_writeable=writeable;
        giochannel_blind->iochannel.close_on_unref=TRUE;        /* run g_io_channel_shutdown() flush on last unref */
-       giochannel_blind->giochannel_ro=giochannel_ro;
+       giochannel_blind->giochannel_orig=giochannel_orig;
        giochannel_blind->offset=0;
-       giochannel_blind->size=captive_giochannel_size(giochannel_ro);
+       giochannel_blind->size=(!giochannel_orig ? 0 : captive_giochannel_size(giochannel_orig));
        giochannel_blind->buffer_hash=g_hash_table_new_full(
                        (GHashFunc)captive_giochannel_blind_hash_func,  /* hash_func */
                        (GEqualFunc)captive_giochannel_blind_equal_func,        /* key_equal_func */
                        (GDestroyNotify)captive_giochannel_blind_key_destroy_func,      /* key_destroy_func */
                        (GDestroyNotify)captive_giochannel_blind_value_destroy_func);   /* value_destroy_func */
 
+       captive_giochannel_setup(&giochannel_blind->iochannel);
+
        return giochannel_blind;
 }
+
+
+gboolean captive_giochannel_blind_get_size(GIOChannel *giochannel,guint64 *size_return)
+{
+struct captive_giochannel_blind *giochannel_blind;
+
+       g_return_val_if_fail(giochannel!=NULL,FALSE);
+       g_return_val_if_fail(size_return!=NULL,FALSE);
+
+       if (giochannel->funcs!=&giochannel_blind_funcs)
+               return FALSE;
+       giochannel_blind=(struct captive_giochannel_blind *)giochannel;
+
+       *size_return=giochannel_blind->size;
+       return TRUE;
+}
+
+
+typedef void (*sorted_array_filter)
+               (const guint64 *keyp,const struct blind_block *blind_block,const struct blind_block ***rpp /* user_data */);
+
+static void captive_giochannel_blind_written_as_sorted_array_foreach
+               (const guint64 *keyp,const struct blind_block *blind_block,const struct blind_block ***rpp /* user_data */)
+{
+       g_return_if_fail(keyp!=NULL);
+       g_return_if_fail(blind_block!=NULL);
+       g_return_if_fail(rpp!=NULL);
+
+       if (!blind_block->data_written)
+               return;
+
+       *((*rpp)++)=blind_block;
+}
+
+static void captive_giochannel_blind_read_as_sorted_array_foreach
+               (const guint64 *keyp,const struct blind_block *blind_block,const struct blind_block ***rpp /* user_data */)
+{
+       g_return_if_fail(keyp!=NULL);
+       g_return_if_fail(blind_block!=NULL);
+       g_return_if_fail(rpp!=NULL);
+
+       if (!blind_block->was_read)
+               return;
+
+       *((*rpp)++)=blind_block;
+}
+
+static int captive_giochannel_blind_as_sorted_array_compat
+               (const struct blind_block *const *ap,const struct blind_block *const *bp)
+{
+       g_return_val_if_fail(ap!=NULL,0);
+       g_return_val_if_fail(*ap!=NULL,0);
+       g_return_val_if_fail(bp!=NULL,0);
+       g_return_val_if_fail(*bp!=NULL,0);
+
+       return ((*ap)->offset>(*bp)->offset) - ((*bp)->offset>(*ap)->offset);
+}
+
+static struct blind_block **captive_giochannel_blind_as_sorted_array
+               (struct captive_giochannel_blind *giochannel_blind,sorted_array_filter filter_func)
+{
+guint hash_size;
+struct blind_block **r,**rp;
+
+       g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
+
+       hash_size=g_hash_table_size(giochannel_blind->buffer_hash);
+       captive_newn(r,hash_size+1);
+       rp=r;
+       g_hash_table_foreach(giochannel_blind->buffer_hash,(GHFunc)filter_func,&rp);
+       g_assert(rp<=r+hash_size);
+       *rp=NULL;
+       qsort(r,rp-r,sizeof(*r),(int (*)(const void *,const void *))captive_giochannel_blind_as_sorted_array_compat);
+
+       return r;
+}
+
+GIOStatus captive_giochannel_blind_commit(GIOChannel *giochannel)
+{
+struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)giochannel;
+struct blind_block **blind_block_array,**blind_blockp;
+GIOStatus errgiostatus;
+
+       g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
+       g_return_val_if_fail(giochannel_blind->giochannel_orig!=NULL,G_IO_STATUS_ERROR);
+
+       errgiostatus=g_io_channel_flush(
+                       giochannel,     /* channel */
+                       NULL);  /* error */
+       g_assert(errgiostatus==G_IO_STATUS_NORMAL);
+
+       blind_block_array=captive_giochannel_blind_as_sorted_array
+                       (giochannel_blind,captive_giochannel_blind_written_as_sorted_array_foreach);
+
+       for (blind_blockp=blind_block_array;*blind_blockp;blind_blockp++) {
+struct blind_block *blind_block=*blind_blockp;
+gsize bytes_written;
+
+               g_assert(blind_block->data_written!=NULL);
+
+               errgiostatus=g_io_channel_seek_position(
+                               giochannel_blind->giochannel_orig,      /* channel */
+                               blind_block->offset,    /* offset */
+                               G_SEEK_SET,     /* type */
+                               NULL);  /* error */
+               g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,errgiostatus);
+               errgiostatus=g_io_channel_write_chars(
+                               giochannel_blind->giochannel_orig,      /* channel */
+                               blind_block->data_written,      /* buf */
+                               GIOCHANNEL_BLIND_BLOCK_SIZE,    /* count */
+                               &bytes_written, /* bytes_written */
+                               NULL);  /* error */
+               g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,errgiostatus);
+               g_return_val_if_fail(bytes_written==GIOCHANNEL_BLIND_BLOCK_SIZE,G_IO_STATUS_ERROR);
+
+               g_free(blind_block->data_written);
+               blind_block->data_written=NULL;
+               }
+
+       g_free(blind_block_array);
+
+       errgiostatus=g_io_channel_flush(
+                       giochannel_blind->giochannel_orig,      /* channel */
+                       NULL);  /* error */
+       g_assert(errgiostatus==G_IO_STATUS_NORMAL);
+
+       return G_IO_STATUS_NORMAL;
+}
+
+
+xmlNode *captive_giochannel_blind_readreport_to_xml(xmlNode *xml_parent,GIOChannel *giochannel)
+{
+struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)giochannel;
+struct blind_block **blind_block_array,**blind_blockp;
+GIOStatus errgiostatus;
+guint8 data_read[1+GIOCHANNEL_BLIND_BLOCK_SIZE];       /* '1+' for leading stub to prevent shorter output of BN_bn2hex() */
+xmlNode *xml_media;
+
+       g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),NULL);
+       g_return_val_if_fail(giochannel_blind->giochannel_orig!=NULL,G_IO_STATUS_ERROR);
+
+       errgiostatus=g_io_channel_flush(
+                       giochannel,     /* channel */
+                       NULL);  /* error */
+       g_assert(errgiostatus==G_IO_STATUS_NORMAL);
+
+       xml_media=xmlNewTextChild(xml_parent,NULL,"media",NULL);
+       xmlNewProp(xml_media,"size",captive_printf_alloca("%" G_GUINT64_FORMAT,giochannel_blind->size));
+
+       blind_block_array=captive_giochannel_blind_as_sorted_array
+                       (giochannel_blind,captive_giochannel_blind_read_as_sorted_array_foreach);
+
+       for (blind_blockp=blind_block_array;*blind_blockp;blind_blockp++) {
+struct blind_block *blind_block=*blind_blockp;
+gsize bytes_read;
+xmlNode *xml_media_read;
+gchar offset_string[64];
+BIGNUM *bignum;
+char *hex,*s;
+gchar hex_out[0
+               +1 /* leading '\n' */
+               +GIOCHANNEL_BLIND_BLOCK_SIZE*2/64*(64+1)        /* each line of 64 characters has EOL '\n' */
+               +1],*gd;        /* terminating '\0' */
+
+               errgiostatus=g_io_channel_seek_position(
+                               giochannel_blind->giochannel_orig,      /* channel */
+                               blind_block->offset,    /* offset */
+                               G_SEEK_SET,     /* type */
+                               NULL);  /* error */
+               g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,NULL);
+               errgiostatus=g_io_channel_read_chars(
+                               giochannel_blind->giochannel_orig,      /* channel */
+                               data_read+1,    /* buf */
+                               GIOCHANNEL_BLIND_BLOCK_SIZE,    /* count */
+                               &bytes_read,    /* bytes_read */
+                               NULL);  /* error */
+               g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,NULL);
+               g_return_val_if_fail(bytes_read==GIOCHANNEL_BLIND_BLOCK_SIZE,NULL);
+
+               /* Convert binary block to 'hex' and reformat line-wrap it to 'hex_out'. */
+               data_read[0]=0xFF;      /* stub to prevent shorter output of BN_bn2hex() */
+               bignum=BN_bin2bn(data_read,1+GIOCHANNEL_BLIND_BLOCK_SIZE,NULL);
+               hex=BN_bn2hex(bignum);
+               BN_free(bignum);
+               g_assert(strlen(hex)==2*(1+GIOCHANNEL_BLIND_BLOCK_SIZE));
+               gd=hex_out;
+               *gd++='\n';
+               for (s=hex+2;s<hex+2+2*GIOCHANNEL_BLIND_BLOCK_SIZE;s+=64,gd+=64+1) {
+                       memcpy(gd,s,64);
+                       gd[64]='\n';
+                       }
+               OPENSSL_free(hex);
+               *gd++=0;
+               g_assert(s==hex+2+2*GIOCHANNEL_BLIND_BLOCK_SIZE);
+               g_assert(gd==hex_out+sizeof(hex_out));
+               xml_media_read=xmlNewTextChild(xml_media,NULL,"block",hex_out);
+               {
+                       g_snprintf(offset_string,sizeof(offset_string),"%" G_GUINT64_FORMAT,blind_block->offset);
+                       xmlNewProp(xml_media_read,"offset",offset_string);
+                       }
+               }
+
+       g_free(blind_block_array);
+
+       return xml_media;
+}
+
+#ifdef HAVE_LIBXML_BUFFERING
+struct captive_giochannel_blind *captive_giochannel_blind_new_from_xml(xmlTextReader *xml_reader)
+{
+struct captive_giochannel_blind *r;
+const xmlChar *xml_name;
+int errint;
+GIOStatus erriostatus;
+gboolean scan_end;
+struct captive_libxml_string_drop_stack *drop_stack=NULL;
+
+       g_return_val_if_fail(xml_reader!=NULL,NULL);
+       g_return_val_if_fail(xmlTextReaderNodeType(xml_reader)==CAPTIVE_XML_TEXT_READER_NODE_TYPE_START,NULL);
+       xml_name=captive_libxml_string_drop(&drop_stack,xmlTextReaderName(xml_reader));
+       g_return_val_if_fail(xml_name!=NULL,NULL);
+       g_return_val_if_fail(!xmlStrcmp(xml_name,"media"),NULL);
+
+       r=captive_giochannel_blind_new(
+                       NULL,   /* giochannel_orig */
+                       TRUE);  /* writeable */
+       r->size=captive_libxml_sscanf_gint64(captive_libxml_string_drop(&drop_stack,xmlTextReaderGetAttribute(xml_reader,"size")));
+
+       scan_end=FALSE;
+       do {
+int got_type;
+
+               errint=xmlTextReaderRead(xml_reader);
+               g_assert(errint==1);
+               switch ((got_type=xmlTextReaderNodeType(xml_reader))) {
+                       case CAPTIVE_XML_TEXT_READER_NODE_TYPE_COMMENT:
+                               break;
+                       case CAPTIVE_XML_TEXT_READER_NODE_TYPE_SIGNIFICANT_WHITESPACE:
+                               break;
+                       case CAPTIVE_XML_TEXT_READER_NODE_TYPE_TEXT:    /* Even empty nodes have some '#text'. */
+                               break;
+                       case CAPTIVE_XML_TEXT_READER_NODE_TYPE_START: {
+const xmlChar *xml_name;
+
+                               xml_name=captive_libxml_string_drop(&drop_stack,xmlTextReaderName(xml_reader));
+                               g_assert(xml_name!=NULL);
+                               if (!xmlStrcmp(xml_name,"block")) {
+guint64 offset;
+gsize bytes_written;
+const xmlChar *xml_text_hex_in,*xml_char_s;
+BIGNUM *bignum;
+int bignum_num_bytes;
+gchar bin_hex[2*GIOCHANNEL_BLIND_BLOCK_SIZE+1],*gd;
+unsigned char bin_out[GIOCHANNEL_BLIND_BLOCK_SIZE];
+
+                                       offset=captive_libxml_sscanf_gint64(
+                                                       captive_libxml_string_drop(&drop_stack,xmlTextReaderGetAttribute(xml_reader,"offset")));
+
+                                       errint=xmlTextReaderRead(xml_reader);
+                                       g_assert(errint==1);
+                                       errint=xmlTextReaderNodeType(xml_reader);
+                                       g_assert(errint==CAPTIVE_XML_TEXT_READER_NODE_TYPE_TEXT);
+                                       xml_text_hex_in=captive_libxml_string_drop(&drop_stack,xmlTextReaderValue(xml_reader));
+                                       g_assert(xml_text_hex_in!=NULL);
+
+                                       /* Convert binary block from hex line-wrapped 'xml_text_hex_in'. */
+                                       gd=bin_hex;
+                                       for (xml_char_s=xml_text_hex_in;*xml_char_s;xml_char_s++)
+                                               if (!isspace(*xml_char_s)) {
+                                                       g_assert(gd<bin_hex+2*GIOCHANNEL_BLIND_BLOCK_SIZE);
+                                                       *gd++=*xml_char_s;
+                                                       }
+                                       *gd=0;
+                                       bignum=NULL;
+                                       errint=BN_hex2bn(&bignum,bin_hex);
+                                       g_assert(errint==2*GIOCHANNEL_BLIND_BLOCK_SIZE);
+                                       g_assert(bignum!=NULL);
+
+                                       /* Leading zeroes are ommited by BN_bn2bin(). */
+                                       bignum_num_bytes=BN_num_bytes(bignum);
+                                       g_assert(bignum_num_bytes<=GIOCHANNEL_BLIND_BLOCK_SIZE);
+                                       memset(bin_out,0,GIOCHANNEL_BLIND_BLOCK_SIZE-bignum_num_bytes);
+                                       errint=BN_bn2bin(bignum,bin_out+(GIOCHANNEL_BLIND_BLOCK_SIZE-bignum_num_bytes));
+                                       g_assert(errint==bignum_num_bytes);
+                                       BN_free(bignum);
+
+                                       erriostatus=g_io_channel_seek_position(
+                                                       &r->iochannel,  /* channel */
+                                                       offset, /* offset */
+                                                       G_SEEK_SET,     /* type */
+                                                       NULL);  /* error */
+                                       g_assert(erriostatus==G_IO_STATUS_NORMAL);
+                                       erriostatus=g_io_channel_write_chars(
+                                                       &r->iochannel,  /* channel */
+                                                       bin_out,        /* buf */
+                                                       GIOCHANNEL_BLIND_BLOCK_SIZE,    /* count */
+                                                       &bytes_written, /* bytes_written */
+                                                       NULL);  /* error */
+                                       g_assert(erriostatus==G_IO_STATUS_NORMAL);
+                                       g_assert(bytes_written==GIOCHANNEL_BLIND_BLOCK_SIZE);
+                                       }
+                               else g_error("Unknown START node: %s",xml_name);
+                               } break;
+                       case CAPTIVE_XML_TEXT_READER_NODE_TYPE_END: {
+const xmlChar *xml_name;
+
+                               xml_name=captive_libxml_string_drop(&drop_stack,xmlTextReaderName(xml_reader));
+                               /**/ if (!xmlStrcmp(xml_name,"media")) {
+                                       scan_end=TRUE;  /* proper cleanup */
+                                       }
+                               else if (!xmlStrcmp(xml_name,"block")) {
+                                       }
+                               else g_error("Unknown END node: %s",xml_name);
+                               } break;
+                       default:
+                               g_error("Unexpected xmlTextReaderNodeType() type %d",got_type);
+                               g_assert_not_reached();
+                       }
+               captive_libxml_string_drop_flush(&drop_stack);
+               } while (!scan_end);
+
+       g_assert(drop_stack==NULL);
+
+       return r;
+}
+#endif /* HAVE_LIBXML_BUFFERING */