/* * UCW Library -- Fast Buffered I/O on Files * * (c) 1997--2007 Martin Mares * (c) 2007 Pavel Charvat * * This software may be freely distributed and used according to the terms * of the GNU Lesser General Public License. */ #include #include #include #include #include #include #include struct fb_file { struct fastbuf fb; int fd; /* File descriptor */ int is_temp_file; int keep_back_buf; /* Optimize for backwards reading */ ucw_off_t wpos; /* Real file position */ uint wlen; /* Window size */ }; #define FB_FILE(f) ((struct fb_file *)(f)) #define FB_BUFFER(f) (byte *)(FB_FILE(f) + 1) static int bfd_refill(struct fastbuf *f) { struct fb_file *F = FB_FILE(f); byte *read_ptr = (f->buffer = FB_BUFFER(f)); uint blen = f->bufend - f->buffer, back = F->keep_back_buf ? blen >> 2 : 0, read_len = blen; /* Forward or no seek */ if (F->wpos <= f->pos) { ucw_off_t diff = f->pos - F->wpos; /* Formula for long forward seeks (prefer lseek()) */ if (diff > ((ucw_off_t)blen << 2)) { long_seek: f->bptr = f->buffer + back; f->bstop = f->buffer + blen; goto seek; } /* Short forward seek (prefer read() to skip data )*/ else if ((uint)diff >= back) { uint skip = diff - back; F->wpos += skip; while (skip) { int l = read(F->fd, f->buffer, MIN(skip, blen)); if (unlikely(l <= 0)) if (l < 0) bthrow(f, "read", "Error reading %s: %m", f->name); else { F->wpos -= skip; goto eof; } skip -= l; } } /* Reuse part of the previous window and append new data (also F->wpos == f->pos) */ else { uint keep = back - (uint)diff; if (keep >= F->wlen) back = diff + (keep = F->wlen); else memmove(f->buffer, f->buffer + F->wlen - keep, keep); read_len -= keep; read_ptr += keep; } f->bptr = f->buffer + back; f->bstop = f->buffer + blen; } /* Backwards seek */ else { ucw_off_t diff = F->wpos - f->pos; /* Formula for long backwards seeks (keep smaller backbuffer than for shorter seeks ) */ if (diff > ((ucw_off_t)blen << 1)) { if ((ucw_off_t)back > f->pos) back = f->pos; goto long_seek; } /* Seek into previous window (do nothing... for example brewind) */ else if ((uint)diff <= F->wlen) { f->bstop = f->buffer + F->wlen; f->bptr = f->bstop - diff; f->pos = F->wpos; return 1; } back *= 3; if ((ucw_off_t)back > f->pos) back = f->pos; f->bptr = f->buffer + back; read_len = blen; f->bstop = f->buffer + read_len; /* Reuse part of previous window */ if (F->wlen && read_len <= back + diff && read_len > back + diff - F->wlen) { uint keep = read_len + F->wlen - back - diff; memmove(f->buffer + read_len - keep, f->buffer, keep); } seek: /* Do lseek() */ F->wpos = f->pos + (f->buffer - f->bptr); if (ucw_seek(F->fd, F->wpos, SEEK_SET) < 0) bthrow(f, "read", "Error seeking %s: %m", f->name); } /* Read (part of) buffer */ do { int l = read(F->fd, read_ptr, read_len); if (unlikely(l < 0)) bthrow(f, "read", "Error reading %s: %m", f->name); if (!l) if (unlikely(read_ptr < f->bptr)) goto eof; else break; /* Incomplete read because of EOF */ read_ptr += l; read_len -= l; F->wpos += l; } while (read_ptr <= f->bptr); if (read_len) f->bstop = read_ptr; f->pos += f->bstop - f->bptr; F->wlen = f->bstop - f->buffer; return f->bstop - f->bptr; eof: /* Seeked behind EOF */ f->bptr = f->bstop = f->buffer; F->wlen = 0; return 0; } static void bfd_spout(struct fastbuf *f) { /* Do delayed lseek() if needed */ if (FB_FILE(f)->wpos != f->pos && ucw_seek(FB_FILE(f)->fd, f->pos, SEEK_SET) < 0) bthrow(f, "write", "Error seeking %s: %m", f->name); int l = f->bptr - f->buffer; byte *c = f->buffer; /* Write the buffer */ FB_FILE(f)->wpos = (f->pos += l); FB_FILE(f)->wlen = 0; while (l) { int z = write(FB_FILE(f)->fd, c, l); if (z <= 0) bthrow(f, "write", "Error writing %s: %m", f->name); l -= z; c += z; } f->bptr = f->bstop = f->buffer = FB_BUFFER(f); } static int bfd_seek(struct fastbuf *f, ucw_off_t pos, int whence) { ASSERT(f->bptr == f->bstop); /* Delay the seek for the next refill() or spout() call (if whence != SEEK_END). */ switch (whence) { case SEEK_SET: f->pos = pos; return 1; case SEEK_END: ; ucw_off_t l = ucw_seek(FB_FILE(f)->fd, pos, SEEK_END); if (l < 0) bthrow(f, "seek", "Error seeking %s: %m", f->name); FB_FILE(f)->wpos = f->pos = l; FB_FILE(f)->wlen = 0; return 1; default: ASSERT(0); } } static void bfd_close(struct fastbuf *f) { bclose_file_helper(f, FB_FILE(f)->fd, FB_FILE(f)->is_temp_file); xfree(f); } static int bfd_config(struct fastbuf *f, uint item, int value) { int orig; switch (item) { case BCONFIG_IS_TEMP_FILE: orig = FB_FILE(f)->is_temp_file; FB_FILE(f)->is_temp_file = value; return orig; case BCONFIG_KEEP_BACK_BUF: orig = FB_FILE(f)->keep_back_buf; FB_FILE(f)->keep_back_buf = value; return orig; default: return -1; } } struct fastbuf * bfdopen_internal(int fd, const char *name, uint buflen) { ASSERT(buflen); int namelen = strlen(name) + 1; struct fb_file *F = xmalloc_zero(sizeof(struct fb_file) + buflen + namelen); struct fastbuf *f = &F->fb; bzero(F, sizeof(*F)); f->buffer = (byte *)(F+1); f->bptr = f->bstop = f->buffer; f->bufend = f->buffer + buflen; f->name = f->bufend; memcpy(f->name, name, namelen); F->fd = fd; f->refill = bfd_refill; f->spout = bfd_spout; f->seek = bfd_seek; f->close = bfd_close; f->config = bfd_config; f->can_overwrite_buffer = 2; return f; } void bfilesync(struct fastbuf *b) { bflush(b); if (fsync(FB_FILE(b)->fd) < 0) msg(L_ERROR, "fsync(%s) failed: %m", b->name); } #ifdef TEST int main(void) { struct fastbuf *f, *t; f = bopen_tmp(16); t = bfdopen_shared(1, 13); for (uint i = 0; i < 16; i++) bwrite(f, "", 7); bprintf(t, "%d\n", (int)btell(f)); brewind(f); bbcopy(f, t, ~0U); bprintf(t, "\n%d %d\n", (int)btell(f), (int)btell(t)); bclose(f); bclose(t); return 0; } #endif