+/* $Id$
+ * glib GIOChannel mapping subrange of parent GIOChannel for libcaptive
+ * Copyright (C) 2003 Jan Kratochvil <project-captive@jankratochvil.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; exactly version 2 of June 1991 is required
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include "config.h"
+
+#include "giochannel-subrange.h"
+#include <glib/gmessages.h>
+#include "captive/macros.h"
+#include "captive/storage.h"
+#include <ctype.h>
+#include "lib.h" /* for captive_giochannel_setup() */
+
+
+/* FIXME: fill 'err' */
+
+struct captive_giochannel_subrange {
+ GIOChannel iochannel;
+ GIOChannel *giochannel_orig; /* reffed by us */
+ guint64 offset; /* gint64 range; read start+offset from 'giochannel_orig' */
+ guint64 start,end;
+ };
+
+
+G_LOCK_DEFINE_STATIC(giochannel_subrange_funcs);
+static GIOFuncs giochannel_subrange_funcs;
+
+
+static gboolean validate_giochannel_subrange(struct captive_giochannel_subrange *giochannel_subrange)
+{
+ g_return_val_if_fail(giochannel_subrange->iochannel.funcs==&giochannel_subrange_funcs,FALSE);
+ g_return_val_if_fail(giochannel_subrange!=NULL,FALSE);
+ g_return_val_if_fail(giochannel_subrange->giochannel_orig!=NULL,FALSE);
+ g_return_val_if_fail((gint64)giochannel_subrange->offset>=0,FALSE); /* gint64 overflow stored in guint64 */
+ g_return_val_if_fail(giochannel_subrange->start<=giochannel_subrange->end,FALSE);
+ g_return_val_if_fail(giochannel_subrange->offset<=(giochannel_subrange->end-giochannel_subrange->start),FALSE);
+
+ return TRUE;
+}
+
+
+static GIOStatus captive_giochannel_subrange_io_read
+ (GIOChannel *channel,gchar *buf,gsize count,gsize *bytes_read,GError **err)
+{
+struct captive_giochannel_subrange *giochannel_subrange=(struct captive_giochannel_subrange *)channel;
+GIOStatus errgiostatus;
+
+ g_return_val_if_fail(validate_giochannel_subrange(giochannel_subrange),G_IO_STATUS_ERROR);
+ g_return_val_if_fail(buf!=NULL,G_IO_STATUS_ERROR);
+ g_return_val_if_fail(bytes_read!=NULL,G_IO_STATUS_ERROR);
+
+ g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: read(offset=0x%llX,count=0x%lX)",G_STRLOC,
+ giochannel_subrange->offset,(gulong)count);
+
+ errgiostatus=g_io_channel_seek_position(
+ giochannel_subrange->giochannel_orig, /* channel */
+ giochannel_subrange->start+giochannel_subrange->offset, /* offset */
+ G_SEEK_SET, /* type */
+ err); /* error */
+ /* During seek in block device such as on URL file:///dev/hda1#captive-fastfat.sys-ro:/
+ * we will do llseek(2) on "/dev/hda1" device from captive_giochannel_size().
+ * Although we are allowed to seek behind EOF on regular files
+ * at least linux-kernel-2.4.19-ac4/fs/block_dev.c/block_llseek() will give
+ * EINVAL on seek behind EOF therefore it must be accepted without complaints by us.
+ */
+ if (errgiostatus!=G_IO_STATUS_NORMAL) {
+ errgiostatus=G_IO_STATUS_EOF;
+ *bytes_read=0;
+ }
+ else {
+ errgiostatus=g_io_channel_read_chars(
+ giochannel_subrange->giochannel_orig, /* channel */
+ buf, /* buf */
+ count, /* count */
+ bytes_read, /* bytes_read */
+ err); /* error */
+ }
+ g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL || errgiostatus==G_IO_STATUS_EOF,errgiostatus);
+ g_return_val_if_fail(*bytes_read<=count,G_IO_STATUS_ERROR);
+ g_return_val_if_fail((errgiostatus==G_IO_STATUS_EOF)==(bytes_read==0),G_IO_STATUS_ERROR);
+
+ giochannel_subrange->offset+=*bytes_read;
+ return (*bytes_read == 0 ? G_IO_STATUS_EOF : G_IO_STATUS_NORMAL);
+}
+
+
+static GIOStatus captive_giochannel_subrange_io_write
+ (GIOChannel *channel,const gchar *buf,gsize count,gsize *bytes_written,GError **err)
+{
+struct captive_giochannel_subrange *giochannel_subrange=(struct captive_giochannel_subrange *)channel;
+GIOStatus errgiostatus;
+
+ g_return_val_if_fail(validate_giochannel_subrange(giochannel_subrange),G_IO_STATUS_ERROR);
+ g_return_val_if_fail(buf!=NULL,G_IO_STATUS_ERROR);
+ g_return_val_if_fail(bytes_written!=NULL,G_IO_STATUS_ERROR);
+
+ g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: write(offset=0x%llX,count=0x%lX)",G_STRLOC,
+ giochannel_subrange->offset,(gulong)count);
+
+ g_return_val_if_fail(giochannel_subrange->start+giochannel_subrange->offset+count<=giochannel_subrange->end,
+ G_IO_STATUS_ERROR);
+
+ errgiostatus=g_io_channel_seek_position(
+ giochannel_subrange->giochannel_orig, /* channel */
+ giochannel_subrange->start+giochannel_subrange->offset, /* offset */
+ G_SEEK_SET, /* type */
+ err); /* error */
+ g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,errgiostatus);
+ errgiostatus=g_io_channel_write_chars(
+ giochannel_subrange->giochannel_orig, /* channel */
+ buf, /* buf */
+ count, /* count */
+ bytes_written, /* bytes_written */
+ err); /* error */
+ g_return_val_if_fail(errgiostatus==G_IO_STATUS_NORMAL,errgiostatus);
+ g_return_val_if_fail(*bytes_written==count,G_IO_STATUS_ERROR);
+
+ giochannel_subrange->offset+=(*bytes_written);
+ return G_IO_STATUS_NORMAL;
+}
+
+
+static GIOStatus captive_giochannel_subrange_io_seek(GIOChannel *channel,gint64 offset,GSeekType type,GError **err)
+{
+struct captive_giochannel_subrange *giochannel_subrange=(struct captive_giochannel_subrange *)channel;
+
+ g_return_val_if_fail(validate_giochannel_subrange(giochannel_subrange),G_IO_STATUS_ERROR);
+
+ switch (type) {
+ case G_SEEK_CUR: giochannel_subrange->offset+=offset; break;
+ case G_SEEK_SET: giochannel_subrange->offset =offset; break;
+ case G_SEEK_END: giochannel_subrange->offset =(giochannel_subrange->end-giochannel_subrange->start)+offset; break;
+ default: g_return_val_if_reached(G_IO_STATUS_ERROR);
+ }
+ g_return_val_if_fail(validate_giochannel_subrange(giochannel_subrange),G_IO_STATUS_ERROR); /* 'offset' overflow? */
+
+ return G_IO_STATUS_NORMAL;
+}
+
+
+static GIOStatus captive_giochannel_subrange_io_close(GIOChannel *channel,GError **err)
+{
+struct captive_giochannel_subrange *giochannel_subrange=(struct captive_giochannel_subrange *)channel;
+GIOStatus erriostatus;
+
+ g_return_val_if_fail(validate_giochannel_subrange(giochannel_subrange),G_IO_STATUS_ERROR);
+
+ if (giochannel_subrange->giochannel_orig) {
+ /* Just a sanity if 'giochannel_orig' is already falsely reffed a bit more... */
+ erriostatus=g_io_channel_flush(
+ giochannel_subrange->giochannel_orig, /* channel */
+ NULL); /* error */
+ g_assert(erriostatus==G_IO_STATUS_NORMAL);
+
+ g_io_channel_unref(giochannel_subrange->giochannel_orig);
+ giochannel_subrange->giochannel_orig=NULL;
+ }
+
+ return G_IO_STATUS_NORMAL;
+}
+
+
+static GSource* captive_giochannel_subrange_io_create_watch(GIOChannel *channel,GIOCondition condition)
+{
+struct captive_giochannel_subrange *giochannel_subrange=(struct captive_giochannel_subrange *)channel;
+
+ g_return_val_if_fail(validate_giochannel_subrange(giochannel_subrange),NULL);
+
+ g_return_val_if_reached(NULL); /* FIXME: NOT IMPLEMENTED YET */
+}
+
+
+static void captive_giochannel_subrange_io_free(GIOChannel *channel)
+{
+struct captive_giochannel_subrange *giochannel_subrange=(struct captive_giochannel_subrange *)channel;
+
+ /* After captive_giochannel_subrange_io_close() 'giochannel_subrange'
+ * may be no longer valid for validate_giochannel_subrange(giochannel_subrange).
+ */
+ g_return_if_fail(giochannel_subrange!=NULL);
+
+ g_assert(giochannel_subrange->giochannel_orig==NULL);
+
+ g_free(giochannel_subrange);
+}
+
+
+static GIOStatus captive_giochannel_subrange_io_set_flags(GIOChannel *channel,GIOFlags flags,GError **err)
+{
+struct captive_giochannel_subrange *giochannel_subrange=(struct captive_giochannel_subrange *)channel;
+
+ g_return_val_if_fail(validate_giochannel_subrange(giochannel_subrange),G_IO_STATUS_ERROR);
+
+ return g_io_channel_set_flags(giochannel_subrange->giochannel_orig,flags,err);
+}
+
+
+static GIOFlags captive_giochannel_subrange_io_get_flags(GIOChannel *channel)
+{
+struct captive_giochannel_subrange *giochannel_subrange=(struct captive_giochannel_subrange *)channel;
+
+ g_return_val_if_fail(validate_giochannel_subrange(giochannel_subrange),0);
+
+ return g_io_channel_get_flags(giochannel_subrange->giochannel_orig);
+}
+
+
+struct captive_giochannel_subrange *captive_giochannel_subrange_new(GIOChannel *giochannel_orig,guint64 start,guint64 end)
+{
+struct captive_giochannel_subrange *giochannel_subrange;
+
+ g_return_val_if_fail(giochannel_orig!=NULL,NULL);
+ g_return_val_if_fail(start<=end,NULL);
+
+ G_LOCK(giochannel_subrange_funcs);
+ giochannel_subrange_funcs.io_read =captive_giochannel_subrange_io_read;
+ giochannel_subrange_funcs.io_write =captive_giochannel_subrange_io_write;
+ giochannel_subrange_funcs.io_seek =captive_giochannel_subrange_io_seek;
+ giochannel_subrange_funcs.io_close =captive_giochannel_subrange_io_close;
+ giochannel_subrange_funcs.io_create_watch=captive_giochannel_subrange_io_create_watch;
+ giochannel_subrange_funcs.io_free =captive_giochannel_subrange_io_free;
+ giochannel_subrange_funcs.io_set_flags =captive_giochannel_subrange_io_set_flags;
+ giochannel_subrange_funcs.io_get_flags =captive_giochannel_subrange_io_get_flags;
+ G_UNLOCK(giochannel_subrange_funcs);
+
+ captive_giochannel_setup(giochannel_orig);
+
+ g_io_channel_ref(giochannel_orig);
+
+ captive_new(giochannel_subrange);
+ g_assert(G_STRUCT_OFFSET(struct captive_giochannel_subrange,iochannel)==0); /* safely re-type-able */
+ g_io_channel_init(&giochannel_subrange->iochannel);
+ giochannel_subrange->iochannel.funcs=&giochannel_subrange_funcs;
+ giochannel_subrange->iochannel.is_seekable =!!(g_io_channel_get_flags(giochannel_orig) & G_IO_FLAG_IS_SEEKABLE);
+ giochannel_subrange->iochannel.is_readable =!!(g_io_channel_get_flags(giochannel_orig) & G_IO_FLAG_IS_READABLE);
+ giochannel_subrange->iochannel.is_writeable=!!(g_io_channel_get_flags(giochannel_orig) & G_IO_FLAG_IS_WRITEABLE);
+ giochannel_subrange->iochannel.close_on_unref=TRUE; /* run g_io_channel_shutdown() flush on last unref */
+ giochannel_subrange->giochannel_orig=giochannel_orig;
+ giochannel_subrange->offset=0;
+ giochannel_subrange->start=start;
+ giochannel_subrange->end=end;
+
+ captive_giochannel_setup(&giochannel_subrange->iochannel);
+
+ return giochannel_subrange;
+}
+
+
+gboolean captive_giochannel_subrange_get_size(GIOChannel *giochannel,guint64 *size_return)
+{
+struct captive_giochannel_subrange *giochannel_subrange;
+
+ g_return_val_if_fail(giochannel!=NULL,FALSE);
+ g_return_val_if_fail(size_return!=NULL,FALSE);
+
+ if (giochannel->funcs!=&giochannel_subrange_funcs)
+ return FALSE;
+ giochannel_subrange=(struct captive_giochannel_subrange *)giochannel;
+
+ *size_return=giochannel_subrange->end-giochannel_subrange->start;
+ return TRUE;
+}