Permit Gnome-VFS mount of Linux block device (where seeks are failing behind EOF)
[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
29
30 #define GIOCHANNEL_BLIND_BLOCK_SIZE (PAGE_SIZE)
31
32 /* FIXME: fill 'err' */
33
34 struct captive_giochannel_blind {
35         GIOChannel iochannel;
36         GIOChannel *giochannel_ro;
37         guint64 offset; /* gint64 range */
38         guint64 size;
39         GHashTable *buffer_hash;        /* (guint64 *) -> (guint8[GIOCHANNEL_BLIND_BLOCK_SIZE]) */
40         };
41
42
43 G_LOCK_DEFINE_STATIC(giochannel_blind_funcs);
44 static GIOFuncs giochannel_blind_funcs;
45
46
47 static guint captive_giochannel_blind_hash_func(const guint64 *key)
48 {
49         g_return_val_if_fail(key!=NULL,0);
50
51         return (*key)^((*key)>>23);
52 }
53
54 static gboolean captive_giochannel_blind_equal_func(const guint64 *ap,const guint64 *bp)
55 {
56         g_return_val_if_fail(ap!=NULL,FALSE);
57         g_return_val_if_fail(bp!=NULL,FALSE);
58
59         return (*ap)==(*bp);
60 }
61
62 static void captive_giochannel_blind_key_destroy_func(guint64 *key)
63 {
64         g_return_if_fail(key!=NULL);
65
66         g_free(key);
67 }
68
69 static void captive_giochannel_blind_value_destroy_func(void *data)
70 {
71         g_return_if_fail(data!=NULL);
72
73         g_free(data);
74 }
75
76
77 static gboolean validate_giochannel_blind(struct captive_giochannel_blind *giochannel_blind)
78 {
79         g_return_val_if_fail(giochannel_blind!=NULL,FALSE);
80         g_return_val_if_fail(giochannel_blind->giochannel_ro!=NULL,FALSE);
81         g_return_val_if_fail((gint64)giochannel_blind->offset>=0,FALSE);        /* gint64 overflow stored in guint64 */
82         g_return_val_if_fail(giochannel_blind->buffer_hash!=NULL,FALSE);
83
84         return TRUE;
85 }
86
87
88 static GIOStatus captive_giochannel_blind_io_read
89                 (GIOChannel *channel,gchar *buf,gsize count,gsize *bytes_read,GError **err)
90 {
91 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
92 gpointer data;
93 guint64 window_bottom,window_top,window_now;
94 guint64 transfer_bottom,transfer_top;
95 GIOStatus errgiostatus;
96 guint64 maxread;        /* maximum offset of end of data we successfuly read */
97
98         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
99         g_return_val_if_fail(buf!=NULL,G_IO_STATUS_ERROR);
100         g_return_val_if_fail(bytes_read!=NULL,G_IO_STATUS_ERROR);
101
102         g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: read(offset=0x%llX,count=0x%lX)",G_STRLOC,
103                         giochannel_blind->offset,(gulong)count);
104
105         window_bottom=CAPTIVE_ROUND_DOWN(giochannel_blind->offset,GIOCHANNEL_BLIND_BLOCK_SIZE);
106         window_top=CAPTIVE_ROUND_UP(giochannel_blind->offset+count,GIOCHANNEL_BLIND_BLOCK_SIZE);
107         maxread=giochannel_blind->offset;
108
109         for (window_now=window_bottom;window_now<window_top;window_now+=GIOCHANNEL_BLIND_BLOCK_SIZE) {
110 gsize bytes_read;
111
112                 transfer_bottom=MAX(window_now,giochannel_blind->offset);
113                 transfer_top=MIN(window_now+GIOCHANNEL_BLIND_BLOCK_SIZE,giochannel_blind->offset+count);
114                 if ((data=g_hash_table_lookup(giochannel_blind->buffer_hash,&window_now))) {
115                         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)",
116                                         G_STRLOC,
117                                         (guint64)window_now,(gulong)(transfer_bottom-giochannel_blind->offset),(gulong)(transfer_bottom-window_now),
118                                         (gulong)(transfer_top-transfer_bottom));
119                         memcpy(
120                                         buf+transfer_bottom-giochannel_blind->offset,   /* dest */
121                                         data+transfer_bottom-window_now,        /* src */
122                                         transfer_top-transfer_bottom);  /* n */
123                         maxread=transfer_top;
124                         continue;
125                         }
126                 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,
127                                 (guint64)window_now,(gulong)(transfer_bottom-giochannel_blind->offset),(guint64)transfer_bottom,
128                                 (gulong)(transfer_top-transfer_bottom));
129                 errgiostatus=g_io_channel_seek_position(
130                                 giochannel_blind->giochannel_ro,        /* channel */
131                                 transfer_bottom,        /* offset */
132                                 G_SEEK_SET,     /* type */
133                                 err);   /* error */
134                 /* During seek in block device such as on URL file:///dev/hda1#captive-fastfat.sys-ro:/
135                  * we will do llseek(2) on "/dev/hda1" device from captive_giochannel_size().
136                  * Although we are allowed to seek behind EOF on regular files
137                  * at least linux-kernel-2.4.19-ac4/fs/block_dev.c/block_llseek() will give
138                  * EINVAL on seek behind EOF therefore it must be accepted without complaints by us.
139                  */
140                 if (errgiostatus!=G_IO_STATUS_NORMAL) {
141                         errgiostatus=G_IO_STATUS_EOF;
142                         bytes_read=0;
143                         }
144                 else {
145                         errgiostatus=g_io_channel_read_chars(
146                                         giochannel_blind->giochannel_ro,        /* channel */
147                                         buf+transfer_bottom-giochannel_blind->offset,   /* buf */
148                                         transfer_top-transfer_bottom,   /* count */
149                                         &bytes_read,    /* bytes_read */
150                                         err);   /* error */
151                         }
152                 g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL || errgiostatus==G_IO_STATUS_EOF,errgiostatus);
153                 g_return_val_if_fail(bytes_read<=(transfer_top-transfer_bottom),G_IO_STATUS_ERROR);
154                 g_return_val_if_fail((errgiostatus==G_IO_STATUS_EOF)==(bytes_read==0),G_IO_STATUS_ERROR);
155                 maxread=transfer_bottom+bytes_read;
156                 if (bytes_read==transfer_top-transfer_bottom)
157                         g_return_val_if_fail(transfer_bottom+bytes_read<=giochannel_blind->size,G_IO_STATUS_ERROR);
158                 else
159                         break;
160                 }
161
162         *bytes_read=maxread-giochannel_blind->offset;
163         giochannel_blind->offset=maxread;
164         return (*bytes_read == 0 ? G_IO_STATUS_EOF : G_IO_STATUS_NORMAL);
165 }
166
167
168 static GIOStatus captive_giochannel_blind_io_write
169                 (GIOChannel *channel,const gchar *buf,gsize count,gsize *bytes_written,GError **err)
170 {
171 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
172 gpointer data;
173 guint64 window_bottom,window_top,window_now;
174 guint64 transfer_bottom,transfer_top;
175 GIOStatus errgiostatus;
176
177         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
178         g_return_val_if_fail(buf!=NULL,G_IO_STATUS_ERROR);
179         g_return_val_if_fail(bytes_written!=NULL,G_IO_STATUS_ERROR);
180
181         g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: write(offset=0x%llX,count=0x%lX)",G_STRLOC,
182                         giochannel_blind->offset,(gulong)count);
183
184         g_return_val_if_fail(giochannel_blind->offset+count<=giochannel_blind->size,G_IO_STATUS_ERROR);
185
186         window_bottom=CAPTIVE_ROUND_DOWN(giochannel_blind->offset,GIOCHANNEL_BLIND_BLOCK_SIZE);
187         window_top=CAPTIVE_ROUND_UP(giochannel_blind->offset+count,GIOCHANNEL_BLIND_BLOCK_SIZE);
188
189         for (window_now=window_bottom;window_now<window_top;window_now+=GIOCHANNEL_BLIND_BLOCK_SIZE) {
190 gsize bytes_read;
191
192                 transfer_bottom=MAX(window_now,giochannel_blind->offset);
193                 transfer_top=MIN(window_now+GIOCHANNEL_BLIND_BLOCK_SIZE,giochannel_blind->offset+count);
194                 if (!(data=g_hash_table_lookup(giochannel_blind->buffer_hash,&window_now))) {
195 guint64 *keyp;
196
197                         data=g_malloc(GIOCHANNEL_BLIND_BLOCK_SIZE);
198                         captive_new(keyp);
199                         *keyp=window_now;
200                         g_hash_table_insert(
201                                         giochannel_blind->buffer_hash,  /* hash_table */
202                                         keyp,   /* key */
203                                         data);  /* value */
204                         g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: write-new-mem(window_now=0x%llX)",G_STRLOC,
205                                         (guint64)window_now);
206
207                         /* Missing lower part of buffer? */
208                         if (transfer_bottom>window_now) {
209                                 g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: write-mem-read-lower(seek=0x%llX,count=0x%lX)",G_STRLOC,
210                                                 (guint64)window_now,(gulong)(transfer_bottom-window_now));
211                                 errgiostatus=g_io_channel_seek_position(
212                                                 giochannel_blind->giochannel_ro,        /* channel */
213                                                 window_now,     /* offset */
214                                                 G_SEEK_SET,     /* type */
215                                                 err);   /* error */
216                                 g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,errgiostatus);
217                                 errgiostatus=g_io_channel_read_chars(
218                                                 giochannel_blind->giochannel_ro,        /* channel */
219                                                 data,   /* buf */
220                                                 transfer_bottom-window_now,     /* count */
221                                                 &bytes_read,    /* bytes_read */
222                                                 err);   /* error */
223                                 g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,errgiostatus);
224                                 g_return_val_if_fail(bytes_read==(transfer_bottom-window_now),G_IO_STATUS_ERROR);
225                                 }
226
227                         /* Missing upper part of buffer? */
228                         if (transfer_top<window_now+GIOCHANNEL_BLIND_BLOCK_SIZE) {
229                                 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,
230                                                 (gulong)(transfer_top-window_now),(guint64)transfer_top,
231                                                 (gulong)(window_now+GIOCHANNEL_BLIND_BLOCK_SIZE-transfer_top));
232                                 errgiostatus=g_io_channel_seek_position(
233                                                 giochannel_blind->giochannel_ro,        /* channel */
234                                                 transfer_top,   /* offset */
235                                                 G_SEEK_SET,     /* type */
236                                                 err);   /* error */
237                                 g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,errgiostatus);
238                                 errgiostatus=g_io_channel_read_chars(
239                                                 giochannel_blind->giochannel_ro,        /* channel */
240                                                 data+transfer_top-window_now,   /* buf */
241                                                 window_now+GIOCHANNEL_BLIND_BLOCK_SIZE-transfer_top,    /* count */
242                                                 &bytes_read,    /* bytes_read */
243                                                 err);   /* error */
244                                 g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL || errgiostatus==G_IO_STATUS_EOF,errgiostatus);
245                                 g_return_val_if_fail(bytes_read<=(window_now+GIOCHANNEL_BLIND_BLOCK_SIZE-transfer_top),G_IO_STATUS_ERROR);
246                                 g_return_val_if_fail((errgiostatus==G_IO_STATUS_EOF)==(bytes_read==0),G_IO_STATUS_ERROR);
247                                 if (bytes_read==window_now+GIOCHANNEL_BLIND_BLOCK_SIZE-transfer_top)
248                                         g_return_val_if_fail(transfer_top+bytes_read<=giochannel_blind->size,G_IO_STATUS_ERROR);
249                                 else
250                                         g_return_val_if_fail(transfer_top+bytes_read==giochannel_blind->size,G_IO_STATUS_ERROR);        /* EOF hit */
251                                 }
252
253                         }
254                 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)",
255                                 G_STRLOC,
256                                 (guint64)window_now,(gulong)(transfer_bottom-window_now),(gulong)(transfer_bottom-giochannel_blind->offset),
257                                 (gulong)(transfer_top-transfer_bottom));
258                 memcpy(
259                                 ((char *)data)+transfer_bottom-window_now,      /* dest */
260                                 buf+transfer_bottom-giochannel_blind->offset,   /* src */
261                                 transfer_top-transfer_bottom);  /* n */
262                 }
263
264         *bytes_written=count;
265         giochannel_blind->offset+=(*bytes_written);
266         return G_IO_STATUS_NORMAL;
267 }
268
269
270 static GIOStatus captive_giochannel_blind_io_seek(GIOChannel *channel,gint64 offset,GSeekType type,GError **err)
271 {
272 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
273
274         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
275
276         switch (type) {
277                 case G_SEEK_CUR: giochannel_blind->offset+=                       offset; break;
278                 case G_SEEK_SET: giochannel_blind->offset =                       offset; break;
279                 case G_SEEK_END: giochannel_blind->offset =giochannel_blind->size+offset; break;
280                 default: g_return_val_if_reached(G_IO_STATUS_ERROR);
281                 }
282         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);    /* 'offset' overflow? */
283
284         return G_IO_STATUS_NORMAL;
285 }
286
287
288 static GIOStatus captive_giochannel_blind_io_close(GIOChannel *channel,GError **err)
289 {
290 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
291 GIOStatus erriostatus;
292
293         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
294
295         /* We are not authorized to destroy 'giochannel_blind->giochannel_ro'. */
296         erriostatus=g_io_channel_flush(
297                         giochannel_blind->giochannel_ro,        /* channel */
298                         NULL);  /* error */
299         g_assert(erriostatus==G_IO_STATUS_NORMAL);
300         giochannel_blind->giochannel_ro=NULL;
301
302         g_hash_table_destroy(giochannel_blind->buffer_hash);
303         giochannel_blind->buffer_hash=NULL;
304
305         return G_IO_STATUS_NORMAL;
306 }
307
308
309 static GSource* captive_giochannel_blind_io_create_watch(GIOChannel *channel,GIOCondition condition)
310 {
311 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
312
313         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),NULL);
314
315         g_return_val_if_reached(NULL);  /* FIXME: NOT IMPLEMENTED YET */
316 }
317
318
319 static void captive_giochannel_blind_io_free(GIOChannel *channel)
320 {
321 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
322
323         /* After captive_giochannel_blind_io_close() 'giochannel_blind'
324          * may be no longer valid for validate_giochannel_blind(giochannel_blind).
325          */
326         g_return_if_fail(giochannel_blind!=NULL);
327
328         g_assert(giochannel_blind->giochannel_ro==NULL);
329         g_assert(giochannel_blind->buffer_hash==NULL);
330
331         g_free(giochannel_blind);
332 }
333
334
335 static GIOStatus captive_giochannel_blind_io_set_flags(GIOChannel *channel,GIOFlags flags,GError **err)
336 {
337 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
338
339         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),G_IO_STATUS_ERROR);
340
341         return g_io_channel_set_flags(giochannel_blind->giochannel_ro,(flags&~G_IO_FLAG_IS_WRITEABLE),err);
342 }
343
344
345 static GIOFlags captive_giochannel_blind_io_get_flags(GIOChannel *channel)
346 {
347 struct captive_giochannel_blind *giochannel_blind=(struct captive_giochannel_blind *)channel;
348
349         g_return_val_if_fail(validate_giochannel_blind(giochannel_blind),0);
350
351         return g_io_channel_get_flags(giochannel_blind->giochannel_ro) | G_IO_FLAG_IS_WRITEABLE;
352 }
353
354
355 struct captive_giochannel_blind *captive_giochannel_blind_new(GIOChannel *giochannel_ro)
356 {
357 struct captive_giochannel_blind *giochannel_blind;
358 GIOStatus erriostatus;
359
360         g_return_val_if_fail(giochannel_ro!=NULL,NULL);
361
362         G_LOCK(giochannel_blind_funcs);
363         giochannel_blind_funcs.io_read        =captive_giochannel_blind_io_read;
364         giochannel_blind_funcs.io_write       =captive_giochannel_blind_io_write;
365         giochannel_blind_funcs.io_seek        =captive_giochannel_blind_io_seek;
366         giochannel_blind_funcs.io_close       =captive_giochannel_blind_io_close;
367         giochannel_blind_funcs.io_create_watch=captive_giochannel_blind_io_create_watch;
368         giochannel_blind_funcs.io_free        =captive_giochannel_blind_io_free;
369         giochannel_blind_funcs.io_set_flags   =captive_giochannel_blind_io_set_flags;
370         giochannel_blind_funcs.io_get_flags   =captive_giochannel_blind_io_get_flags;
371         G_UNLOCK(giochannel_blind_funcs);
372
373         erriostatus=g_io_channel_set_encoding(giochannel_ro,
374                         NULL,   /* encoding; force binary data */
375                         NULL);  /* error */
376         g_assert(erriostatus==G_IO_STATUS_NORMAL);
377
378         captive_new(giochannel_blind);
379         g_assert(G_STRUCT_OFFSET(struct captive_giochannel_blind,iochannel)==0);        /* safely re-type-able */
380         g_io_channel_init(&giochannel_blind->iochannel);
381         giochannel_blind->iochannel.funcs=&giochannel_blind_funcs;
382         giochannel_blind->iochannel.is_seekable=TRUE;
383         giochannel_blind->iochannel.is_readable=TRUE;
384         giochannel_blind->iochannel.is_writeable=TRUE;
385         giochannel_blind->iochannel.close_on_unref=TRUE;        /* run g_io_channel_shutdown() flush on last unref */
386         giochannel_blind->giochannel_ro=giochannel_ro;
387         giochannel_blind->offset=0;
388         giochannel_blind->size=captive_giochannel_size(giochannel_ro);
389         giochannel_blind->buffer_hash=g_hash_table_new_full(
390                         (GHashFunc)captive_giochannel_blind_hash_func,  /* hash_func */
391                         (GEqualFunc)captive_giochannel_blind_equal_func,        /* key_equal_func */
392                         (GDestroyNotify)captive_giochannel_blind_key_destroy_func,      /* key_destroy_func */
393                         (GDestroyNotify)captive_giochannel_blind_value_destroy_func);   /* value_destroy_func */
394
395         return giochannel_blind;
396 }