/** @file midl.c * @brief ldap bdb back-end ID List functions */ /* $OpenLDAP$ */ /* This work is part of OpenLDAP Software . * * Copyright 2000-2021 The OpenLDAP Foundation. * Portions Copyright 2001-2021 Howard Chu, Symas Corp. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted only as authorized by the OpenLDAP * Public License. * * A copy of this license is available in the file LICENSE in the * top-level directory of the distribution or, alternatively, at * . */ #include #include #include #include #include #include "midl.h" /** @defgroup internal LMDB Internals * @{ */ /** @defgroup idls ID List Management * @{ */ #define CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) ) unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id ) { /* * binary search of id in ids * if found, returns position of id * if not found, returns first position greater than id */ unsigned base = 0; unsigned cursor = 1; int val = 0; unsigned n = ids[0]; unsigned end = n; binary_search: while( 0 < n ) { unsigned pivot = n >> 1; cursor = base + pivot + 1; val = CMP( ids[cursor], id ); unsigned x = cursor; // skip past empty and block length entries while(((intptr_t)ids[x]) <= 0) { if (++x > end) { // reached the end, go to lower half n = pivot; val = 0; end = cursor; goto binary_search; } } val = CMP( ids[x], id ); if( val < 0 ) { n = pivot; end = cursor; } else if ( val > 0 ) { base = cursor; n -= pivot + 1; } else { return cursor; } } if( val > 0 && (intptr_t)ids[cursor] > 0) ++cursor; return cursor; } int mdb_midl_insert( MDB_IDL* ids_ref, MDB_ID id, int insertion_count ) { MDB_IDL ids = *ids_ref; unsigned x, i; int rc; x = mdb_midl_search( ids, id ); //assert( x > 0 ); if( x < 1 ) { /* internal error */ fprintf(stderr, "negative search index error\n"); return -2; } if ( x <= ids[0] && ids[x] == id ) { /* duplicate */ //assert(0); fprintf(stderr, "duplicate value error\n"); return -1; } if (x > ids[0]) { // need to grow if ((rc = mdb_midl_need(ids_ref, 2)) != 0) return rc; ids = *ids_ref; if (insertion_count == 1) { ids[x] = 0; ids[0] = x; } else { ids[x] = 0; ids[x + 1] = 0; ids[0] = x + 1; } } unsigned before = x; // this will end up pointing to an entry or zero right before a block of empty space while ((intptr_t)ids[--before] <= 0 && before > 0) { // move past empty entries (and the length entry) } while ((intptr_t)ids[x] <= 0 && x < ids[0]) { x++;} intptr_t next_id = ids[x]; intptr_t next_count = ids[x - 1]; if (next_count < 0) next_count = -next_count; else next_count = 1; if (id - next_count <= next_id && next_id > 0) { if (id - next_count < next_id) { fprintf(stderr, "overlapping duplicate entry %u\n", id); return -1; } // connected to next entry intptr_t count = next_count + insertion_count; // ids[x + 1] = id; // no need to adjust id, so since we are adding to the end of the block if (before > 0) { MDB_ID previous_id = before > 0 ? ids[before] : 0; int previous_count = before > 1 ? -ids[before - 1] : 0; if (previous_count < 1) previous_count = 1; if (previous_id - insertion_count <= id) { if (previous_id - insertion_count < id) { fprintf(stderr, "overlapping duplicate entry"); return -1; } // the block we just added to can now be connected to previous entry count += previous_count; if (previous_count > 1) { ids[before - 1] = 0; // remove previous length } ids[before] = 0; // remove previous id if (next_count == 1) { // we can safely add the new count to the empty space ids[x - 1] = -count; // update the count return 0; } } } if (next_count > 1) { ids[x - 1] = -count; // update the count } else if (ids[x - 1] == 0) { ids[x - 1] = -1 - insertion_count; // we can switch to length-2 block in place } else { id = -1 - insertion_count; // switching a single entry to a block size of 2 goto insert_id; } return 0; } if (before > 0) { MDB_ID previous_id = before > 0 ? ids[before] : 0; int count = before > 1 ? -ids[before - 1] : 0; if (count < 1) count = 1; if (previous_id - insertion_count <= id) { if (previous_id - insertion_count < id) { fprintf(stderr, "overlapping duplicate entry"); return -1; } // connected to previous entry ids[before] = id; // adjust the starting block to include this if (count > 1) { ids[before - 1] -= insertion_count; // can just update the count to include this id return 0; } else { id = -1 - insertion_count; // switching a single entry to a block size of 2 x = before; goto insert_id; } } } if (x == 1 && ids[0] > 2 && ids[1] == 0 && ids[2] == 0 && ids[3] == 0) { // this occurs when we have an empty list if (insertion_count > 1) { ids[2] = -insertion_count; ids[3] = id; } else ids[2] = id; return 0; } if (!ids[before + 1]) { // there is an empty slot we can use, find a place in the middle i = before + 3 < x ? (before + 2) : (before + 1); if (i >= ids[0]) { mdb_midl_need(ids_ref, 1); ids = *ids_ref; ids[0] = i; } ids[i] = id; if (insertion_count == 1) return 0; // done // else insert the length x = i; id = -insertion_count; } intptr_t last_id; insert_id: // move items to try to make room last_id = id; if ((intptr_t)ids[x - 1] < 0) x--; do { i = x; do { next_id = ids[i]; ids[i++] = last_id; if (i > ids[0]) { // it is full, need to expand mdb_midl_need(ids_ref, 1); ids = *ids_ref; ids[0] = i; ids[i] = next_id; next_id = 0; // break out; } last_id = next_id; } while(next_id); } while ((intptr_t) id > 0 && insertion_count > 1 && (id = last_id = -insertion_count)); if (i > 0 && ((int) i - x > (ids[0] >> 2) + 4)) { // or too many moves. TODO: This threshold should actually be more like the square root of the length // respread the ids (this will replace the reference too) mdb_midl_respread(ids_ref); } return 0; } MDB_IDL mdb_midl_alloc(int num) { MDB_IDL ids = malloc((num+2) * sizeof(MDB_ID)); if (ids) { *ids++ = num; *ids = 0; } return ids; } void mdb_midl_free(MDB_IDL ids) { if (ids) free(ids-1); } int mdb_midl_is_empty(MDB_IDL idl) { if (idl == NULL) return 1; unsigned n = idl[0]; for (unsigned i = 1; i <= n; i++) { if (idl[i]) return 0; } return 1; } void mdb_midl_shrink( MDB_IDL *idp ) { MDB_IDL ids = *idp; if (*(--ids) > MDB_IDL_UM_MAX && (ids = realloc(ids, (MDB_IDL_UM_MAX+2) * sizeof(MDB_ID)))) { *ids++ = MDB_IDL_UM_MAX; *idp = ids; } } static int mdb_midl_grow( MDB_IDL *idp, int num ) { MDB_IDL idn = *idp-1; /* grow it */ idn = realloc(idn, (*idn + num + 2) * sizeof(MDB_ID)); if (!idn) return ENOMEM; *idn++ += num; *idp = idn; return 0; } int mdb_midl_need( MDB_IDL *idp, unsigned num ) { MDB_IDL ids = *idp; num += ids[0]; if (num > ids[-1]) { num = (num + num/4 + (256 + 2)) & -256; // fprintf(stderr, "Resizing id list to %u\n", num); if (!(ids = realloc(ids-1, num * sizeof(MDB_ID)))) return ENOMEM; *ids++ = num - 2; *idp = ids; } return 0; } int mdb_midl_append( MDB_IDL *idp, MDB_ID id ) { MDB_IDL ids = *idp; /* Too big? */ if (ids[0] >= ids[-1]) { if (mdb_midl_grow(idp, MDB_IDL_UM_MAX)) return ENOMEM; ids = *idp; } ids[0]++; ids[ids[0]] = id; return 0; } int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app ) { MDB_IDL ids = *idp; /* Too big? */ if (ids[0] + app[0] >= ids[-1]) { if (mdb_midl_grow(idp, app[0])) return ENOMEM; ids = *idp; } memcpy(&ids[ids[0]+1], &app[1], app[0] * sizeof(MDB_ID)); ids[0] += app[0]; return 0; } int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n ) { MDB_ID *ids = *idp, len = ids[0]; /* Too big? */ if (len + n > ids[-1]) { if (mdb_midl_grow(idp, n | MDB_IDL_UM_MAX)) return ENOMEM; ids = *idp; } ids[0] = len + n; ids += len; while (n) ids[n--] = id++; return 0; } int mdb_midl_xmerge( MDB_IDL* idp, MDB_IDL merge ) { for (unsigned i = 1; i <= merge[0]; i++) { intptr_t entry = merge[i]; int count = 1; if (entry <= 0) { if (entry == 0) continue; count = -entry; entry = merge[++i]; } int rc; if ((rc = mdb_midl_insert(idp, entry, count)) != 0) { return rc; } } return 0; } /* Quicksort + Insertion sort for small arrays */ #define SMALL 8 #define MIDL_SWAP(a,b) { itmp=(a); (a)=(b); (b)=itmp; } void mdb_midl_sort( MDB_IDL ids ) { /* Max possible depth of int-indexed tree * 2 items/level */ int istack[sizeof(int)*CHAR_BIT * 2]; int i,j,k,l,ir,jstack; MDB_ID a, itmp; ir = (int)ids[0]; l = 1; jstack = 0; for(;;) { if (ir - l < SMALL) { /* Insertion sort */ for (j=l+1;j<=ir;j++) { a = ids[j]; for (i=j-1;i>=1;i--) { if (ids[i] >= a) break; ids[i+1] = ids[i]; } ids[i+1] = a; } if (jstack == 0) break; ir = istack[jstack--]; l = istack[jstack--]; } else { k = (l + ir) >> 1; /* Choose median of left, center, right */ MIDL_SWAP(ids[k], ids[l+1]); if (ids[l] < ids[ir]) { MIDL_SWAP(ids[l], ids[ir]); } if (ids[l+1] < ids[ir]) { MIDL_SWAP(ids[l+1], ids[ir]); } if (ids[l] < ids[l+1]) { MIDL_SWAP(ids[l], ids[l+1]); } i = l+1; j = ir; a = ids[l+1]; for(;;) { do i++; while(ids[i] > a); do j--; while(ids[j] < a); if (j < i) break; MIDL_SWAP(ids[i],ids[j]); } ids[l+1] = ids[j]; ids[j] = a; jstack += 2; if (ir-i+1 >= j-l) { istack[jstack] = ir; istack[jstack-1] = i; ir = j-1; } else { istack[jstack] = j-1; istack[jstack-1] = l; l = i; } } } } MDB_IDL mdb_midl_pack(MDB_IDL idl) { if (!idl) return NULL; MDB_IDL packed = mdb_midl_alloc(idl[0]); unsigned j = 1; for (unsigned i = 1; i < idl[0]; i++) { intptr_t entry = idl[i]; if (entry) packed[j++] = entry; } if (j == 1) { // empty list, just treat as no list mdb_midl_free(packed); return NULL; } packed[0] = j - 1; return packed; } unsigned mdb_midl_pack_count(MDB_IDL idl) { unsigned count = 0; if (idl) { for (unsigned i = 1; i < idl[0]; i++) { if (idl[i]) count++; } } return count; } int mdb_midl_respread( MDB_IDL *idp ) { MDB_IDL ids = *idp; unsigned j = 1; unsigned size = ids[0]; unsigned new_size = 0; unsigned entry_count = 0; // first, do compaction for (unsigned i = 1; i <= size; i++) { intptr_t entry; while (!(entry = ids[i])) { if (++i > ids[0]) goto expand; } ids[j++] = entry; new_size += entry < 0 ? 2 : 1; // one for the entry, and one for the length if it is a block if (++entry_count & 1) new_size++; // and one for empty space on every other if (entry < 0) ids[j++] = ids[++i]; // this was a block with a length } expand: mdb_midl_need(idp, new_size - ids[0]); ids = *idp; ids[0] = new_size; j--; // re-spread out the entries with gaps for growth for (unsigned i = new_size; i > 0;) { intptr_t pgno = ids[j--]; ids[i--] = pgno; intptr_t entry = ids[j]; if (entry < 0) { ids[i--] = entry; j--; } if (entry_count-- & 1) ids[i--] = 0; // empty slot for growth } return 0; } int mdb_midl_print( FILE *fp, MDB_IDL ids ) { if (ids == NULL) { fprintf(fp, "freelist: NULL\n"); return 0; } unsigned i; fprintf(fp, "freelist: %u/%u: ", ids[0], ids[-1]); for (i=1; i<=ids[0]; i++) { intptr_t entry = ids[i]; if (entry < 0) { fprintf(fp, "%li-%li ", ids[i+1] - entry - 1, ids[i+1]); i++; } else if (ids[i] == 0) { fprintf(fp, "_"); } else { fprintf(fp, "%lu ", (unsigned long)ids[i]); } } fprintf(fp, "\n"); return 0; } unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id ) { /* * binary search of id in ids * if found, returns position of id * if not found, returns first position greater than id */ unsigned base = 0; unsigned cursor = 1; int val = 0; unsigned n = (unsigned)ids[0].mid; while( 0 < n ) { unsigned pivot = n >> 1; cursor = base + pivot + 1; val = CMP( id, ids[cursor].mid ); if( val < 0 ) { n = pivot; } else if ( val > 0 ) { base = cursor; n -= pivot + 1; } else { return cursor; } } if( val > 0 ) { ++cursor; } return cursor; } int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id ) { unsigned x, i; x = mdb_mid2l_search( ids, id->mid ); if( x < 1 ) { /* internal error */ return -2; } if ( x <= ids[0].mid && ids[x].mid == id->mid ) { /* duplicate */ return -1; } if ( ids[0].mid >= MDB_IDL_UM_MAX ) { /* too big */ return -2; } else { /* insert id */ ids[0].mid++; for (i=(unsigned)ids[0].mid; i>x; i--) ids[i] = ids[i-1]; ids[x] = *id; } return 0; } int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id ) { /* Too big? */ if (ids[0].mid >= MDB_IDL_UM_MAX) { return -2; } ids[0].mid++; ids[ids[0].mid] = *id; return 0; } MDB_ID2L mdb_mid2l_alloc(int num) { MDB_ID2L ids = malloc((num+2) * sizeof(MDB_ID2)); if (ids) { ids->mid = num; ids++; ids->mid = 0; } return ids; } void mdb_mid2l_free(MDB_ID2L ids) { if (ids) free(ids-1); } int mdb_mid2l_need( MDB_ID2L *idp, unsigned num ) { MDB_ID2L ids = *idp; num += ids[0].mid; if (num > ids[-1].mid) { num = (num + num/4 + (256 + 2)) & -256; if (!(ids = realloc(ids-1, num * sizeof(MDB_ID2)))) return ENOMEM; ids[0].mid = num - 2; *idp = ids+1; } return 0; } #if MDB_RPAGE_CACHE unsigned mdb_mid3l_search( MDB_ID3L ids, MDB_ID id ) { /* * binary search of id in ids * if found, returns position of id * if not found, returns first position greater than id */ unsigned base = 0; unsigned cursor = 1; int val = 0; unsigned n = (unsigned)ids[0].mid; while( 0 < n ) { unsigned pivot = n >> 1; cursor = base + pivot + 1; val = CMP( id, ids[cursor].mid ); if( val < 0 ) { n = pivot; } else if ( val > 0 ) { base = cursor; n -= pivot + 1; } else { return cursor; } } if( val > 0 ) { ++cursor; } return cursor; } int mdb_mid3l_insert( MDB_ID3L ids, MDB_ID3 *id ) { unsigned x, i; x = mdb_mid3l_search( ids, id->mid ); if( x < 1 ) { /* internal error */ return -2; } if ( x <= ids[0].mid && ids[x].mid == id->mid ) { /* duplicate */ return -1; } /* insert id */ ids[0].mid++; for (i=(unsigned)ids[0].mid; i>x; i--) ids[i] = ids[i-1]; ids[x] = *id; return 0; } #endif /* MDB_RPAGE_CACHE */ /** @} */ /** @} */