204 lines
5 KiB
C
204 lines
5 KiB
C
/* cdb_make_put.c: "advanced" cdb_make_put routine
|
|
*
|
|
* This file is a part of tinycdb package by Michael Tokarev, mjt+cdb@corpit.ru.
|
|
* Public domain.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <assert.h>
|
|
#include "cdb_int.h"
|
|
|
|
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);
|
|
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;
|
|
}
|
|
|