/** * @file shred.cpp * @brief shred drive * @author hendrik schutter * @date 22.08.2022 */ #include "../include/reHDD.h" #include // For rand(), srand() #include // For time() to seed random number generator using namespace std; #ifdef __cplusplus extern "C" { #endif #include "../tfnoisegen/tfprng.h" #ifdef __cplusplus } #endif const static char *randomsrc = (char *)"/dev/urandom"; Shred::Shred() { // Seed random number generator for epsilon-greedy exploration srand(static_cast(time(nullptr))); // Allocate aligned buffers for maximum chunk size if (posix_memalign((void **)&caTfngData, 4096, CHUNK_SIZE_MAX) != 0) { Logger::logThis()->error("Failed to allocate aligned buffer for tfng data"); caTfngData = nullptr; } if (posix_memalign((void **)&caReadBuffer, 4096, CHUNK_SIZE_MAX) != 0) { Logger::logThis()->error("Failed to allocate aligned buffer for read buffer"); caReadBuffer = nullptr; } // Initialize adaptive tracking variables currentChunkSize = CHUNK_SIZE_START; bestChunkSize = CHUNK_SIZE_START; chunkCounter = 0; totalChunkCounter = 0; // Track total chunks for periodic re-exploration warmupCounter = 0; // Track warm-up measurements bestThroughputMBps = 0.0; lastThroughputMBps = 0.0; bytesWrittenInMeasurement = 0; throughputIncreasing = true; // Initialize multi-armed bandit exploration state explorationMode = false; explorationChunkSize = CHUNK_SIZE_START; Logger::logThis()->info("Adaptive chunk size optimization ENABLED (Multi-Armed Bandit) - Starting with " + to_string(currentChunkSize / (1024 * 1024)) + " MB chunks"); Logger::logThis()->info("Configuration: min=" + to_string(CHUNK_SIZE_MIN / (1024 * 1024)) + "MB, max=" + to_string(CHUNK_SIZE_MAX / (1024 * 1024)) + "MB, step=" + to_string(CHUNK_SIZE_STEP_UP / (1024 * 1024)) + "MB"); Logger::logThis()->info("Exploration: " + to_string((int)(EXPLORATION_EPSILON * 100)) + "% epsilon-greedy + periodic every " + to_string(REEXPLORATION_INTERVAL) + " chunks"); Logger::logThis()->info("Warm-up: First " + to_string(WARMUP_MEASUREMENTS) + " measurements ignored (cold start protection)"); } Shred::~Shred() { if (caTfngData != nullptr) { free(caTfngData); caTfngData = nullptr; } if (caReadBuffer != nullptr) { free(caReadBuffer); caReadBuffer = nullptr; } } /** * \brief Start performance measurement interval * \return void */ void Shred::startMeasurement() { measurementStartTime = std::chrono::high_resolution_clock::now(); bytesWrittenInMeasurement = 0; chunkCounter = 0; } /** * \brief shred drive with shred * \param pointer of Drive instance * \param file descriptor for signaling * \return 0 on success, -1 on error */ void Shred::evaluateThroughput(Drive *drive) { auto measurementEndTime = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed = measurementEndTime - measurementStartTime; double elapsedSeconds = elapsed.count(); if (elapsedSeconds > 0.0) { double throughputMBps = (bytesWrittenInMeasurement / (1024.0 * 1024.0)) / elapsedSeconds; lastThroughputMBps = throughputMBps; // Warm-up period - ignore first measurements warmupCounter++; bool isWarmup = (warmupCounter <= WARMUP_MEASUREMENTS); if (isWarmup) { Logger::logThis()->info("WARM-UP #" + to_string(warmupCounter) + "/" + to_string(WARMUP_MEASUREMENTS) + " - ChunkSize: " + to_string(currentChunkSize / (1024 * 1024)) + " MB, " + "Throughput: " + to_string((int)throughputMBps) + " MB/s (not used for optimization)" + " - Drive: " + drive->getSerial()); } else { Logger::logThis()->info("Throughput measurement - ChunkSize: " + to_string(currentChunkSize / (1024 * 1024)) + " MB, " + "Throughput: " + to_string((int)throughputMBps) + " MB/s, " + "Best: " + to_string((int)bestThroughputMBps) + " MB/s" + " - Drive: " + drive->getSerial()); // Check if this is better than our best (only after warm-up) if (throughputMBps > bestThroughputMBps) { bestThroughputMBps = throughputMBps; bestChunkSize = currentChunkSize; throughputIncreasing = true; Logger::logThis()->info("NEW BEST throughput: " + to_string((int)bestThroughputMBps) + " MB/s with " + to_string(currentChunkSize / (1024 * 1024)) + " MB chunks - Drive: " + drive->getSerial()); } else if (currentChunkSize == bestChunkSize) { // Update best throughput when measuring at best chunk size // This ensures bestThroughputMBps reflects CURRENT performance, not old burst if (throughputMBps < bestThroughputMBps) { Logger::logThis()->info("Updating best throughput: " + to_string((int)bestThroughputMBps) + " MB/s -> " + to_string((int)throughputMBps) + " MB/s " + "(sustained performance at best chunk size: " + to_string(bestChunkSize / (1024 * 1024)) + " MB)" + " - Drive: " + drive->getSerial()); bestThroughputMBps = throughputMBps; } throughputIncreasing = false; } else { throughputIncreasing = false; } } } // Adjust chunk size for next measurement interval (skip during warm-up) if (warmupCounter > WARMUP_MEASUREMENTS) { adjustChunkSize(drive); } // Start new measurement startMeasurement(); } /** * \brief Determine if we should explore (epsilon-greedy + periodic re-exploration) * \return true if should explore, false if should exploit */ bool Shred::shouldExplore() { // Periodic re-exploration: every REEXPLORATION_INTERVAL chunks if (totalChunkCounter > 0 && (totalChunkCounter % REEXPLORATION_INTERVAL) == 0) { return true; } // Epsilon-greedy: random exploration with probability EXPLORATION_EPSILON double randomValue = static_cast(rand()) / RAND_MAX; return (randomValue < EXPLORATION_EPSILON); } /** * \brief Perform exploration - try a random chunk size * \param pointer to Drive instance * \return void */ void Shred::performExploration(Drive *drive) { size_t savedChunkSize = currentChunkSize; // Generate random chunk size between MIN and MAX (aligned to STEP boundaries) size_t numSteps = (CHUNK_SIZE_MAX - CHUNK_SIZE_MIN) / CHUNK_SIZE_STEP_UP; size_t randomStep = rand() % (numSteps + 1); explorationChunkSize = CHUNK_SIZE_MIN + (randomStep * CHUNK_SIZE_STEP_UP); // Clamp to valid range (safety check) if (explorationChunkSize < CHUNK_SIZE_MIN) explorationChunkSize = CHUNK_SIZE_MIN; if (explorationChunkSize > CHUNK_SIZE_MAX) explorationChunkSize = CHUNK_SIZE_MAX; // Enter exploration mode explorationMode = true; currentChunkSize = explorationChunkSize; // Enhanced logging with debug info Logger::logThis()->info("EXPLORATION MODE: Testing " + to_string(explorationChunkSize / (1024 * 1024)) + " MB chunks " + "(randomStep=" + to_string(randomStep) + "/" + to_string(numSteps) + ", " + "was " + to_string(savedChunkSize / (1024 * 1024)) + " MB, " + "best: " + to_string(bestChunkSize / (1024 * 1024)) + " MB)" + " - Drive: " + drive->getSerial()); } /** * \brief Adjust chunk size based on throughput trend (Multi-Armed Bandit) * \param pointer to Drive instance * \return void */ void Shred::adjustChunkSize(Drive *drive) { size_t oldChunkSize = currentChunkSize; // Check if we should explore instead of exploit if (shouldExplore()) { performExploration(drive); return; } // Exit exploration mode if we were in it if (explorationMode) { explorationMode = false; // CRITICAL: Return to best known chunk size, not current if (currentChunkSize != bestChunkSize) { currentChunkSize = bestChunkSize; Logger::logThis()->info("EXPLORATION ENDED - Returning to best known: " + to_string(bestChunkSize / (1024 * 1024)) + " MB" + " (exploration tested " + to_string(oldChunkSize / (1024 * 1024)) + " MB)" + " - Drive: " + drive->getSerial()); } else { Logger::logThis()->info("EXPLORATION ENDED - Staying at current best: " + to_string(bestChunkSize / (1024 * 1024)) + " MB" + " - Drive: " + drive->getSerial()); } return; } // Normal exploitation mode: hill-climbing with symmetric steps if (throughputIncreasing) { // Throughput is improving - increase chunk size (symmetric step) currentChunkSize += CHUNK_SIZE_STEP_UP; // Clamp to maximum if (currentChunkSize > CHUNK_SIZE_MAX) { currentChunkSize = CHUNK_SIZE_MAX; Logger::logThis()->info("Reached maximum chunk size: " + to_string(currentChunkSize / (1024 * 1024)) + " MB" + " - Drive: " + drive->getSerial()); } } else { // Throughput decreased - decrease chunk size (symmetric step) if (currentChunkSize > CHUNK_SIZE_STEP_DOWN) { currentChunkSize -= CHUNK_SIZE_STEP_DOWN; } // Clamp to minimum if (currentChunkSize < CHUNK_SIZE_MIN) { currentChunkSize = CHUNK_SIZE_MIN; Logger::logThis()->info("Reached minimum chunk size: " + to_string(currentChunkSize / (1024 * 1024)) + " MB" + " (best remains: " + to_string(bestChunkSize / (1024 * 1024)) + " MB)" + " - Drive: " + drive->getSerial()); } } if (oldChunkSize != currentChunkSize) { Logger::logThis()->info("Adjusted chunk size: " + to_string(oldChunkSize / (1024 * 1024)) + " MB -> " + to_string(currentChunkSize / (1024 * 1024)) + " MB" + " (best: " + to_string(bestChunkSize / (1024 * 1024)) + " MB)" + " - Drive: " + drive->getSerial()); } } /** * \brief Get current chunk size for adaptive mode * \return current chunk size in bytes */ size_t Shred::getCurrentChunkSize() const { return currentChunkSize; } /** * \brief shred drive with shred * \param pointer of Drive instance * \param file descriptor for signaling * \return 0 on success, -1 on error */ int Shred::shredDrive(Drive *drive, int *ipSignalFd) { ostringstream address; address << (void const *)&(*drive); Logger::logThis()->info("Shred-Task started - Drive: " + drive->getModelName() + "-" + drive->getSerial() + " @" + address.str()); // Mark as started but NOT shredded yet drive->bWasShredStarted = true; drive->bWasShredded = false; drive->bWasChecked = false; drive->setTaskPercentage(0.0); drive->u32DriveChecksumAfterShredding = UINT32_MAX; drive->state = Drive::TaskState::SHRED_ACTIVE; #ifdef DRYRUN for (int i = 0; i <= 100; i++) { if (drive->state.load() != Drive::TaskState::SHRED_ACTIVE) { Logger::logThis()->info("Shred-Task aborted during DRYRUN - Drive: " + drive->getSerial()); drive->setTaskPercentage(i + 0.05); drive->state = Drive::TaskState::NONE; drive->bWasShredded = false; // CRITICAL: Mark as NOT shredded on abort return -1; } drive->setTaskPercentage((double)i); write(*ipSignalFd, "A", 1); usleep(20000); } // Only mark as shredded if DRYRUN completed successfully drive->bWasShredded = true; drive->setTaskPercentage(0.0); drive->state = Drive::TaskState::NONE; Logger::logThis()->info("DRYRUN completed - Drive: " + drive->getSerial()); return 0; #endif #ifndef DRYRUN string sDrivePath = drive->getPath(); const char *cpDrivePath = sDrivePath.c_str(); unsigned char ucKey[TFNG_KEY_SIZE]; // Validate buffers were allocated if (caTfngData == nullptr || caReadBuffer == nullptr) { Logger::logThis()->error("Shred-Task: Aligned buffers not allocated! - Drive: " + drive->getSerial()); return -1; } // Open random source Logger::logThis()->info("Shred-Task: Opening random source: " + string(randomsrc) + " - Drive: " + drive->getSerial()); randomSrcFileDiscr = open(randomsrc, O_RDONLY | O_LARGEFILE); if (randomSrcFileDiscr == -1) { int savedErrno = errno; Logger::logThis()->error("Shred-Task: Open random source failed! Path: " + string(randomsrc) + " - Error: " + strerror(savedErrno) + " (errno: " + to_string(savedErrno) + ")" + " - Drive: " + drive->getSerial()); // Reset drive state on error - NOT shredded drive->state = Drive::TaskState::NONE; drive->setTaskPercentage(0.0); drive->bWasShredStarted = false; drive->bWasShredded = false; return -1; } Logger::logThis()->info("Shred-Task: Random source opened successfully (fd: " + to_string(randomSrcFileDiscr) + ") - Drive: " + drive->getSerial()); // Open disk driveFileDiscr = open(cpDrivePath, O_RDWR | O_LARGEFILE); if (driveFileDiscr == -1) { int savedErrno = errno; string errorDetail; switch (savedErrno) { case ENOMEDIUM: errorDetail = "No medium found (drive may be empty or disconnected)"; break; case EACCES: errorDetail = "Permission denied (need root/sudo?)"; break; case ENOENT: errorDetail = "Drive not found (device may have been removed)"; break; case EROFS: errorDetail = "Read-only file system"; break; case EBUSY: errorDetail = "Drive is busy (may be mounted or in use)"; break; case EINVAL: errorDetail = "Invalid argument"; break; default: errorDetail = strerror(savedErrno); break; } Logger::logThis()->error("Shred-Task: Open drive failed! Path: " + string(cpDrivePath) + " - Error: " + errorDetail + " (errno: " + to_string(savedErrno) + ")" + " - Drive: " + drive->getSerial() + " - Model: " + drive->getModelName()); // Close random source before returning close(randomSrcFileDiscr); randomSrcFileDiscr = -1; // Reset drive state on error - NOT shredded drive->state = Drive::TaskState::NONE; drive->setTaskPercentage(0.0); drive->bWasShredStarted = false; drive->bWasShredded = false; return -1; } Logger::logThis()->info("Shred-Task: Drive opened successfully (fd: " + to_string(driveFileDiscr) + ") - Drive: " + drive->getSerial()); // Read key for random generator Logger::logThis()->info("Shred-Task: Reading random key - Drive: " + drive->getSerial()); ssize_t readRet = read(randomSrcFileDiscr, ucKey, sizeof(ucKey)); if (readRet <= 0) { int savedErrno = errno; Logger::logThis()->error("Shred-Task: Read random key failed! Expected: " + to_string(sizeof(ucKey)) + " bytes, Got: " + to_string(readRet) + " bytes" + " - Error: " + strerror(savedErrno) + " (errno: " + to_string(savedErrno) + ")" + " - Drive: " + drive->getSerial()); cleanup(); // Reset drive state on error - NOT shredded drive->state = Drive::TaskState::NONE; drive->setTaskPercentage(0.0); drive->bWasShredStarted = false; drive->bWasShredded = false; return -1; } Logger::logThis()->info("Shred-Task: Random key read successfully (" + to_string(readRet) + " bytes) - Drive: " + drive->getSerial()); tfng_prng_seedkey(ucKey); this->ulDriveByteSize = getDriveSizeInBytes(driveFileDiscr); if (this->ulDriveByteSize == 0) { Logger::logThis()->error("Shred-Task: Drive size is 0 bytes! Drive may be empty or size detection failed - Drive: " + drive->getSerial()); cleanup(); // Reset drive state on error - NOT shredded drive->state = Drive::TaskState::NONE; drive->setTaskPercentage(0.0); drive->bWasShredStarted = false; drive->bWasShredded = false; return -1; } Drive::ShredSpeed shredSpeed = drive->sShredSpeed.load(); shredSpeed.chronoShredTimestamp = std::chrono::system_clock::now(); shredSpeed.ulSpeedMetricBytesWritten = 0U; drive->sShredSpeed.store(shredSpeed); #ifdef LOG_LEVEL_HIGH Logger::logThis()->info("Shred-Task: Bytes-Size of Drive: " + to_string(this->ulDriveByteSize) + " - Drive: " + drive->getSerial()); #endif // Start first measurement interval startMeasurement(); // Main shredding loop for (unsigned int uiShredIterationCounter = 0U; uiShredIterationCounter < SHRED_ITERATIONS; uiShredIterationCounter++) { // Logger::logThis()->info("Shred-Task: Starting iteration " + to_string(uiShredIterationCounter + 1) + "/" + to_string(SHRED_ITERATIONS) + " - Drive: " + drive->getSerial()); unsigned long ulDriveByteCounter = 0U; if (uiShredIterationCounter == (SHRED_ITERATIONS - 1)) { // last shred iteration --> overwrite (just the write chunk) bytes with zeros instead with random data memset(caTfngData, 0U, CHUNK_SIZE_MAX); } while (ulDriveByteCounter < ulDriveByteSize) { size_t activeChunkSize = getCurrentChunkSize(); int iBytesToShred = 0; if (uiShredIterationCounter != (SHRED_ITERATIONS - 1)) { tfng_prng_genrandom(caTfngData, activeChunkSize); } if ((ulDriveByteSize - ulDriveByteCounter) < activeChunkSize) { iBytesToShred = (ulDriveByteSize - ulDriveByteCounter); } else { iBytesToShred = activeChunkSize; } int iByteShredded = write(driveFileDiscr, caTfngData, iBytesToShred); if (iByteShredded <= 0) { int savedErrno = errno; Logger::logThis()->error("Shred-Task: Write to drive failed! Attempted: " + to_string(iBytesToShred) + " bytes, Written: " + to_string(iByteShredded) + " bytes" + " - Position: " + to_string(ulDriveByteCounter) + "/" + to_string(ulDriveByteSize) + " - Iteration: " + to_string(uiShredIterationCounter + 1) + "/" + to_string(SHRED_ITERATIONS) + " - Error: " + strerror(savedErrno) + " (errno: " + to_string(savedErrno) + ")" + " - Drive: " + drive->getSerial()); cleanup(); // CRITICAL: Mark as NOT shredded on write failure drive->state = Drive::TaskState::NONE; drive->setTaskPercentage(0.0); drive->bWasShredded = false; drive->bWasChecked = false; return -1; } auto shredSpeed = drive->sShredSpeed.load(); shredSpeed.ulSpeedMetricBytesWritten += iByteShredded; drive->sShredSpeed.store(shredSpeed); ulDriveByteCounter += iByteShredded; ulDriveByteOverallCount += iByteShredded; bytesWrittenInMeasurement += iByteShredded; chunkCounter++; totalChunkCounter++; // Track total chunks for periodic re-exploration // Evaluate throughput after measurement interval if (chunkCounter >= CHUNK_MEASURE_INTERVAL) { evaluateThroughput(drive); } d32Percent = this->calcProgress(); #ifdef LOG_LEVEL_HIGH Logger::logThis()->info("Shred-Task: ByteCount: " + to_string(ulDriveByteCounter) + " - iteration: " + to_string((uiShredIterationCounter + 1)) + " - progress: " + to_string(d32Percent) + "%" + " - Drive: " + drive->getSerial()); #endif if ((d32Percent - d32TmpPercent) >= 0.01) { // set shred percentage drive->setTaskPercentage(d32TmpPercent); d32TmpPercent = d32Percent; // signal process in shredding write(*ipSignalFd, "A", 1); } if (drive->state != Drive::TaskState::SHRED_ACTIVE) { drive->setTaskPercentage(0); d32Percent = 0.00; d32TmpPercent = 0.00; ulDriveByteCounter = 0U; Logger::logThis()->info("Aborted shred for: " + drive->getModelName() + "-" + drive->getSerial()); cleanup(); return -1; } } Logger::logThis()->info("Shred-Task: Iteration " + to_string(uiShredIterationCounter + 1) + "/" + to_string(SHRED_ITERATIONS) + " completed - Drive: " + drive->getSerial()); // Rewind drive for next iteration if (0 != iRewindDrive(driveFileDiscr)) { Logger::logThis()->error("Shred-Task: Unable to rewind drive after iteration " + to_string(uiShredIterationCounter + 1) + " - Drive: " + drive->getSerial()); cleanup(); // CRITICAL: Mark as NOT shredded on rewind failure drive->state = Drive::TaskState::NONE; drive->setTaskPercentage(0.0); drive->bWasShredded = false; drive->bWasChecked = false; return -1; } } Logger::logThis()->info("Shred completed - Optimal chunk size: " + to_string(bestChunkSize / (1024 * 1024)) + " MB, " + "Best throughput: " + to_string((int)bestThroughputMBps) + " MB/s" + " - Drive: " + drive->getSerial()); // All shred iterations completed successfully tfng_prng_seedkey(NULL); // ONLY mark as shredded if ALL iterations completed AND fsync succeeded drive->bWasShredded = true; Logger::logThis()->info("Shred-Task finished - Drive: " + drive->getModelName() + "-" + drive->getSerial() + " @" + address.str()); #ifdef ZERO_CHECK drive->state = Drive::TaskState::CHECK_ACTIVE; Logger::logThis()->info("Check-Task started - Drive: " + drive->getModelName() + "-" + drive->getSerial() + " @" + address.str()); drive->u32DriveChecksumAfterShredding = uiCalcChecksum(driveFileDiscr, drive, ipSignalFd); if (drive->u32DriveChecksumAfterShredding != 0) { drive->state = Drive::TaskState::CHECK_FAILED; Logger::logThis()->error("Check-Task: Checksum verification failed! Expected: 0, Got: " + to_string(drive->u32DriveChecksumAfterShredding) + " - Drive: " + drive->getSerial()); } else { drive->state = Drive::TaskState::CHECK_SUCCESSFUL; drive->bWasChecked = true; Logger::logThis()->info("Check-Task: Checksum verification passed (zero) - Drive: " + drive->getSerial()); } #endif cleanup(); #endif // Final state handling - ONLY process if shred actually completed Drive::TaskState finalState = drive->state.load(); // Only do final processing if we reached a completion state // (not if we returned early with errors) if ((finalState == Drive::TaskState::SHRED_ACTIVE) || (finalState == Drive::TaskState::CHECK_SUCCESSFUL) || (finalState == Drive::TaskState::CHECK_FAILED)) { if (finalState != Drive::TaskState::CHECK_FAILED) { Logger::logThis()->info("Shred-Task: Triggering print for drive - Drive: " + drive->getSerial()); Printer::getPrinter()->print(drive); } else { Logger::logThis()->warning("Shred-Task: Skipping print due to checksum failure - Drive: " + drive->getSerial()); } drive->state = Drive::TaskState::NONE; drive->setTaskPercentage(0.0); Logger::logThis()->info("Completed shred/check for: " + drive->getModelName() + "-" + drive->getSerial()); } return 0; } /** * \brief calc shredding progress in % * \return double percentage */ double Shred::calcProgress() { unsigned int uiMaxShredIteration = SHRED_ITERATIONS; #ifdef ZERO_CHECK uiMaxShredIteration++; // increment because we will check after SHRED_ITERATIONS the drive for non-zero bytes #endif if (this->ulDriveByteSize == 0) return 0.0; return (double)(((double)ulDriveByteOverallCount) / ((double)this->ulDriveByteSize * uiMaxShredIteration)) * 100.0; } /** * \brief rewind drive to beginning * \param file descriptor * \return 0 on success, -1 on error */ int Shred::iRewindDrive(fileDescriptor file) { off_t result = lseek(file, 0L, SEEK_SET); if (result == -1) { int savedErrno = errno; Logger::logThis()->error("Unable to rewind drive! Error: " + string(strerror(savedErrno)) + " (errno: " + to_string(savedErrno) + ") - fileDescriptor: " + to_string(file)); return -1; } else if (result != 0) { Logger::logThis()->error("Rewind position mismatch! Expected: 0, Got: " + to_string(result) + " - fileDescriptor: " + to_string(file)); return -1; } return 0; } /** * \brief get drive size in bytes * \param file descriptor * \return size in bytes, 0 on error */ long Shred::getDriveSizeInBytes(fileDescriptor file) { off_t liDriveSizeTmp = lseek(file, 0L, SEEK_END); if (liDriveSizeTmp == -1) { int savedErrno = errno; Logger::logThis()->error("Unable to get drive size! Error: " + string(strerror(savedErrno)) + " (errno: " + to_string(savedErrno) + ") - fileDescriptor: " + to_string(file)); return 0L; } if (0 != iRewindDrive(file)) { Logger::logThis()->error("Unable to rewind after size detection - fileDescriptor: " + to_string(file)); return 0L; } #ifdef DEMO_DRIVE_SIZE liDriveSizeTmp = DEMO_DRIVE_SIZE; Logger::logThis()->info("DEMO_DRIVE_SIZE active - using size: " + to_string(liDriveSizeTmp) + " bytes"); #endif return liDriveSizeTmp; } /** * \brief calculate checksum of drive (verify all zeros) * \param file descriptor * \param pointer to Drive instance * \param signal file descriptor * \return checksum value (0 = all zeros) */ unsigned int Shred::uiCalcChecksum(fileDescriptor file, Drive *drive, int *ipSignalFd) { unsigned int uiChecksum = 0; unsigned long ulDriveByteCounter = 0U; Logger::logThis()->info("Check-Task: Starting checksum verification - Drive: " + drive->getSerial()); size_t checkChunkSize = CHUNK_SIZE_MAX; while (ulDriveByteCounter < ulDriveByteSize) { // Check if task was aborted if (drive->state.load() != Drive::TaskState::CHECK_ACTIVE) { Logger::logThis()->info("Check-Task: Aborted by user at " + to_string(d32Percent) + "% - Drive: " + drive->getSerial()); return UINT32_MAX; // Return non-zero to indicate incomplete check } int iBytesToCheck = 0; if ((ulDriveByteSize - ulDriveByteCounter) < checkChunkSize) { iBytesToCheck = (ulDriveByteSize - ulDriveByteCounter); } else { iBytesToCheck = checkChunkSize; } int iReadBytes = read(file, caReadBuffer, iBytesToCheck); if (iReadBytes <= 0) { int savedErrno = errno; Logger::logThis()->error("Check-Task: Read failed! Attempted: " + to_string(iBytesToCheck) + " bytes, Read: " + to_string(iReadBytes) + " bytes" + " - Position: " + to_string(ulDriveByteCounter) + "/" + to_string(ulDriveByteSize) + " - Error: " + strerror(savedErrno) + " (errno: " + to_string(savedErrno) + ")" + " - Drive: " + drive->getSerial()); return UINT32_MAX; // Return non-zero to indicate read failure } for (int iReadBytesCounter = 0U; iReadBytesCounter < iReadBytes; iReadBytesCounter++) { uiChecksum += caReadBuffer[iReadBytesCounter]; } ulDriveByteCounter += iReadBytes; ulDriveByteOverallCount += iReadBytes; d32Percent = this->calcProgress(); auto shredSpeed = drive->sShredSpeed.load(); shredSpeed.ulSpeedMetricBytesWritten += iReadBytes; drive->sShredSpeed.store(shredSpeed); #ifdef LOG_LEVEL_HIGH Logger::logThis()->info("Check-Task: ByteCount: " + to_string(ulDriveByteCounter) + " - progress: " + to_string(d32Percent) + "%" + " - checksum so far: " + to_string(uiChecksum) + " - Drive: " + drive->getSerial()); #endif if (((d32Percent - d32TmpPercent) >= 0.01) || (d32Percent == 100.0)) { drive->setTaskPercentage(d32TmpPercent); d32TmpPercent = d32Percent; #ifdef LOG_LEVEL_HIGH Logger::logThis()->info("send progress signal to main loop (check)"); #endif write(*ipSignalFd, "A", 1); } } Logger::logThis()->info("Check-Task: Verification complete - Final checksum: " + to_string(uiChecksum) + " - Drive: " + drive->getSerial()); drive->bWasChecked = true; return uiChecksum; } /** * \brief cleanup - close file descriptors */ void Shred::cleanup() { if (driveFileDiscr != -1) { Logger::logThis()->info("Shred-Task: Closing drive file descriptor: " + to_string(driveFileDiscr)); close(driveFileDiscr); driveFileDiscr = -1; } if (randomSrcFileDiscr != -1) { Logger::logThis()->info("Shred-Task: Closing random source file descriptor: " + to_string(randomSrcFileDiscr)); close(randomSrcFileDiscr); randomSrcFileDiscr = -1; } }