diff --git a/include/drive.h b/include/drive.h index bafe7e5..3220a3a 100644 --- a/include/drive.h +++ b/include/drive.h @@ -72,7 +72,10 @@ private: uint32_t u32ErrorCount = 0U; uint32_t u32PowerOnHours = 0U; // in hours uint32_t u32PowerCycles = 0U; - uint32_t u32Temperature = 0U; // in Fahrenheit, just kidding: degree Celsius + uint32_t u32Temperature = 0U; // in Fahrenheit, just kidding: degree Celsius + uint32_t u32ReallocatedSectors = 0U; // ID 0x05 - Reallocated Sectors Count + uint32_t u32PendingSectors = 0U; // ID 0xC5 - Current Pending Sector Count + uint32_t u32UncorrectableSectors = 0U; // ID 0xC6 - Offline Uncorrectable Sector Count } sSmartData; private: @@ -106,6 +109,9 @@ public: uint32_t getPowerOnHours(void); // in hours uint32_t getPowerCycles(void); uint32_t getTemperature(void); // in Fahrenheit, just kidding: degree Celsius + uint32_t getReallocatedSectors(void); + uint32_t getPendingSectors(void); + uint32_t getUncorrectableSectors(void); void checkFrozenDrive(void); void setDriveSMARTData(std::string modelFamily, @@ -115,7 +121,10 @@ public: uint32_t errorCount, uint32_t powerOnHours, uint32_t powerCycles, - uint32_t temperature); + uint32_t temperature, + uint32_t reallocatedSectors, + uint32_t pendingSectors, + uint32_t uncorrectableSectors); std::string sCapacityToText(); std::string sErrorCountToText(); diff --git a/include/reHDD.h b/include/reHDD.h index 40f93e4..d63f7f0 100644 --- a/include/reHDD.h +++ b/include/reHDD.h @@ -8,7 +8,7 @@ #ifndef REHDD_H_ #define REHDD_H_ -#define REHDD_VERSION "V1.3.0" +#define REHDD_VERSION "V1.4.0-dev" // Drive handling Settings #define WORSE_HOURS 19200 // mark drive if at this limit or beyond @@ -20,7 +20,7 @@ // Logger Settings #define LOG_PATH "./reHDD.log" -#define DESCRIPTION "reHDD - Copyright Hendrik Schutter 2025" +#define DESCRIPTION "reHDD - Copyright Hendrik Schutter 2026" #define DEVICE_ID "generic" #define SOFTWARE_VERSION REHDD_VERSION #define HARDWARE_VERSION "generic" diff --git a/include/smart.h b/include/smart.h index 2d4f4fd..bafccc2 100644 --- a/include/smart.h +++ b/include/smart.h @@ -10,24 +10,29 @@ #include "reHDD.h" +/** + * @brief SMART data reader for drives + * + * Parses smartctl JSON output to extract: + * - Device information (model, serial, capacity) + * - Power statistics (hours, cycles) + * - Temperature + * - Critical sector counts (reallocated, pending, uncorrectable) + * + * Uses deterministic state machine parser for reliable multi-line JSON parsing. + */ class SMART { protected: public: + /** + * @brief Read S.M.A.R.T. data from drive and populate Drive object + * @param drive Pointer to Drive instance to populate with SMART data + */ static void readSMARTData(Drive *drive); private: - SMART(void); - - static bool parseExitStatus(std::string sLine, uint8_t &status); - static bool parseModelFamily(std::string sLine, std::string &modelFamily); - static bool parseModelName(std::string sLine, std::string &modelName); - static bool parseSerial(std::string sLine, std::string &serial); - static bool parseCapacity(std::string sLine, uint64_t &capacity); - static bool parseErrorCount(std::string sLine, uint32_t &errorCount); - static bool parsePowerOnHours(std::string sLine, uint32_t &powerOnHours); - static bool parsePowerCycles(std::string sLine, uint32_t &powerCycles); - static bool parseTemperature(std::string sLine, uint32_t &temperature); + SMART(void); // Utility class - no instances }; -#endif // SMART_H_ \ No newline at end of file +#endif // SMART_H_ diff --git a/include/tui.h b/include/tui.h index 09f5304..489504a 100644 --- a/include/tui.h +++ b/include/tui.h @@ -67,17 +67,17 @@ private: WINDOW* dialog; WINDOW* smartWarning; - static void centerTitle(WINDOW* pwin, const char* title); - static WINDOW* createOverViewWindow(int iXSize, int iYSize); - static WINDOW* createDetailViewWindow(int iXSize, int iYSize, int iXStart, Drive& drive); - static WINDOW* overwriteDetailViewWindow(int iXSize, int iYSize, int iXStart); - static WINDOW* createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, int iListIndex, std::string sModelFamily, std::string sSerial, std::string sCapacity, std::string sState, std::string sTime, std::string sSpeed, std::string sTemp, std::string sConnection, bool bSelected); - static WINDOW* createSystemStats(int iXSize, int iYSize, int iXStart, int iYStart); - static WINDOW* createMenuView(int iXSize, int iYSize, int iXStart, int iYStart, struct MenuState menustate); - static WINDOW* createDialog(int iXSize, int iYSize, int iXStart, int iYStart, std::string selectedTask, std::string optionA, std::string optionB); - static WINDOW* createFrozenWarning(int iXSize, int iYSize, int iXStart, int iYStart, std::string sPath, std::string sModelFamily, std::string sModelName, std::string sSerial, std::string sProgress); - static WINDOW* createSmartWarning(int iXSize, int iYSize, int iXStart, int iYStart, std::string sPath, uint32_t u32PowerOnHours, uint32_t u32PowerCycles, uint32_t u32ErrorCount, uint32_t u32Temperature); - static WINDOW* createZeroChecksumWarning(int iXSize, int iYSize, int iXStart, int iYStart, std::string sPath, std::string sModelFamily, std::string sModelName, std::string sSerial, uint32_t u32Checksum); + static void centerTitle(WINDOW *pwin, const char *title); + static WINDOW *createOverViewWindow(int iXSize, int iYSize); + static WINDOW *createDetailViewWindow(int iXSize, int iYSize, int iXStart, Drive &drive); + static WINDOW *overwriteDetailViewWindow(int iXSize, int iYSize, int iXStart); + static WINDOW *createEntryWindow(int iXSize, int iYSize, int iXStart, int iYStart, int iListIndex, std::string sModelFamily, std::string sSerial, std::string sCapacity, std::string sState, std::string sTime, std::string sSpeed, std::string sTemp, std::string sConnection, bool bSelected); + static WINDOW *createSystemStats(int iXSize, int iYSize, int iXStart, int iYStart); + static WINDOW *createMenuView(int iXSize, int iYSize, int iXStart, int iYStart, struct MenuState menustate); + static WINDOW *createDialog(int iXSize, int iYSize, int iXStart, int iYStart, std::string selectedTask, std::string optionA, std::string optionB); + static WINDOW *createFrozenWarning(int iXSize, int iYSize, int iXStart, int iYStart, std::string sPath, std::string sModelFamily, std::string sModelName, std::string sSerial, std::string sProgress); + static WINDOW *createSmartWarning(int iXSize, int iYSize, int iXStart, int iYStart, std::string sPath, uint32_t u32PowerOnHours, uint32_t u32PowerCycles, uint32_t u32ErrorCount, uint32_t u32Temperature, uint32_t u32ReallocatedSectors, uint32_t u32PendingSectors, uint32_t u32UncorrectableSectors); + static WINDOW *createZeroChecksumWarning(int iXSize, int iYSize, int iXStart, int iYStart, std::string sPath, std::string sModelFamily, std::string sModelName, std::string sSerial, uint32_t u32Checksum); void displaySelectedDrive(Drive& drive, int stdscrX, int stdscrY); std::string formatTimeDuration(time_t u32Duration); diff --git a/src/drive.cpp b/src/drive.cpp index c1683ce..58ffb76 100644 --- a/src/drive.cpp +++ b/src/drive.cpp @@ -140,6 +140,21 @@ uint32_t Drive::getTemperature(void) return sSmartData.u32Temperature; } +uint32_t Drive::getReallocatedSectors(void) +{ + return sSmartData.u32ReallocatedSectors; +} + +uint32_t Drive::getPendingSectors(void) +{ + return sSmartData.u32PendingSectors; +} + +uint32_t Drive::getUncorrectableSectors(void) +{ + return sSmartData.u32UncorrectableSectors; +} + string Drive::sCapacityToText() { char acBuffer[16]; @@ -226,7 +241,10 @@ void Drive::setDriveSMARTData(string modelFamily, uint32_t errorCount, uint32_t powerOnHours, uint32_t powerCycle, - uint32_t temperature) + uint32_t temperature, + uint32_t reallocatedSectors, + uint32_t pendingSectors, + uint32_t uncorrectableSectors) { this->sSmartData.sModelFamily = modelFamily; this->sSmartData.sModelName = modelName; @@ -236,6 +254,9 @@ void Drive::setDriveSMARTData(string modelFamily, this->sSmartData.u32PowerOnHours = powerOnHours; this->sSmartData.u32PowerCycles = powerCycle; this->sSmartData.u32Temperature = temperature; + this->sSmartData.u32ReallocatedSectors = reallocatedSectors; + this->sSmartData.u32PendingSectors = pendingSectors; + this->sSmartData.u32UncorrectableSectors = uncorrectableSectors; } void Drive::setTimestamp() diff --git a/src/reHDD.cpp b/src/reHDD.cpp index e7b0a98..37707af 100644 --- a/src/reHDD.cpp +++ b/src/reHDD.cpp @@ -332,7 +332,7 @@ void reHDD::filterNewDrives(list *plistOldDrives, list *plistNewDr { itOld->bIsOffline = false; // drive is still attached // copy new smart data to existing drive - itOld->setDriveSMARTData(itNew->getModelFamily(), itNew->getModelName(), itNew->getSerial(), itNew->getCapacity(), itNew->getErrorCount(), itNew->getPowerOnHours(), itNew->getPowerCycles(), itNew->getTemperature()); + itOld->setDriveSMARTData(itNew->getModelFamily(), itNew->getModelName(), itNew->getSerial(), itNew->getCapacity(), itNew->getErrorCount(), itNew->getPowerOnHours(), itNew->getPowerCycles(), itNew->getTemperature(), itNew->getReallocatedSectors(), itNew->getPendingSectors(), itNew->getUncorrectableSectors()); #ifdef LOG_LEVEL_HIGH Logger::logThis()->info("Delete new drive, because already attached: " + itNew->getModelName()); #endif diff --git a/src/shred.cpp b/src/shred.cpp index eef0436..3c98379 100644 --- a/src/shred.cpp +++ b/src/shred.cpp @@ -78,9 +78,10 @@ void Shred::startMeasurement() } /** - * \brief Evaluate throughput after measurement interval and adjust chunk size - * \param pointer to Drive instance - * \return void + * \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) { @@ -194,8 +195,11 @@ 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; @@ -203,11 +207,25 @@ int Shred::shredDrive(Drive* drive, int* ipSignalFd) #ifdef DRYRUN for (int i = 0; i <= 100; i++) { - drive->setTaskPercentage(i + 0.05); + 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 @@ -223,42 +241,111 @@ int Shred::shredDrive(Drive* drive, int* ipSignalFd) } #endif - // open random source + // 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) { - std::string errorMsg(strerror(errno)); - Logger::logThis()->error("Shred-Task: Open random source failed! " + errorMsg + " - Drive: " + drive->getSerial()); - perror(randomsrc); - cleanup(); + 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 + // Open disk driveFileDiscr = open(cpDrivePath, O_RDWR | O_LARGEFILE); if (driveFileDiscr == -1) { - std::string errorMsg(strerror(errno)); - Logger::logThis()->error("Shred-Task: Open drive failed! " + errorMsg + " - Drive: " + drive->getSerial()); - perror(cpDrivePath); - cleanup(); + 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 + // 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) { - std::string errorMsg(strerror(errno)); - Logger::logThis()->error("Shred-Task: Read random key failed! " + errorMsg + " - Drive: " + drive->getSerial()); - perror(randomsrc); + 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; @@ -272,9 +359,11 @@ int Shred::shredDrive(Drive* drive, int* ipSignalFd) // Start first measurement interval startMeasurement(); #endif - + // 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)) @@ -319,10 +408,20 @@ int Shred::shredDrive(Drive* drive, int* ipSignalFd) if (iByteShredded <= 0) { - std::string errorMsg(strerror(errno)); - Logger::logThis()->error("Shred-Task: Write to drive failed! " + errorMsg + " - Drive: " + drive->getSerial()); - perror("unable to write random data"); + 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; } @@ -347,7 +446,10 @@ int Shred::shredDrive(Drive* drive, int* ipSignalFd) 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()); + 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) @@ -369,10 +471,21 @@ int Shred::shredDrive(Drive* drive, int* ipSignalFd) } } + 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! - Drive: " + drive->getSerial()); + 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; } } @@ -384,8 +497,10 @@ int Shred::shredDrive(Drive* drive, int* ipSignalFd) " - Drive: " + drive->getSerial()); #endif + // 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()); @@ -397,27 +512,44 @@ int Shred::shredDrive(Drive* drive, int* ipSignalFd) if (drive->u32DriveChecksumAfterShredding != 0) { drive->state = Drive::TaskState::CHECK_FAILED; - Logger::logThis()->info("Shred-Task: Checksum not zero: " + to_string(drive->u32DriveChecksumAfterShredding) + " - Drive: " + drive->getSerial()); + 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; - Logger::logThis()->info("Shred-Task: Checksum zero: " + to_string(drive->u32DriveChecksumAfterShredding) + " - Drive: " + drive->getSerial()); + drive->bWasChecked = true; + Logger::logThis()->info("Check-Task: Checksum verification passed (zero) - Drive: " + drive->getSerial()); } #endif + cleanup(); #endif - if ((drive->state.load() == Drive::TaskState::SHRED_ACTIVE) || (drive->state.load() == Drive::TaskState::CHECK_SUCCESSFUL) || (drive->state == Drive::TaskState::CHECK_FAILED)) + // 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 (drive->state != 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("Finished shred/check for: " + drive->getModelName() + "-" + drive->getSerial()); + Logger::logThis()->info("Completed shred/check for: " + drive->getModelName() + "-" + drive->getSerial()); } + return 0; } @@ -432,47 +564,77 @@ double Shred::calcProgress() #ifdef ZERO_CHECK uiMaxShredIteration++; #endif + if (this->ulDriveByteSize == 0) return 0.0; - return (double)(((double)ulDriveByteOverallCount) / ((double)this->ulDriveByteSize * uiMaxShredIteration)) * 100.0f; + + 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) { - if (0 != lseek(file, 0L, SEEK_SET)) + off_t result = lseek(file, 0L, SEEK_SET); + + if (result == -1) { - perror("unable to rewind drive"); - Logger::logThis()->info("Unable to rewind drive! - fileDescriptor: " + to_string(file)); + 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 + else if (result != 0) { - return 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) { - long liDriveSizeTmp = lseek(file, 0L, SEEK_END); + off_t liDriveSizeTmp = lseek(file, 0L, SEEK_END); if (liDriveSizeTmp == -1) { - perror("unable to get drive size"); - Logger::logThis()->info("Unable to get drive size! - fileDescriptor: " + to_string(file)); + 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)) { - liDriveSizeTmp = 0L; + 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; @@ -486,6 +648,13 @@ unsigned int Shred::uiCalcChecksum(fileDescriptor file, Drive* drive, int* ipSig 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) { @@ -496,6 +665,18 @@ unsigned int Shred::uiCalcChecksum(fileDescriptor file, Drive* drive, int* ipSig 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]; @@ -508,7 +689,10 @@ unsigned int Shred::uiCalcChecksum(fileDescriptor file, Drive* drive, int* ipSig drive->sShredSpeed.store(shredSpeed); #ifdef LOG_LEVEL_HIGH - Logger::logThis()->info("Shred-Task (Checksum): ByteCount: " + to_string(ulDriveByteCounter) + " - progress: " + to_string(d32Percent) + " - Drive: " + drive->getSerial()); + 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)) @@ -521,12 +705,29 @@ unsigned int Shred::uiCalcChecksum(fileDescriptor file, Drive* drive, int* ipSig 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() { - close(driveFileDiscr); - close(randomSrcFileDiscr); + 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; + } } diff --git a/src/smart.cpp b/src/smart.cpp index 2a10855..0733565 100644 --- a/src/smart.cpp +++ b/src/smart.cpp @@ -6,306 +6,382 @@ */ #include "../include/reHDD.h" +#include // For WIFSIGNALED, WTERMSIG using namespace std; +/** + * \brief Parse context for SMART attribute values + */ +struct SMARTParseContext +{ + // Device information (top-level JSON fields) + string modelFamily; + string modelName; + string serial; + uint64_t capacity; + + // Power and temperature (top-level JSON fields) + uint32_t errorCount; + uint32_t powerOnHours; + uint32_t powerCycles; + uint32_t temperature; + + // Critical sector counts (from ata_smart_attributes table) + uint32_t reallocatedSectors; // ID 5 + uint32_t pendingSectors; // ID 197 + uint32_t uncorrectableSectors; // ID 198 + + // Parser state machine + enum State + { + SEARCHING, // Looking for next field + IN_ATTRIBUTE_5, // Inside ID 5 object + IN_ATTRIBUTE_197, // Inside ID 197 object + IN_ATTRIBUTE_198, // Inside ID 198 object + IN_RAW_SECTION // Inside "raw": { } of current attribute + }; + + State state; + int currentAttributeId; // Which attribute are we parsing? (5, 197, 198) + + SMARTParseContext() + : capacity(0), + errorCount(0), + powerOnHours(0), + powerCycles(0), + temperature(0), + reallocatedSectors(0), + pendingSectors(0), + uncorrectableSectors(0), + state(SEARCHING), + currentAttributeId(0) + { + } +}; + +/** + * \brief Extract JSON string value + * \param line containing "key": "value" + * \return extracted string value + */ +static string extractStringValue(const string &line) +{ + size_t colonPos = line.find(": "); + if (colonPos == string::npos) + return ""; + + size_t firstQuote = line.find('"', colonPos + 2); + if (firstQuote == string::npos) + return ""; + + size_t secondQuote = line.find('"', firstQuote + 1); + if (secondQuote == string::npos) + return ""; + + return line.substr(firstQuote + 1, secondQuote - firstQuote - 1); +} + +/** + * \brief Extract JSON integer value + * \param line containing "key": number + * \return extracted integer value + */ +static uint64_t extractIntegerValue(const string &line) +{ + size_t colonPos = line.find(": "); + if (colonPos == string::npos) + return 0; + + string valueStr = line.substr(colonPos + 2); + + // Remove whitespace, commas, braces + valueStr.erase(remove_if(valueStr.begin(), valueStr.end(), + [](char c) + { return c == ' ' || c == ',' || c == '}' || c == '\n'; }), + valueStr.end()); + + // Verify it's a valid number + if (valueStr.empty() || valueStr.find_first_not_of("0123456789") != string::npos) + return 0; + + try + { + return stoull(valueStr); + } + catch (...) + { + return 0; + } +} + +/** + * \brief Process a single line of JSON output + * \param line from smartctl JSON output + * \param context parsing context with state + * \return void + */ +static void processLine(const string &line, SMARTParseContext &ctx) +{ + // Trim whitespace for consistent parsing + string trimmed = line; + size_t firstNonSpace = trimmed.find_first_not_of(" \t\r\n"); + if (firstNonSpace != string::npos) + { + trimmed = trimmed.substr(firstNonSpace); + } + + // Parse top-level device information + if (trimmed.find("\"model_family\":") == 0) + { + ctx.modelFamily = extractStringValue(line); + return; + } + + if (trimmed.find("\"model_name\":") == 0) + { + ctx.modelName = extractStringValue(line); + return; + } + + if (trimmed.find("\"serial_number\":") == 0) + { + ctx.serial = extractStringValue(line); + return; + } + + // Parse capacity from user_capacity.bytes + if (trimmed.find("\"bytes\":") == 0) + { + ctx.capacity = extractIntegerValue(line); + return; + } + + // Parse error count from self_test log + if (trimmed.find("\"error_count_total\":") == 0) + { + ctx.errorCount = extractIntegerValue(line); + return; + } + + // Parse power-on hours + if (trimmed.find("\"hours\":") == 0) + { + ctx.powerOnHours = extractIntegerValue(line); + return; + } + + // Parse power cycle count + if (trimmed.find("\"power_cycle_count\":") == 0) + { + ctx.powerCycles = extractIntegerValue(line); + return; + } + + // Parse temperature + if (trimmed.find("\"current\":") == 0 && ctx.temperature == 0) + { + // Only parse first occurrence (temperature section, not other "current" fields) + ctx.temperature = extractIntegerValue(line); + return; + } + + // State machine for SMART attributes parsing + switch (ctx.state) + { + case SMARTParseContext::SEARCHING: + // Look for critical attribute IDs + if (trimmed.find("\"id\": 5,") == 0) + { + ctx.state = SMARTParseContext::IN_ATTRIBUTE_5; + ctx.currentAttributeId = 5; + } + else if (trimmed.find("\"id\": 197,") == 0) + { + ctx.state = SMARTParseContext::IN_ATTRIBUTE_197; + ctx.currentAttributeId = 197; + } + else if (trimmed.find("\"id\": 198,") == 0) + { + ctx.state = SMARTParseContext::IN_ATTRIBUTE_198; + ctx.currentAttributeId = 198; + } + break; + + case SMARTParseContext::IN_ATTRIBUTE_5: + case SMARTParseContext::IN_ATTRIBUTE_197: + case SMARTParseContext::IN_ATTRIBUTE_198: + // Look for "raw": { start + if (trimmed.find("\"raw\":") == 0) + { + ctx.state = SMARTParseContext::IN_RAW_SECTION; + } + // Look for end of attribute object (more indented closing brace = end of attribute) + // " }," or " }" at attribute level (6 spaces) + else if (line.find(" },") == 0 || line.find(" }") == 0) + { + ctx.state = SMARTParseContext::SEARCHING; + ctx.currentAttributeId = 0; + } + break; + + case SMARTParseContext::IN_RAW_SECTION: + // Look for "value": number inside raw section + if (trimmed.find("\"value\":") == 0) + { + uint64_t value = extractIntegerValue(line); + + // Store value in appropriate field based on current attribute + if (ctx.currentAttributeId == 5) + { + ctx.reallocatedSectors = static_cast(value); + } + else if (ctx.currentAttributeId == 197) + { + ctx.pendingSectors = static_cast(value); + } + else if (ctx.currentAttributeId == 198) + { + ctx.uncorrectableSectors = static_cast(value); + } + + // Stay in raw section - closing brace will exit + } + // Look for end of raw object (less indented = back to attribute level) + // " }" at raw level (8 spaces) + else if (line.find(" }") == 0) + { + // Return to attribute state (raw section closed) + ctx.state = (ctx.currentAttributeId == 5) ? SMARTParseContext::IN_ATTRIBUTE_5 : (ctx.currentAttributeId == 197) ? SMARTParseContext::IN_ATTRIBUTE_197 + : SMARTParseContext::IN_ATTRIBUTE_198; + } + break; + } +} + /** * \brief get and set S.M.A.R.T. values in Drive - * \param pointer of Drive instance + * \param pointer of Drive instance * \return void */ void SMART::readSMARTData(Drive *drive) { - string modelFamily; - string modelName; - string serial; - uint64_t capacity = 0U; - uint32_t errorCount = 0U; - uint32_t powerOnHours = 0U; - uint32_t powerCycles = 0U; - uint32_t temperature = 0U; + SMARTParseContext ctx; + uint8_t exitStatus = 255U; - modelFamily.clear(); - modelName.clear(); - serial.clear(); + // Command order optimized for USB adapters + // Standard commands first, then device-specific variants + string sSmartctlCommands[] = { + " --json -a ", // Try standard first + " --json -d sat -a ", // SAT (SCSI/ATA Translation) - most USB adapters + " --json -d usbjmicron -a ", // USB JMicron + " --json -d usbprolific -a ", // USB Prolific + " --json -d usbsunplus -a " // USB Sunplus + }; - string sSmartctlCommands[] = {" --json -a ", " --json -d sntjmicron -a ", " --json -d sntasmedia -a ", " --json -d sntrealtek -a ", " --json -d sat -a "}; - - for (string sSmartctlCommand : sSmartctlCommands) + for (const string &sSmartctlCommand : sSmartctlCommands) { - string sCMD = ("smartctl"); + // Build command with timeout + string sCMD = "timeout 5 smartctl"; // 5 second timeout prevents hanging sCMD.append(sSmartctlCommand); sCMD.append(drive->getPath()); - const char *cpComand = sCMD.c_str(); + // Note: stderr NOT suppressed for debugging - // Logger::logThis()->info(cpComand); + Logger::logThis()->info("SMART: Executing: " + sCMD); - FILE *outputfileSmart = popen(cpComand, "r"); - size_t len = 0U; // length of found line - char *cLine = NULL; // found line - uint8_t status = 255U; - - while ((getline(&cLine, &len, outputfileSmart)) != -1) + // Execute smartctl with timeout protection + FILE *outputfileSmart = popen(sCMD.c_str(), "r"); + if (outputfileSmart == nullptr) { - string sLine = string(cLine); + Logger::logThis()->error("SMART: Failed to execute smartctl"); + continue; + } - SMART::parseExitStatus(sLine, status); - SMART::parseModelFamily(sLine, modelFamily); - SMART::parseModelName(sLine, modelName); - SMART::parseSerial(sLine, serial); - SMART::parseCapacity(sLine, capacity); - SMART::parseErrorCount(sLine, errorCount); - SMART::parsePowerOnHours(sLine, powerOnHours); - SMART::parsePowerCycles(sLine, powerCycles); - SMART::parseTemperature(sLine, temperature); + // Reset context for new attempt + ctx = SMARTParseContext(); + + // Parse output line by line + char *cLine = nullptr; + size_t len = 0; + int lineCount = 0; + + while (getline(&cLine, &len, outputfileSmart) != -1) + { + string sLine(cLine); + lineCount++; + + // Parse exit status + if (sLine.find("\"exit_status\":") != string::npos) + { + exitStatus = static_cast(extractIntegerValue(sLine)); + } + + // Process this line + processLine(sLine, ctx); } free(cLine); - pclose(outputfileSmart); + int pcloseStatus = pclose(outputfileSmart); - if (status == 0U) + Logger::logThis()->info("SMART: Parsed " + to_string(lineCount) + " lines, exit status: " + to_string(exitStatus)); + + // Check if timeout killed the process + if (WIFSIGNALED(pcloseStatus) && WTERMSIG(pcloseStatus) == SIGTERM) { - // Found S.M.A.R.T. data with this command - // Logger::logThis()->info("Found S.M.A.R.T. data with this command"); - break; + Logger::logThis()->warning("SMART: Command timed out (5s) - skipping to next variant"); + continue; } - } - drive->setDriveSMARTData(modelFamily, modelName, serial, capacity, errorCount, powerOnHours, powerCycles, temperature); // write data in drive -} - -/** - * \brief parse ExitStatus - * \param string output line of smartctl - * \param uint8_t parsed status - * \return bool if parsing was possible - */ -bool SMART::parseExitStatus(string sLine, uint8_t &status) -{ - string search("\"exit_status\": "); - size_t found = sLine.find(search); - if (found != string::npos) - { - sLine.erase(0U, sLine.find(": ") + 1U); - status = stol(sLine); - return true; - } - else - { - return false; - } -} - -/** - * \brief parse ModelFamily - * \param string output line of smartctl - * \param string parsed model family - * \return bool if parsing was possible - */ -bool SMART::parseModelFamily(string sLine, string &modelFamily) -{ - string search("\"model_family\": "); - size_t found = sLine.find(search); - if (found != string::npos) - { - sLine.erase(0U, sLine.find(": ") + 3U); - if (sLine.length() >= 3U) + // IGNORE exit status - instead check if we got valid data! + // Exit status 64 means "error log contains errors" but SMART data is still valid + // Exit status 4 means "some prefail attributes concerning" but data is valid + // What matters: Did we parse model name and serial? + if (!ctx.modelName.empty() && !ctx.serial.empty()) { - sLine.erase(sLine.length() - 3U, 3U); - } - modelFamily = sLine; - return true; - } - else - { - return false; - } -} + Logger::logThis()->info("SMART: Successfully parsed data"); + Logger::logThis()->info("SMART: Model: " + ctx.modelName); + Logger::logThis()->info("SMART: Serial: " + ctx.serial); + Logger::logThis()->info("SMART: Capacity: " + to_string(ctx.capacity) + " bytes"); + Logger::logThis()->info("SMART: Power-On Hours: " + to_string(ctx.powerOnHours)); + Logger::logThis()->info("SMART: Temperature: " + to_string(ctx.temperature) + " C"); + Logger::logThis()->info("SMART: Reallocated Sectors: " + to_string(ctx.reallocatedSectors)); + Logger::logThis()->info("SMART: Pending Sectors: " + to_string(ctx.pendingSectors)); + Logger::logThis()->info("SMART: Uncorrectable Sectors: " + to_string(ctx.uncorrectableSectors)); -/** - * \brief parse ModelName - * \param string output line of smartctl - * \param string parsed model name - * \return bool if parsing was possible - */ -bool SMART::parseModelName(string sLine, string &modelName) -{ - string search("\"model_name\": "); - size_t found = sLine.find(search); - if (found != string::npos) - { - sLine.erase(0U, sLine.find(": ") + 3U); - if (sLine.length() >= 3U) - { - sLine.erase(sLine.length() - 3U, 3U); - } - modelName = sLine; - return true; - } - else - { - return false; - } -} + if (exitStatus != 0) + { + Logger::logThis()->info("SMART: Note - exit status " + to_string(exitStatus) + " indicates warnings/errors in SMART log"); + } -/** - * \brief parse Serial - * \param string output line of smartctl - * \param string parsed serial - * \return bool if parsing was possible - */ -bool SMART::parseSerial(string sLine, string &serial) -{ - string search("\"serial_number\": "); - size_t found = sLine.find(search); - if (found != string::npos) - { - sLine.erase(0, sLine.find(": ") + 3); - if (sLine.length() >= 3U) - { - sLine.erase(sLine.length() - 3U, 3U); - } - serial = sLine; - return true; - } - else - { - return false; - } -} - -/** - * \brief parse Capacity - * \param string output line of smartctl - * \param string parsed capacity - * \return bool if parsing was possible - */ -bool SMART::parseCapacity(string sLine, uint64_t &capacity) -{ - string search("\"bytes\": "); - size_t found = sLine.find(search); - if (found != string::npos) - { - sLine.erase(0, sLine.find(": ") + 2); - if (sLine.length() >= 1U) - { - sLine.erase(sLine.length() - 1U, 1U); - } - capacity = stol(sLine); - return true; - } - else - { - return false; - } -} - -/** - * \brief parse ErrorCount - * \param string output line of smartctl - * \param uint32_t parsed error count - * \return bool if parsing was possible - */ -bool SMART::parseErrorCount(string sLine, uint32_t &errorCount) -{ - string search("\"error_count_total\": "); - size_t found = sLine.find(search); - if (found != string::npos) - { - sLine.erase(0U, sLine.find(": ") + 2U); - if (sLine.length() >= 2U) - { - sLine.erase(sLine.length() - 2U, 2U); - } - errorCount = stol(sLine); - return true; - } - else - { - return false; - } -} - -/** - * \brief parse PowerOnHours - * \param string output line of smartctl\ - * \param uint32_t parsed power on hours - * \return bool if parsing was possible - */ -bool SMART::parsePowerOnHours(string sLine, uint32_t &powerOnHours) -{ - string search("\"hours\": "); - size_t found = sLine.find(search); - if (found != string::npos) - { - sLine.erase(0U, sLine.find(": ") + 2U); - if (sLine.length() >= 1U) - { - sLine.erase(sLine.length() - 1U, 1U); - } - powerOnHours = stol(sLine); - return true; - } - else - { - return false; - } -} - -/** - * \brief parse PowerCycle - * \param string output line of smartctl - * \param uint32_t parsed power cycles - * \return bool if parsing was possible - */ -bool SMART::parsePowerCycles(string sLine, uint32_t &powerCycles) -{ - string search("\"power_cycle_count\": "); - size_t found = sLine.find(search); - if (found != string::npos) - { - sLine.erase(0, sLine.find(": ") + 2); - if (sLine.length() >= 2U) - { - sLine.erase(sLine.length() - 2U, 2U); - } - powerCycles = stol(sLine); - return true; - } - else - { - return false; - } -} - -/** - * \brief parse temperature - * \param string output line of smartctl - * \param uint32_t parsed temperature - * \return bool if parsing was possible - */ -bool SMART::parseTemperature(string sLine, uint32_t &temperature) -{ - string search("\"current\": "); - size_t found = sLine.find(search); - if (found != string::npos) - { - sLine.erase(0U, sLine.find(": ") + 2U); - if (sLine.length() >= 1U) - { - sLine.erase(sLine.length() - 1U, 2U); - } - if (sLine == "{") - { - temperature = 0U; // this drive doesn't support temperature + break; // Success - we got data! } else { - temperature = stol(sLine); + Logger::logThis()->warning("SMART: No valid data parsed (exit status: " + to_string(exitStatus) + ")"); } - return true; } - else + + // Check if we got ANY data + if (ctx.modelName.empty() && ctx.serial.empty()) { - return false; + Logger::logThis()->warning("SMART: No SMART data available for this drive - may not support SMART or need root privileges"); + + // Try basic device info without SMART (use hdparm or similar as fallback) + // For now, just log that SMART is not available + ctx.modelName = "SMART not available"; + ctx.serial = "N/A"; } + + // Write parsed data to drive + drive->setDriveSMARTData( + ctx.modelFamily, + ctx.modelName, + ctx.serial, + ctx.capacity, + ctx.errorCount, + ctx.powerOnHours, + ctx.powerCycles, + ctx.temperature, + ctx.reallocatedSectors, + ctx.pendingSectors, + ctx.uncorrectableSectors); } diff --git a/src/tui.cpp b/src/tui.cpp index 49e01b8..a21bd8c 100644 --- a/src/tui.cpp +++ b/src/tui.cpp @@ -110,10 +110,10 @@ void TUI::updateTUI(list *plistDrives, uint8_t u8SelectedEntry) bSelectedEntry = true; // mark this drive in entries list displaySelectedDrive(*it, u16StdscrX, u16StdscrY); - if ((it->getPowerOnHours() >= WORSE_HOURS) || (it->getPowerCycles() >= WORSE_POWERUP) || (it->getErrorCount() > 0) || (it->getTemperature() >= WORSE_TEMPERATURE)) + if ((it->getPowerOnHours() >= WORSE_HOURS) || (it->getPowerCycles() >= WORSE_POWERUP) || (it->getErrorCount() > 0) || (it->getTemperature() >= WORSE_TEMPERATURE) || (it->getReallocatedSectors() > 0) || (it->getPendingSectors() > 0) || (it->getUncorrectableSectors() > 0)) { // smart values are bad --> show warning - smartWarning = createSmartWarning(50, 10, ((u16StdscrX) - (int)(u16StdscrX / 2) + 35), (int)(u16StdscrY / 2) - 5, it->getPath(), it->getPowerOnHours(), it->getPowerCycles(), it->getErrorCount(), it->getTemperature()); + smartWarning = createSmartWarning(50, 14, ((u16StdscrX) - (int)(u16StdscrX / 2) + 35), (int)(u16StdscrY / 2) - 7, it->getPath(), it->getPowerOnHours(), it->getPowerCycles(), it->getErrorCount(), it->getTemperature(), it->getReallocatedSectors(), it->getPendingSectors(), it->getUncorrectableSectors()); wrefresh(smartWarning); } } @@ -721,7 +721,7 @@ void TUI::displaySelectedDrive(Drive &drive, int stdscrX, int stdscrY) } } -WINDOW *TUI::createSmartWarning(int iXSize, int iYSize, int iXStart, int iYStart, string sPath, uint32_t u32PowerOnHours, uint32_t u32PowerCycles, uint32_t u32ErrorCount, uint32_t u32Temperature) +WINDOW *TUI::createSmartWarning(int iXSize, int iYSize, int iXStart, int iYStart, string sPath, uint32_t u32PowerOnHours, uint32_t u32PowerCycles, uint32_t u32ErrorCount, uint32_t u32Temperature, uint32_t u32ReallocatedSectors, uint32_t u32PendingSectors, uint32_t u32UncorrectableSectors) { WINDOW *newWindow; newWindow = newwin(iYSize, iXSize, iYStart, iXStart); @@ -763,6 +763,28 @@ WINDOW *TUI::createSmartWarning(int iXSize, int iYSize, int iXStart, int iYStart { string sLineTmp = "Drive too hot: " + to_string(u32Temperature) + " C"; mvwaddstr(newWindow, u16Line++, (iXSize / 2) - (sLine01.size() / 2), sLineTmp.c_str()); + u16Line++; } + + if (u32ReallocatedSectors > 0) + { + string sLineTmp = "CRITICAL: Reallocated sectors detected: " + to_string(u32ReallocatedSectors); + mvwaddstr(newWindow, u16Line++, (iXSize / 2) - (sLine01.size() / 2), sLineTmp.c_str()); + u16Line++; + } + + if (u32PendingSectors > 0) + { + string sLineTmp = "CRITICAL: Pending sectors detected: " + to_string(u32PendingSectors); + mvwaddstr(newWindow, u16Line++, (iXSize / 2) - (sLine01.size() / 2), sLineTmp.c_str()); + u16Line++; + } + + if (u32UncorrectableSectors > 0) + { + string sLineTmp = "CRITICAL: Uncorrectable sectors: " + to_string(u32UncorrectableSectors); + mvwaddstr(newWindow, u16Line++, (iXSize / 2) - (sLine01.size() / 2), sLineTmp.c_str()); + } + return newWindow; }