/* allocate some space from the free list. The offset returned points to a unconnected tdb1_record within the database with room for at least length bytes of total data 0 is returned if the space could not be allocated */ tdb1_off_t tdb1_allocate(struct tdb_context *tdb, tdb1_len_t length, struct tdb1_record *rec) { tdb1_off_t rec_ptr, last_ptr, newrec_ptr; struct { tdb1_off_t rec_ptr, last_ptr; tdb1_len_t rec_len; } bestfit; float multiplier = 1.0; if (tdb1_lock(tdb, -1, F_WRLCK) == -1) return 0; /* over-allocate to reduce fragmentation */ length *= 1.25; /* Extra bytes required for tailer */ length += sizeof(tdb1_off_t); length = TDB1_ALIGN(length, TDB1_ALIGNMENT); again: last_ptr = TDB1_FREELIST_TOP; /* read in the freelist top */ if (tdb1_ofs_read(tdb, TDB1_FREELIST_TOP, &rec_ptr) == -1) goto fail; bestfit.rec_ptr = 0; bestfit.last_ptr = 0; bestfit.rec_len = 0; /* this is a best fit allocation strategy. Originally we used a first fit strategy, but it suffered from massive fragmentation issues when faced with a slowly increasing record size. */ while (rec_ptr) { if (tdb1_rec_free_read(tdb, rec_ptr, rec) == -1) { goto fail; } if (rec->rec_len >= length) { if (bestfit.rec_ptr == 0 || rec->rec_len < bestfit.rec_len) { bestfit.rec_len = rec->rec_len; bestfit.rec_ptr = rec_ptr; bestfit.last_ptr = last_ptr; } } /* move to the next record */ last_ptr = rec_ptr; rec_ptr = rec->next; /* if we've found a record that is big enough, then stop searching if its also not too big. The definition of 'too big' changes as we scan through */ if (bestfit.rec_len > 0 && bestfit.rec_len < length * multiplier) { break; } /* this multiplier means we only extremely rarely search more than 50 or so records. At 50 records we accept records up to 11 times larger than what we want */ multiplier *= 1.05; } if (bestfit.rec_ptr != 0) { if (tdb1_rec_free_read(tdb, bestfit.rec_ptr, rec) == -1) { goto fail; } newrec_ptr = tdb1_allocate_ofs(tdb, length, bestfit.rec_ptr, rec, bestfit.last_ptr); tdb1_unlock(tdb, -1, F_WRLCK); return newrec_ptr; } /* we didn't find enough space. See if we can expand the database and if we can then try again */ if (tdb1_expand(tdb, length + sizeof(*rec)) == 0) goto again; fail: tdb1_unlock(tdb, -1, F_WRLCK); return 0; }