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