]> mj.ucw.cz Git - libucw.git/blob - ucw/fastbuf.c
7bec4bfd4b08b8d9a20b53f249cd2fbb8d700f03
[libucw.git] / ucw / fastbuf.c
1 /*
2  *      UCW Library -- Fast Buffered I/O
3  *
4  *      (c) 1997--2011 Martin Mares <mj@ucw.cz>
5  *
6  *      This software may be freely distributed and used according to the terms
7  *      of the GNU Lesser General Public License.
8  */
9
10 #undef LOCAL_DEBUG
11
12 #include "ucw/lib.h"
13 #include "ucw/fastbuf.h"
14 #include "ucw/respool.h"
15 #include "ucw/trans.h"
16 #include "ucw/stkstring.h"
17
18 #include <stdio.h>
19 #include <stdlib.h>
20
21 void bclose(struct fastbuf *f)
22 {
23   if (f)
24     {
25       bflush(f);
26       res_detach(f->res);
27       DBG("FB: closing", f);
28       if (f->close)
29         f->close(f); /* Should always free all internal resources, even if it throws an exception */
30     }
31 }
32
33 void NONRET bthrow(struct fastbuf *f, const char *id, const char *fmt, ...)
34 {
35   ASSERT(!(f->flags & FB_DEAD)); /* Only one bthrow() is allowed before bclose() */
36   DBG("FB: throwing %s", id);
37   va_list args;
38   va_start(args, fmt);
39   if (!f->res)
40     die("Fastbuf %s error: %s", f->name ? : "<fb>", stk_vprintf(fmt, args));
41   f->flags |= FB_DEAD;
42   f->bptr = f->bstop = f->bufend; /* Reset the buffer to guard consecutive seek/read/write */
43   trans_vthrow(id, f, fmt, args);
44 }
45
46 int brefill(struct fastbuf *f, int allow_eof)
47 {
48   DBG("FB: refill");
49   ASSERT(!(f->flags & FB_DEAD) && f->buffer <= f->bstop && f->bstop <= f->bptr && f->bptr <= f->bufend);
50   if (!f->refill)
51     bthrow(f, "fb.read", "Stream not readable");
52   if (f->refill(f))
53     {
54       ASSERT(f->buffer <= f->bptr && f->bptr < f->bstop && f->bstop <= f->bufend);
55       return 1;
56     }
57   else
58     {
59       ASSERT(f->buffer <= f->bptr && f->bptr == f->bstop && f->bstop <= f->bufend);
60       if (!allow_eof && (f->flags & FB_DIE_ON_EOF))
61         bthrow(f, "fb.eof", "Unexpected EOF");
62       return 0;
63     }
64 }
65
66 static void do_spout(struct fastbuf *f)
67 {
68   DBG("FB: spout");
69   ASSERT(!(f->flags & FB_DEAD) && f->buffer <= f->bstop && f->bstop <= f->bptr && f->bptr <= f->bufend); /* Check write mode possibly with unflushed data */
70   if (!f->spout)
71     bthrow(f, "fb.write", "Stream not writeable");
72   f->spout(f);
73   ASSERT(f->buffer <= f->bstop && f->bstop <= f->bptr && f->bptr <= f->bufend);
74 }
75
76 void bspout(struct fastbuf *f)
77 {
78   do_spout(f);
79   if (f->bstop == f->bufend)
80     {
81       do_spout(f);
82       ASSERT(f->bstop < f->bufend);
83     }
84 }
85
86 void bflush(struct fastbuf *f)
87 {
88   if (f->bptr > f->bstop)
89     do_spout(f);
90   else
91     f->bptr = f->bstop; /* XXX: Skip the rest of the reading buffer ==> it breaks the position of the FE cursor */
92   DBG("FB: flushed");
93 }
94
95 static void do_seek(struct fastbuf *f, ucw_off_t pos, int whence)
96 {
97   bflush(f);
98   DBG("FB: seeking to pos=%lld whence=%d %p %p %p %p", (long long)pos, whence, f->buffer, f->bstop, f->bptr, f->bufend);
99   if (!f->seek || !f->seek(f, pos, whence))
100     bthrow(f, "fb.seek", "Stream not seekable");
101   DBG("FB: seeked %p %p %p %p", f->buffer, f->bstop, f->bptr, f->bufend);
102   ASSERT(f->buffer <= f->bstop && f->bstop <= f->bptr && f->bptr <= f->bufend);
103   if (whence == SEEK_SET)
104     ASSERT(pos == btell(f));
105   else
106     ASSERT(btell(f) >= 0);
107 }
108
109 inline void bsetpos(struct fastbuf *f, ucw_off_t pos)
110 {
111   /* We can optimize seeks only when reading */
112   if (f->bptr < f->bstop && pos <= f->pos && pos >= f->pos - (f->bstop - f->buffer)) /* If bptr == bstop, then [buffer, bstop] may be undefined */
113     f->bptr = f->bstop + (pos - f->pos);
114   else if (pos != btell(f))
115     {
116       if (pos < 0)
117         bthrow(f, "fb.seek", "Seek out of range");
118       do_seek(f, pos, SEEK_SET);
119     }
120 }
121
122 void bseek(struct fastbuf *f, ucw_off_t pos, int whence)
123 {
124   switch (whence)
125     {
126     case SEEK_SET:
127       bsetpos(f, pos);
128       break;
129     case SEEK_CUR:
130       bsetpos(f, btell(f) + pos); /* btell() is non-negative, so an overflow will always throw "Seek out of range" in bsetpos() */
131       break;
132     case SEEK_END:
133       if (pos > 0)
134         bthrow(f, "fb.seek", "Seek out of range");
135       do_seek(f, pos, SEEK_END);
136       break;
137     default:
138       die("bseek: invalid whence=%d", whence);
139     }
140 }
141
142 int bgetc_slow(struct fastbuf *f)
143 {
144   if (f->bptr < f->bstop)
145     return *f->bptr++;
146   if (!brefill(f, 0))
147     return -1;
148   return *f->bptr++;
149 }
150
151 int bpeekc_slow(struct fastbuf *f)
152 {
153   if (f->bptr < f->bstop)
154     return *f->bptr;
155   if (!brefill(f, 0))
156     return -1;
157   return *f->bptr;
158 }
159
160 int beof_slow(struct fastbuf *f)
161 {
162   return f->bptr >= f->bstop && !brefill(f, 1);
163 }
164
165 void bputc_slow(struct fastbuf *f, uns c)
166 {
167   if (f->bptr >= f->bufend)
168     bspout(f);
169   *f->bptr++ = c;
170 }
171
172 uns bread_slow(struct fastbuf *f, void *b, uns l, uns check)
173 {
174   uns total = 0;
175   while (l)
176     {
177       uns k = f->bstop - f->bptr;
178
179       if (!k)
180         {
181           brefill(f, check);
182           k = f->bstop - f->bptr;
183           if (!k)
184             break;
185         }
186       if (k > l)
187         k = l;
188       memcpy(b, f->bptr, k);
189       f->bptr += k;
190       b = (byte *)b + k;
191       l -= k;
192       total += k;
193     }
194   if (check && total && l)
195     bthrow(f, "fb.read", "breadb: short read");
196   return total;
197 }
198
199 void bwrite_slow(struct fastbuf *f, const void *b, uns l)
200 {
201   while (l)
202     {
203       uns k = f->bufend - f->bptr;
204
205       if (!k)
206         {
207           bspout(f);
208           k = f->bufend - f->bptr;
209         }
210       if (k > l)
211         k = l;
212       memcpy(f->bptr, b, k);
213       f->bptr += k;
214       b = (byte *)b + k;
215       l -= k;
216     }
217 }
218
219 void bbcopy_slow(struct fastbuf *f, struct fastbuf *t, uns l)
220 {
221   while (l)
222     {
223       byte *fptr, *tptr;
224       uns favail, tavail, n;
225
226       favail = bdirect_read_prepare(f, &fptr);
227       if (!favail)
228         {
229           if (l == ~0U)
230             return;
231           bthrow(f, "fb.read", "bbcopy: source exhausted");
232         }
233       tavail = bdirect_write_prepare(t, &tptr);
234       n = MIN(l, favail);
235       n = MIN(n, tavail);
236       memcpy(tptr, fptr, n);
237       bdirect_read_commit(f, fptr + n);
238       bdirect_write_commit(t, tptr + n);
239       if (l != ~0U)
240         l -= n;
241     }
242 }
243
244 int bconfig(struct fastbuf *f, uns item, int value)
245 {
246   return (f->config && !(f->flags & FB_DEAD)) ? f->config(f, item, value) : -1;
247 }
248
249 void brewind(struct fastbuf *f)
250 {
251   bflush(f);
252   bsetpos(f, 0);
253 }
254
255 int bskip_slow(struct fastbuf *f, uns len)
256 {
257   while (len)
258     {
259       byte *buf;
260       uns l = bdirect_read_prepare(f, &buf);
261       if (!l)
262         return 0;
263       l = MIN(l, len);
264       bdirect_read_commit(f, buf+l);
265       len -= l;
266     }
267   return 1;
268 }
269
270 ucw_off_t bfilesize(struct fastbuf *f)
271 {
272   if (!f)
273     return 0;
274   if (!f->seek)
275     return -1;
276   ucw_off_t pos = btell(f);
277   bflush(f);
278   if (!f->seek(f, 0, SEEK_END))
279     return -1;
280   ucw_off_t len = btell(f);
281   bsetpos(f, pos);
282   return len;
283 }
284
285 /* Resources */
286
287 static void fb_res_detach(struct resource *r)
288 {
289   struct fastbuf *f = r->priv;
290   f->res = NULL;
291 }
292
293 static void fb_res_free(struct resource *r)
294 {
295   struct fastbuf *f = r->priv;
296   f->res = NULL;
297   bclose(f);
298 }
299
300 static void fb_res_dump(struct resource *r, uns indent UNUSED)
301 {
302   struct fastbuf *f = r->priv;
303   printf(" name=%s\n", f->name);
304 }
305
306 static const struct res_class fb_res_class = {
307   .name = "fastbuf",
308   .detach = fb_res_detach,
309   .dump = fb_res_dump,
310   .free = fb_res_free,
311 };
312
313 struct fastbuf *fb_tie(struct fastbuf *f)
314 {
315   f->res = res_new(&fb_res_class, f);
316   return f;
317 }