/* * 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/openChapter.c#4 $ */ #include "openChapter.h" #include "compiler.h" #include "logger.h" #include "memoryAlloc.h" #include "numeric.h" #include "zone.h" static int readOpenChapters(ReadPortal *portal); static int writeOpenChapters(IndexComponent *component, BufferedWriter *writer, unsigned int zone); const IndexComponentInfo OPEN_CHAPTER_INFO = { .kind = RL_KIND_OPEN_CHAPTER, .name = "open chapter", .saveOnly = true, .chapterSync = false, .multiZone = false, .ioStorage = true, .loader = readOpenChapters, .saver = writeOpenChapters, .incremental = NULL, }; static const byte OPEN_CHAPTER_MAGIC[] = "ALBOC"; static const byte OPEN_CHAPTER_VERSION[] = "02.00"; enum { OPEN_CHAPTER_MAGIC_LENGTH = sizeof(OPEN_CHAPTER_MAGIC) - 1, OPEN_CHAPTER_VERSION_LENGTH = sizeof(OPEN_CHAPTER_VERSION) - 1 }; /**********************************************************************/ static int fillDeltaChapterIndex(OpenChapterZone **chapterZones, unsigned int zoneCount, OpenChapterIndex *index, UdsChunkRecord *collatedRecords) { // Find a record to replace any deleted records, and fill the chapter if // it was closed early. The last record in any filled zone is guaranteed // to not have been deleted in this chapter, so use one of those. OpenChapterZone *fillChapterZone = NULL; UdsChunkRecord *fillRecord = NULL; unsigned int z; for (z = 0; z < zoneCount; ++z) { fillChapterZone = chapterZones[z]; if (fillChapterZone->size == fillChapterZone->capacity) { fillRecord = &fillChapterZone->records[fillChapterZone->size]; break; } } int result = ASSERT((fillRecord != NULL), "some open chapter zone filled"); if (result != UDS_SUCCESS) { return result; } result = ASSERT(!fillChapterZone->slots[fillChapterZone->size].recordDeleted, "chapter fill record not deleted"); if (result != UDS_SUCCESS) { return result; } const Geometry *geometry = index->geometry; unsigned int pagesPerChapter = geometry->recordPagesPerChapter; unsigned int recordsPerPage = geometry->recordsPerPage; int overflowCount = 0; unsigned int recordsAdded = 0; unsigned int zone = 0; unsigned int page; for (page = 0; page < pagesPerChapter; page++) { unsigned int i; for (i = 0; i < recordsPerPage; i++, recordsAdded++, zone = (zone + 1) % zoneCount) { // The record arrays are 1-based. unsigned int recordNumber = 1 + (recordsAdded / zoneCount); // If the zone has been exhausted, or the record was deleted, // add the fill record to the chapter. if (recordNumber > chapterZones[zone]->size || chapterZones[zone]->slots[recordNumber].recordDeleted) { collatedRecords[1 + recordsAdded] = *fillRecord; continue; } UdsChunkRecord *nextRecord = &chapterZones[zone]->records[recordNumber]; collatedRecords[1 + recordsAdded] = *nextRecord; int result = putOpenChapterIndexRecord(index, &nextRecord->name, page); switch (result) { case UDS_SUCCESS: break; case UDS_OVERFLOW: overflowCount++; break; default: logErrorWithStringError(result, "failed to build open chapter index"); return result; } } } if (overflowCount > 0) { logWarning("Failed to add %d entries to chapter index", overflowCount); } return UDS_SUCCESS; } /**********************************************************************/ int closeOpenChapter(OpenChapterZone **chapterZones, unsigned int zoneCount, Volume *volume, OpenChapterIndex *chapterIndex, UdsChunkRecord *collatedRecords, uint64_t virtualChapterNumber) { // Empty the delta chapter index, and prepare it for the new virtual chapter. emptyOpenChapterIndex(chapterIndex, virtualChapterNumber); // Map each non-deleted record name to its record page number in the delta // chapter index. int result = fillDeltaChapterIndex(chapterZones, zoneCount, chapterIndex, collatedRecords); if (result != UDS_SUCCESS) { return result; } // Pass the populated chapter index and the records to the volume, which // will generate and write the index and record pages for the chapter. return writeChapter(volume, chapterIndex, collatedRecords); } /**********************************************************************/ int saveOpenChapters(Index *index, BufferedWriter *writer) { int result = writeToBufferedWriter(writer, OPEN_CHAPTER_MAGIC, OPEN_CHAPTER_MAGIC_LENGTH); if (result != UDS_SUCCESS) { return result; } result = writeToBufferedWriter(writer, OPEN_CHAPTER_VERSION, OPEN_CHAPTER_VERSION_LENGTH); if (result != UDS_SUCCESS) { return result; } uint32_t totalRecords = 0; unsigned int i; for (i = 0; i < index->zoneCount; i++) { totalRecords += openChapterSize(index->zones[i]->openChapter); } // Store the record count in little-endian order. byte totalRecordData[sizeof(totalRecords)]; storeUInt32LE(totalRecordData, totalRecords); result = writeToBufferedWriter(writer, totalRecordData, sizeof(totalRecordData)); if (result != UDS_SUCCESS) { return result; } // Only write out the records that have been added and not deleted. uint32_t recordsAdded = 0; unsigned int recordIndex = 1; while(recordsAdded < totalRecords) { unsigned int i; for (i = 0; i < index->zoneCount; i++) { if (recordIndex > index->zones[i]->openChapter->size) { continue; } if (index->zones[i]->openChapter->slots[recordIndex].recordDeleted) { continue; } UdsChunkRecord *record = &index->zones[i]->openChapter->records[recordIndex]; result = writeToBufferedWriter(writer, record, sizeof(UdsChunkRecord)); if (result != UDS_SUCCESS) { return result; } recordsAdded++; } recordIndex++; } return flushBufferedWriter(writer); } /**********************************************************************/ uint64_t computeSavedOpenChapterSize(Geometry *geometry) { return OPEN_CHAPTER_MAGIC_LENGTH + OPEN_CHAPTER_VERSION_LENGTH + sizeof(uint32_t) + geometry->recordsPerChapter * sizeof(UdsChunkRecord); } /**********************************************************************/ static int writeOpenChapters(IndexComponent *component, BufferedWriter *writer, unsigned int zone) { int result = ASSERT((zone == 0), "open chapter write not zoned"); if (result != UDS_SUCCESS) { return result; } Index *index = indexComponentData(component); return saveOpenChapters(index, writer); } /** * Read the version field from a buffered reader, checking whether it is a * supported version. Returns (via a pointer parameter) the matching * version constant, which can be used by comparing to the version * constants using simple pointer equality. * * @param [in] reader A buffered reader. * @param [out] version The version constant that was matched. * * @return UDS_SUCCESS or an error code if the file could not be read or * the version is invalid or unsupported **/ static int readVersion(BufferedReader *reader, const byte **version) { byte buffer[OPEN_CHAPTER_VERSION_LENGTH]; int result = readFromBufferedReader(reader, buffer, sizeof(buffer)); if (result != UDS_SUCCESS) { return result; } if (memcmp(OPEN_CHAPTER_VERSION, buffer, sizeof(buffer)) != 0) { return logErrorWithStringError(UDS_CORRUPT_COMPONENT, "Invalid open chapter version: %.*s", (int) sizeof(buffer), buffer); } *version = OPEN_CHAPTER_VERSION; return UDS_SUCCESS; } /**********************************************************************/ static int loadVersion20(Index *index, BufferedReader *reader) { byte numRecordsData[sizeof(uint32_t)]; int result = readFromBufferedReader(reader, &numRecordsData, sizeof(numRecordsData)); if (result != UDS_SUCCESS) { return result; } uint32_t numRecords = getUInt32LE(numRecordsData); // Keep track of which zones cannot accept any more records. bool fullFlags[MAX_ZONES] = { false, }; // Assign records to the correct zones. UdsChunkRecord record; uint32_t records; for (records = 0; records < numRecords; records++) { result = readFromBufferedReader(reader, &record, sizeof(UdsChunkRecord)); if (result != UDS_SUCCESS) { return result; } unsigned int zone = 0; if (index->zoneCount > 1) { // A read-only index has no master index, but it also has only one zone. zone = getMasterIndexZone(index->masterIndex, &record.name); } // Add records until the open chapter zone almost runs out of space. // The chapter can't be closed here, so don't add the last record. if (!fullFlags[zone]) { unsigned int remaining; result = putOpenChapter(index->zones[zone]->openChapter, &record.name, &record.data, &remaining); fullFlags[zone] = (remaining <= 1); if (result != UDS_SUCCESS) { return result; } } } return UDS_SUCCESS; } /**********************************************************************/ int loadOpenChapters(Index *index, BufferedReader *reader) { // Read and check the magic number. int result = verifyBufferedData(reader, OPEN_CHAPTER_MAGIC, OPEN_CHAPTER_MAGIC_LENGTH); if (result != UDS_SUCCESS) { return result; } // Read and check the version. const byte *version = NULL; result = readVersion(reader, &version); if (result != UDS_SUCCESS) { return result; } return loadVersion20(index, reader); } /**********************************************************************/ int readOpenChapters(ReadPortal *portal) { Index *index = indexComponentData(portal->component); BufferedReader *reader; int result = getBufferedReaderForPortal(portal, 0, &reader); if (result != UDS_SUCCESS) { return result; } return loadOpenChapters(index, reader); }