]> mj.ucw.cz Git - libucw.git/blobdiff - ucw/fastbuf.c
Resources: Allow res_free(NULL) and res_detach(NULL)
[libucw.git] / ucw / fastbuf.c
index fb2cc49f88513f73e16d4035a0dc26c417baf5d2..4068aab89adef2e30868530da34ea70686d5e1b5 100644 (file)
@@ -1,16 +1,19 @@
 /*
  *     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.
  */
 
+#undef LOCAL_DEBUG
+
 #include "ucw/lib.h"
 #include "ucw/fastbuf.h"
 #include "ucw/respool.h"
 #include "ucw/trans.h"
+#include "ucw/stkstring.h"
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -19,70 +22,104 @@ void bclose(struct fastbuf *f)
 {
   if (f)
     {
-      if (!(f->flags & FB_DEAD))
-        bflush(f);
-      if (f->close)
-       f->close(f);
+      bflush(f);
       if (f->res)
-       res_drop(f->res);
+       {
+         res_drop(f->res);
+         f->res = NULL;
+       }
+      DBG("FB: closing", f);
+      if (f->close)
+       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, ...)
 {
-  ASSERT(!(f->flags & FB_DEAD));
-  f->flags |= FB_DEAD;
+  ASSERT(!(f->flags & FB_DEAD)); /* Only one bthrow() is allowed before bclose() */
+  DBG("FB: throwing %s", id);
   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(id, f, fmt, args);
 }
 
 int brefill(struct fastbuf *f, int allow_eof)
 {
-  ASSERT(f->bptr >= f->bstop);
+  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, "fb.read", "Stream not readable");
   if (f->refill(f))
     {
-      ASSERT(f->bptr < f->bstop);
+      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, "fb.eof", "Unexpected EOF");
-      ASSERT(f->bptr == f->bstop);
       return 0;
     }
 }
 
-void bspout(struct fastbuf *f)
+static void do_spout(struct fastbuf *f)
 {
-  ASSERT(f->bptr > f->bstop || f->bptr >= f->bufend);
+  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, "fb.write", "Stream not writeable");
   f->spout(f);
-  ASSERT(f->bptr < f->bufend);
+  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)
-    bspout(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");
+}
+
+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, "fb.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);
 }
 
 inline 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))
-       bthrow(f, "fb.seek", "Stream not seekable");
+      if (pos < 0)
+       bthrow(f, "fb.seek", "Seek out of range");
+      do_seek(f, pos, SEEK_SET);
     }
 }
 
@@ -91,13 +128,15 @@ void bseek(struct fastbuf *f, ucw_off_t pos, int whence)
   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))
-       bthrow(f, "fb.seek", "Stream not seekable");
+      if (pos > 0)
+       bthrow(f, "fb.seek", "Seek out of range");
+      do_seek(f, pos, SEEK_END);
       break;
     default:
       die("bseek: invalid whence=%d", whence);
@@ -181,8 +220,7 @@ void bwrite_slow(struct fastbuf *f, const void *b, uns l)
     }
 }
 
-void
-bbcopy_slow(struct fastbuf *f, struct fastbuf *t, uns l)
+void bbcopy_slow(struct fastbuf *f, struct fastbuf *t, uns l)
 {
   while (l)
     {
@@ -194,7 +232,7 @@ bbcopy_slow(struct fastbuf *f, struct fastbuf *t, uns l)
        {
          if (l == ~0U)
            return;
-         die("bbcopy: source exhausted");
+         bthrow(f, "fb.read", "bbcopy: source exhausted");
        }
       tavail = bdirect_write_prepare(t, &tptr);
       n = MIN(l, favail);
@@ -207,21 +245,18 @@ bbcopy_slow(struct fastbuf *f, struct fastbuf *t, uns l)
     }
 }
 
-int
-bconfig(struct fastbuf *f, uns item, int value)
+int bconfig(struct fastbuf *f, uns 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, uns len)
 {
   while (len)
     {
@@ -236,11 +271,12 @@ bskip_slow(struct fastbuf *f, uns 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))
@@ -252,26 +288,23 @@ bfilesize(struct fastbuf *f)
 
 /* Resources */
 
-static void
-fb_res_detach(struct resource *r)
+static void fb_res_detach(struct resource *r)
 {
   struct fastbuf *f = r->priv;
   f->res = NULL;
 }
 
-static void
-fb_res_free(struct resource *r)
+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)
+static void fb_res_dump(struct resource *r, uns indent UNUSED)
 {
   struct fastbuf *f = r->priv;
-  printf(" name=%s", f->name);
+  printf(" name=%s\n", f->name);
 }
 
 static const struct res_class fb_res_class = {
@@ -281,8 +314,8 @@ static const struct res_class fb_res_class = {
   .free = fb_res_free,
 };
 
-void
-fb_tie(struct fastbuf *f)
+struct fastbuf *fb_tie(struct fastbuf *f)
 {
   f->res = res_new(&fb_res_class, f);
+  return f;
 }