/* * 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/vdo-releases/aluminum/src/c++/vdo/base/vdoLoad.c#17 $ */ #include "vdoLoad.h" #include "logger.h" #include "memoryAlloc.h" #include "adminCompletion.h" #include "blockMap.h" #include "completion.h" #include "constants.h" #include "hashZone.h" #include "header.h" #include "logicalZone.h" #include "physicalZone.h" #include "readOnlyRebuild.h" #include "recoveryJournal.h" #include "releaseVersions.h" #include "slabDepot.h" #include "slabSummary.h" #include "threadConfig.h" #include "types.h" #include "vdoInternal.h" #include "vdoRecovery.h" #include "volumeGeometry.h" /** * Extract the VDO from an AdminCompletion, checking that the current operation * is a load. * * @param completion The AdminCompletion's sub-task completion * * @return The VDO **/ static inline VDO *vdoFromLoadSubTask(VDOCompletion *completion) { return vdoFromAdminSubTask(completion, ADMIN_OPERATION_LOAD); } /** * Finish aborting a load now that any entry to read-only mode is complete. * This callback is registered in abortLoad(). * * @param completion The sub-task completion **/ static void finishAborting(VDOCompletion *completion) { VDO *vdo = vdoFromLoadSubTask(completion); vdo->closeRequired = false; finishParentCallback(completion); } /** * Make sure the recovery journal is closed when aborting a load. * * @param completion The sub-task completion **/ static void closeRecoveryJournalForAbort(VDOCompletion *completion) { VDO *vdo = vdoFromLoadSubTask(completion); prepareAdminSubTask(vdo, finishAborting, finishAborting); drainRecoveryJournal(vdo->recoveryJournal, ADMIN_STATE_SAVING, completion); } /** * Clean up after an error loading a VDO. This error handler is set in * loadCallback() and loadVDOComponents(). * * @param completion The sub-task completion **/ static void abortLoad(VDOCompletion *completion) { VDO *vdo = vdoFromLoadSubTask(completion); logErrorWithStringError(completion->result, "aborting load"); if (vdo->readOnlyNotifier == NULL) { // There are no threads, so we're done finishParentCallback(completion); return; } // Preserve the error. setCompletionResult(completion->parent, completion->result); if (vdo->recoveryJournal == NULL) { prepareAdminSubTask(vdo, finishAborting, finishAborting); } else { prepareAdminSubTaskOnThread(vdo, closeRecoveryJournalForAbort, closeRecoveryJournalForAbort, getJournalZoneThread(getThreadConfig(vdo))); } waitUntilNotEnteringReadOnlyMode(vdo->readOnlyNotifier, completion); } /** * Wait for the VDO to be in read-only mode. * * @param completion The sub-task completion **/ static void waitForReadOnlyMode(VDOCompletion *completion) { prepareToFinishParent(completion, completion->parent); setCompletionResult(completion, VDO_READ_ONLY); VDO *vdo = vdoFromLoadSubTask(completion); waitUntilNotEnteringReadOnlyMode(vdo->readOnlyNotifier, completion); } /** * Finish loading the VDO after an error, but leave it in read-only * mode. This error handler is set in makeDirty(), scrubSlabs(), and * loadVDOComponents(). * * @param completion The sub-task completion **/ static void continueLoadReadOnly(VDOCompletion *completion) { VDO *vdo = vdoFromLoadSubTask(completion); logErrorWithStringError(completion->result, "Entering read-only mode due to load error"); enterReadOnlyMode(vdo->readOnlyNotifier, completion->result); waitForReadOnlyMode(completion); } /** * Exit recovery mode if necessary now that online slab scrubbing or loading * is complete. This callback is registrered in scrubSlabs(). * * @param completion The slab scrubber completion **/ static void finishScrubbingSlabs(VDOCompletion *completion) { VDO *vdo = completion->parent; assertOnAdminThread(vdo, __func__); if (inRecoveryMode(vdo)) { leaveRecoveryMode(vdo); } else { logInfo("VDO commencing normal operation"); } } /** * Handle an error scrubbing or loading all slabs after the VDO has come * online. This error handler is registered in scrubSlabs(). * * @param completion The slab scrubber completion **/ static void handleScrubAllError(VDOCompletion *completion) { VDO *vdo = completion->parent; enterReadOnlyMode(vdo->readOnlyNotifier, completion->result); } /** * Initiate slab scrubbing if necessary. This callback is registered in * prepareToComeOnline(). * * @param completion The sub-task completion **/ static void scrubSlabs(VDOCompletion *completion) { VDO *vdo = vdoFromLoadSubTask(completion); if (!hasUnrecoveredSlabs(vdo->depot)) { finishParentCallback(completion); return; } if (requiresRecovery(vdo)) { enterRecoveryMode(vdo); } prepareAdminSubTask(vdo, finishParentCallback, continueLoadReadOnly); scrubAllUnrecoveredSlabs(vdo->depot, vdo, finishScrubbingSlabs, handleScrubAllError, 0, completion); } /** * This is the error handler for slab scrubbing. It is registered in * prepareToComeOnline(). * * @param completion The sub-task completion **/ static void handleScrubbingError(VDOCompletion *completion) { VDO *vdo = vdoFromLoadSubTask(completion); enterReadOnlyMode(vdo->readOnlyNotifier, completion->result); waitForReadOnlyMode(completion); } /** * This is the callback after the super block is written. It prepares the block * allocator to come online and start allocating. It is registered in * makeDirty(). * * @param completion The sub-task completion **/ static void prepareToComeOnline(VDOCompletion *completion) { VDO *vdo = vdoFromLoadSubTask(completion); SlabDepotLoadType loadType = NORMAL_LOAD; if (requiresReadOnlyRebuild(vdo)) { loadType = REBUILD_LOAD; } else if (requiresRecovery(vdo)) { loadType = RECOVERY_LOAD; } initializeBlockMapFromJournal(vdo->blockMap, vdo->recoveryJournal); prepareAdminSubTask(vdo, scrubSlabs, handleScrubbingError); prepareToAllocate(vdo->depot, loadType, completion); } /** * Mark the super block as dirty now that everything has been loaded or * rebuilt. * * @param completion The sub-task completion **/ static void makeDirty(VDOCompletion *completion) { VDO *vdo = vdoFromLoadSubTask(completion); if (isReadOnly(vdo->readOnlyNotifier)) { finishCompletion(completion->parent, VDO_READ_ONLY); return; } vdo->state = VDO_DIRTY; prepareAdminSubTask(vdo, prepareToComeOnline, continueLoadReadOnly); saveVDOComponentsAsync(vdo, completion); } /** * Callback to do the destructive parts of a load now that the new VDO device * is being resumed. * * @param completion The sub-task completion **/ static void loadCallback(VDOCompletion *completion) { VDO *vdo = vdoFromLoadSubTask(completion); assertOnAdminThread(vdo, __func__); // Prepare the recovery journal for new entries. openRecoveryJournal(vdo->recoveryJournal, vdo->depot, vdo->blockMap); vdo->closeRequired = true; if (isReadOnly(vdo->readOnlyNotifier)) { // In read-only mode we don't use the allocator and it may not // even be readable, so use the default structure. finishCompletion(completion->parent, VDO_READ_ONLY); return; } if (requiresReadOnlyRebuild(vdo)) { prepareAdminSubTask(vdo, makeDirty, abortLoad); launchRebuild(vdo, completion); return; } if (requiresRebuild(vdo)) { prepareAdminSubTask(vdo, makeDirty, continueLoadReadOnly); launchRecovery(vdo, completion); return; } prepareAdminSubTask(vdo, makeDirty, continueLoadReadOnly); loadSlabDepot(vdo->depot, (wasNew(vdo) ? ADMIN_STATE_FORMATTING : ADMIN_STATE_LOADING), completion, NULL); } /**********************************************************************/ int performVDOLoad(VDO *vdo) { return performAdminOperation(vdo, ADMIN_OPERATION_LOAD, NULL, loadCallback, loadCallback); } /**********************************************************************/ __attribute__((warn_unused_result)) static int startVDODecode(VDO *vdo, bool validateConfig) { int result = validateVDOVersion(vdo); if (result != VDO_SUCCESS) { return result; } result = decodeVDOComponent(vdo); if (result != VDO_SUCCESS) { return result; } if (!validateConfig) { return VDO_SUCCESS; } if (vdo->loadConfig.nonce != vdo->nonce) { return logErrorWithStringError(VDO_BAD_NONCE, "Geometry nonce %" PRIu64 " does not match superblock nonce %llu", vdo->loadConfig.nonce, vdo->nonce); } BlockCount blockCount = vdo->layer->getBlockCount(vdo->layer); return validateVDOConfig(&vdo->config, blockCount, true); } /**********************************************************************/ __attribute__((warn_unused_result)) static int finishVDODecode(VDO *vdo) { Buffer *buffer = getComponentBuffer(vdo->superBlock); const ThreadConfig *threadConfig = getThreadConfig(vdo); int result = makeRecoveryJournal(vdo->nonce, vdo->layer, getVDOPartition(vdo->layout, RECOVERY_JOURNAL_PARTITION), vdo->completeRecoveries, vdo->config.recoveryJournalSize, RECOVERY_JOURNAL_TAIL_BUFFER_SIZE, vdo->readOnlyNotifier, threadConfig, &vdo->recoveryJournal); if (result != VDO_SUCCESS) { return result; } result = decodeRecoveryJournal(vdo->recoveryJournal, buffer); if (result != VDO_SUCCESS) { return result; } result = decodeSlabDepot(buffer, threadConfig, vdo->nonce, vdo->layer, getVDOPartition(vdo->layout, SLAB_SUMMARY_PARTITION), vdo->readOnlyNotifier, vdo->recoveryJournal, &vdo->depot); if (result != VDO_SUCCESS) { return result; } result = decodeBlockMap(buffer, vdo->config.logicalBlocks, threadConfig, &vdo->blockMap); if (result != VDO_SUCCESS) { return result; } ASSERT_LOG_ONLY((contentLength(buffer) == 0), "All decoded component data was used"); return VDO_SUCCESS; } /** * Decode the component data portion of a super block and fill in the * corresponding portions of the VDO being loaded. This will also allocate the * recovery journal and slab depot. If this method is called with an * asynchronous layer (i.e. a thread config which specifies at least one base * thread), the block map and packer will be constructed as well. * * @param vdo The VDO being loaded * @param validateConfig Whether to validate the config * * @return VDO_SUCCESS or an error **/ __attribute__((warn_unused_result)) static int decodeVDO(VDO *vdo, bool validateConfig) { int result = startVDODecode(vdo, validateConfig); if (result != VDO_SUCCESS) { return result; } const ThreadConfig *threadConfig = getThreadConfig(vdo); result = makeReadOnlyNotifier(inReadOnlyMode(vdo), threadConfig, vdo->layer, &vdo->readOnlyNotifier); if (result != VDO_SUCCESS) { return result; } result = enableReadOnlyEntry(vdo); if (result != VDO_SUCCESS) { return result; } result = decodeVDOLayout(getComponentBuffer(vdo->superBlock), &vdo->layout); if (result != VDO_SUCCESS) { return result; } result = finishVDODecode(vdo); if (result != VDO_SUCCESS) { return result; } result = makeFlusher(vdo); if (result != VDO_SUCCESS) { return result; } BlockCount maximumAge = getConfiguredBlockMapMaximumAge(vdo); BlockCount journalLength = getRecoveryJournalLength(vdo->config.recoveryJournalSize); if ((maximumAge > (journalLength / 2)) || (maximumAge < 1)) { return VDO_BAD_CONFIGURATION; } result = makeBlockMapCaches(vdo->blockMap, vdo->layer, vdo->readOnlyNotifier, vdo->recoveryJournal, vdo->nonce, getConfiguredCacheSize(vdo), maximumAge); if (result != VDO_SUCCESS) { return result; } result = ALLOCATE(threadConfig->hashZoneCount, HashZone *, __func__, &vdo->hashZones); if (result != VDO_SUCCESS) { return result; } for (ZoneCount zone = 0; zone < threadConfig->hashZoneCount; zone++) { result = makeHashZone(vdo, zone, &vdo->hashZones[zone]); if (result != VDO_SUCCESS) { return result; } } result = makeLogicalZones(vdo, &vdo->logicalZones); if (result != VDO_SUCCESS) { return result; } result = ALLOCATE(threadConfig->physicalZoneCount, PhysicalZone *, __func__, &vdo->physicalZones); if (result != VDO_SUCCESS) { return result; } for (ZoneCount zone = 0; zone < threadConfig->physicalZoneCount; zone++) { result = makePhysicalZone(vdo, zone, &vdo->physicalZones[zone]); if (result != VDO_SUCCESS) { return result; } } return makePacker(vdo->layer, DEFAULT_PACKER_INPUT_BINS, DEFAULT_PACKER_OUTPUT_BINS, threadConfig, &vdo->packer); } /** * Load the components of a VDO. This is the super block load callback * set by loadCallback(). * * @param completion The sub-task completion **/ static void loadVDOComponents(VDOCompletion *completion) { VDO *vdo = vdoFromLoadSubTask(completion); prepareCompletion(completion, finishParentCallback, abortLoad, completion->callbackThreadID, completion->parent); finishCompletion(completion, decodeVDO(vdo, true)); } /** * Callback to initiate a pre-load, registered in prepareToLoadVDO(). * * @param completion The sub-task completion **/ static void preLoadCallback(VDOCompletion *completion) { VDO *vdo = vdoFromLoadSubTask(completion); assertOnAdminThread(vdo, __func__); prepareAdminSubTask(vdo, loadVDOComponents, abortLoad); loadSuperBlockAsync(completion, getFirstBlockOffset(vdo), &vdo->superBlock); } /**********************************************************************/ int prepareToLoadVDO(VDO *vdo, const VDOLoadConfig *loadConfig) { vdo->loadConfig = *loadConfig; return performAdminOperation(vdo, ADMIN_OPERATION_LOAD, NULL, preLoadCallback, preLoadCallback); } /**********************************************************************/ __attribute__((warn_unused_result)) static int decodeSynchronousVDO(VDO *vdo, bool validateConfig) { int result = startVDODecode(vdo, validateConfig); if (result != VDO_SUCCESS) { return result; } result = decodeVDOLayout(getComponentBuffer(vdo->superBlock), &vdo->layout); if (result != VDO_SUCCESS) { return result; } return finishVDODecode(vdo); } /**********************************************************************/ int loadVDOSuperblock(PhysicalLayer *layer, VolumeGeometry *geometry, bool validateConfig, VDODecoder *decoder, VDO **vdoPtr) { VDO *vdo; int result = makeVDO(layer, &vdo); if (result != VDO_SUCCESS) { return result; } setLoadConfigFromGeometry(geometry, &vdo->loadConfig); result = loadSuperBlock(layer, getFirstBlockOffset(vdo), &vdo->superBlock); if (result != VDO_SUCCESS) { freeVDO(&vdo); return result; } result = ((decoder == NULL) ? decodeSynchronousVDO(vdo, validateConfig) : decoder(vdo, validateConfig)); if (result != VDO_SUCCESS) { freeVDO(&vdo); return result; } *vdoPtr = vdo; return VDO_SUCCESS; } /**********************************************************************/ int loadVDO(PhysicalLayer *layer, bool validateConfig, VDODecoder *decoder, VDO **vdoPtr) { VolumeGeometry geometry; int result = loadVolumeGeometry(layer, &geometry); if (result != VDO_SUCCESS) { return result; } return loadVDOSuperblock(layer, &geometry, validateConfig, decoder, vdoPtr); }