Пример #1
0
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);
}
Пример #2
0
//--------------|---------------------------------------------
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
    }