void hxercise(HXFILE *hp, char const *str, FILE *fp) { int maxrec = hxmaxrec(hp), reclim = maxrec * 2, buflim = reclim; char *recspc = malloc(reclim), *rec = recspc + 5; char *bufspc = malloc(buflim), *buf = bufspc + 7; int i, test[2] = {}; # define OK(...) ++test[_ok(fp, __VA_ARGS__)] memset(recspc, fill, reclim); int len1 = hx_load(hp, rec, 0, str); OK(len1 > 0 && len1 <= maxrec, "hx_load(,,0,) returns: %d in 1..%d for '%s'", len1, maxrec, str); OK(memspan(recspc, fill, reclim), "hx_load(,rec,0,) leaves rec unchanged"); int len2 = hx_load(hp, rec, len1, str); OK(len2 == len1 && memspan(recspc, fill, 5) && memspan(rec+len1, fill, reclim-5-len1), "hx_load(,,%d,): %d", len1, len2); char rec2[len1]; memcpy(rec2, rec, len1); OK(!hx_diff(hp, rec2, rec), "diff of copy"); OK(hx_hash(hp, rec2) == hx_hash(hp, rec), "hash of copy"); memset(recspc, fill, reclim); for (i = 1; i < len1; ++i) { len2 = hx_load(hp, rec, i, str); OK(len2 == len1 && memspan(recspc, fill, 5) && !memcmp(rec, buf, i) && memspan(rec+i, fill, reclim-5-i), "hx_load(,,%d,): %d", i ,len2); } memcpy(buf, rec, len1); for (i = len1; i > 0; --i) { if (hx_test(hp, buf, i)) { buf[i] ^= 0x5A; OK(hx_test(hp, buf, i), "retest %d", i); } } memset(bufspc, fill, buflim); len2 = hx_save(hp, rec2, len1, buf, 0); OK(memspan(bufspc, fill, buflim), "hx_save(,,,,0) leaves target unchanged"); if (len2 > buflim - 5) { buflim *= 2; bufspc = realloc(bufspc, buflim); memset(bufspc, fill, buflim); buf = bufspc + 7; } int len3 = hx_save(hp, rec2, len1, buf, len2 + 1); OK(len3 == len2 && memspan(buf, fill, 7) && memspan(buf, fill, buflim-7-len2) && buf[len3-1] && (int)strlen(buf) == len3-1 && strchr(buf, '\t') && !strchr(buf, '\n'), "hx_save(,,%d,,%d): %d", len1, len2+1, len3); DEBUG2("+%d -%d: '%s'", test[0], test[1], str); free(recspc); free(bufspc); }
//--------------|--------------------------------------------- HXRET hxput(HXFILE * hp, char const *recp, int leng) { HXLOCAL loc, *locp = &loc; if (!hp || leng < 0 || !recp || leng > hxmaxrec(hp) || !(hp->mode & HX_UPDATE) || !hp->test) return HXERR_BAD_REQUEST; if (leng && !hx_test(hp, recp, leng)) return HXERR_BAD_RECORD; if (SCANNING(hp) && hx_diff(hp, recp, RECDATA(_hxcurrec(hp)))) return HXERR_BAD_REQUEST; ENTER(locp, hp, recp, 3); _hxlockset(locp, leng ? HIGH_LOCK : HEAD_LOCK); if (IS_MMAP(hp)) _hxremap(locp); int may_find = 1, loops = HX_MAX_CHAIN; int newsize = leng ? leng + sizeof(HXREC) : 0; HXBUF *currp = &locp->buf[0], *prevp = &locp->buf[1]; // If scanning is on an overflow page, and hxdel might // empty the page, hxput after hxnext can't just jump to // the right page, because (prevp) is not loaded, // so deleting currp would hard. _hxload(locp, currp, SCANNING(hp) && (leng || IS_HEAD(hp->buffer.pgno)) ? hp->buffer.pgno : locp->head); while (1) { int pos, hindpos, skip = 0; PAGENO nextpg = currp->next; if (!--loops) LEAVE(locp, HXERR_BAD_FILE); // Search for the key (an old record to be deleted). // If SCANNING: the file is locked, and the matching // record must be there. pos = !may_find ? -1 : !SCANNING(hp) ? _hxfind(locp, currp, locp->hash, recp, &hindpos) : currp->pgno == hp->buffer.pgno ? hp->currpos : -1; if (pos >= 0) { char *oldp = currp->data + pos; COUNT oldsize = RECSIZE(oldp); int delta = newsize - oldsize; locp->ret = RECLENG(oldp); may_find = 0; assert(!currp->delta); currp->delpos = pos; currp->delta = delta; if (!newsize) { // hxdel or remove after inserted previously. _hxremove(currp, pos, oldsize); currp->recs--; if (SCANNING(hp)) hp->recsize = 0; } else if (FITS(hp, currp, delta, 0)) { // replace if (delta) { memmove(oldp + newsize, oldp + oldsize, currp->used - pos - oldsize); currp->used += delta; STSH(leng, oldp + sizeof(PAGENO)); if (SCANNING(hp)) hp->recsize = newsize; DEINDEX(currp); // force indexify } memcpy(oldp + sizeof(HXREC), recp, leng); STAIN(currp); newsize = 0; } else if (SCANNING(hp)) { // At this point we are stuck: if we delete the old copy of // the record, we are committed to inserting the new copy // somewhere else, but that might require changing links // or even growing the file: a NO-NO during a hxnext scan. LEAVE(locp, HXERR_BAD_REQUEST); } else { // Delete old version and continue (insert elsewhere). _hxremove(currp, pos, oldsize); currp->recs--; } } if (currp->used && !IS_HEAD(currp->pgno) && SHRUNK(prevp)) skip = !_hxshift(locp, locp->head, 0, currp, prevp, NULL); // Insert the new record if it fits. if (newsize && FITS(hp, currp, newsize, 1)) { HXREC hdr; STLG(locp->hash, &hdr.hash); STSH(leng, &hdr.leng); _hxappend(currp, (char *)&hdr, sizeof hdr); _hxappend(currp, recp, leng); currp->recs++; newsize = 0; } // If the current page contains only data of OTHER heads // -- and hence, must be at the END of a chain -- // unlink it from this chain. If the page is empty, // unlink it AND put it in the freemap. if (IS_HEAD(currp->pgno)) { skip = 0; } else if (!currp->used) { skip = 1; _hxputfreed(locp, currp); if (SCANNING(hp) && hp->buffer.pgno == currp->pgno) hp->buffer.used = 0; } else if (currp->next || !SHRUNK(currp)) { skip = 0; } else if (!skip) { // If skip not set by _hxshift above... char const *rp, *ep; FOR_EACH_REC(rp, currp, ep) if (locp->head == _hxhead(locp, RECHASH(rp))) break; skip = rp == ep; // No recs for locp->head in this tail. } if (skip) LINK(prevp, nextpg); else SWAP(prevp, currp); sync_save(locp, currp); if (!newsize && !prevp->next) break; if (!newsize && !may_find && !SHRUNK(prevp)) break; if (prevp->next) { _hxload(locp, currp, prevp->next); continue; } // We are at the end of the chain, and rec not yet inserted. // Unlocking is necessary even if tail is not shared; // it may be hp->tail.pgno in some other process. if (!FILE_HELD(hp) && !IS_HEAD(prevp->pgno)) _hxunlock(locp, prevp->pgno, 1); // _hxshare/_hxfindfree may update the map (root etc). // Split MUST be locked before root, else risk deadlock. _hxlockset(locp, BOTH_LOCK); if (IS_MMAP(hp)) _hxremap(locp); // At this point assert: // - head is locked, split is locked, // - head matches hash, npages matches filesize. // After locking the split, no other process can change // the file size. may_find = 0; COUNT need = IS_HEAD(prevp->pgno) ? newsize : 0; if (!_hxshare(locp, currp, need) && !_hxgetfreed(locp, currp) && !_hxfindfree(locp, currp)) { // _hxgrow will zero samehead if it splits locp->head. PAGENO samehead = locp->head; // _hxgrow will change the file length. A concurrent // hxget/hxdel could miscalculate locp->head as // being the newly-added page. _hxlock(locp, locp->npages, 0); _hxgrow(locp, currp, need, &samehead); DEBUG3("head=%u samehead=%u", locp->head, samehead); if (!samehead) { _hxputfreed(locp, currp); _hxpoint(locp); _hxload(locp, currp, locp->head); loops = HX_MAX_CHAIN; continue; } } // _hxgrow may clobber prevp, so we reload it. Even if // prevp->pgno == locp->head, prevp may contain an // obsolete copy of the head page. The empty page is // always appended to head. _hxshare only returns true // if currp is head and currp->next is 0, so it can't // clobber it. _hxsave(locp, prevp); _hxload(locp, prevp, locp->head); LINK(currp, prevp->next); LINK(prevp, currp->pgno); currp->orig = DATASIZE(hp); // make SHRUNK be true }