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