/* $Id: cdb_init.c,v 1.12 2008-11-06 18:07:04 mjt Exp $ * cdb_init, cdb_free and cdb_read routines * * This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru. * Public domain. */ #include "cdb.h" void cdb_pack(unsigned num, unsigned char buf[4]) { buf[0] = num & 255; num >>= 8; buf[1] = num & 255; num >>= 8; buf[2] = num & 255; buf[3] = num >> 8; } int cdb_make_start(struct cdb_make *cdbmp, int fd) { memset (cdbmp, 0, sizeof(*cdbmp)); cdbmp->cdb_fd = fd; cdbmp->cdb_dpos = 2048; cdbmp->cdb_bpos = cdbmp->cdb_buf + 2048; return 0; } int _cdb_make_fullwrite(int fd, const unsigned char *buf, unsigned len) { while (len) { int l = write (fd, buf, len); if (l > 0) { len -= l; buf += l; } else if (l < 0 && errno != EINTR) return -1; } return 0; } int _cdb_make_flush(struct cdb_make *cdbmp) { unsigned len = cdbmp->cdb_bpos - cdbmp->cdb_buf; if (len) { if (_cdb_make_fullwrite (cdbmp->cdb_fd, cdbmp->cdb_buf, len) < 0) return -1; cdbmp->cdb_bpos = cdbmp->cdb_buf; } return 0; } int _cdb_make_write(struct cdb_make *cdbmp, const unsigned char *ptr, unsigned len) { unsigned l = sizeof(cdbmp->cdb_buf) - (cdbmp->cdb_bpos - cdbmp->cdb_buf); cdbmp->cdb_dpos += len; if (len > l) { memcpy (cdbmp->cdb_bpos, ptr, l); cdbmp->cdb_bpos += l; if (_cdb_make_flush (cdbmp) < 0) return -1; ptr += l; len -= l; l = len / sizeof(cdbmp->cdb_buf); if (l) { l *= sizeof(cdbmp->cdb_buf); if (_cdb_make_fullwrite (cdbmp->cdb_fd, ptr, l) < 0) return -1; ptr += l; len -= l; } } if (len) { memcpy (cdbmp->cdb_bpos, ptr, len); cdbmp->cdb_bpos += len; } return 0; } static int cdb_make_finish_internal(struct cdb_make *cdbmp) { unsigned hcnt[256]; /* hash table counts */ unsigned hpos[256]; /* hash table positions */ struct cdb_rec *htab; unsigned char *p; struct cdb_rl *rl; unsigned hsize; unsigned t, i; if (((0xffffffff - cdbmp->cdb_dpos) >> 3) < cdbmp->cdb_rcnt) return errno = ENOMEM, -1; /* count htab sizes and reorder reclists */ hsize = 0; for (t = 0; t < 256; ++t) { struct cdb_rl *rlt = NULL; i = 0; rl = cdbmp->cdb_rec[t]; while (rl) { struct cdb_rl *rln = rl->next; rl->next = rlt; rlt = rl; i += rl->cnt; rl = rln; } cdbmp->cdb_rec[t] = rlt; if (hsize < (hcnt[t] = i << 1)) hsize = hcnt[t]; } /* allocate memory to hold max htable */ htab = (struct cdb_rec*) malloc ((hsize + 2) * sizeof(struct cdb_rec)); if (!htab) return errno = ENOENT, -1; p = (unsigned char *) htab; htab += 2; /* build hash tables */ for (t = 0; t < 256; ++t) { unsigned len, hi; hpos[t] = cdbmp->cdb_dpos; if ((len = hcnt[t]) == 0) continue; for (i = 0; i < len; ++i) htab[i].hval = htab[i].rpos = 0; for (rl = cdbmp->cdb_rec[t]; rl; rl = rl->next) for (i = 0; i < rl->cnt; ++i) { hi = (rl->rec[i].hval >> 8) % len; while (htab[hi].rpos) if (++hi == len) hi = 0; htab[hi] = rl->rec[i]; } for (i = 0; i < len; ++i) { cdb_pack (htab[i].hval, p + (i << 3)); cdb_pack (htab[i].rpos, p + (i << 3) + 4); } if (_cdb_make_write (cdbmp, p, len << 3) < 0) { free (p); return -1; } } free (p); if (_cdb_make_flush (cdbmp) < 0) return -1; p = cdbmp->cdb_buf; for (t = 0; t < 256; ++t) { cdb_pack (hpos[t], p + (t << 3)); cdb_pack (hcnt[t], p + (t << 3) + 4); } if (lseek (cdbmp->cdb_fd, 0, 0) != 0 || _cdb_make_fullwrite (cdbmp->cdb_fd, p, 2048) != 0) return -1; return 0; } static void cdb_make_free(struct cdb_make *cdbmp) { unsigned t; for (t = 0; t < 256; ++t) { struct cdb_rl *rl = cdbmp->cdb_rec[t]; while (rl) { struct cdb_rl *tm = rl; rl = rl->next; free (tm); } } } int cdb_make_finish(struct cdb_make *cdbmp) { int r = cdb_make_finish_internal (cdbmp); cdb_make_free (cdbmp); return r; } int _cdb_make_add(struct cdb_make *cdbmp, unsigned hval, const void *key, unsigned klen, const void *val, unsigned vlen) { unsigned char rlen[8]; struct cdb_rl *rl; unsigned i; if (klen > 0xffffffff - (cdbmp->cdb_dpos + 8) || vlen > 0xffffffff - (cdbmp->cdb_dpos + klen + 8)) return errno = ENOMEM, -1; i = hval & 255; rl = cdbmp->cdb_rec[i]; if (!rl || rl->cnt >= sizeof(rl->rec) / sizeof(rl->rec[0])) { rl = (struct cdb_rl*) malloc (sizeof(struct cdb_rl)); if (!rl) return errno = ENOMEM, -1; rl->cnt = 0; rl->next = cdbmp->cdb_rec[i]; cdbmp->cdb_rec[i] = rl; } i = rl->cnt++; rl->rec[i].hval = hval; rl->rec[i].rpos = cdbmp->cdb_dpos; ++cdbmp->cdb_rcnt; cdb_pack (klen, rlen); cdb_pack (vlen, rlen + 4); if (_cdb_make_write (cdbmp, rlen, 8) < 0 || _cdb_make_write (cdbmp, key, klen) < 0 || _cdb_make_write (cdbmp, val, vlen) < 0) return -1; return 0; } int cdb_make_add(struct cdb_make *cdbmp, const void *key, unsigned klen, const void *val, unsigned vlen) { return _cdb_make_add (cdbmp, cdb_hash (key, klen), key, klen, val, vlen); } static void fixup_rpos(struct cdb_make *cdbmp, unsigned rpos, unsigned rlen) { unsigned i; struct cdb_rl *rl; register struct cdb_rec *rp, *rs; for (i = 0; i < 256; ++i) { for (rl = cdbmp->cdb_rec[i]; rl; rl = rl->next) for (rs = rl->rec, rp = rs + rl->cnt; --rp >= rs;) if (rp->rpos <= rpos) goto nexthash; else rp->rpos -= rlen; nexthash: ; } } static int remove_record(struct cdb_make *cdbmp, unsigned rpos, unsigned rlen) { unsigned pos, len; int r, fd; len = cdbmp->cdb_dpos - rpos - rlen; cdbmp->cdb_dpos -= rlen; if (!len) return 0; /* it was the last record, nothing to do */ pos = rpos; fd = cdbmp->cdb_fd; do { r = len > sizeof(cdbmp->cdb_buf) ? sizeof(cdbmp->cdb_buf) : len; if (lseek (fd, pos + rlen, SEEK_SET) < 0 || (r = read (fd, cdbmp->cdb_buf, r)) <= 0) return -1; if (lseek (fd, pos, SEEK_SET) < 0 || _cdb_make_fullwrite (fd, cdbmp->cdb_buf, r) < 0) return -1; pos += r; len -= r; } while (len); g_assert (cdbmp->cdb_dpos == pos); fixup_rpos (cdbmp, rpos, rlen); return 0; } static int zerofill_record(struct cdb_make *cdbmp, unsigned rpos, unsigned rlen) { if (rpos + rlen == cdbmp->cdb_dpos) { cdbmp->cdb_dpos = rpos; return 0; } if (lseek (cdbmp->cdb_fd, rpos, SEEK_SET) < 0) return -1; memset (cdbmp->cdb_buf, 0, sizeof(cdbmp->cdb_buf)); cdb_pack (rlen - 8, cdbmp->cdb_buf + 4); for (;;) { rpos = rlen > sizeof(cdbmp->cdb_buf) ? sizeof(cdbmp->cdb_buf) : rlen; if (_cdb_make_fullwrite (cdbmp->cdb_fd, cdbmp->cdb_buf, rpos) < 0) return -1; rlen -= rpos; if (!rlen) return 0; memset (cdbmp->cdb_buf + 4, 0, 4); } } /* return: 0 = not found, 1 = error, or record length */ static unsigned match(struct cdb_make *cdbmp, unsigned pos, const char *key, unsigned klen) { int len; unsigned rlen; if (lseek (cdbmp->cdb_fd, pos, SEEK_SET) < 0) return 1; if (read (cdbmp->cdb_fd, cdbmp->cdb_buf, 8) != 8) return 1; if (cdb_unpack (cdbmp->cdb_buf) != klen) return 0; /* record length; check its validity */ rlen = cdb_unpack (cdbmp->cdb_buf + 4); if (rlen > cdbmp->cdb_dpos - pos - klen - 8) return errno = EPROTO, 1; /* someone changed our file? */ rlen += klen + 8; while (klen) { len = klen > sizeof(cdbmp->cdb_buf) ? sizeof(cdbmp->cdb_buf) : klen; len = read (cdbmp->cdb_fd, cdbmp->cdb_buf, len); if (len <= 0) return 1; if (memcmp (cdbmp->cdb_buf, key, len) != 0) return 0; key += len; klen -= len; } return rlen; } static int findrec(struct cdb_make *cdbmp, const void *key, unsigned klen, unsigned hval, enum cdb_put_mode mode) { struct cdb_rl *rl; struct cdb_rec *rp, *rs; unsigned r; int seeked = 0; int ret = 0; for (rl = cdbmp->cdb_rec[hval & 255]; rl; rl = rl->next) for (rs = rl->rec, rp = rs + rl->cnt; --rp >= rs;) { if (rp->hval != hval) continue; /*XXX this explicit flush may be unnecessary having * smarter match() that looks into cdb_buf too, but * most of a time here spent in finding hash values * (above), not keys */ if (!seeked && _cdb_make_flush (cdbmp) < 0) return -1; seeked = 1; r = match (cdbmp, rp->rpos, key, klen); if (!r) continue; if (r == 1) return -1; ret = 1; switch (mode) { case CDB_FIND_REMOVE: if (remove_record (cdbmp, rp->rpos, r) < 0) return -1; break; case CDB_FIND_FILL0: if (zerofill_record (cdbmp, rp->rpos, r) < 0) return -1; break; default: goto finish; } memmove (rp, rp + 1, (rs + rl->cnt - 1 - rp) * sizeof(*rp)); --rl->cnt; --cdbmp->cdb_rcnt; } finish: if (seeked && lseek (cdbmp->cdb_fd, cdbmp->cdb_dpos, SEEK_SET) < 0) return -1; return ret; } int cdb_make_find(struct cdb_make *cdbmp, const void *key, unsigned klen, enum cdb_put_mode mode) { return findrec (cdbmp, key, klen, cdb_hash (key, klen), mode); } int cdb_make_exists(struct cdb_make *cdbmp, const void *key, unsigned klen) { return cdb_make_find (cdbmp, key, klen, CDB_FIND); } int cdb_make_put(struct cdb_make *cdbmp, const void *key, unsigned klen, const void *val, unsigned vlen, enum cdb_put_mode mode) { unsigned hval = cdb_hash (key, klen); int r; switch (mode) { case CDB_PUT_REPLACE: case CDB_PUT_INSERT: case CDB_PUT_WARN: case CDB_PUT_REPLACE0: r = findrec (cdbmp, key, klen, hval, mode); if (r < 0) return -1; if (r && mode == CDB_PUT_INSERT) return errno = EEXIST, 1; break; case CDB_PUT_ADD: r = 0; break; default: return errno = EINVAL, -1; } if (_cdb_make_add (cdbmp, hval, key, klen, val, vlen) < 0) return -1; return r; } unsigned cdb_unpack(const unsigned char buf[4]) { unsigned n = buf[3]; n <<= 8; n |= buf[2]; n <<= 8; n |= buf[1]; n <<= 8; n |= buf[0]; return n; } int cdb_seqnext(unsigned *cptr, struct cdb *cdbp) { unsigned klen, vlen; unsigned pos = *cptr; unsigned dend = cdbp->cdb_dend; const unsigned char *mem = cdbp->cdb_mem; if (pos > dend - 8) return 0; klen = cdb_unpack(mem + pos); vlen = cdb_unpack(mem + pos + 4); pos += 8; if (dend - klen < pos || dend - vlen < pos + klen) return errno = EPROTO, -1; cdbp->cdb_kpos = pos; cdbp->cdb_klen = klen; cdbp->cdb_vpos = pos + klen; cdbp->cdb_vlen = vlen; *cptr = pos + klen + vlen; return 1; } /* read a chunk from file, ignoring interrupts (EINTR) */ int cdb_bread(int fd, void *buf, int len) { int l; while(len > 0) { do l = read(fd, buf, len); while(l < 0 && errno == EINTR); if (l <= 0) { if (!l) errno = EIO; return -1; } buf = (char*)buf + l; len -= l; } return 0; } /* find a given key in cdb file, seek a file pointer to it's value and place data length to *dlenp. */ int cdb_seek(int fd, const void *key, unsigned klen, unsigned *dlenp) { unsigned htstart; /* hash table start position */ unsigned htsize; /* number of elements in a hash table */ unsigned httodo; /* hash table elements left to look */ unsigned hti; /* hash table index */ unsigned pos; /* position in a file */ unsigned hval; /* key's hash value */ unsigned char rbuf[64]; /* read buffer */ int needseek = 1; /* if we should seek to a hash slot */ hval = cdb_hash(key, klen); pos = (hval & 0xff) << 3; /* position in TOC */ /* read the hash table parameters */ if (lseek(fd, pos, SEEK_SET) < 0 || cdb_bread(fd, rbuf, 8) < 0) return -1; if ((htsize = cdb_unpack(rbuf + 4)) == 0) return 0; hti = (hval >> 8) % htsize; /* start position in hash table */ httodo = htsize; htstart = cdb_unpack(rbuf); for(;;) { if (needseek && lseek(fd, htstart + (hti << 3), SEEK_SET) < 0) return -1; if (cdb_bread(fd, rbuf, 8) < 0) return -1; if ((pos = cdb_unpack(rbuf + 4)) == 0) /* not found */ return 0; if (cdb_unpack(rbuf) != hval) /* hash value not matched */ needseek = 0; else { /* hash value matched */ if (lseek(fd, pos, SEEK_SET) < 0 || cdb_bread(fd, rbuf, 8) < 0) return -1; if (cdb_unpack(rbuf) == klen) { /* key length matches */ /* read the key from file and compare with wanted */ unsigned l = klen, c; const char *k = (const char*)key; if (dlenp) *dlenp = cdb_unpack(rbuf + 4); /* save value length */ for(;;) { if (!l) /* the whole key read and matches, return */ return 1; c = l > sizeof(rbuf) ? sizeof(rbuf) : l; if (cdb_bread(fd, rbuf, c) < 0) return -1; if (memcmp(rbuf, k, c) != 0) /* no, it differs, stop here */ break; k += c; l -= c; } } needseek = 1; /* we're looked to other place, should seek back */ } if (!--httodo) return 0; if (++hti == htsize) { hti = 0; needseek = 1; } } }