/* * Copyright (c) 2020 Red Hat, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * $Id: //eng/uds-releases/jasper/src/uds/deltaIndex.c#7 $ */ #include "deltaIndex.h" #include "bits.h" #include "buffer.h" #include "compiler.h" #include "cpu.h" #include "errors.h" #include "logger.h" #include "memoryAlloc.h" #include "permassert.h" #include "stringUtils.h" #include "typeDefs.h" #include "uds.h" #include "zone.h" /* * A delta index is a key-value store, where each entry maps an address * (the key) to a payload (the value). The entries are sorted by address, * and only the delta between successive addresses is stored in the entry. * The addresses are assumed to be uniformly distributed,and the deltas are * therefore exponentially distributed. * * The entries could be stored in a single DeltaList, but for efficiency we * use multiple DeltaLists. These lists are stored in a single chunk of * memory managed by the DeltaMemory module. The DeltaMemory module can * move the data around in memory, so we never keep any byte pointers into * DeltaList memory. We only keep offsets into the memory. * * The delta lists are stored as bit streams. These bit streams are stored * in little endian order, and all offsets into DeltaMemory are bit * offsets. * * All entries are stored as a fixed length payload (the value) followed by a * variable length key (the delta). Always strictly in little endian order. * * A collision entry is used when two block names have the same delta list * address. A collision entry is encoded with DELTA==0, and has 256 * extension bits containing the full block name. * * There is a special exception to be noted. The DELTA==0 encoding usually * indicates a collision with the preceding entry. But for the first entry * in any delta list there is no preceding entry, so the DELTA==0 encoding * at the beginning of a delta list indicates a normal entry. * * The Huffman code is driven by 3 parameters: * * MINBITS This is the number of bits in the smallest code * * BASE This is the number of values coded using a code of length MINBITS * * INCR This is the number of values coded by using one additional bit. * * These parameters are related by: * * BASE + INCR == 1 << MINBITS * * When we create an index, we need to know the mean delta. From the mean * delta, we compute these three parameters. The math for the Huffman code * of an exponential distribution says that we compute: * * INCR = log(2) * MEAN_DELTA * * Then we find the smallest MINBITS so that * * 1 << MINBITS > INCR * * And then: * * BASE = (1 << MINBITS) - INCR * * Now we need a code such that * * - The first BASE values code using MINBITS bits * - The next INCR values code using MINBITS+1 bits. * - The next INCR values code using MINBITS+2 bits. * - The next INCR values code using MINBITS+3 bits. * - (and so on). * * ENCODE(DELTA): * * if (DELTA < BASE) { * put DELTA in MINBITS bits; * } else { * T1 = (DELTA - BASE) % INCR + BASE; * T2 = (DELTA - BASE) / INCR; * put T1 in MINBITS bits; * put 0 in T2 bits; * put 1 in 1 bit; * } * * DECODE(BIT_STREAM): * * T1 = next MINBITS bits of stream; * if (T1 < BASE) { * DELTA = T1; * } else { * Scan bits in the stream until reading a 1, * setting T2 to the number of 0 bits read; * DELTA = T2 * INCR + T1; * } * * The bit field utilities that we use on the delta lists assume that it is * possible to read a few bytes beyond the end of the bit field. So we * make sure to allocates some extra bytes at the end of memory containing * the delta lists. Look for POST_FIELD_GUARD_BYTES to find the code * related to this. * * And note that the decode bit stream code includes a step that skips over * 0 bits until the first 1 bit is found. A corrupted delta list could * cause this step to run off the end of the delta list memory. As an * extra protection against this happening, the guard bytes at the end * should be set to all ones. */ /** * Constants and structures for the saved delta index. "DI" is for * deltaIndex, and -##### is a number to increment when the format of the * data changes. **/ enum { MAGIC_SIZE = 8 }; static const char MAGIC_DI_START[] = "DI-00002"; struct di_header { char magic[MAGIC_SIZE]; // MAGIC_DI_START uint32_t zoneNumber; uint32_t numZones; uint32_t firstList; uint32_t numLists; uint64_t recordCount; uint64_t collisionCount; }; //********************************************************************** // Methods for dealing with mutable delta list headers //********************************************************************** /** * Move the start of the delta list bit stream without moving the end. * * @param deltaList The delta list header * @param increment The change in the start of the delta list **/ static INLINE void moveDeltaListStart(DeltaList *deltaList, int increment) { deltaList->startOffset += increment; deltaList->size -= increment; } /** * Move the end of the delta list bit stream without moving the start. * * @param deltaList The delta list header * @param increment The change in the end of the delta list **/ static INLINE void moveDeltaListEnd(DeltaList *deltaList, int increment) { deltaList->size += increment; } //********************************************************************** // Methods for dealing with immutable delta list headers packed //********************************************************************** // Header data used for immutable delta index pages. These data are // followed by the delta list offset table. typedef struct __attribute__((packed)) deltaPageHeader { uint64_t nonce; // Externally-defined nonce uint64_t virtualChapterNumber; // The virtual chapter number uint16_t firstList; // Index of the first delta list on the page uint16_t numLists; // Number of delta lists on the page } DeltaPageHeader; // Immutable delta lists are packed into pages containing a header that // encodes the delta list information into 19 bits per list (64KB bit offset) enum { IMMUTABLE_HEADER_SIZE = 19 }; /** * Get the bit offset to the immutable delta list header * * @param listNumber The delta list number * * @return the offset of immutable delta list header **/ static INLINE unsigned int getImmutableHeaderOffset(unsigned int listNumber) { return (sizeof(DeltaPageHeader) * CHAR_BIT + listNumber * IMMUTABLE_HEADER_SIZE); } /** * Get the bit offset to the start of the immutable delta list bit stream * * @param memory The memory page containing the delta lists * @param listNumber The delta list number * * @return the start of the delta list **/ static INLINE unsigned int getImmutableStart(const byte *memory, unsigned int listNumber) { return getField(memory, getImmutableHeaderOffset(listNumber), IMMUTABLE_HEADER_SIZE); } /** * Set the bit offset to the start of the immutable delta list bit stream * * @param memory The memory page containing the delta lists * @param listNumber The delta list number * @param startOffset The start of the delta list **/ static INLINE void setImmutableStart(byte *memory, unsigned int listNumber, unsigned int startOffset) { setField(startOffset, memory, getImmutableHeaderOffset(listNumber), IMMUTABLE_HEADER_SIZE); } //********************************************************************** // Methods for dealing with Delta List Entries //********************************************************************** /** * Decode a delta index entry delta value. The DeltaIndexEntry basically * describes the previous list entry, and has had its offset field changed to * point to the subsequent entry. We decode the bit stream and update the * DeltaListEntry to describe the entry. * * @param deltaEntry The delta index entry **/ static INLINE void decodeDelta(DeltaIndexEntry *deltaEntry) { const DeltaMemory *deltaZone = deltaEntry->deltaZone; const byte *memory = deltaZone->memory; uint64_t deltaOffset = getDeltaEntryOffset(deltaEntry) + deltaEntry->valueBits; const byte *addr = memory + deltaOffset / CHAR_BIT; int offset = deltaOffset % CHAR_BIT; uint32_t data = getUInt32LE(addr) >> offset; addr += sizeof(uint32_t); int keyBits = deltaZone->minBits; unsigned int delta = data & ((1 << keyBits) - 1); if (delta >= deltaZone->minKeys) { data >>= keyBits; if (data == 0) { keyBits = sizeof(uint32_t) * CHAR_BIT - offset; while ((data = getUInt32LE(addr)) == 0) { addr += sizeof(uint32_t); keyBits += sizeof(uint32_t) * CHAR_BIT; } } keyBits += ffs(data); delta += (keyBits - deltaZone->minBits - 1) * deltaZone->incrKeys; } deltaEntry->delta = delta; deltaEntry->key += delta; // Check for a collision, a delta of zero not at the start of the list. if (unlikely((delta == 0) && (deltaEntry->offset > 0))) { deltaEntry->isCollision = true; // The small duplication of this math in the two arms of this if statement // makes a tiny but measurable difference in performance. deltaEntry->entryBits = deltaEntry->valueBits + keyBits + COLLISION_BITS; } else { deltaEntry->isCollision = false; deltaEntry->entryBits = deltaEntry->valueBits + keyBits; } } /** * Delete bits from a delta list at the offset of the specified delta index * entry. * * @param deltaEntry The delta index entry * @param size The number of bits to delete **/ static void deleteBits(const DeltaIndexEntry *deltaEntry, int size) { DeltaList *deltaList = deltaEntry->deltaList; byte *memory = deltaEntry->deltaZone->memory; // Compute how many bits are retained before and after the deleted bits uint32_t totalSize = getDeltaListSize(deltaList); uint32_t beforeSize = deltaEntry->offset; uint32_t afterSize = totalSize - deltaEntry->offset - size; // Determine whether to add to the available space either before or after // the delta list. We prefer to move the least amount of data. If it is // exactly the same, try to add to the smaller amount of free space. bool beforeFlag; if (beforeSize < afterSize) { beforeFlag = true; } else if (afterSize < beforeSize) { beforeFlag = false; } else { uint64_t freeBefore = getDeltaListStart(&deltaList[0]) - getDeltaListEnd(&deltaList[-1]); uint64_t freeAfter = getDeltaListStart(&deltaList[1]) - getDeltaListEnd(&deltaList[ 0]); beforeFlag = freeBefore < freeAfter; } uint64_t source, destination; uint32_t count; if (beforeFlag) { source = getDeltaListStart(deltaList); destination = source + size; moveDeltaListStart(deltaList, size); count = beforeSize; } else { moveDeltaListEnd(deltaList, -size); destination = getDeltaListStart(deltaList) + deltaEntry->offset; source = destination + size; count = afterSize; } moveBits(memory, source, memory, destination, count); } /** * Get the offset of the collision field in a DeltaIndexEntry * * @param entry The delta index record * * @return the offset of the start of the collision name **/ static INLINE uint64_t getCollisionOffset(const DeltaIndexEntry *entry) { return (getDeltaEntryOffset(entry) + entry->entryBits - COLLISION_BITS); } /** * Encode a delta index entry delta. * * @param deltaEntry The delta index entry **/ static void encodeDelta(const DeltaIndexEntry *deltaEntry) { const DeltaMemory *deltaZone = deltaEntry->deltaZone; byte *memory = deltaZone->memory; uint64_t offset = getDeltaEntryOffset(deltaEntry) + deltaEntry->valueBits; if (deltaEntry->delta < deltaZone->minKeys) { setField(deltaEntry->delta, memory, offset, deltaZone->minBits); return; } unsigned int temp = deltaEntry->delta - deltaZone->minKeys; unsigned int t1 = (temp % deltaZone->incrKeys) + deltaZone->minKeys; unsigned int t2 = temp / deltaZone->incrKeys; setField(t1, memory, offset, deltaZone->minBits); setZero(memory, offset + deltaZone->minBits, t2); setOne(memory, offset + deltaZone->minBits + t2, 1); } /** * Encode a delta index entry. * * @param deltaEntry The delta index entry * @param value The value associated with the entry * @param name For collision entries, the 256 bit full name. **/ static void encodeEntry(const DeltaIndexEntry *deltaEntry, unsigned int value, const byte *name) { byte *memory = deltaEntry->deltaZone->memory; uint64_t offset = getDeltaEntryOffset(deltaEntry); setField(value, memory, offset, deltaEntry->valueBits); encodeDelta(deltaEntry); if (name != NULL) { setBytes(memory, getCollisionOffset(deltaEntry), name, COLLISION_BYTES); } } /** * Insert bits into a delta list at the offset of the specified delta index * entry. * * @param deltaEntry The delta index entry * @param size The number of bits to insert * * @return UDS_SUCCESS or an error code **/ static int insertBits(DeltaIndexEntry *deltaEntry, int size) { DeltaMemory *deltaZone = deltaEntry->deltaZone; DeltaList *deltaList = deltaEntry->deltaList; // Compute how many bits are in use before and after the inserted bits uint32_t totalSize = getDeltaListSize(deltaList); uint32_t beforeSize = deltaEntry->offset; uint32_t afterSize = totalSize - deltaEntry->offset; if ((unsigned int) (totalSize + size) > UINT16_MAX) { deltaEntry->listOverflow = true; deltaZone->overflowCount++; return UDS_OVERFLOW; } // Compute how many bits are available before and after the delta list uint64_t freeBefore = getDeltaListStart(&deltaList[0]) - getDeltaListEnd(&deltaList[-1]); uint64_t freeAfter = getDeltaListStart(&deltaList[1]) - getDeltaListEnd(&deltaList[ 0]); bool beforeFlag; if (((unsigned int) size <= freeBefore) && ((unsigned int) size <= freeAfter)) { // We have enough space to use either before or after the list. Prefer // to move the least amount of data. If it is exactly the same, try to // take from the larger amount of free space. if (beforeSize < afterSize) { beforeFlag = true; } else if (afterSize < beforeSize) { beforeFlag = false; } else { beforeFlag = freeBefore > freeAfter; } } else if ((unsigned int) size <= freeBefore) { // There is space before but not after beforeFlag = true; } else if ((unsigned int) size <= freeAfter) { // There is space after but not before beforeFlag = false; } else { // Neither of the surrounding spaces is large enough for this request, // Extend and/or rebalance the delta list memory choosing to move the // least amount of data. unsigned int growingIndex = deltaEntry->listNumber + 1; beforeFlag = beforeSize < afterSize; if (!beforeFlag) { growingIndex++; } int result = extendDeltaMemory(deltaZone, growingIndex, (size + CHAR_BIT - 1) / CHAR_BIT, true); if (result != UDS_SUCCESS) { return result; } } uint64_t source, destination; uint32_t count; if (beforeFlag) { source = getDeltaListStart(deltaList); destination = source - size; moveDeltaListStart(deltaList, -size); count = beforeSize; } else { moveDeltaListEnd(deltaList, size); source = getDeltaListStart(deltaList) + deltaEntry->offset; destination = source + size; count = afterSize; } byte *memory = deltaZone->memory; moveBits(memory, source, memory, destination, count); return UDS_SUCCESS; } /** * Get the amount of memory to allocate for each zone * * @param numZones The number of zones in the index * @param memorySize The number of bytes in memory for the index * * @return the number of bytes to allocate for a single zone **/ static INLINE size_t getZoneMemorySize(unsigned int numZones, size_t memorySize) { size_t zoneSize = memorySize / numZones; // Round the size up so that each zone is a multiple of 64K in size. enum { ALLOC_BOUNDARY = 64 * KILOBYTE }; return (zoneSize + ALLOC_BOUNDARY - 1) & -ALLOC_BOUNDARY; } /** * Validate delta index parameters * * @param meanDelta The mean delta value * @param numPayloadBits The number of bits in the payload or value **/ static bool invalidParameters(unsigned int meanDelta, unsigned int numPayloadBits) { const unsigned int minDelta = 10; const unsigned int maxDelta = 1 << MAX_FIELD_BITS; if ((meanDelta < minDelta) || (meanDelta > maxDelta)) { logWarning("error initializing delta index: " "meanDelta (%u) is not in the range %u to %u", meanDelta, minDelta, maxDelta); return true; } if (numPayloadBits > MAX_FIELD_BITS) { logWarning("error initializing delta index: Too many payload bits (%u)", numPayloadBits); return true; } return false; } /** * Set a delta index entry to be a collision * * @param deltaEntry The delta index entry **/ static void setCollision(DeltaIndexEntry *deltaEntry) { deltaEntry->isCollision = true; deltaEntry->entryBits += COLLISION_BITS; } /** * Set the delta in a delta index entry. * * @param deltaEntry The delta index entry * @param delta The new delta **/ static void setDelta(DeltaIndexEntry *deltaEntry, unsigned int delta) { const DeltaMemory *deltaZone = deltaEntry->deltaZone; deltaEntry->delta = delta; int keyBits = (deltaZone->minBits + ((deltaZone->incrKeys - deltaZone->minKeys + delta) / deltaZone->incrKeys)); deltaEntry->entryBits = deltaEntry->valueBits + keyBits; } //********************************************************************** // External functions declared in deltaIndex.h //********************************************************************** int initializeDeltaIndex(DeltaIndex *deltaIndex, unsigned int numZones, unsigned int numLists, unsigned int meanDelta, unsigned int numPayloadBits, size_t memorySize) { size_t memSize = getZoneMemorySize(numZones, memorySize); if (invalidParameters(meanDelta, numPayloadBits)) { return UDS_INVALID_ARGUMENT; } int result = ALLOCATE(numZones, DeltaMemory, "Delta Index Zones", &deltaIndex->deltaZones); if (result != UDS_SUCCESS) { return result; } deltaIndex->numZones = numZones; deltaIndex->numLists = numLists; deltaIndex->listsPerZone = (numLists + numZones - 1) / numZones; deltaIndex->isMutable = true; deltaIndex->tag = 'm'; unsigned int z; for (z = 0; z < numZones; z++) { unsigned int firstListInZone = z * deltaIndex->listsPerZone; unsigned int numListsInZone = deltaIndex->listsPerZone; if (z == numZones - 1) { /* * The last zone gets fewer lists if numZones doesn't evenly divide * numLists. We'll have an underflow if the assertion below doesn't * hold. (And it turns out that the assertion is equivalent to * numZones <= 1 + (numLists / numZones) + (numLists % numZones) * in the case that numZones doesn't evenly divide numlists. * If numLists >= numZones * numZones, then the above inequality * will always hold.) */ if (deltaIndex->numLists <= firstListInZone) { uninitializeDeltaIndex(deltaIndex); return logErrorWithStringError(UDS_INVALID_ARGUMENT, "%u delta-lists not enough for %u zones", numLists, numZones); } numListsInZone = deltaIndex->numLists - firstListInZone; } int result = initializeDeltaMemory(&deltaIndex->deltaZones[z], memSize, firstListInZone, numListsInZone, meanDelta, numPayloadBits); if (result != UDS_SUCCESS) { uninitializeDeltaIndex(deltaIndex); return result; } } return UDS_SUCCESS; } /**********************************************************************/ static bool verifyDeltaIndexPage(uint64_t nonce, uint16_t numLists, uint64_t expectedNonce, byte *memory, size_t memSize) { // Verify the nonce. A mismatch here happens in normal operation when we are // doing a rebuild but haven't written the entire volume once. if (nonce != expectedNonce) { return false; } // Verify that the number of delta lists can fit in the page. if (numLists > (memSize - sizeof(DeltaPageHeader)) * CHAR_BIT / IMMUTABLE_HEADER_SIZE) { return false; } // Verify that the first delta list is immediately after the last delta list // header. if (getImmutableStart(memory, 0) != getImmutableHeaderOffset(numLists + 1)) { return false; } // Verify that the lists are in the correct order. unsigned int i; for (i = 0; i < numLists; i++) { if (getImmutableStart(memory, i) > getImmutableStart(memory, i + 1)) { return false; } } // Verify that the last list ends on the page, and that there is room for the // post-field guard bits. if (getImmutableStart(memory, numLists) > (memSize - POST_FIELD_GUARD_BYTES) * CHAR_BIT) { return false; } // Verify that the guard bytes are correctly set to all ones. for (i = 0; i < POST_FIELD_GUARD_BYTES; i++) { byte guardByte = memory[memSize - POST_FIELD_GUARD_BYTES + i]; if (guardByte != (byte) ~0) { return false; } } // All verifications passed. return true; } /**********************************************************************/ int initializeDeltaIndexPage(DeltaIndexPage *deltaIndexPage, uint64_t expectedNonce, unsigned int meanDelta, unsigned int numPayloadBits, byte *memory, size_t memSize) { const DeltaPageHeader *header = (const DeltaPageHeader *) memory; if (invalidParameters(meanDelta, numPayloadBits)) { return UDS_INVALID_ARGUMENT; } // First assume that the header is little endian uint64_t nonce = getUInt64LE((const byte *) &header->nonce); uint64_t vcn = getUInt64LE((const byte *) &header->virtualChapterNumber); uint16_t firstList = getUInt16LE((const byte *) &header->firstList); uint16_t numLists = getUInt16LE((const byte *) &header->numLists); if (!verifyDeltaIndexPage(nonce, numLists, expectedNonce, memory, memSize)) { // That failed, so try big endian nonce = getUInt64BE((const byte *) &header->nonce); vcn = getUInt64BE((const byte *) &header->virtualChapterNumber); firstList = getUInt16BE((const byte *) &header->firstList); numLists = getUInt16BE((const byte *) &header->numLists); if (!verifyDeltaIndexPage(nonce, numLists, expectedNonce, memory, memSize)) { // Also failed. Do not log this as an error. It happens in normal // operation when we are doing a rebuild but haven't written the entire // volume once. return UDS_CORRUPT_COMPONENT; } } deltaIndexPage->deltaIndex.deltaZones = &deltaIndexPage->deltaMemory; deltaIndexPage->deltaIndex.numZones = 1; deltaIndexPage->deltaIndex.numLists = numLists; deltaIndexPage->deltaIndex.listsPerZone = numLists; deltaIndexPage->deltaIndex.isMutable = false; deltaIndexPage->deltaIndex.tag = 'p'; deltaIndexPage->virtualChapterNumber = vcn; deltaIndexPage->lowestListNumber = firstList; deltaIndexPage->highestListNumber = firstList + numLists - 1; initializeDeltaMemoryPage(&deltaIndexPage->deltaMemory, (byte *) memory, memSize, numLists, meanDelta, numPayloadBits); return UDS_SUCCESS; } /**********************************************************************/ void uninitializeDeltaIndex(DeltaIndex *deltaIndex) { if (deltaIndex != NULL) { unsigned int z; for (z = 0; z < deltaIndex->numZones; z++) { uninitializeDeltaMemory(&deltaIndex->deltaZones[z]); } FREE(deltaIndex->deltaZones); memset(deltaIndex, 0, sizeof(DeltaIndex)); } } /**********************************************************************/ void emptyDeltaIndex(const DeltaIndex *deltaIndex) { unsigned int z; for (z = 0; z < deltaIndex->numZones; z++) { emptyDeltaLists(&deltaIndex->deltaZones[z]); } } /**********************************************************************/ void emptyDeltaIndexZone(const DeltaIndex *deltaIndex, unsigned int zoneNumber) { emptyDeltaLists(&deltaIndex->deltaZones[zoneNumber]); } /**********************************************************************/ int packDeltaIndexPage(const DeltaIndex *deltaIndex, uint64_t headerNonce, bool headerNativeEndian, byte *memory, size_t memSize, uint64_t virtualChapterNumber, unsigned int firstList, unsigned int *numLists) { if (!deltaIndex->isMutable) { return logErrorWithStringError(UDS_BAD_STATE, "Cannot pack an immutable index"); } if (deltaIndex->numZones != 1) { return logErrorWithStringError(UDS_BAD_STATE, "Cannot pack a delta index page when the" " index has %u zones", deltaIndex->numZones); } if (firstList > deltaIndex->numLists) { return logErrorWithStringError(UDS_BAD_STATE, "Cannot pack a delta index page when the" " first list (%u) is larger than the number" " of lists (%u)", firstList, deltaIndex->numLists); } const DeltaMemory *deltaZone = &deltaIndex->deltaZones[0]; DeltaList *deltaLists = &deltaZone->deltaLists[firstList + 1]; unsigned int maxLists = deltaIndex->numLists - firstList; // Compute how many lists will fit on the page int numBits = memSize * CHAR_BIT; // Subtract the size of the fixed header and 1 delta list offset numBits -= getImmutableHeaderOffset(1); // Subtract the guard bytes of memory so that allow us to freely read a // short distance past the end of any byte we are interested in. numBits -= POST_FIELD_GUARD_BYTES * CHAR_BIT; if (numBits < IMMUTABLE_HEADER_SIZE) { // This page is too small to contain even one empty delta list return logErrorWithStringError(UDS_OVERFLOW, "Chapter Index Page of %zu bytes is too" " small", memSize); } unsigned int nLists = 0; while (nLists < maxLists) { // Each list requires 1 delta list offset and the list data int bits = IMMUTABLE_HEADER_SIZE + getDeltaListSize(&deltaLists[nLists]); if (bits > numBits) { break; } nLists++; numBits -= bits; } *numLists = nLists; // Construct the page header DeltaPageHeader *header = (DeltaPageHeader *) memory; if (headerNativeEndian) { header->nonce = headerNonce; header->virtualChapterNumber = virtualChapterNumber; header->firstList = firstList; header->numLists = nLists; } else { storeUInt64LE((byte *) &header->nonce, headerNonce); storeUInt64LE((byte *) &header->virtualChapterNumber, virtualChapterNumber); storeUInt16LE((byte *) &header->firstList, firstList); storeUInt16LE((byte *) &header->numLists, nLists); } // Construct the delta list offset table, making sure that the memory // page is large enough. unsigned int offset = getImmutableHeaderOffset(nLists + 1); setImmutableStart(memory, 0, offset); unsigned int i; for (i = 0; i < nLists; i++) { offset += getDeltaListSize(&deltaLists[i]); setImmutableStart(memory, i + 1, offset); } // Copy the delta list data onto the memory page for (i = 0; i < nLists; i++) { DeltaList *deltaList = &deltaLists[i]; moveBits(deltaZone->memory, getDeltaListStart(deltaList), memory, getImmutableStart(memory, i), getDeltaListSize(deltaList)); } // Set all the bits in the guard bytes. Do not use the bit field // utilities. memset(memory + memSize - POST_FIELD_GUARD_BYTES, ~0, POST_FIELD_GUARD_BYTES); return UDS_SUCCESS; } /**********************************************************************/ void setDeltaIndexTag(DeltaIndex *deltaIndex, byte tag) { deltaIndex->tag = tag; unsigned int z; for (z = 0; z < deltaIndex->numZones; z++) { deltaIndex->deltaZones[z].tag = tag; } } /**********************************************************************/ __attribute__((warn_unused_result)) static int decodeDeltaIndexHeader(Buffer *buffer, struct di_header *header) { int result = getBytesFromBuffer(buffer, MAGIC_SIZE, &header->magic); if (result != UDS_SUCCESS) { return result; } result = getUInt32LEFromBuffer(buffer, &header->zoneNumber); if (result != UDS_SUCCESS) { return result; } result = getUInt32LEFromBuffer(buffer, &header->numZones); if (result != UDS_SUCCESS) { return result; } result = getUInt32LEFromBuffer(buffer, &header->firstList); if (result != UDS_SUCCESS) { return result; } result = getUInt32LEFromBuffer(buffer, &header->numLists); if (result != UDS_SUCCESS) { return result; } result = getUInt64LEFromBuffer(buffer, &header->recordCount); if (result != UDS_SUCCESS) { return result; } result = getUInt64LEFromBuffer(buffer, &header->collisionCount); if (result != UDS_SUCCESS) { return result; } result = ASSERT_LOG_ONLY(contentLength(buffer) == 0, "%zu bytes decoded of %zu expected", bufferLength(buffer) - contentLength(buffer), bufferLength(buffer)); return result; } /**********************************************************************/ __attribute__((warn_unused_result)) static int readDeltaIndexHeader(BufferedReader *reader, struct di_header *header) { Buffer *buffer; int result = makeBuffer(sizeof(*header), &buffer); if (result != UDS_SUCCESS) { return result; } result = readFromBufferedReader(reader, getBufferContents(buffer), bufferLength(buffer)); if (result != UDS_SUCCESS) { freeBuffer(&buffer); return logWarningWithStringError(result, "failed to read delta index header"); } result = resetBufferEnd(buffer, bufferLength(buffer)); if (result != UDS_SUCCESS) { freeBuffer(&buffer); return result; } result = decodeDeltaIndexHeader(buffer, header); freeBuffer(&buffer); return result; } /**********************************************************************/ int startRestoringDeltaIndex(const DeltaIndex *deltaIndex, BufferedReader **bufferedReaders, int numReaders) { if (!deltaIndex->isMutable) { return logErrorWithStringError(UDS_BAD_STATE, "Cannot restore to an immutable index"); } if (numReaders <= 0) { return logWarningWithStringError(UDS_INVALID_ARGUMENT, "No delta index files"); } unsigned int numZones = numReaders; if (numZones > MAX_ZONES) { return logErrorWithStringError(UDS_INVALID_ARGUMENT, "zone count %u must not exceed MAX_ZONES", numZones); } unsigned long recordCount = 0; unsigned long collisionCount = 0; unsigned int firstList[MAX_ZONES]; unsigned int numLists[MAX_ZONES]; BufferedReader *reader[MAX_ZONES]; bool zoneFlags[MAX_ZONES] = { false, }; // Read the header from each file, and make sure we have a matching set unsigned int z; for (z = 0; z < numZones; z++) { struct di_header header; int result = readDeltaIndexHeader(bufferedReaders[z], &header); if (result != UDS_SUCCESS) { return logWarningWithStringError(result, "failed to read delta index header"); } if (memcmp(header.magic, MAGIC_DI_START, MAGIC_SIZE) != 0) { return logWarningWithStringError(UDS_CORRUPT_COMPONENT, "delta index file has bad magic" " number"); } if (numZones != header.numZones) { return logWarningWithStringError(UDS_CORRUPT_COMPONENT, "delta index files contain mismatched" " zone counts (%u,%u)", numZones, header.numZones); } if (header.zoneNumber >= numZones) { return logWarningWithStringError(UDS_CORRUPT_COMPONENT, "delta index files contains zone %u of" " %u zones", header.zoneNumber, numZones); } if (zoneFlags[header.zoneNumber]) { return logWarningWithStringError(UDS_CORRUPT_COMPONENT, "delta index files contain two of zone" " %u", header.zoneNumber); } reader[header.zoneNumber] = bufferedReaders[z]; firstList[header.zoneNumber] = header.firstList; numLists[header.zoneNumber] = header.numLists; zoneFlags[header.zoneNumber] = true; recordCount += header.recordCount; collisionCount += header.collisionCount; } unsigned int listNext = 0; for (z = 0; z < numZones; z++) { if (firstList[z] != listNext) { return logWarningWithStringError(UDS_CORRUPT_COMPONENT, "delta index file for zone %u starts" " with list %u instead of list %u", z, firstList[z], listNext); } listNext += numLists[z]; } if (listNext != deltaIndex->numLists) { return logWarningWithStringError(UDS_CORRUPT_COMPONENT, "delta index files contain %u delta lists" " instead of %u delta lists", listNext, deltaIndex->numLists); } if (collisionCount > recordCount) { return logWarningWithStringError(UDS_CORRUPT_COMPONENT, "delta index files contain %ld collisions" " and %ld records", collisionCount, recordCount); } emptyDeltaIndex(deltaIndex); deltaIndex->deltaZones[0].recordCount = recordCount; deltaIndex->deltaZones[0].collisionCount = collisionCount; // Read the delta list sizes from the files, and distribute each of them // to proper zone for (z = 0; z < numZones; z++) { unsigned int i; for (i = 0; i < numLists[z]; i++) { byte deltaListSizeData[sizeof(uint16_t)]; int result = readFromBufferedReader(reader[z], deltaListSizeData, sizeof(deltaListSizeData)); if (result != UDS_SUCCESS) { return logWarningWithStringError(result, "failed to read delta index size"); } uint16_t deltaListSize = getUInt16LE(deltaListSizeData); unsigned int listNumber = firstList[z] + i; unsigned int zoneNumber = getDeltaIndexZone(deltaIndex, listNumber); const DeltaMemory *deltaZone = &deltaIndex->deltaZones[zoneNumber]; listNumber -= deltaZone->firstList; deltaZone->deltaLists[listNumber + 1].size = deltaListSize; } } // Prepare each zone to start receiving the delta list data for (z = 0; z < deltaIndex->numZones; z++) { int result = startRestoringDeltaMemory(&deltaIndex->deltaZones[z]); if (result != UDS_SUCCESS) { return result; } } return UDS_SUCCESS; } /**********************************************************************/ bool isRestoringDeltaIndexDone(const DeltaIndex *deltaIndex) { unsigned int z; for (z = 0; z < deltaIndex->numZones; z++) { if (!areDeltaMemoryTransfersDone(&deltaIndex->deltaZones[z])) { return false; } } return true; } /**********************************************************************/ int restoreDeltaListToDeltaIndex(const DeltaIndex *deltaIndex, const DeltaListSaveInfo *dlsi, const byte data[DELTA_LIST_MAX_BYTE_COUNT]) { // Make sure the data are intended for this delta list. Do not // log an error, as this may be valid data for another delta index. if (dlsi->tag != deltaIndex->tag) { return UDS_CORRUPT_COMPONENT; } if (dlsi->index >= deltaIndex->numLists) { return logWarningWithStringError(UDS_CORRUPT_COMPONENT, "invalid delta list number %u of %u", dlsi->index, deltaIndex->numLists); } unsigned int zoneNumber = getDeltaIndexZone(deltaIndex, dlsi->index); return restoreDeltaList(&deltaIndex->deltaZones[zoneNumber], dlsi, data); } /**********************************************************************/ void abortRestoringDeltaIndex(const DeltaIndex *deltaIndex) { unsigned int z; for (z = 0; z < deltaIndex->numZones; z++) { abortRestoringDeltaMemory(&deltaIndex->deltaZones[z]); } } /**********************************************************************/ __attribute__((warn_unused_result)) static int encodeDeltaIndexHeader(Buffer *buffer, struct di_header *header) { int result = putBytes(buffer, MAGIC_SIZE, MAGIC_DI_START); if (result != UDS_SUCCESS) { return result; } result = putUInt32LEIntoBuffer(buffer, header->zoneNumber); if (result != UDS_SUCCESS) { return result; } result = putUInt32LEIntoBuffer(buffer, header->numZones); if (result != UDS_SUCCESS) { return result; } result = putUInt32LEIntoBuffer(buffer, header->firstList); if (result != UDS_SUCCESS) { return result; } result = putUInt32LEIntoBuffer(buffer, header->numLists); if (result != UDS_SUCCESS) { return result; } result = putUInt64LEIntoBuffer(buffer, header->recordCount); if (result != UDS_SUCCESS) { return result; } result = putUInt64LEIntoBuffer(buffer, header->collisionCount); if (result != UDS_SUCCESS) { return result; } result = ASSERT_LOG_ONLY(contentLength(buffer) == sizeof(*header), "%zu bytes encoded of %zu expected", contentLength(buffer), sizeof(*header)); return result; } /**********************************************************************/ int startSavingDeltaIndex(const DeltaIndex *deltaIndex, unsigned int zoneNumber, BufferedWriter *bufferedWriter) { DeltaMemory *deltaZone = &deltaIndex->deltaZones[zoneNumber]; struct di_header header; memcpy(header.magic, MAGIC_DI_START, MAGIC_SIZE); header.zoneNumber = zoneNumber; header.numZones = deltaIndex->numZones; header.firstList = deltaZone->firstList; header.numLists = deltaZone->numLists; header.recordCount = deltaZone->recordCount; header.collisionCount = deltaZone->collisionCount; Buffer *buffer; int result = makeBuffer(sizeof(struct di_header), &buffer); if (result != UDS_SUCCESS) { return result; } result = encodeDeltaIndexHeader(buffer, &header); if (result != UDS_SUCCESS) { freeBuffer(&buffer); return result; } result = writeToBufferedWriter(bufferedWriter, getBufferContents(buffer), contentLength(buffer)); freeBuffer(&buffer); if (result != UDS_SUCCESS) { return logWarningWithStringError(result, "failed to write delta index header"); } unsigned int i; for (i = 0; i < deltaZone->numLists; i++) { uint16_t deltaListSize = getDeltaListSize(&deltaZone->deltaLists[i + 1]); byte data[2]; storeUInt16LE(data, deltaListSize); result = writeToBufferedWriter(bufferedWriter, data, sizeof(data)); if (result != UDS_SUCCESS) { return logWarningWithStringError(result, "failed to write delta list size"); } } startSavingDeltaMemory(deltaZone, bufferedWriter); return UDS_SUCCESS; } /**********************************************************************/ bool isSavingDeltaIndexDone(const DeltaIndex *deltaIndex, unsigned int zoneNumber) { return areDeltaMemoryTransfersDone(&deltaIndex->deltaZones[zoneNumber]); } /**********************************************************************/ int finishSavingDeltaIndex(const DeltaIndex *deltaIndex, unsigned int zoneNumber) { return finishSavingDeltaMemory(&deltaIndex->deltaZones[zoneNumber]); } /**********************************************************************/ int abortSavingDeltaIndex(const DeltaIndex *deltaIndex, unsigned int zoneNumber) { abortSavingDeltaMemory(&deltaIndex->deltaZones[zoneNumber]); return UDS_SUCCESS; } /**********************************************************************/ size_t computeDeltaIndexSaveBytes(unsigned int numLists, size_t memorySize) { // The exact amount of memory used depends upon the number of zones. // Compute the maximum potential memory size. size_t maxMemSize = memorySize; unsigned int numZones; for (numZones = 1; numZones <= MAX_ZONES; numZones++) { size_t memSize = getZoneMemorySize(numZones, memorySize); if (memSize > maxMemSize) { maxMemSize = memSize; } } // Saving a delta index requires a header ... return (sizeof(struct di_header) // ... plus a DeltaListSaveInfo per delta list // plus an extra byte per delta list ... + numLists * (sizeof(DeltaListSaveInfo) + 1) // ... plus the delta list memory + maxMemSize); } /**********************************************************************/ int validateDeltaIndex(const DeltaIndex *deltaIndex) { unsigned int z; for (z = 0; z < deltaIndex->numZones; z++) { int result = validateDeltaLists(&deltaIndex->deltaZones[z]); if (result != UDS_SUCCESS) { return result; } } return UDS_SUCCESS; } /**********************************************************************/ static int assertNotAtEnd(const DeltaIndexEntry *deltaEntry, int errorCode) { return ASSERT_WITH_ERROR_CODE(!deltaEntry->atEnd, errorCode, "operation is invalid because the list entry " "is at the end of the delta list"); } /**********************************************************************/ static void prefetchDeltaList(const DeltaMemory *deltaZone, const DeltaList *deltaList) { const byte *memory = deltaZone->memory; const byte *addr = &memory[getDeltaListStart(deltaList) / CHAR_BIT]; unsigned int size = getDeltaListSize(deltaList) / CHAR_BIT; prefetchRange(addr, size, false); } /**********************************************************************/ int startDeltaIndexSearch(const DeltaIndex *deltaIndex, unsigned int listNumber, unsigned int key, bool readOnly, DeltaIndexEntry *deltaEntry) { int result = ASSERT_WITH_ERROR_CODE((listNumber < deltaIndex->numLists), UDS_CORRUPT_DATA, "Delta list number (%u) is out of range (%u)", listNumber, deltaIndex->numLists); if (result != UDS_SUCCESS) { return result; } unsigned int zoneNumber = getDeltaIndexZone(deltaIndex, listNumber); DeltaMemory *deltaZone = &deltaIndex->deltaZones[zoneNumber]; listNumber -= deltaZone->firstList; result = ASSERT_WITH_ERROR_CODE((listNumber < deltaZone->numLists), UDS_CORRUPT_DATA, "Delta list number (%u)" " is out of range (%u) for zone (%u)", listNumber, deltaZone->numLists, zoneNumber); if (result != UDS_SUCCESS) { return result; } DeltaList *deltaList; if (deltaIndex->isMutable) { deltaList = &deltaZone->deltaLists[listNumber + 1]; if (!readOnly) { // Here is the lazy writing of the index for a checkpoint lazyFlushDeltaList(deltaZone, listNumber); } } else { // Translate the immutable delta list header into a temporary full // delta list header deltaList = &deltaEntry->tempDeltaList; deltaList->startOffset = getImmutableStart(deltaZone->memory, listNumber); unsigned int endOffset = getImmutableStart(deltaZone->memory, listNumber + 1); deltaList->size = endOffset - deltaList->startOffset; deltaList->saveKey = 0; deltaList->saveOffset = 0; } if (key > deltaList->saveKey) { deltaEntry->key = deltaList->saveKey; deltaEntry->offset = deltaList->saveOffset; } else { deltaEntry->key = 0; deltaEntry->offset = 0; if (key == 0) { // This usually means we're about to walk the entire delta list, so get // all of it into the CPU cache. prefetchDeltaList(deltaZone, deltaList); } } deltaEntry->atEnd = false; deltaEntry->deltaZone = deltaZone; deltaEntry->deltaList = deltaList; deltaEntry->entryBits = 0; deltaEntry->isCollision = false; deltaEntry->listNumber = listNumber; deltaEntry->listOverflow = false; deltaEntry->valueBits = deltaZone->valueBits; return UDS_SUCCESS; } /**********************************************************************/ __attribute__((__noinline__)) int nextDeltaIndexEntry(DeltaIndexEntry *deltaEntry) { int result = assertNotAtEnd(deltaEntry, UDS_BAD_STATE); if (result != UDS_SUCCESS) { return result; } const DeltaList *deltaList = deltaEntry->deltaList; deltaEntry->offset += deltaEntry->entryBits; unsigned int size = getDeltaListSize(deltaList); if (unlikely(deltaEntry->offset >= size)) { deltaEntry->atEnd = true; deltaEntry->delta = 0; deltaEntry->isCollision = false; return ASSERT_WITH_ERROR_CODE((deltaEntry->offset == size), UDS_CORRUPT_DATA, "next offset past end of delta list"); } decodeDelta(deltaEntry); unsigned int nextOffset = deltaEntry->offset + deltaEntry->entryBits; if (nextOffset > size) { // This is not an assertion because validateChapterIndexPage() wants to // handle this error. logWarning("Decoded past the end of the delta list"); return UDS_CORRUPT_DATA; } return UDS_SUCCESS; } /**********************************************************************/ int rememberDeltaIndexOffset(const DeltaIndexEntry *deltaEntry) { int result = ASSERT(!deltaEntry->isCollision, "entry is not a collision"); if (result != UDS_SUCCESS) { return result; } DeltaList *deltaList = deltaEntry->deltaList; deltaList->saveKey = deltaEntry->key - deltaEntry->delta; deltaList->saveOffset = deltaEntry->offset; return UDS_SUCCESS; } /**********************************************************************/ int getDeltaIndexEntry(const DeltaIndex *deltaIndex, unsigned int listNumber, unsigned int key, const byte *name, bool readOnly, DeltaIndexEntry *deltaEntry) { int result = startDeltaIndexSearch(deltaIndex, listNumber, key, readOnly, deltaEntry); if (result != UDS_SUCCESS) { return result; } do { result = nextDeltaIndexEntry(deltaEntry); if (result != UDS_SUCCESS) { return result; } } while (!deltaEntry->atEnd && (key > deltaEntry->key)); result = rememberDeltaIndexOffset(deltaEntry); if (result != UDS_SUCCESS) { return result; } if (!deltaEntry->atEnd && (key == deltaEntry->key)) { DeltaIndexEntry collisionEntry; collisionEntry = *deltaEntry; for (;;) { result = nextDeltaIndexEntry(&collisionEntry); if (result != UDS_SUCCESS) { return result; } if (collisionEntry.atEnd || !collisionEntry.isCollision) { break; } byte collisionName[COLLISION_BYTES]; getBytes(deltaEntry->deltaZone->memory, getCollisionOffset(&collisionEntry), collisionName, COLLISION_BYTES); if (memcmp(collisionName, name, COLLISION_BYTES) == 0) { *deltaEntry = collisionEntry; break; } } } return UDS_SUCCESS; } /**********************************************************************/ int getDeltaEntryCollision(const DeltaIndexEntry *deltaEntry, byte *name) { int result = assertNotAtEnd(deltaEntry, UDS_BAD_STATE); if (result != UDS_SUCCESS) { return result; } result = ASSERT_WITH_ERROR_CODE(deltaEntry->isCollision, UDS_BAD_STATE, "Cannot get full block name from a" " non-collision delta index entry"); if (result != UDS_SUCCESS) { return result; } getBytes(deltaEntry->deltaZone->memory, getCollisionOffset(deltaEntry), name, COLLISION_BYTES); return UDS_SUCCESS; } /**********************************************************************/ static int assertMutableEntry(const DeltaIndexEntry *deltaEntry) { return ASSERT_WITH_ERROR_CODE(deltaEntry->deltaList != &deltaEntry->tempDeltaList, UDS_BAD_STATE, "delta index is mutable"); } /**********************************************************************/ int setDeltaEntryValue(const DeltaIndexEntry *deltaEntry, unsigned int value) { int result = assertMutableEntry(deltaEntry); if (result != UDS_SUCCESS) { return result; } result = assertNotAtEnd(deltaEntry, UDS_BAD_STATE); if (result != UDS_SUCCESS) { return result; } result = ASSERT_WITH_ERROR_CODE(((value & ((1 << deltaEntry->valueBits) - 1)) == value), UDS_INVALID_ARGUMENT, "Value (%u) being set in a delta index is " "too large (must fit in %u bits)", value, deltaEntry->valueBits); if (result != UDS_SUCCESS) { return result; } setField(value, deltaEntry->deltaZone->memory, getDeltaEntryOffset(deltaEntry), deltaEntry->valueBits); return UDS_SUCCESS; } /**********************************************************************/ int putDeltaIndexEntry(DeltaIndexEntry *deltaEntry, unsigned int key, unsigned int value, const byte *name) { int result = assertMutableEntry(deltaEntry); if (result != UDS_SUCCESS) { return result; } if (deltaEntry->isCollision) { /* * The caller wants us to insert a collision entry onto a collision * entry. This happens when we find a collision and attempt to add the * name again to the index. This is normally a fatal error unless we * are replaying a closed chapter while we are rebuilding a master * index. */ return UDS_DUPLICATE_NAME; } if (deltaEntry->offset < deltaEntry->deltaList->saveOffset) { // The saved entry offset is after the new entry and will no longer be // valid, so replace it with the insertion point. result = rememberDeltaIndexOffset(deltaEntry); if (result != UDS_SUCCESS) { return result; } } if (name != NULL) { // We are inserting a collision entry which is placed after this entry result = assertNotAtEnd(deltaEntry, UDS_BAD_STATE); if (result != UDS_SUCCESS) { return result; } result = ASSERT((key == deltaEntry->key), "incorrect key for collision entry"); if (result != UDS_SUCCESS) { return result; } deltaEntry->offset += deltaEntry->entryBits; setDelta(deltaEntry, 0); setCollision(deltaEntry); result = insertBits(deltaEntry, deltaEntry->entryBits); } else if (deltaEntry->atEnd) { // We are inserting a new entry at the end of the delta list result = ASSERT((key >= deltaEntry->key), "key past end of list"); if (result != UDS_SUCCESS) { return result; } setDelta(deltaEntry, key - deltaEntry->key); deltaEntry->key = key; deltaEntry->atEnd = false; result = insertBits(deltaEntry, deltaEntry->entryBits); } else { // We are inserting a new entry which requires the delta in the // following entry to be updated. result = ASSERT((key < deltaEntry->key), "key precedes following entry"); if (result != UDS_SUCCESS) { return result; } result = ASSERT((key >= deltaEntry->key - deltaEntry->delta), "key effects following entry's delta"); if (result != UDS_SUCCESS) { return result; } int oldEntrySize = deltaEntry->entryBits; DeltaIndexEntry nextEntry = *deltaEntry; unsigned int nextValue = getDeltaEntryValue(&nextEntry); setDelta(deltaEntry, key - (deltaEntry->key - deltaEntry->delta)); deltaEntry->key = key; setDelta(&nextEntry, nextEntry.key - key); nextEntry.offset += deltaEntry->entryBits; // The 2 new entries are always bigger than the 1 entry we are replacing int additionalSize = deltaEntry->entryBits + nextEntry.entryBits - oldEntrySize; result = insertBits(deltaEntry, additionalSize); if (result != UDS_SUCCESS) { return result; } encodeEntry(&nextEntry, nextValue, NULL); } if (result != UDS_SUCCESS) { return result; } encodeEntry(deltaEntry, value, name); DeltaMemory *deltaZone = deltaEntry->deltaZone; deltaZone->recordCount++; deltaZone->collisionCount += deltaEntry->isCollision ? 1 : 0; return UDS_SUCCESS; } /**********************************************************************/ int removeDeltaIndexEntry(DeltaIndexEntry *deltaEntry) { int result = assertMutableEntry(deltaEntry); if (result != UDS_SUCCESS) { return result; } DeltaIndexEntry nextEntry = *deltaEntry; result = nextDeltaIndexEntry(&nextEntry); if (result != UDS_SUCCESS) { return result; } DeltaMemory *deltaZone = deltaEntry->deltaZone; if (deltaEntry->isCollision) { // This is a collision entry, so just remove it deleteBits(deltaEntry, deltaEntry->entryBits); nextEntry.offset = deltaEntry->offset; deltaZone->collisionCount -= 1; } else if (nextEntry.atEnd) { // This entry is at the end of the list, so just remove it deleteBits(deltaEntry, deltaEntry->entryBits); nextEntry.key -= deltaEntry->delta; nextEntry.offset = deltaEntry->offset; } else { // The delta in the next entry needs to be updated. unsigned int nextValue = getDeltaEntryValue(&nextEntry); int oldSize = deltaEntry->entryBits + nextEntry.entryBits; if (nextEntry.isCollision) { // The next record is a collision. It needs to be rewritten as a // non-collision with a larger delta. nextEntry.isCollision = false; deltaZone->collisionCount -= 1; } setDelta(&nextEntry, deltaEntry->delta + nextEntry.delta); nextEntry.offset = deltaEntry->offset; // The 1 new entry is always smaller than the 2 entries we are replacing deleteBits(deltaEntry, oldSize - nextEntry.entryBits); encodeEntry(&nextEntry, nextValue, NULL); } deltaZone->recordCount--; deltaZone->discardCount++; *deltaEntry = nextEntry; DeltaList *deltaList = deltaEntry->deltaList; if (deltaEntry->offset < deltaList->saveOffset) { // The saved entry offset is after the entry we just removed and it // will no longer be valid. We must force the next search to start at // the beginning. deltaList->saveKey = 0; deltaList->saveOffset = 0; } return UDS_SUCCESS; } /**********************************************************************/ unsigned int getDeltaIndexZoneFirstList(const DeltaIndex *deltaIndex, unsigned int zoneNumber) { return deltaIndex->deltaZones[zoneNumber].firstList; } /**********************************************************************/ unsigned int getDeltaIndexZoneNumLists(const DeltaIndex *deltaIndex, unsigned int zoneNumber) { return deltaIndex->deltaZones[zoneNumber].numLists; } /**********************************************************************/ uint64_t getDeltaIndexZoneDlistBitsUsed(const DeltaIndex *deltaIndex, unsigned int zoneNumber) { uint64_t bitCount = 0; const DeltaMemory *deltaZone = &deltaIndex->deltaZones[zoneNumber]; unsigned int i; for (i = 0; i < deltaZone->numLists; i++) { bitCount += getDeltaListSize(&deltaZone->deltaLists[i + 1]); } return bitCount; } /**********************************************************************/ uint64_t getDeltaIndexDlistBitsUsed(const DeltaIndex *deltaIndex) { uint64_t bitCount = 0; unsigned int z; for (z = 0; z < deltaIndex->numZones; z++) { bitCount += getDeltaIndexZoneDlistBitsUsed(deltaIndex, z); } return bitCount; } /**********************************************************************/ uint64_t getDeltaIndexDlistBitsAllocated(const DeltaIndex *deltaIndex) { uint64_t byteCount = 0; unsigned int z; for (z = 0; z < deltaIndex->numZones; z++) { const DeltaMemory *deltaZone = &deltaIndex->deltaZones[z]; byteCount += deltaZone->size; } return byteCount * CHAR_BIT; } /**********************************************************************/ void getDeltaIndexStats(const DeltaIndex *deltaIndex, DeltaIndexStats *stats) { memset(stats, 0, sizeof(DeltaIndexStats)); stats->memoryAllocated = deltaIndex->numZones * sizeof(DeltaMemory); unsigned int z; for (z = 0; z < deltaIndex->numZones; z++) { const DeltaMemory *deltaZone = &deltaIndex->deltaZones[z]; stats->memoryAllocated += getDeltaMemoryAllocated(deltaZone); stats->rebalanceTime += deltaZone->rebalanceTime; stats->rebalanceCount += deltaZone->rebalanceCount; stats->recordCount += deltaZone->recordCount; stats->collisionCount += deltaZone->collisionCount; stats->discardCount += deltaZone->discardCount; stats->overflowCount += deltaZone->overflowCount; stats->numLists += deltaZone->numLists; } } /**********************************************************************/ unsigned int getDeltaIndexPageCount(unsigned int numEntries, unsigned int numLists, unsigned int meanDelta, unsigned int numPayloadBits, size_t bytesPerPage) { // Compute the number of bits needed for all the entries size_t bitsPerIndex = getDeltaMemorySize(numEntries, meanDelta, numPayloadBits); // Compute the number of bits needed for a single delta list unsigned int bitsPerDeltaList = bitsPerIndex / numLists; // Adjust the bits per index, adding the immutable delta list headers bitsPerIndex += numLists * IMMUTABLE_HEADER_SIZE; // Compute the number of usable bits on an immutable index page unsigned int bitsPerPage = (bytesPerPage - sizeof(DeltaPageHeader)) * CHAR_BIT; // Adjust the bits per page, taking away one immutable delta list header // and one delta list representing internal fragmentation bitsPerPage -= IMMUTABLE_HEADER_SIZE + bitsPerDeltaList; // Now compute the number of pages needed return (bitsPerIndex + bitsPerPage - 1) / bitsPerPage; } /**********************************************************************/ void logDeltaIndexEntry(DeltaIndexEntry *deltaEntry) { logRatelimit(logInfo, "List 0x%X Key 0x%X Offset 0x%X%s%s ListSize 0x%X%s", deltaEntry->listNumber, deltaEntry->key, deltaEntry->offset, deltaEntry->atEnd ? " end" : "", deltaEntry->isCollision ? " collision" : "", getDeltaListSize(deltaEntry->deltaList), deltaEntry->listOverflow ? " overflow" : ""); deltaEntry->listOverflow = false; }