/*
* UCW Library -- Fast Buffered I/O
*
- * (c) 1997--2007 Martin Mares <mj@ucw.cz>
+ * (c) 1997--2011 Martin Mares <mj@ucw.cz>
*
* This software may be freely distributed and used according to the terms
* of the GNU Lesser General Public License.
*/
-#include "ucw/lib.h"
-#include "ucw/fastbuf.h"
+#undef LOCAL_DEBUG
+
+#include <ucw/lib.h>
+#include <ucw/fastbuf.h>
+#include <ucw/resource.h>
+#include <ucw/trans.h>
+#include <ucw/stkstring.h>
#include <stdio.h>
#include <stdlib.h>
if (f)
{
bflush(f);
+ res_detach(f->res);
+ DBG("FB: closing", f);
if (f->close)
- f->close(f);
+ f->close(f); /* Should always free all internal resources, even if it throws an exception */
+ }
+}
+
+void NONRET bthrow(struct fastbuf *f, const char *id, const char *fmt, ...)
+{
+ DBG("FB: throwing %s", full_id);
+ char full_id[16];
+ snprintf(full_id, sizeof(full_id), "ucw.fb.%s", id);
+ ASSERT(!(f->flags & FB_DEAD)); /* Only one bthrow() is allowed before bclose() */
+ va_list args;
+ va_start(args, fmt);
+ if (!f->res)
+ die("Fastbuf %s error: %s", f->name ? : "<fb>", stk_vprintf(fmt, args));
+ f->flags |= FB_DEAD;
+ f->bptr = f->bstop = f->bufend; /* Reset the buffer to guard consecutive seek/read/write */
+ trans_vthrow(full_id, f, fmt, args);
+}
+
+int brefill(struct fastbuf *f, int allow_eof)
+{
+ DBG("FB: refill");
+ ASSERT(!(f->flags & FB_DEAD) && f->buffer <= f->bstop && f->bstop <= f->bptr && f->bptr <= f->bufend);
+ if (!f->refill)
+ bthrow(f, "read", "Stream not readable");
+ if (f->refill(f))
+ {
+ ASSERT(f->buffer <= f->bptr && f->bptr < f->bstop && f->bstop <= f->bufend);
+ return 1;
+ }
+ else
+ {
+ ASSERT(f->buffer <= f->bptr && f->bptr == f->bstop && f->bstop <= f->bufend);
+ if (!allow_eof && (f->flags & FB_DIE_ON_EOF))
+ bthrow(f, "eof", "Unexpected EOF");
+ return 0;
+ }
+}
+
+static void do_spout(struct fastbuf *f)
+{
+ DBG("FB: spout");
+ ASSERT(!(f->flags & FB_DEAD) && f->buffer <= f->bstop && f->bstop <= f->bptr && f->bptr <= f->bufend); /* Check write mode possibly with unflushed data */
+ if (!f->spout)
+ bthrow(f, "write", "Stream not writeable");
+ f->spout(f);
+ ASSERT(f->buffer <= f->bstop && f->bstop <= f->bptr && f->bptr <= f->bufend);
+}
+
+void bspout(struct fastbuf *f)
+{
+ do_spout(f);
+ if (f->bstop == f->bufend)
+ {
+ do_spout(f);
+ ASSERT(f->bstop < f->bufend);
}
}
void bflush(struct fastbuf *f)
{
if (f->bptr > f->bstop)
- f->spout(f);
- else if (f->bstop > f->buffer)
- f->bptr = f->bstop = f->buffer;
+ do_spout(f);
+ else
+ f->bptr = f->bstop; /* XXX: Skip the rest of the reading buffer ==> it breaks the position of the FE cursor */
+ DBG("FB: flushed");
}
-inline void bsetpos(struct fastbuf *f, ucw_off_t pos)
+static void do_seek(struct fastbuf *f, ucw_off_t pos, int whence)
+{
+ bflush(f);
+ DBG("FB: seeking to pos=%lld whence=%d %p %p %p %p", (long long)pos, whence, f->buffer, f->bstop, f->bptr, f->bufend);
+ if (!f->seek || !f->seek(f, pos, whence))
+ bthrow(f, "seek", "Stream not seekable");
+ DBG("FB: seeked %p %p %p %p", f->buffer, f->bstop, f->bptr, f->bufend);
+ ASSERT(f->buffer <= f->bstop && f->bstop <= f->bptr && f->bptr <= f->bufend);
+ if (whence == SEEK_SET)
+ ASSERT(pos == btell(f));
+ else
+ ASSERT(btell(f) >= 0);
+}
+
+void bsetpos(struct fastbuf *f, ucw_off_t pos)
{
/* We can optimize seeks only when reading */
- if (pos >= f->pos - (f->bstop - f->buffer) && pos <= f->pos)
+ if (f->bptr < f->bstop && pos <= f->pos && pos >= f->pos - (f->bstop - f->buffer)) /* If bptr == bstop, then [buffer, bstop] may be undefined */
f->bptr = f->bstop + (pos - f->pos);
- else
+ else if (pos != btell(f))
{
- bflush(f);
- if (!f->seek || !f->seek(f, pos, SEEK_SET))
- die("bsetpos: stream not seekable");
+ if (pos < 0)
+ bthrow(f, "seek", "Seek out of range");
+ do_seek(f, pos, SEEK_SET);
}
}
switch (whence)
{
case SEEK_SET:
- return bsetpos(f, pos);
+ bsetpos(f, pos);
+ break;
case SEEK_CUR:
- return bsetpos(f, btell(f) + pos);
+ bsetpos(f, btell(f) + pos); /* btell() is non-negative, so an overflow will always throw "Seek out of range" in bsetpos() */
+ break;
case SEEK_END:
- bflush(f);
- if (!f->seek || !f->seek(f, pos, SEEK_END))
- die("bseek: stream not seekable");
+ if (pos > 0)
+ bthrow(f, "seek", "Seek out of range");
+ do_seek(f, pos, SEEK_END);
break;
default:
die("bseek: invalid whence=%d", whence);
{
if (f->bptr < f->bstop)
return *f->bptr++;
- if (!f->refill(f))
+ if (!brefill(f, 0))
return -1;
return *f->bptr++;
}
{
if (f->bptr < f->bstop)
return *f->bptr;
- if (!f->refill(f))
+ if (!brefill(f, 0))
return -1;
return *f->bptr;
}
-void bputc_slow(struct fastbuf *f, uns c)
+int beof_slow(struct fastbuf *f)
+{
+ return f->bptr >= f->bstop && !brefill(f, 1);
+}
+
+void bputc_slow(struct fastbuf *f, uint c)
{
if (f->bptr >= f->bufend)
- f->spout(f);
+ bspout(f);
*f->bptr++ = c;
}
-uns bread_slow(struct fastbuf *f, void *b, uns l, uns check)
+uint bread_slow(struct fastbuf *f, void *b, uint l, uint check)
{
- uns total = 0;
+ uint total = 0;
while (l)
{
- uns k = f->bstop - f->bptr;
+ uint k = f->bstop - f->bptr;
if (!k)
{
- f->refill(f);
+ brefill(f, check);
k = f->bstop - f->bptr;
if (!k)
break;
total += k;
}
if (check && total && l)
- die("breadb: short read");
+ bthrow(f, "eof", "breadb: short read");
return total;
}
-void bwrite_slow(struct fastbuf *f, const void *b, uns l)
+void bwrite_slow(struct fastbuf *f, const void *b, uint l)
{
while (l)
{
- uns k = f->bufend - f->bptr;
+ uint k = f->bufend - f->bptr;
if (!k)
{
- f->spout(f);
+ bspout(f);
k = f->bufend - f->bptr;
}
if (k > l)
}
}
-void
-bbcopy_slow(struct fastbuf *f, struct fastbuf *t, uns l)
+void bbcopy_slow(struct fastbuf *f, struct fastbuf *t, uint l)
{
while (l)
{
byte *fptr, *tptr;
- uns favail, tavail, n;
+ uint favail, tavail, n;
favail = bdirect_read_prepare(f, &fptr);
if (!favail)
{
if (l == ~0U)
return;
- die("bbcopy: source exhausted");
+ bthrow(f, "eof", "bbcopy: source exhausted");
}
tavail = bdirect_write_prepare(t, &tptr);
n = MIN(l, favail);
}
}
-int
-bconfig(struct fastbuf *f, uns item, int value)
+int bconfig(struct fastbuf *f, uint item, int value)
{
- return f->config ? f->config(f, item, value) : -1;
+ return (f->config && !(f->flags & FB_DEAD)) ? f->config(f, item, value) : -1;
}
-void
-brewind(struct fastbuf *f)
+void brewind(struct fastbuf *f)
{
bflush(f);
bsetpos(f, 0);
}
-int
-bskip_slow(struct fastbuf *f, uns len)
+int bskip_slow(struct fastbuf *f, uint len)
{
while (len)
{
byte *buf;
- uns l = bdirect_read_prepare(f, &buf);
+ uint l = bdirect_read_prepare(f, &buf);
if (!l)
return 0;
l = MIN(l, len);
return 1;
}
-ucw_off_t
-bfilesize(struct fastbuf *f)
+ucw_off_t bfilesize(struct fastbuf *f)
{
if (!f)
return 0;
+ if (!f->seek)
+ return -1;
ucw_off_t pos = btell(f);
bflush(f);
if (!f->seek(f, 0, SEEK_END))
bsetpos(f, pos);
return len;
}
+
+/* Resources */
+
+static void fb_res_detach(struct resource *r)
+{
+ struct fastbuf *f = r->priv;
+ f->res = NULL;
+}
+
+static void fb_res_free(struct resource *r)
+{
+ struct fastbuf *f = r->priv;
+ f->res = NULL;
+ bclose(f);
+}
+
+static void fb_res_dump(struct resource *r, uint indent UNUSED)
+{
+ struct fastbuf *f = r->priv;
+ printf(" name=%s\n", f->name);
+}
+
+static const struct res_class fb_res_class = {
+ .name = "fastbuf",
+ .detach = fb_res_detach,
+ .dump = fb_res_dump,
+ .free = fb_res_free,
+};
+
+struct fastbuf *fb_tie(struct fastbuf *f)
+{
+ f->res = res_new(&fb_res_class, f);
+ return f;
+}