b59170255754c77c2b4817754c97d9edbcc86d11
[captive.git] / src / libcaptive / client / giochannel-blind.c
1 /* $Id$
2  * glib r/w GIOChannel buffered-over r/o GIOChannel for libcaptive
3  * Copyright (C) 2003 Jan Kratochvil <project-captive@jankratochvil.net>
4  * 
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; exactly version 2 of June 1991 is required
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19
20 #include "config.h"
21
22 #include "giochannel-blind.h"
23 #include "reactos/internal/mm.h"        /* for PAGE_SIZE */
24 #include <glib/ghash.h>
25 #include <glib/gmessages.h>
26 #include "captive/macros.h"
27 #include "captive/storage.h"
28 #include <openssl/bn.h>
29 #include <openssl/crypto.h>
30 #include <libxml/tree.h>
31 #include <libxml/xmlreader.h>
32 #include "captive/libxml.h"
33 #include <ctype.h>
34
35
36 /* CONFIG: */
37 /* It should be the divisor of all offsets/sizes written by W32 filesystems.
38  */
39 #define GIOCHANNEL_BLIND_BLOCK_SIZE 512
40
41
42 /* FIXME: fill 'err' */
43
44 struct captive_giochannel_blind {
45         GIOChannel iochannel;
46         GIOChannel *giochannel_orig;
47         guint64 offset; /* gint64 range */
48         guint64 size;
49         GHashTable *buffer_hash;        /* (guint64 *) -> (struct blind_block *)  (guint8[GIOCHANNEL_BLIND_BLOCK_SIZE]) */
50         };
51
52 struct blind_block {
53         guint64 offset;
54         gboolean was_read,was_written;
55         guint8 *data_written;   /* [GIOCHANNEL_BLIND_BLOCK_SIZE] */
56         };
57
58
59 G_LOCK_DEFINE_STATIC(giochannel_blind_funcs);
60 static GIOFuncs giochannel_blind_funcs;
61
62
63 static guint captive_giochannel_blind_hash_func(const guint64 *keyp)
64 {
65         g_return_val_if_fail(keyp!=NULL,0);
66
67         return (*keyp)^((*keyp)>>23);
68 }
69
70 static gboolean captive_giochannel_blind_equal_func(const guint64 *ap,const guint64 *bp)
71 {
72         g_return_val_if_fail(ap!=NULL,FALSE);
73         g_return_val_if_fail(bp!=NULL,FALSE);
74
75         return (*ap)==(*bp);
76 }
77
78 static void captive_giochannel_blind_key_destroy_func(guint64 *keyp)
79 {
80         g_return_if_fail(keyp!=NULL);
81
82         g_free(keyp);
83 }
84
85 static void captive_giochannel_blind_value_destroy_func(struct blind_block *blind_block)
86 {
87         g_return_if_fail(blind_block!=NULL);
88
89         g_free(blind_block->data_written);
90         g_free(blind_block);
91 }
92
93
94 static gboolean validate_giochannel_blind(struct captive_giochannel_blind *giochannel_blind)
95 {
96         g_return_val_if_fail(giochannel_blind->iochannel.funcs==&giochannel_blind_funcs,FALSE);
97         g_return_val_if_fail(giochannel_blind!=NULL,FALSE);
98         /* 'giochannel_blind->giochannel_orig' may be NULL. */
99         g_return_val_if_fail((gint64)giochannel_blind->offset>=0,FALSE);        /* gint64 overflow stored in guint64 */
100         g_return_val_if_fail(giochannel_blind->buffer_hash!=NULL,FALSE);
101
102         return TRUE;
103 }
104
105
106 static GIOStatus captive_giochannel_blind_io_read
107                 (GIOChannel *channel,gchar *buf,gsize count,gsize *bytes_read,GError **err)
108 {
109 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
110 guint64 window_bottom,window_top,window_now;
111 guint64 transfer_bottom,transfer_top;
112 GIOStatus errgiostatus;
113 guint64 maxread;        /* maximum offset of end of data we successfuly read */
114 struct blind_block *blind_block;
115
116         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
117         g_return_val_if_fail(buf!=NULL,G_IO_STATUS_ERROR);
118         g_return_val_if_fail(bytes_read!=NULL,G_IO_STATUS_ERROR);
119
120         g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: read(offset=0x%llX,count=0x%lX)",G_STRLOC,
121                         giochannel_blind->offset,(gulong)count);
122
123         window_bottom=CAPTIVE_ROUND_DOWN64(giochannel_blind->offset,GIOCHANNEL_BLIND_BLOCK_SIZE);
124         window_top=CAPTIVE_ROUND_UP64(giochannel_blind->offset+count,GIOCHANNEL_BLIND_BLOCK_SIZE);
125         maxread=giochannel_blind->offset;
126
127         for (window_now=window_bottom;window_now<window_top;window_now+=GIOCHANNEL_BLIND_BLOCK_SIZE) {
128 gsize bytes_read;
129
130                 transfer_bottom=MAX(window_now,giochannel_blind->offset);
131                 transfer_top=MIN(window_now+GIOCHANNEL_BLIND_BLOCK_SIZE,giochannel_blind->offset+count);
132                 if ((blind_block=g_hash_table_lookup(giochannel_blind->buffer_hash,&window_now)) && blind_block->data_written) {
133                         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)",
134                                         G_STRLOC,
135                                         (guint64)window_now,(gulong)(transfer_bottom-giochannel_blind->offset),(gulong)(transfer_bottom-window_now),
136                                         (gulong)(transfer_top-transfer_bottom));
137                         memcpy(
138                                         buf+transfer_bottom-giochannel_blind->offset,   /* dest */
139                                         blind_block->data_written+transfer_bottom-window_now,   /* src */
140                                         transfer_top-transfer_bottom);  /* n */
141                         blind_block->was_read=TRUE;
142                         maxread=transfer_top;
143                         continue;
144                         }
145                 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,
146                                 (guint64)window_now,(gulong)(transfer_bottom-giochannel_blind->offset),(guint64)transfer_bottom,
147                                 (gulong)(transfer_top-transfer_bottom));
148                 if (!giochannel_blind->giochannel_orig) {
149                         g_error("%s: Missing block at offset 0x%llX",G_STRLOC,(unsigned long long)window_now);
150                         g_return_val_if_reached(G_IO_STATUS_ERROR);
151                         }
152                 errgiostatus=g_io_channel_seek_position(
153                                 giochannel_blind->giochannel_orig,      /* channel */
154                                 transfer_bottom,        /* offset */
155                                 G_SEEK_SET,     /* type */
156                                 err);   /* error */
157                 /* During seek in block device such as on URL file:///dev/hda1#captive-fastfat.sys-ro:/
158                  * we will do llseek(2) on "/dev/hda1" device from captive_giochannel_size().
159                  * Although we are allowed to seek behind EOF on regular files
160                  * at least linux-kernel-2.4.19-ac4/fs/block_dev.c/block_llseek() will give
161                  * EINVAL on seek behind EOF therefore it must be accepted without complaints by us.
162                  */
163                 if (errgiostatus!=G_IO_STATUS_NORMAL) {
164                         errgiostatus=G_IO_STATUS_EOF;
165                         bytes_read=0;
166                         }
167                 else {
168                         errgiostatus=g_io_channel_read_chars(
169                                         giochannel_blind->giochannel_orig,      /* channel */
170                                         buf+transfer_bottom-giochannel_blind->offset,   /* buf */
171                                         transfer_top-transfer_bottom,   /* count */
172                                         &bytes_read,    /* bytes_read */
173                                         err);   /* error */
174                         }
175                 g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL || errgiostatus==G_IO_STATUS_EOF,errgiostatus);
176                 g_return_val_if_fail(bytes_read<=(transfer_top-transfer_bottom),G_IO_STATUS_ERROR);
177                 g_return_val_if_fail((errgiostatus==G_IO_STATUS_EOF)==(bytes_read==0),G_IO_STATUS_ERROR);
178                 if (bytes_read) {
179                         if (!blind_block) {
180 guint64 *keyp;
181
182                                 captive_new(blind_block);
183                                 blind_block->offset=window_now;
184                                 blind_block->was_read=FALSE;
185                                 blind_block->was_written=FALSE;
186                                 blind_block->data_written=NULL;
187                                 captive_new(keyp);
188                                 *keyp=window_now;
189                                 g_hash_table_insert(
190                                                 giochannel_blind->buffer_hash,  /* hash_table */
191                                                 keyp,   /* key */
192                                                 blind_block);   /* value */
193                                 }
194                         blind_block->was_read=TRUE;
195                         }
196                 maxread=transfer_bottom+bytes_read;
197                 if (bytes_read==transfer_top-transfer_bottom)
198                         g_return_val_if_fail(transfer_bottom+bytes_read<=giochannel_blind->size,G_IO_STATUS_ERROR);
199                 else
200                         break;
201                 }
202
203         *bytes_read=maxread-giochannel_blind->offset;
204         giochannel_blind->offset=maxread;
205         return (*bytes_read == 0 ? G_IO_STATUS_EOF : G_IO_STATUS_NORMAL);
206 }
207
208
209 static GIOStatus captive_giochannel_blind_io_write
210                 (GIOChannel *channel,const gchar *buf,gsize count,gsize *bytes_written,GError **err)
211 {
212 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
213 guint64 window_bottom,window_top,window_now;
214 guint64 transfer_bottom,transfer_top;
215 GIOStatus errgiostatus;
216 struct blind_block *blind_block;
217
218         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
219         g_return_val_if_fail(buf!=NULL,G_IO_STATUS_ERROR);
220         g_return_val_if_fail(bytes_written!=NULL,G_IO_STATUS_ERROR);
221
222         g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: write(offset=0x%llX,count=0x%lX)",G_STRLOC,
223                         giochannel_blind->offset,(gulong)count);
224
225         g_return_val_if_fail(giochannel_blind->offset+count<=giochannel_blind->size,G_IO_STATUS_ERROR);
226
227         g_return_val_if_fail(giochannel_blind->iochannel.is_writeable==TRUE,G_IO_STATUS_ERROR);
228
229         window_bottom=CAPTIVE_ROUND_DOWN64(giochannel_blind->offset,GIOCHANNEL_BLIND_BLOCK_SIZE);
230         window_top=CAPTIVE_ROUND_UP64(giochannel_blind->offset+count,GIOCHANNEL_BLIND_BLOCK_SIZE);
231
232         for (window_now=window_bottom;window_now<window_top;window_now+=GIOCHANNEL_BLIND_BLOCK_SIZE) {
233 gsize bytes_read;
234
235                 transfer_bottom=MAX(window_now,giochannel_blind->offset);
236                 transfer_top=MIN(window_now+GIOCHANNEL_BLIND_BLOCK_SIZE,giochannel_blind->offset+count);
237                 if (!(blind_block=g_hash_table_lookup(giochannel_blind->buffer_hash,&window_now)) || !blind_block->data_written) {
238                         if (!blind_block) {
239 guint64 *keyp;
240
241                                 captive_new(blind_block);
242                                 blind_block->offset=window_now;
243                                 blind_block->was_read=FALSE;
244                                 blind_block->was_written=FALSE;
245                                 captive_new(keyp);
246                                 *keyp=window_now;
247                                 g_hash_table_insert(
248                                                 giochannel_blind->buffer_hash,  /* hash_table */
249                                                 keyp,   /* key */
250                                                 blind_block);   /* value */
251                                 }
252                         blind_block->data_written=g_malloc(GIOCHANNEL_BLIND_BLOCK_SIZE);
253                         g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: write-new-mem(window_now=0x%llX)",G_STRLOC,
254                                         (guint64)window_now);
255
256                         /* Missing lower part of buffer? */
257                         if (transfer_bottom>window_now) {
258                                 g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: write-mem-read-lower(seek=0x%llX,count=0x%lX)",G_STRLOC,
259                                                 (guint64)window_now,(gulong)(transfer_bottom-window_now));
260                                 if (!giochannel_blind->giochannel_orig) {
261                                         g_error("Missing block for partial read at offset 0x%llX",(unsigned long long)window_now);
262                                         g_assert_not_reached();
263                                         }
264                                 errgiostatus=g_io_channel_seek_position(
265                                                 giochannel_blind->giochannel_orig,      /* channel */
266                                                 window_now,     /* offset */
267                                                 G_SEEK_SET,     /* type */
268                                                 err);   /* error */
269                                 g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,errgiostatus);
270                                 errgiostatus=g_io_channel_read_chars(
271                                                 giochannel_blind->giochannel_orig,      /* channel */
272                                                 blind_block->data_written,      /* buf */
273                                                 transfer_bottom-window_now,     /* count */
274                                                 &bytes_read,    /* bytes_read */
275                                                 err);   /* error */
276                                 g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,errgiostatus);
277                                 g_return_val_if_fail(bytes_read==(transfer_bottom-window_now),G_IO_STATUS_ERROR);
278                                 blind_block->was_read=TRUE;     /* FIXME: Support non-block-aligned buffers. */
279                                 }
280
281                         /* Missing upper part of buffer? */
282                         if (transfer_top<window_now+GIOCHANNEL_BLIND_BLOCK_SIZE) {
283                                 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,
284                                                 (gulong)(transfer_top-window_now),(guint64)transfer_top,
285                                                 (gulong)(window_now+GIOCHANNEL_BLIND_BLOCK_SIZE-transfer_top));
286                                 if (!giochannel_blind->giochannel_orig) {
287                                         g_error("Missing block for partial read at offset 0x%llX",(unsigned long long)window_now);
288                                         g_assert_not_reached();
289                                         }
290                                 errgiostatus=g_io_channel_seek_position(
291                                                 giochannel_blind->giochannel_orig,      /* channel */
292                                                 transfer_top,   /* offset */
293                                                 G_SEEK_SET,     /* type */
294                                                 err);   /* error */
295                                 g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,errgiostatus);
296                                 errgiostatus=g_io_channel_read_chars(
297                                                 giochannel_blind->giochannel_orig,      /* channel */
298                                                 blind_block->data_written+transfer_top-window_now,      /* buf */
299                                                 window_now+GIOCHANNEL_BLIND_BLOCK_SIZE-transfer_top,    /* count */
300                                                 &bytes_read,    /* bytes_read */
301                                                 err);   /* error */
302                                 g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL || errgiostatus==G_IO_STATUS_EOF,errgiostatus);
303                                 g_return_val_if_fail(bytes_read<=(window_now+GIOCHANNEL_BLIND_BLOCK_SIZE-transfer_top),G_IO_STATUS_ERROR);
304                                 g_return_val_if_fail((errgiostatus==G_IO_STATUS_EOF)==(bytes_read==0),G_IO_STATUS_ERROR);
305                                 if (bytes_read==window_now+GIOCHANNEL_BLIND_BLOCK_SIZE-transfer_top)
306                                         g_return_val_if_fail(transfer_top+bytes_read<=giochannel_blind->size,G_IO_STATUS_ERROR);
307                                 else
308                                         g_return_val_if_fail(transfer_top+bytes_read==giochannel_blind->size,G_IO_STATUS_ERROR);        /* EOF hit */
309                                 blind_block->was_read=TRUE;     /* FIXME: Support non-block-aligned buffers. */
310                                 }
311
312                         }
313                 g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: write-by-memcpy(window_now=0x%llX,dest=data+0x%lX,src=buf+0x%lX,n=0x%lX)",
314                                 G_STRLOC,
315                                 (guint64)window_now,(gulong)(transfer_bottom-window_now),(gulong)(transfer_bottom-giochannel_blind->offset),
316                                 (gulong)(transfer_top-transfer_bottom));
317                 g_assert(blind_block); g_assert(blind_block->data_written);
318                 memcpy(
319                                 ((char *)blind_block->data_written)+transfer_bottom-window_now, /* dest */
320                                 buf+transfer_bottom-giochannel_blind->offset,   /* src */
321                                 transfer_top-transfer_bottom);  /* n */
322                 blind_block->was_written=TRUE;
323                 }
324
325         *bytes_written=count;
326         giochannel_blind->offset+=(*bytes_written);
327         return G_IO_STATUS_NORMAL;
328 }
329
330
331 static GIOStatus captive_giochannel_blind_io_seek(GIOChannel *channel,gint64 offset,GSeekType type,GError **err)
332 {
333 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
334
335         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
336
337         switch (type) {
338                 case G_SEEK_CUR: giochannel_blind->offset+=                       offset; break;
339                 case G_SEEK_SET: giochannel_blind->offset =                       offset; break;
340                 case G_SEEK_END: giochannel_blind->offset =giochannel_blind->size+offset; break;
341                 default: g_return_val_if_reached(G_IO_STATUS_ERROR);
342                 }
343         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);    /* 'offset' overflow? */
344
345         return G_IO_STATUS_NORMAL;
346 }
347
348
349 static GIOStatus captive_giochannel_blind_io_close(GIOChannel *channel,GError **err)
350 {
351 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
352 GIOStatus erriostatus;
353
354         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
355
356         if (giochannel_blind->giochannel_orig) {
357                 /* We are not authorized to destroy 'giochannel_blind->giochannel_orig'. */
358                 erriostatus=g_io_channel_flush(
359                                 giochannel_blind->giochannel_orig,      /* channel */
360                                 NULL);  /* error */
361                 g_assert(erriostatus==G_IO_STATUS_NORMAL);
362                 giochannel_blind->giochannel_orig=NULL;
363                 }
364
365         g_hash_table_destroy(giochannel_blind->buffer_hash);
366         giochannel_blind->buffer_hash=NULL;
367
368         return G_IO_STATUS_NORMAL;
369 }
370
371
372 static GSource* captive_giochannel_blind_io_create_watch(GIOChannel *channel,GIOCondition condition)
373 {
374 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
375
376         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),NULL);
377
378         g_return_val_if_reached(NULL);  /* FIXME: NOT IMPLEMENTED YET */
379 }
380
381
382 static void captive_giochannel_blind_io_free(GIOChannel *channel)
383 {
384 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
385
386         /* After captive_giochannel_blind_io_close() 'giochannel_blind'
387          * may be no longer valid for validate_giochannel_blind(giochannel_blind).
388          */
389         g_return_if_fail(giochannel_blind!=NULL);
390
391         g_assert(giochannel_blind->giochannel_orig==NULL);
392         g_assert(giochannel_blind->buffer_hash==NULL);
393
394         g_free(giochannel_blind);
395 }
396
397
398 static GIOStatus captive_giochannel_blind_io_set_flags(GIOChannel *channel,GIOFlags flags,GError **err)
399 {
400 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
401
402         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
403
404         if (!giochannel_blind->giochannel_orig)
405                 return G_IO_STATUS_NORMAL;
406
407         return g_io_channel_set_flags(giochannel_blind->giochannel_orig,(flags&~G_IO_FLAG_IS_WRITEABLE),err);
408 }
409
410
411 static GIOFlags captive_giochannel_blind_io_get_flags(GIOChannel *channel)
412 {
413 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
414
415         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),0);
416
417         if (!giochannel_blind->giochannel_orig)
418                 return G_IO_FLAG_IS_READABLE | G_IO_FLAG_IS_WRITEABLE | G_IO_FLAG_IS_SEEKABLE;
419
420         return g_io_channel_get_flags(giochannel_blind->giochannel_orig) | G_IO_FLAG_IS_WRITEABLE;
421 }
422
423 void captive_giochannel_setup(GIOChannel *giochannel)
424 {
425 GIOStatus erriostatus;
426
427         g_return_if_fail(giochannel!=NULL);
428
429         if (g_io_channel_get_encoding(giochannel)) {
430                 if (!g_io_channel_get_buffered(giochannel))     /* Prevent: Need to have NULL encoding to set the buffering state ... */
431                         g_io_channel_set_buffered(giochannel,TRUE);     /* Prevent: Need to set the channel buffered before setting the encoding. */
432                 erriostatus=g_io_channel_set_encoding(giochannel,
433                                 NULL,   /* encoding; force binary data */
434                                 NULL);  /* error */
435                 g_assert(erriostatus==G_IO_STATUS_NORMAL);
436                 }
437         erriostatus=g_io_channel_flush(giochannel,
438                         NULL);  /* error */
439         g_assert(erriostatus==G_IO_STATUS_NORMAL);
440         g_io_channel_set_buffered(giochannel,FALSE);
441 }
442
443
444 struct captive_giochannel_blind *captive_giochannel_blind_new(GIOChannel *giochannel_orig,gboolean writeable)
445 {
446 struct captive_giochannel_blind *giochannel_blind;
447
448         /* 'giochannel_orig' may be NULL if no fallback capability exists. */
449
450         G_LOCK(giochannel_blind_funcs);
451         giochannel_blind_funcs.io_read        =captive_giochannel_blind_io_read;
452         giochannel_blind_funcs.io_write       =captive_giochannel_blind_io_write;
453         giochannel_blind_funcs.io_seek        =captive_giochannel_blind_io_seek;
454         giochannel_blind_funcs.io_close       =captive_giochannel_blind_io_close;
455         giochannel_blind_funcs.io_create_watch=captive_giochannel_blind_io_create_watch;
456         giochannel_blind_funcs.io_free        =captive_giochannel_blind_io_free;
457         giochannel_blind_funcs.io_set_flags   =captive_giochannel_blind_io_set_flags;
458         giochannel_blind_funcs.io_get_flags   =captive_giochannel_blind_io_get_flags;
459         G_UNLOCK(giochannel_blind_funcs);
460
461         if (giochannel_orig)
462                 captive_giochannel_setup(giochannel_orig);
463
464         captive_new(giochannel_blind);
465         g_assert(G_STRUCT_OFFSET(struct captive_giochannel_blind,iochannel)==0);        /* safely re-type-able */
466         g_io_channel_init(&giochannel_blind->iochannel);
467         giochannel_blind->iochannel.funcs=&giochannel_blind_funcs;
468         giochannel_blind->iochannel.is_seekable=TRUE;
469         giochannel_blind->iochannel.is_readable=TRUE;
470         /* readonly captive_giochannel_blind can be used to track read access. */
471         giochannel_blind->iochannel.is_writeable=writeable;
472         giochannel_blind->iochannel.close_on_unref=TRUE;        /* run g_io_channel_shutdown() flush on last unref */
473         giochannel_blind->giochannel_orig=giochannel_orig;
474         giochannel_blind->offset=0;
475         giochannel_blind->size=(!giochannel_orig ? 0 : captive_giochannel_size(giochannel_orig));
476         giochannel_blind->buffer_hash=g_hash_table_new_full(
477                         (GHashFunc)captive_giochannel_blind_hash_func,  /* hash_func */
478                         (GEqualFunc)captive_giochannel_blind_equal_func,        /* key_equal_func */
479                         (GDestroyNotify)captive_giochannel_blind_key_destroy_func,      /* key_destroy_func */
480                         (GDestroyNotify)captive_giochannel_blind_value_destroy_func);   /* value_destroy_func */
481
482         captive_giochannel_setup(&giochannel_blind->iochannel);
483
484         return giochannel_blind;
485 }
486
487
488 gboolean captive_giochannel_blind_get_size(GIOChannel *giochannel,guint64 *size_return)
489 {
490 struct captive_giochannel_blind *giochannel_blind;
491
492         g_return_val_if_fail(giochannel!=NULL,FALSE);
493         g_return_val_if_fail(size_return!=NULL,FALSE);
494
495         if (giochannel->funcs!=&giochannel_blind_funcs)
496                 return FALSE;
497         giochannel_blind=(struct captive_giochannel_blind *)giochannel;
498
499         *size_return=giochannel_blind->size;
500         return TRUE;
501 }
502
503
504 typedef void (*sorted_array_filter)
505                 (const guint64 *keyp,const struct blind_block *blind_block,const struct blind_block ***rpp /* user_data */);
506
507 static void captive_giochannel_blind_written_as_sorted_array_foreach
508                 (const guint64 *keyp,const struct blind_block *blind_block,const struct blind_block ***rpp /* user_data */)
509 {
510         g_return_if_fail(keyp!=NULL);
511         g_return_if_fail(blind_block!=NULL);
512         g_return_if_fail(rpp!=NULL);
513
514         if (!blind_block->data_written)
515                 return;
516
517         *((*rpp)++)=blind_block;
518 }
519
520 static void captive_giochannel_blind_read_as_sorted_array_foreach
521                 (const guint64 *keyp,const struct blind_block *blind_block,const struct blind_block ***rpp /* user_data */)
522 {
523         g_return_if_fail(keyp!=NULL);
524         g_return_if_fail(blind_block!=NULL);
525         g_return_if_fail(rpp!=NULL);
526
527         if (!blind_block->was_read)
528                 return;
529
530         *((*rpp)++)=blind_block;
531 }
532
533 static int captive_giochannel_blind_as_sorted_array_compat
534                 (const struct blind_block *const *ap,const struct blind_block *const *bp)
535 {
536         g_return_val_if_fail(ap!=NULL,0);
537         g_return_val_if_fail(*ap!=NULL,0);
538         g_return_val_if_fail(bp!=NULL,0);
539         g_return_val_if_fail(*bp!=NULL,0);
540
541         return ((*ap)->offset>(*bp)->offset) - ((*bp)->offset>(*ap)->offset);
542 }
543
544 static struct blind_block **captive_giochannel_blind_as_sorted_array
545                 (struct captive_giochannel_blind *giochannel_blind,sorted_array_filter filter_func)
546 {
547 guint hash_size;
548 struct blind_block **r,**rp;
549
550         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
551
552         hash_size=g_hash_table_size(giochannel_blind->buffer_hash);
553         captive_newn(r,hash_size+1);
554         rp=r;
555         g_hash_table_foreach(giochannel_blind->buffer_hash,(GHFunc)filter_func,&rp);
556         g_assert(rp<=r+hash_size);
557         *rp=NULL;
558         qsort(r,rp-r,sizeof(*r),(int (*)(const void *,const void *))captive_giochannel_blind_as_sorted_array_compat);
559
560         return r;
561 }
562
563 GIOStatus captive_giochannel_blind_commit(GIOChannel *giochannel)
564 {
565 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)giochannel;
566 struct blind_block **blind_block_array,**blind_blockp;
567 GIOStatus errgiostatus;
568
569         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
570         g_return_val_if_fail(giochannel_blind->giochannel_orig!=NULL,G_IO_STATUS_ERROR);
571
572         errgiostatus=g_io_channel_flush(
573                         giochannel,     /* channel */
574                         NULL);  /* error */
575         g_assert(errgiostatus==G_IO_STATUS_NORMAL);
576
577         blind_block_array=captive_giochannel_blind_as_sorted_array
578                         (giochannel_blind,captive_giochannel_blind_written_as_sorted_array_foreach);
579
580         for (blind_blockp=blind_block_array;*blind_blockp;blind_blockp++) {
581 struct blind_block *blind_block=*blind_blockp;
582 gsize bytes_written;
583
584                 g_assert(blind_block->data_written!=NULL);
585
586                 errgiostatus=g_io_channel_seek_position(
587                                 giochannel_blind->giochannel_orig,      /* channel */
588                                 blind_block->offset,    /* offset */
589                                 G_SEEK_SET,     /* type */
590                                 NULL);  /* error */
591                 g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,errgiostatus);
592                 errgiostatus=g_io_channel_write_chars(
593                                 giochannel_blind->giochannel_orig,      /* channel */
594                                 blind_block->data_written,      /* buf */
595                                 GIOCHANNEL_BLIND_BLOCK_SIZE,    /* count */
596                                 &bytes_written, /* bytes_written */
597                                 NULL);  /* error */
598                 g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,errgiostatus);
599                 g_return_val_if_fail(bytes_written==GIOCHANNEL_BLIND_BLOCK_SIZE,G_IO_STATUS_ERROR);
600
601                 g_free(blind_block->data_written);
602                 blind_block->data_written=NULL;
603                 }
604
605         g_free(blind_block_array);
606
607         errgiostatus=g_io_channel_flush(
608                         giochannel_blind->giochannel_orig,      /* channel */
609                         NULL);  /* error */
610         g_assert(errgiostatus==G_IO_STATUS_NORMAL);
611
612         return G_IO_STATUS_NORMAL;
613 }
614
615
616 xmlNode *captive_giochannel_blind_readreport_to_xml(xmlNode *xml_parent,GIOChannel *giochannel)
617 {
618 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)giochannel;
619 struct blind_block **blind_block_array,**blind_blockp;
620 GIOStatus errgiostatus;
621 guint8 data_read[1+GIOCHANNEL_BLIND_BLOCK_SIZE];        /* '1+' for leading stub to prevent shorter output of BN_bn2hex() */
622 xmlNode *xml_media;
623
624         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),NULL);
625         g_return_val_if_fail(giochannel_blind->giochannel_orig!=NULL,G_IO_STATUS_ERROR);
626
627         errgiostatus=g_io_channel_flush(
628                         giochannel,     /* channel */
629                         NULL);  /* error */
630         g_assert(errgiostatus==G_IO_STATUS_NORMAL);
631
632         xml_media=xmlNewTextChild(xml_parent,NULL,"media",NULL);
633         xmlNewProp(xml_media,"size",captive_printf_alloca("%" G_GUINT64_FORMAT,giochannel_blind->size));
634
635         blind_block_array=captive_giochannel_blind_as_sorted_array
636                         (giochannel_blind,captive_giochannel_blind_read_as_sorted_array_foreach);
637
638         for (blind_blockp=blind_block_array;*blind_blockp;blind_blockp++) {
639 struct blind_block *blind_block=*blind_blockp;
640 gsize bytes_read;
641 xmlNode *xml_media_read;
642 gchar offset_string[64];
643 BIGNUM *bignum;
644 char *hex,*s;
645 gchar hex_out[0
646                 +1 /* leading '\n' */
647                 +GIOCHANNEL_BLIND_BLOCK_SIZE*2/64*(64+1)        /* each line of 64 characters has EOL '\n' */
648                 +1],*gd;        /* terminating '\0' */
649
650                 errgiostatus=g_io_channel_seek_position(
651                                 giochannel_blind->giochannel_orig,      /* channel */
652                                 blind_block->offset,    /* offset */
653                                 G_SEEK_SET,     /* type */
654                                 NULL);  /* error */
655                 g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,NULL);
656                 errgiostatus=g_io_channel_read_chars(
657                                 giochannel_blind->giochannel_orig,      /* channel */
658                                 data_read+1,    /* buf */
659                                 GIOCHANNEL_BLIND_BLOCK_SIZE,    /* count */
660                                 &bytes_read,    /* bytes_read */
661                                 NULL);  /* error */
662                 g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,NULL);
663                 g_return_val_if_fail(bytes_read==GIOCHANNEL_BLIND_BLOCK_SIZE,NULL);
664
665                 /* Convert binary block to 'hex' and reformat line-wrap it to 'hex_out'. */
666                 data_read[0]=0xFF;      /* stub to prevent shorter output of BN_bn2hex() */
667                 bignum=BN_bin2bn(data_read,1+GIOCHANNEL_BLIND_BLOCK_SIZE,NULL);
668                 hex=BN_bn2hex(bignum);
669                 BN_free(bignum);
670                 g_assert(strlen(hex)==2*(1+GIOCHANNEL_BLIND_BLOCK_SIZE));
671                 gd=hex_out;
672                 *gd++='\n';
673                 for (s=hex+2;s<hex+2+2*GIOCHANNEL_BLIND_BLOCK_SIZE;s+=64,gd+=64+1) {
674                         memcpy(gd,s,64);
675                         gd[64]='\n';
676                         }
677                 OPENSSL_free(hex);
678                 *gd++=0;
679                 g_assert(s==hex+2+2*GIOCHANNEL_BLIND_BLOCK_SIZE);
680                 g_assert(gd==hex_out+sizeof(hex_out));
681                 xml_media_read=xmlNewTextChild(xml_media,NULL,"block",hex_out);
682                 {
683                         g_snprintf(offset_string,sizeof(offset_string),"%" G_GUINT64_FORMAT,blind_block->offset);
684                         xmlNewProp(xml_media_read,"offset",offset_string);
685                         }
686                 }
687
688         g_free(blind_block_array);
689
690         return xml_media;
691 }
692
693
694 struct captive_giochannel_blind *captive_giochannel_blind_new_from_xml(xmlTextReader *xml_reader)
695 {
696 struct captive_giochannel_blind *r;
697 const xmlChar *xml_name;
698 int errint;
699 GIOStatus erriostatus;
700 gboolean scan_end;
701 struct captive_libxml_string_drop_stack *drop_stack=NULL;
702
703         g_return_val_if_fail(xml_reader!=NULL,NULL);
704         g_return_val_if_fail(xmlTextReaderNodeType(xml_reader)==CAPTIVE_XML_TEXT_READER_NODE_TYPE_START,NULL);
705         xml_name=captive_libxml_string_drop(&drop_stack,xmlTextReaderName(xml_reader));
706         g_return_val_if_fail(xml_name!=NULL,NULL);
707         g_return_val_if_fail(!xmlStrcmp(xml_name,"media"),NULL);
708
709         r=captive_giochannel_blind_new(
710                         NULL,   /* giochannel_orig */
711                         TRUE);  /* writeable */
712         r->size=captive_libxml_sscanf_gint64(captive_libxml_string_drop(&drop_stack,xmlTextReaderGetAttribute(xml_reader,"size")));
713
714         scan_end=FALSE;
715         do {
716 int got_type;
717
718                 errint=xmlTextReaderRead(xml_reader);
719                 g_assert(errint==1);
720                 switch ((got_type=xmlTextReaderNodeType(xml_reader))) {
721                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_COMMENT:
722                                 break;
723                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_TEXT:    /* Even empty nodes have some '#text'. */
724                                 break;
725                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_START: {
726 const xmlChar *xml_name;
727
728                                 xml_name=captive_libxml_string_drop(&drop_stack,xmlTextReaderName(xml_reader));
729                                 g_assert(xml_name!=NULL);
730                                 if (!xmlStrcmp(xml_name,"block")) {
731 guint64 offset;
732 gsize bytes_written;
733 const xmlChar *xml_text_hex_in,*xml_char_s;
734 BIGNUM *bignum;
735 int bignum_num_bytes;
736 gchar bin_hex[2*GIOCHANNEL_BLIND_BLOCK_SIZE+1],*gd;
737 unsigned char bin_out[GIOCHANNEL_BLIND_BLOCK_SIZE];
738
739                                         offset=captive_libxml_sscanf_gint64(
740                                                         captive_libxml_string_drop(&drop_stack,xmlTextReaderGetAttribute(xml_reader,"offset")));
741
742                                         errint=xmlTextReaderRead(xml_reader);
743                                         g_assert(errint==1);
744                                         errint=xmlTextReaderNodeType(xml_reader);
745                                         g_assert(errint==CAPTIVE_XML_TEXT_READER_NODE_TYPE_TEXT);
746                                         xml_text_hex_in=captive_libxml_string_drop(&drop_stack,xmlTextReaderValue(xml_reader));
747                                         g_assert(xml_text_hex_in!=NULL);
748
749                                         /* Convert binary block from hex line-wrapped 'xml_text_hex_in'. */
750                                         gd=bin_hex;
751                                         for (xml_char_s=xml_text_hex_in;*xml_char_s;xml_char_s++)
752                                                 if (!isspace(*xml_char_s)) {
753                                                         g_assert(gd<bin_hex+2*GIOCHANNEL_BLIND_BLOCK_SIZE);
754                                                         *gd++=*xml_char_s;
755                                                         }
756                                         *gd=0;
757                                         bignum=NULL;
758                                         errint=BN_hex2bn(&bignum,bin_hex);
759                                         g_assert(errint==2*GIOCHANNEL_BLIND_BLOCK_SIZE);
760                                         g_assert(bignum!=NULL);
761
762                                         /* Leading zeroes are ommited by BN_bn2bin(). */
763                                         bignum_num_bytes=BN_num_bytes(bignum);
764                                         g_assert(bignum_num_bytes<=GIOCHANNEL_BLIND_BLOCK_SIZE);
765                                         memset(bin_out,0,GIOCHANNEL_BLIND_BLOCK_SIZE-bignum_num_bytes);
766                                         errint=BN_bn2bin(bignum,bin_out+(GIOCHANNEL_BLIND_BLOCK_SIZE-bignum_num_bytes));
767                                         g_assert(errint==bignum_num_bytes);
768                                         BN_free(bignum);
769
770                                         erriostatus=g_io_channel_seek_position(
771                                                         &r->iochannel,  /* channel */
772                                                         offset, /* offset */
773                                                         G_SEEK_SET,     /* type */
774                                                         NULL);  /* error */
775                                         g_assert(erriostatus==G_IO_STATUS_NORMAL);
776                                         erriostatus=g_io_channel_write_chars(
777                                                         &r->iochannel,  /* channel */
778                                                         bin_out,        /* buf */
779                                                         GIOCHANNEL_BLIND_BLOCK_SIZE,    /* count */
780                                                         &bytes_written, /* bytes_written */
781                                                         NULL);  /* error */
782                                         g_assert(erriostatus==G_IO_STATUS_NORMAL);
783                                         g_assert(bytes_written==GIOCHANNEL_BLIND_BLOCK_SIZE);
784                                         }
785                                 else g_error("Unknown START node: %s",xml_name);
786                                 } break;
787                         case CAPTIVE_XML_TEXT_READER_NODE_TYPE_END: {
788 const xmlChar *xml_name;
789
790                                 xml_name=captive_libxml_string_drop(&drop_stack,xmlTextReaderName(xml_reader));
791                                 /**/ if (!xmlStrcmp(xml_name,"media")) {
792                                         scan_end=TRUE;  /* proper cleanup */
793                                         }
794                                 else if (!xmlStrcmp(xml_name,"block")) {
795                                         }
796                                 else g_error("Unknown END node: %s",xml_name);
797                                 } break;
798                         default:
799                                 g_error("Unexpected xmlTextReaderNodeType() type %d",got_type);
800                                 g_assert_not_reached();
801                         }
802                 captive_libxml_string_drop_flush(&drop_stack);
803                 } while (!scan_end);
804
805         g_assert(drop_stack==NULL);
806
807         return r;
808 }