/* * 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/masterIndex005.c#3 $ */ #include "masterIndex005.h" #include "buffer.h" #include "compiler.h" #include "errors.h" #include "hashUtils.h" #include "logger.h" #include "memoryAlloc.h" #include "uds.h" #include "zone.h" /* * The master index is a kept as a delta index where the payload is a * chapter number. The master index adds 2 basic functions to the delta * index: * * (1) How to get the delta list number and address out of the chunk name. * * (2) Dealing with chapter numbers, and especially the lazy flushing of * chapters from the index. * * There are three ways of expressing chapter numbers: virtual, index, and * rolling. The interface to the the master index uses virtual chapter * numbers, which are 64 bits long. We do not store such large values in * memory, so we internally use a binary value using the minimal number of * bits. * * The delta index stores the index chapter number, which is the low-order * bits of the virtual chapter number. * * When we need to deal with ordering of index chapter numbers, we roll the * index chapter number around so that the smallest one we are using has * the representation 0. See convertIndexToVirtual() or * flushInvalidEntries() for an example of this technique. */ typedef struct __attribute__((aligned(CACHE_LINE_BYTES))) masterIndexZone { uint64_t virtualChapterLow; // The lowest virtual chapter indexed uint64_t virtualChapterHigh; // The highest virtual chapter indexed long numEarlyFlushes; // The number of early flushes } MasterIndexZone; typedef struct { MasterIndex common; // Common master index methods DeltaIndex deltaIndex; // The delta index uint64_t *flushChapters; // The first chapter to be flushed MasterIndexZone *masterZones; // The Zones uint64_t volumeNonce; // The volume nonce uint64_t chapterZoneBits; // Expected size of a chapter (per zone) uint64_t maxZoneBits; // Maximum size index (per zone) unsigned int addressBits; // Number of bits in address mask unsigned int addressMask; // Mask to get address within delta list unsigned int chapterBits; // Number of bits in chapter number unsigned int chapterMask; // Largest storable chapter number unsigned int numChapters; // Number of chapters used unsigned int numDeltaLists; // The number of delta lists unsigned int numZones; // The number of zones } MasterIndex5; typedef struct chapterRange { unsigned int chapterStart; // The first chapter unsigned int chapterCount; // The number of chapters } ChapterRange; // Constants for the magic byte of a MasterIndexRecord static const byte masterIndexRecordMagic = 0xAA; static const byte badMagic = 0; /* * In production, the default value for minMasterIndexDeltaLists will be * replaced by MAX_ZONES*MAX_ZONES. Some unit tests will replace * minMasterIndexDeltaLists with the non-default value 1, because those * tests really want to run with a single delta list. */ unsigned int minMasterIndexDeltaLists; /** * Maximum of two unsigned ints * * @param a One unsigned int * @param b Another unsigned int * * @return the bigger one **/ static INLINE unsigned int maxUint(unsigned int a, unsigned int b) { return a > b ? a : b; } /** * Extract the address from a block name. * * @param mi5 The master index * @param name The block name * * @return the address **/ static INLINE unsigned int extractAddress(const MasterIndex5 *mi5, const UdsChunkName *name) { return extractMasterIndexBytes(name) & mi5->addressMask; } /** * Extract the delta list number from a block name. * * @param mi5 The master index * @param name The block name * * @return the delta list number **/ static INLINE unsigned int extractDListNum(const MasterIndex5 *mi5, const UdsChunkName *name) { uint64_t bits = extractMasterIndexBytes(name); return (bits >> mi5->addressBits) % mi5->numDeltaLists; } /** * Get the master index zone containing a given master index record * * @param record The master index record * * @return the master index zone **/ static INLINE const MasterIndexZone *getMasterZone(const MasterIndexRecord *record) { const MasterIndex5 *mi5 = container_of(record->masterIndex, MasterIndex5, common); return &mi5->masterZones[record->zoneNumber]; } /** * Convert an index chapter number to a virtual chapter number. * * @param record The master index record * @param indexChapter The index chapter number * * @return the virtual chapter number **/ static INLINE uint64_t convertIndexToVirtual(const MasterIndexRecord *record, unsigned int indexChapter) { const MasterIndex5 *mi5 = container_of(record->masterIndex, MasterIndex5, common); const MasterIndexZone *masterZone = getMasterZone(record); unsigned int rollingChapter = ((indexChapter - masterZone->virtualChapterLow) & mi5->chapterMask); return masterZone->virtualChapterLow + rollingChapter; } /** * Convert a virtual chapter number to an index chapter number. * * @param mi5 The master index * @param virtualChapter The virtual chapter number * * @return the index chapter number **/ static INLINE unsigned int convertVirtualToIndex(const MasterIndex5 *mi5, uint64_t virtualChapter) { return virtualChapter & mi5->chapterMask; } /** * Determine whether a virtual chapter number is in the range being indexed * * @param record The master index record * @param virtualChapter The virtual chapter number * * @return true if the virtual chapter number is being indexed **/ static INLINE bool isVirtualChapterIndexed(const MasterIndexRecord *record, uint64_t virtualChapter) { const MasterIndexZone *masterZone = getMasterZone(record); return ((virtualChapter >= masterZone->virtualChapterLow) && (virtualChapter <= masterZone->virtualChapterHigh)); } /***********************************************************************/ /** * Flush an invalid entry from the master index, advancing to the next * valid entry. * * @param record Updated to describe the next valid record * @param flushRange Range of chapters to flush from the index * @param nextChapterToInvalidate Updated to record the next chapter that we * will need to invalidate * * @return UDS_SUCCESS or an error code **/ static INLINE int flushInvalidEntries(MasterIndexRecord *record, ChapterRange *flushRange, unsigned int *nextChapterToInvalidate) { const MasterIndex5 *mi5 = container_of(record->masterIndex, MasterIndex5, common); int result = nextDeltaIndexEntry(&record->deltaEntry); if (result != UDS_SUCCESS) { return result; } while (!record->deltaEntry.atEnd) { unsigned int indexChapter = getDeltaEntryValue(&record->deltaEntry); unsigned int relativeChapter = ((indexChapter - flushRange->chapterStart) & mi5->chapterMask); if (likely(relativeChapter >= flushRange->chapterCount)) { if (relativeChapter < *nextChapterToInvalidate) { *nextChapterToInvalidate = relativeChapter; } break; } result = removeDeltaIndexEntry(&record->deltaEntry); if (result != UDS_SUCCESS) { return result; } } return UDS_SUCCESS; } /** * Find the delta index entry, or the insertion point for a delta index * entry, while processing chapter LRU flushing. * * @param record Updated to describe the entry being looked for * @param listNumber The delta list number * @param key The address field being looked for * @param flushRange The range of chapters to flush from the index * * @return UDS_SUCCESS or an error code **/ static int getMasterIndexEntry(MasterIndexRecord *record, unsigned int listNumber, unsigned int key, ChapterRange *flushRange) { const MasterIndex5 *mi5 = container_of(record->masterIndex, MasterIndex5, common); unsigned int nextChapterToInvalidate = mi5->chapterMask; int result = startDeltaIndexSearch(&mi5->deltaIndex, listNumber, 0, false, &record->deltaEntry); if (result != UDS_SUCCESS) { return result; } do { result = flushInvalidEntries(record, flushRange, &nextChapterToInvalidate); if (result != UDS_SUCCESS) { return result; } } while (!record->deltaEntry.atEnd && (key > record->deltaEntry.key)); result = rememberDeltaIndexOffset(&record->deltaEntry); if (result != UDS_SUCCESS) { return result; } // We probably found the record we want, but we need to keep going MasterIndexRecord otherRecord = *record; if (!otherRecord.deltaEntry.atEnd && (key == otherRecord.deltaEntry.key)) { for (;;) { result = flushInvalidEntries(&otherRecord, flushRange, &nextChapterToInvalidate); if (result != UDS_SUCCESS) { return result; } if (otherRecord.deltaEntry.atEnd || !otherRecord.deltaEntry.isCollision) { break; } byte collisionName[UDS_CHUNK_NAME_SIZE]; result = getDeltaEntryCollision(&otherRecord.deltaEntry, collisionName); if (result != UDS_SUCCESS) { return result; } if (memcmp(collisionName, record->name, UDS_CHUNK_NAME_SIZE) == 0) { // This collision record is the one we are looking for *record = otherRecord; break; } } } while (!otherRecord.deltaEntry.atEnd) { result = flushInvalidEntries(&otherRecord, flushRange, &nextChapterToInvalidate); if (result != UDS_SUCCESS) { return result; } } nextChapterToInvalidate += flushRange->chapterStart; nextChapterToInvalidate &= mi5->chapterMask; flushRange->chapterStart = nextChapterToInvalidate; flushRange->chapterCount = 0; return UDS_SUCCESS; } /***********************************************************************/ /** * Terminate and clean up the master index * * @param masterIndex The master index to terminate **/ static void freeMasterIndex_005(MasterIndex *masterIndex) { if (masterIndex != NULL) { MasterIndex5 *mi5 = container_of(masterIndex, MasterIndex5, common); FREE(mi5->flushChapters); mi5->flushChapters = NULL; FREE(mi5->masterZones); mi5->masterZones = NULL; uninitializeDeltaIndex(&mi5->deltaIndex); FREE(masterIndex); } } /** * Constants and structures for the saved master index file. "MI5" is for * masterIndex005, and "-XXXX" is a number to increment when the format of * the data changes. **/ enum { MAGIC_SIZE = 8 }; static const char MAGIC_MI_START[] = "MI5-0005"; struct mi005_data { char magic[MAGIC_SIZE]; // MAGIC_MI_START uint64_t volumeNonce; uint64_t virtualChapterLow; uint64_t virtualChapterHigh; unsigned int firstList; unsigned int numLists; }; /***********************************************************************/ /** * Set the tag value used when saving and/or restoring a master index. * * @param masterIndex The master index * @param tag The tag value **/ static void setMasterIndexTag_005(MasterIndex *masterIndex, byte tag) { MasterIndex5 *mi5 = container_of(masterIndex, MasterIndex5, common); setDeltaIndexTag(&mi5->deltaIndex, tag); } /***********************************************************************/ __attribute__((warn_unused_result)) static int encodeMasterIndexHeader(Buffer *buffer, struct mi005_data *header) { int result = putBytes(buffer, MAGIC_SIZE, MAGIC_MI_START); if (result != UDS_SUCCESS) { return result; } result = putUInt64LEIntoBuffer(buffer, header->volumeNonce); if (result != UDS_SUCCESS) { return result; } result = putUInt64LEIntoBuffer(buffer, header->virtualChapterLow); if (result != UDS_SUCCESS) { return result; } result = putUInt64LEIntoBuffer(buffer, header->virtualChapterHigh); 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 = ASSERT_LOG_ONLY(contentLength(buffer) == sizeof(struct mi005_data), "%zu bytes of config written, of %zu expected", contentLength(buffer), sizeof(struct mi005_data)); return result; } /** * Start saving a master index to a buffered output stream. * * @param masterIndex The master index * @param zoneNumber The number of the zone to save * @param bufferedWriter The index state component being written * * @return UDS_SUCCESS on success, or an error code on failure **/ static int startSavingMasterIndex_005(const MasterIndex *masterIndex, unsigned int zoneNumber, BufferedWriter *bufferedWriter) { const MasterIndex5 *mi5 = const_container_of(masterIndex, MasterIndex5, common); MasterIndexZone *masterZone = &mi5->masterZones[zoneNumber]; unsigned int firstList = getDeltaIndexZoneFirstList(&mi5->deltaIndex, zoneNumber); unsigned int numLists = getDeltaIndexZoneNumLists(&mi5->deltaIndex, zoneNumber); struct mi005_data header; memset(&header, 0, sizeof(header)); memcpy(header.magic, MAGIC_MI_START, MAGIC_SIZE); header.volumeNonce = mi5->volumeNonce; header.virtualChapterLow = masterZone->virtualChapterLow; header.virtualChapterHigh = masterZone->virtualChapterHigh; header.firstList = firstList; header.numLists = numLists; Buffer *buffer; int result = makeBuffer(sizeof(struct mi005_data), &buffer); if (result != UDS_SUCCESS) { return result; } result = encodeMasterIndexHeader(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 master index header"); } result = makeBuffer(numLists * sizeof(uint64_t), &buffer); if (result != UDS_SUCCESS) { return result; } uint64_t *firstFlushChapter = &mi5->flushChapters[firstList]; result = putUInt64LEsIntoBuffer(buffer, numLists, firstFlushChapter); 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 master index flush " "ranges"); } return startSavingDeltaIndex(&mi5->deltaIndex, zoneNumber, bufferedWriter); } /***********************************************************************/ /** * Have all the data been written while saving a master index to an output * stream? If the answer is yes, it is still necessary to call * finishSavingMasterIndex(), which will return quickly. * * @param masterIndex The master index * @param zoneNumber The number of the zone to save * * @return true if all the data are written **/ static bool isSavingMasterIndexDone_005(const MasterIndex *masterIndex, unsigned int zoneNumber) { const MasterIndex5 *mi5 = const_container_of(masterIndex, MasterIndex5, common); return isSavingDeltaIndexDone(&mi5->deltaIndex, zoneNumber); } /***********************************************************************/ /** * Finish saving a master index to an output stream. Force the writing of * all of the remaining data. If an error occurred asynchronously during * the save operation, it will be returned here. * * @param masterIndex The master index * @param zoneNumber The number of the zone to save * * @return UDS_SUCCESS on success, or an error code on failure **/ static int finishSavingMasterIndex_005(const MasterIndex *masterIndex, unsigned int zoneNumber) { const MasterIndex5 *mi5 = const_container_of(masterIndex, MasterIndex5, common); return finishSavingDeltaIndex(&mi5->deltaIndex, zoneNumber); } /***********************************************************************/ /** * Abort saving a master index to an output stream. If an error occurred * asynchronously during the save operation, it will be dropped. * * @param masterIndex The master index * @param zoneNumber The number of the zone to save * * @return UDS_SUCCESS on success, or an error code on failure **/ static int abortSavingMasterIndex_005(const MasterIndex *masterIndex, unsigned int zoneNumber) { const MasterIndex5 *mi5 = const_container_of(masterIndex, MasterIndex5, common); return abortSavingDeltaIndex(&mi5->deltaIndex, zoneNumber); } /***********************************************************************/ __attribute__((warn_unused_result)) static int decodeMasterIndexHeader(Buffer *buffer, struct mi005_data *header) { int result = getBytesFromBuffer(buffer, sizeof(header->magic), &header->magic); if (result != UDS_SUCCESS) { return result; } result = getUInt64LEFromBuffer(buffer, &header->volumeNonce); if (result != UDS_SUCCESS) { return result; } result = getUInt64LEFromBuffer(buffer, &header->virtualChapterLow); if (result != UDS_SUCCESS) { return result; } result = getUInt64LEFromBuffer(buffer, &header->virtualChapterHigh); 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 = ASSERT_LOG_ONLY(contentLength(buffer) == 0, "%zu bytes decoded of %zu expected", bufferLength(buffer) - contentLength(buffer), bufferLength(buffer)); if (result != UDS_SUCCESS) { result = UDS_CORRUPT_COMPONENT; } return result; } /** * Start restoring the master index from multiple buffered readers * * @param masterIndex The master index to restore into * @param bufferedReaders The buffered readers to read the master index from * @param numReaders The number of buffered readers * * @return UDS_SUCCESS on success, or an error code on failure **/ static int startRestoringMasterIndex_005(MasterIndex *masterIndex, BufferedReader **bufferedReaders, int numReaders) { if (masterIndex == NULL) { return logWarningWithStringError(UDS_BAD_STATE, "cannot restore to null master index"); } MasterIndex5 *mi5 = container_of(masterIndex, MasterIndex5, common); emptyDeltaIndex(&mi5->deltaIndex); uint64_t virtualChapterLow = 0; uint64_t virtualChapterHigh = 0; int i; for (i = 0; i < numReaders; i++) { Buffer *buffer; int result = makeBuffer(sizeof(struct mi005_data), &buffer); if (result != UDS_SUCCESS) { return result; } result = readFromBufferedReader(bufferedReaders[i], getBufferContents(buffer), bufferLength(buffer)); if (result != UDS_SUCCESS) { freeBuffer(&buffer); return logWarningWithStringError(result, "failed to read master index header"); } result = resetBufferEnd(buffer, bufferLength(buffer)); if (result != UDS_SUCCESS) { freeBuffer(&buffer); return result; } struct mi005_data header; result = decodeMasterIndexHeader(buffer, &header); freeBuffer(&buffer); if (result != UDS_SUCCESS) { return result; } if (memcmp(header.magic, MAGIC_MI_START, MAGIC_SIZE) != 0) { return logWarningWithStringError(UDS_CORRUPT_COMPONENT, "master index file had bad magic" " number"); } if (mi5->volumeNonce == 0) { mi5->volumeNonce = header.volumeNonce; } else if (header.volumeNonce != mi5->volumeNonce) { return logWarningWithStringError(UDS_CORRUPT_COMPONENT, "master index volume nonce incorrect"); } if (i == 0) { virtualChapterLow = header.virtualChapterLow; virtualChapterHigh = header.virtualChapterHigh; } else if (virtualChapterHigh != header.virtualChapterHigh) { return logWarningWithStringError(UDS_CORRUPT_COMPONENT, "Inconsistent master index zone files:" " Chapter range is [%llu,%" PRIu64 "], chapter range %d is [%" PRIu64 ",%llu]", virtualChapterLow, virtualChapterHigh, i, header.virtualChapterLow, header.virtualChapterHigh); } else if (virtualChapterLow < header.virtualChapterLow) { virtualChapterLow = header.virtualChapterLow; } uint64_t *firstFlushChapter = &mi5->flushChapters[header.firstList]; result = makeBuffer(header.numLists * sizeof(uint64_t), &buffer); if (result != UDS_SUCCESS) { return result; } result = readFromBufferedReader(bufferedReaders[i], getBufferContents(buffer), bufferLength(buffer)); if (result != UDS_SUCCESS) { freeBuffer(&buffer); return logWarningWithStringError(result, "failed to read master index flush" " ranges"); } result = resetBufferEnd(buffer, bufferLength(buffer)); if (result != UDS_SUCCESS) { freeBuffer(&buffer); return result; } result = getUInt64LEsFromBuffer(buffer, header.numLists, firstFlushChapter); freeBuffer(&buffer); if (result != UDS_SUCCESS) { return result; } } unsigned int z; for (z = 0; z < mi5->numZones; z++) { memset(&mi5->masterZones[z], 0, sizeof(MasterIndexZone)); mi5->masterZones[z].virtualChapterLow = virtualChapterLow; mi5->masterZones[z].virtualChapterHigh = virtualChapterHigh; } int result = startRestoringDeltaIndex(&mi5->deltaIndex, bufferedReaders, numReaders); if (result != UDS_SUCCESS) { return logWarningWithStringError(result, "restoring delta index failed"); } return UDS_SUCCESS; } /***********************************************************************/ /** * Have all the data been read while restoring a master index from an * input stream? * * @param masterIndex The master index to restore into * * @return true if all the data are read **/ static bool isRestoringMasterIndexDone_005(const MasterIndex *masterIndex) { const MasterIndex5 *mi5 = const_container_of(masterIndex, MasterIndex5, common); return isRestoringDeltaIndexDone(&mi5->deltaIndex); } /***********************************************************************/ /** * Restore a saved delta list * * @param masterIndex The master index to restore into * @param dlsi The DeltaListSaveInfo describing the delta list * @param data The saved delta list bit stream * * @return error code or UDS_SUCCESS **/ static int restoreDeltaListToMasterIndex_005(MasterIndex *masterIndex, const DeltaListSaveInfo *dlsi, const byte data[DELTA_LIST_MAX_BYTE_COUNT]) { MasterIndex5 *mi5 = container_of(masterIndex, MasterIndex5, common); return restoreDeltaListToDeltaIndex(&mi5->deltaIndex, dlsi, data); } /***********************************************************************/ /** * Abort restoring a master index from an input stream. * * @param masterIndex The master index **/ static void abortRestoringMasterIndex_005(MasterIndex *masterIndex) { MasterIndex5 *mi5 = container_of(masterIndex, MasterIndex5, common); abortRestoringDeltaIndex(&mi5->deltaIndex); } /***********************************************************************/ static void removeNewestChapters(MasterIndex5 *mi5, unsigned int zoneNumber, uint64_t virtualChapter) { // Get the range of delta lists belonging to this zone unsigned int firstList = getDeltaIndexZoneFirstList(&mi5->deltaIndex, zoneNumber); unsigned int numLists = getDeltaIndexZoneNumLists(&mi5->deltaIndex, zoneNumber); unsigned int lastList = firstList + numLists - 1; if (virtualChapter > mi5->chapterMask) { // The virtual chapter number is large enough so that we can use the // normal LRU mechanism without an unsigned underflow. virtualChapter -= mi5->chapterMask + 1; // Eliminate the newest chapters by renumbering them to become the // oldest chapters unsigned int i; for (i = firstList; i <= lastList; i++) { if (virtualChapter < mi5->flushChapters[i]) { mi5->flushChapters[i] = virtualChapter; } } } else { // Underflow will prevent the fast path. Do it the slow and painful way. MasterIndexZone *masterZone = &mi5->masterZones[zoneNumber]; ChapterRange range; range.chapterStart = convertVirtualToIndex(mi5, virtualChapter); range.chapterCount = (mi5->chapterMask + 1 - (virtualChapter - masterZone->virtualChapterLow)); UdsChunkName name; memset(&name, 0, sizeof(UdsChunkName)); MasterIndexRecord record = (MasterIndexRecord) { .magic = masterIndexRecordMagic, .masterIndex = &mi5->common, .name = &name, .zoneNumber = zoneNumber, }; unsigned int i; for (i = firstList; i <= lastList; i++) { ChapterRange tempRange = range; getMasterIndexEntry(&record, i, 0, &tempRange); } } } /***********************************************************************/ /** * Set the open chapter number on a zone. The master index zone will be * modified to index the proper number of chapters ending with the new open * chapter. * * @param masterIndex The master index * @param zoneNumber The zone number * @param virtualChapter The new open chapter number **/ static void setMasterIndexZoneOpenChapter_005(MasterIndex *masterIndex, unsigned int zoneNumber, uint64_t virtualChapter) { MasterIndex5 *mi5 = container_of(masterIndex, MasterIndex5, common); MasterIndexZone *masterZone = &mi5->masterZones[zoneNumber]; // Take care here to avoid underflow of an unsigned value. Note that // this is the smallest valid virtual low. We may or may not actually // use this value. uint64_t newVirtualLow = (virtualChapter >= mi5->numChapters ? virtualChapter - mi5->numChapters + 1 : 0); if (virtualChapter <= masterZone->virtualChapterLow) { /* * Moving backwards and the new range is totally before the old range. * Note that moving to the lowest virtual chapter counts as totally before * the old range, as we need to remove the entries in the open chapter. */ emptyDeltaIndexZone(&mi5->deltaIndex, zoneNumber); masterZone->virtualChapterLow = virtualChapter; masterZone->virtualChapterHigh = virtualChapter; } else if (virtualChapter <= masterZone->virtualChapterHigh) { // Moving backwards and the new range overlaps the old range. Note // that moving to the same open chapter counts as backwards, as we need // to remove the entries in the open chapter. removeNewestChapters(mi5, zoneNumber, virtualChapter); masterZone->virtualChapterHigh = virtualChapter; } else if (newVirtualLow < masterZone->virtualChapterLow) { // Moving forwards and we can keep all the old chapters masterZone->virtualChapterHigh = virtualChapter; } else if (newVirtualLow <= masterZone->virtualChapterHigh) { // Moving forwards and we can keep some old chapters masterZone->virtualChapterLow = newVirtualLow; masterZone->virtualChapterHigh = virtualChapter; } else { // Moving forwards and the new range is totally after the old range masterZone->virtualChapterLow = virtualChapter; masterZone->virtualChapterHigh = virtualChapter; } // Check to see if the zone data has grown to be too large if (masterZone->virtualChapterLow < masterZone->virtualChapterHigh) { uint64_t usedBits = getDeltaIndexZoneDlistBitsUsed(&mi5->deltaIndex, zoneNumber); if (usedBits > mi5->maxZoneBits) { // Expire enough chapters to free the desired space uint64_t expireCount = 1 + (usedBits - mi5->maxZoneBits) / mi5->chapterZoneBits; if (expireCount == 1) { logRatelimit(logInfo, "masterZone %u: At chapter %" PRIu64 ", expiring chapter %llu early", zoneNumber, virtualChapter, masterZone->virtualChapterLow); masterZone->numEarlyFlushes++; masterZone->virtualChapterLow++; } else { uint64_t firstExpired = masterZone->virtualChapterLow; if (firstExpired + expireCount < masterZone->virtualChapterHigh) { masterZone->numEarlyFlushes += expireCount; masterZone->virtualChapterLow += expireCount; } else { masterZone->numEarlyFlushes += masterZone->virtualChapterHigh - masterZone->virtualChapterLow; masterZone->virtualChapterLow = masterZone->virtualChapterHigh; } logRatelimit(logInfo, "masterZone %u: At chapter %" PRIu64 ", expiring chapters %llu to %llu early", zoneNumber, virtualChapter, firstExpired, masterZone->virtualChapterLow - 1); } } } } /***********************************************************************/ /** * Set the open chapter number. The master index will be modified to index * the proper number of chapters ending with the new open chapter. * * @param masterIndex The master index * @param virtualChapter The new open chapter number **/ static void setMasterIndexOpenChapter_005(MasterIndex *masterIndex, uint64_t virtualChapter) { MasterIndex5 *mi5 = container_of(masterIndex, MasterIndex5, common); unsigned int z; for (z = 0; z < mi5->numZones; z++) { // In normal operation, we advance forward one chapter at a time. // Log all abnormal changes. MasterIndexZone *masterZone = &mi5->masterZones[z]; bool logMove = virtualChapter != masterZone->virtualChapterHigh + 1; if (logMove) { logDebug("masterZone %u: The range of indexed chapters is moving from [%" PRIu64 ", %llu] ...", z, masterZone->virtualChapterLow, masterZone->virtualChapterHigh); } setMasterIndexZoneOpenChapter_005(masterIndex, z, virtualChapter); if (logMove) { logDebug("masterZone %u: ... and moving to [%llu, %llu]", z, masterZone->virtualChapterLow, masterZone->virtualChapterHigh); } } } /***********************************************************************/ /** * Find the master index zone associated with a chunk name * * @param masterIndex The master index * @param name The chunk name * * @return the zone that the chunk name belongs to **/ static unsigned int getMasterIndexZone_005(const MasterIndex *masterIndex, const UdsChunkName *name) { const MasterIndex5 *mi5 = const_container_of(masterIndex, MasterIndex5, common); unsigned int deltaListNumber = extractDListNum(mi5, name); return getDeltaIndexZone(&mi5->deltaIndex, deltaListNumber); } /***********************************************************************/ /** * Do a quick read-only lookup of the chunk name and return information * needed by the index code to process the chunk name. * * @param masterIndex The master index * @param name The chunk name * @param triage Information about the chunk name * * @return UDS_SUCCESS or an error code **/ static int lookupMasterIndexName_005(const MasterIndex *masterIndex, const UdsChunkName *name, MasterIndexTriage *triage) { triage->isSample = false; triage->inSampledChapter = false; triage->zone = getMasterIndexZone_005(masterIndex, name); return UDS_SUCCESS; } /***********************************************************************/ /** * Do a quick read-only lookup of the sampled chunk name and return * information needed by the index code to process the chunk name. * * @param masterIndex The master index * @param name The chunk name * @param triage Information about the chunk name. The zone and * isSample fields are already filled in. Set * inSampledChapter and virtualChapter if the chunk * name is found in the index. * * @return UDS_SUCCESS or an error code **/ static int lookupMasterIndexSampledName_005(const MasterIndex *masterIndex, const UdsChunkName *name, MasterIndexTriage *triage) { const MasterIndex5 *mi5 = const_container_of(masterIndex, MasterIndex5, common); unsigned int address = extractAddress(mi5, name); unsigned int deltaListNumber = extractDListNum(mi5, name); DeltaIndexEntry deltaEntry; int result = getDeltaIndexEntry(&mi5->deltaIndex, deltaListNumber, address, name->name, true, &deltaEntry); if (result != UDS_SUCCESS) { return result; } triage->inSampledChapter = !deltaEntry.atEnd && (deltaEntry.key == address); if (triage->inSampledChapter) { const MasterIndexZone *masterZone = &mi5->masterZones[triage->zone]; unsigned int indexChapter = getDeltaEntryValue(&deltaEntry); unsigned int rollingChapter = ((indexChapter - masterZone->virtualChapterLow) & mi5->chapterMask); triage->virtualChapter = masterZone->virtualChapterLow + rollingChapter; if (triage->virtualChapter > masterZone->virtualChapterHigh) { triage->inSampledChapter = false; } } return UDS_SUCCESS; } /***********************************************************************/ /** * Find the master index record associated with a block name * * This is always the first routine to be called when dealing with a delta * master index entry. The fields of the record parameter should be * examined to determine the state of the record: * * If isFound is false, then we did not find an entry for the block * name. Information is saved in the MasterIndexRecord so that * putMasterIndexRecord() will insert an entry for that block name at * the proper place. * * If isFound is true, then we did find an entry for the block name. * Information is saved in the MasterIndexRecord so that the "chapter" * and "isCollision" fields reflect the entry found. * Calls to removeMasterIndexRecord() will remove the entry, calls to * setMasterIndexRecordChapter() can modify the entry, and calls to * putMasterIndexRecord() can insert a collision record with this * entry. * * @param masterIndex The master index to search * @param name The chunk name * @param record Set to the info about the record searched for * * @return UDS_SUCCESS or an error code **/ static int getMasterIndexRecord_005(MasterIndex *masterIndex, const UdsChunkName *name, MasterIndexRecord *record) { MasterIndex5 *mi5 = container_of(masterIndex, MasterIndex5, common); unsigned int address = extractAddress(mi5, name); unsigned int deltaListNumber = extractDListNum(mi5, name); uint64_t flushChapter = mi5->flushChapters[deltaListNumber]; record->magic = masterIndexRecordMagic; record->masterIndex = masterIndex; record->mutex = NULL; record->name = name; record->zoneNumber = getDeltaIndexZone(&mi5->deltaIndex, deltaListNumber); const MasterIndexZone *masterZone = getMasterZone(record); int result; if (flushChapter < masterZone->virtualChapterLow) { ChapterRange range; uint64_t flushCount = masterZone->virtualChapterLow - flushChapter; range.chapterStart = convertVirtualToIndex(mi5, flushChapter); range.chapterCount = (flushCount > mi5->chapterMask ? mi5->chapterMask + 1 : flushCount); result = getMasterIndexEntry(record, deltaListNumber, address, &range); flushChapter = convertIndexToVirtual(record, range.chapterStart); if (flushChapter > masterZone->virtualChapterHigh) { flushChapter = masterZone->virtualChapterHigh; } mi5->flushChapters[deltaListNumber] = flushChapter; } else { result = getDeltaIndexEntry(&mi5->deltaIndex, deltaListNumber, address, name->name, false, &record->deltaEntry); } if (result != UDS_SUCCESS) { return result; } record->isFound = (!record->deltaEntry.atEnd && (record->deltaEntry.key == address)); if (record->isFound) { unsigned int indexChapter = getDeltaEntryValue(&record->deltaEntry); record->virtualChapter = convertIndexToVirtual(record, indexChapter); } record->isCollision = record->deltaEntry.isCollision; return UDS_SUCCESS; } /***********************************************************************/ /** * Create a new record associated with a block name. * * @param record The master index record found by getRecord() * @param virtualChapter The chapter number where block info is found * * @return UDS_SUCCESS or an error code **/ int putMasterIndexRecord(MasterIndexRecord *record, uint64_t virtualChapter) { const MasterIndex5 *mi5 = container_of(record->masterIndex, MasterIndex5, common); if (record->magic != masterIndexRecordMagic) { return logWarningWithStringError(UDS_BAD_STATE, "bad magic number in master index record"); } if (!isVirtualChapterIndexed(record, virtualChapter)) { const MasterIndexZone *masterZone = getMasterZone(record); return logWarningWithStringError(UDS_INVALID_ARGUMENT, "cannot put record into chapter number %" PRIu64 " that is out of the valid range %" PRIu64 " to %llu", virtualChapter, masterZone->virtualChapterLow, masterZone->virtualChapterHigh); } unsigned int address = extractAddress(mi5, record->name); if (unlikely(record->mutex != NULL)) { lockMutex(record->mutex); } int result = putDeltaIndexEntry(&record->deltaEntry, address, convertVirtualToIndex(mi5, virtualChapter), record->isFound ? record->name->name : NULL); if (unlikely(record->mutex != NULL)) { unlockMutex(record->mutex); } switch (result) { case UDS_SUCCESS: record->virtualChapter = virtualChapter; record->isCollision = record->deltaEntry.isCollision; record->isFound = true; break; case UDS_OVERFLOW: logRatelimit(logWarningWithStringError, UDS_OVERFLOW, "Master index entry dropped due to overflow condition"); logDeltaIndexEntry(&record->deltaEntry); break; default: break; } return result; } /**********************************************************************/ static INLINE int validateRecord(MasterIndexRecord *record) { if (record->magic != masterIndexRecordMagic) { return logWarningWithStringError( UDS_BAD_STATE, "bad magic number in master index record"); } if (!record->isFound) { return logWarningWithStringError(UDS_BAD_STATE, "illegal operation on new record"); } return UDS_SUCCESS; } /***********************************************************************/ /** * Remove an existing record. * * @param record The master index record found by getRecord() * * @return UDS_SUCCESS or an error code **/ int removeMasterIndexRecord(MasterIndexRecord *record) { int result = validateRecord(record); if (result != UDS_SUCCESS) { return result; } // Mark the record so that it cannot be used again record->magic = badMagic; if (unlikely(record->mutex != NULL)) { lockMutex(record->mutex); } result = removeDeltaIndexEntry(&record->deltaEntry); if (unlikely(record->mutex != NULL)) { unlockMutex(record->mutex); } return result; } /***********************************************************************/ /** * Set the chapter number associated with a block name. * * @param record The master index record found by getRecord() * @param virtualChapter The chapter number where the block info is now found. * * @return UDS_SUCCESS or an error code **/ int setMasterIndexRecordChapter(MasterIndexRecord *record, uint64_t virtualChapter) { const MasterIndex5 *mi5 = container_of(record->masterIndex, MasterIndex5, common); int result = validateRecord(record); if (result != UDS_SUCCESS) { return result; } if (!isVirtualChapterIndexed(record, virtualChapter)) { const MasterIndexZone *masterZone = getMasterZone(record); return logWarningWithStringError(UDS_INVALID_ARGUMENT, "cannot set chapter number %" PRIu64 " that is out of the valid range %" PRIu64 " to %llu", virtualChapter, masterZone->virtualChapterLow, masterZone->virtualChapterHigh); } if (unlikely(record->mutex != NULL)) { lockMutex(record->mutex); } result = setDeltaEntryValue(&record->deltaEntry, convertVirtualToIndex(mi5, virtualChapter)); if (unlikely(record->mutex != NULL)) { unlockMutex(record->mutex); } if (result != UDS_SUCCESS) { return result; } record->virtualChapter = virtualChapter; return UDS_SUCCESS; } /***********************************************************************/ /** * Get the number of bytes used for master index entries. * * @param masterIndex The master index * * @return The number of bytes in use **/ static size_t getMasterIndexMemoryUsed_005(const MasterIndex *masterIndex) { const MasterIndex5 *mi5 = const_container_of(masterIndex, MasterIndex5, common); uint64_t bits = getDeltaIndexDlistBitsUsed(&mi5->deltaIndex); return (bits + CHAR_BIT - 1) / CHAR_BIT; } /***********************************************************************/ /** * Return the master index stats. There is only one portion of the master * index in this implementation, and we call it the dense portion of the * index. * * @param masterIndex The master index * @param dense Stats for the dense portion of the index * @param sparse Stats for the sparse portion of the index **/ static void getMasterIndexStats_005(const MasterIndex *masterIndex, MasterIndexStats *dense, MasterIndexStats *sparse) { const MasterIndex5 *mi5 = const_container_of(masterIndex, MasterIndex5, common); DeltaIndexStats dis; getDeltaIndexStats(&mi5->deltaIndex, &dis); dense->memoryAllocated = (dis.memoryAllocated + sizeof(MasterIndex5) + mi5->numDeltaLists * sizeof(uint64_t) + mi5->numZones * sizeof(MasterIndexZone)); dense->rebalanceTime = dis.rebalanceTime; dense->rebalanceCount = dis.rebalanceCount; dense->recordCount = dis.recordCount; dense->collisionCount = dis.collisionCount; dense->discardCount = dis.discardCount; dense->overflowCount = dis.overflowCount; dense->numLists = dis.numLists; dense->earlyFlushes = 0; unsigned int z; for (z = 0; z < mi5->numZones; z++) { dense->earlyFlushes += mi5->masterZones[z].numEarlyFlushes; } memset(sparse, 0, sizeof(MasterIndexStats)); } /***********************************************************************/ /** * Determine whether a given chunk name is a hook. * * @param masterIndex The master index * @param name The block name * * @return whether to use as sample **/ static bool isMasterIndexSample_005(const MasterIndex *masterIndex __attribute__((unused)), const UdsChunkName *name __attribute__((unused))) { return false; } /***********************************************************************/ typedef struct { unsigned int addressBits; // Number of bits in address mask unsigned int chapterBits; // Number of bits in chapter number unsigned int meanDelta; // The mean delta unsigned long numDeltaLists; // The number of delta lists unsigned long numChapters; // Number of chapters used size_t numBitsPerChapter; // The number of bits per chapter size_t memorySize; // The number of bytes of delta list memory size_t targetFreeSize; // The number of free bytes we desire } Parameters005; /***********************************************************************/ static int computeMasterIndexParameters005(const Configuration *config, Parameters005 *params) { enum { DELTA_LIST_SIZE = 256 }; /* * For a given zone count, setting the the minimum number of delta lists * to the square of the number of zones ensures that the distribution of * delta lists over zones doesn't underflow, leaving the last zone with * an invalid number of delta lists. See the explanation in * initializeDeltaIndex(). Because we can restart with a different number * of zones but the number of delta lists is invariant across restart, * we must use the largest number of zones to compute this minimum. */ unsigned long minDeltaLists = (minMasterIndexDeltaLists ? minMasterIndexDeltaLists : MAX_ZONES * MAX_ZONES); Geometry *geometry = config->geometry; unsigned long recordsPerChapter = geometry->recordsPerChapter; params->numChapters = geometry->chaptersPerVolume; unsigned long recordsPerVolume = recordsPerChapter * params->numChapters; unsigned int numAddresses = config->masterIndexMeanDelta * DELTA_LIST_SIZE; params->numDeltaLists = maxUint(recordsPerVolume / DELTA_LIST_SIZE, minDeltaLists); params->addressBits = computeBits(numAddresses - 1); params->chapterBits = computeBits(params->numChapters - 1); if ((unsigned int) params->numDeltaLists != params->numDeltaLists) { return logWarningWithStringError(UDS_INVALID_ARGUMENT, "cannot initialize master index with %lu" " delta lists", params->numDeltaLists); } if (params->addressBits > 31) { return logWarningWithStringError(UDS_INVALID_ARGUMENT, "cannot initialize master index with %u" " address bits", params->addressBits); } if (geometry->sparseChaptersPerVolume > 0) { return logWarningWithStringError(UDS_INVALID_ARGUMENT, "cannot initialize dense master index" " with %u sparse chapters", geometry->sparseChaptersPerVolume); } if (recordsPerChapter == 0) { return logWarningWithStringError(UDS_INVALID_ARGUMENT, "cannot initialize master index with %lu" " records per chapter", recordsPerChapter); } if (params->numChapters == 0) { return logWarningWithStringError(UDS_INVALID_ARGUMENT, "cannot initialize master index with %lu" " chapters per volume", params->numChapters); } /* * We can now compute the probability that a delta list is not touched during * the writing of an entire chapter. The computation is: * * double pNotTouched = pow((double) (params->numDeltaLists - 1) * / params->numDeltaLists, * recordsPerChapter); * * For the standard index sizes, about 78% of the delta lists are not * touched, and therefore contain dead index entries that have not been * eliminated by the lazy LRU processing. We can then compute how many dead * index entries accumulate over time. The computation is: * * double invalidChapters = pNotTouched / (1.0 - pNotTouched); * * For the standard index sizes, we will need about 3.5 chapters of space for * the dead index entries in a 1K chapter index. Since we do not want to do * that floating point computation, we use 4 chapters per 1K of chapters. */ unsigned long invalidChapters = maxUint(params->numChapters / 256, 2); unsigned long chaptersInMasterIndex = params->numChapters + invalidChapters; unsigned long entriesInMasterIndex = recordsPerChapter * chaptersInMasterIndex; // Compute the mean delta unsigned long addressSpan = params->numDeltaLists << params->addressBits; params->meanDelta = addressSpan / entriesInMasterIndex; // Project how large we expect a chapter to be params->numBitsPerChapter = getDeltaMemorySize(recordsPerChapter, params->meanDelta, params->chapterBits); // Project how large we expect the index to be size_t numBitsPerIndex = params->numBitsPerChapter * chaptersInMasterIndex; size_t expectedIndexSize = numBitsPerIndex / CHAR_BIT; /* * Set the total memory to be 6% larger than the expected index size. We * want this number to be large enough that the we do not do a great many * rebalances as the list when the list is full. We use MasterIndex_p1 * to tune this setting. */ params->memorySize = expectedIndexSize * 106 / 100; // Set the target free size to 5% of the expected index size params->targetFreeSize = expectedIndexSize / 20; return UDS_SUCCESS; } /***********************************************************************/ int computeMasterIndexSaveBytes005(const Configuration *config, size_t *numBytes) { Parameters005 params = { .addressBits = 0 }; int result = computeMasterIndexParameters005(config, ¶ms); if (result != UDS_SUCCESS) { return result; } // Saving a MasterIndex005 needs a header plus one uint64_t per delta // list plus the delta index. *numBytes = (sizeof(struct mi005_data) + params.numDeltaLists * sizeof(uint64_t) + computeDeltaIndexSaveBytes(params.numDeltaLists, params.memorySize)); return UDS_SUCCESS; } /***********************************************************************/ int makeMasterIndex005(const Configuration *config, unsigned int numZones, uint64_t volumeNonce, MasterIndex **masterIndex) { Parameters005 params = { .addressBits = 0 }; int result = computeMasterIndexParameters005(config, ¶ms); if (result != UDS_SUCCESS) { return result; } MasterIndex5 *mi5; result = ALLOCATE(1, MasterIndex5, "master index", &mi5); if (result != UDS_SUCCESS) { *masterIndex = NULL; return result; } mi5->common.abortRestoringMasterIndex = abortRestoringMasterIndex_005; mi5->common.abortSavingMasterIndex = abortSavingMasterIndex_005; mi5->common.finishSavingMasterIndex = finishSavingMasterIndex_005; mi5->common.freeMasterIndex = freeMasterIndex_005; mi5->common.getMasterIndexMemoryUsed = getMasterIndexMemoryUsed_005; mi5->common.getMasterIndexRecord = getMasterIndexRecord_005; mi5->common.getMasterIndexStats = getMasterIndexStats_005; mi5->common.getMasterIndexZone = getMasterIndexZone_005; mi5->common.isMasterIndexSample = isMasterIndexSample_005; mi5->common.isRestoringMasterIndexDone = isRestoringMasterIndexDone_005; mi5->common.isSavingMasterIndexDone = isSavingMasterIndexDone_005; mi5->common.lookupMasterIndexName = lookupMasterIndexName_005; mi5->common.lookupMasterIndexSampledName = lookupMasterIndexSampledName_005; mi5->common.restoreDeltaListToMasterIndex = restoreDeltaListToMasterIndex_005; mi5->common.setMasterIndexOpenChapter = setMasterIndexOpenChapter_005; mi5->common.setMasterIndexTag = setMasterIndexTag_005; mi5->common.setMasterIndexZoneOpenChapter = setMasterIndexZoneOpenChapter_005; mi5->common.startRestoringMasterIndex = startRestoringMasterIndex_005; mi5->common.startSavingMasterIndex = startSavingMasterIndex_005; mi5->addressBits = params.addressBits; mi5->addressMask = (1u << params.addressBits) - 1; mi5->chapterBits = params.chapterBits; mi5->chapterMask = (1u << params.chapterBits) - 1; mi5->numChapters = params.numChapters; mi5->numDeltaLists = params.numDeltaLists; mi5->numZones = numZones; mi5->chapterZoneBits = params.numBitsPerChapter / numZones; mi5->volumeNonce = volumeNonce; result = initializeDeltaIndex(&mi5->deltaIndex, numZones, params.numDeltaLists, params.meanDelta, params.chapterBits, params.memorySize); if (result == UDS_SUCCESS) { mi5->maxZoneBits = ((getDeltaIndexDlistBitsAllocated(&mi5->deltaIndex) - params.targetFreeSize * CHAR_BIT) / numZones); } // Initialize the chapter flush ranges to be empty. This depends upon // allocate returning zeroed memory. if (result == UDS_SUCCESS) { result = ALLOCATE(params.numDeltaLists, uint64_t, "first chapter to flush", &mi5->flushChapters); } // Initialize the virtual chapter ranges to start at zero. This depends // upon allocate returning zeroed memory. if (result == UDS_SUCCESS) { result = ALLOCATE(numZones, MasterIndexZone, "master index zones", &mi5->masterZones); } if (result == UDS_SUCCESS) { *masterIndex = &mi5->common; } else { freeMasterIndex_005(&mi5->common); *masterIndex = NULL; } return result; }