/** * @file reHDD.cpp * @brief app logic * @author hendrik schutter * @date 01.05.2020 */ #include "../include/reHDD.h" static int fdNewDrivesInformPipe[2]; // File descriptor for pipe that informs if new drives are found static int fdShredInformPipe[2]; // File descriptor for pipe that informs if a wipe thread signals static std::mutex mxDrives; list listNewDrives; // store found drives that are updated every 5sec static list listDrives; // stores all drive data from scan thread TUI *ui; static uint8_t u8SelectedEntry; static fd_set selectSet; /** * \brief app constructor * \param void * \return instance of App */ reHDD::reHDD(void) { u8SelectedEntry = 0U; } /** * \brief app logic * \param void * \return void */ void reHDD::app_logic(void) { ui = new TUI(); ui->initTUI(); pipe(fdNewDrivesInformPipe); pipe(fdShredInformPipe); thread thDevices(ThreadScanDevices); // start thread that scans for drives thread thUserInput(ThreadUserInput); // start thread that reads user input thread thCheckFrozenDrives(ThreadCheckFrozenDrives); // start thread that checks timeout for drives while (1) { FD_ZERO(&selectSet); FD_SET(fdNewDrivesInformPipe[0], &selectSet); FD_SET(fdShredInformPipe[0], &selectSet); select(FD_SETSIZE, &selectSet, NULL, NULL, NULL); if (FD_ISSET(fdNewDrivesInformPipe[0], &selectSet)) { mxDrives.lock(); char dummy; read(fdNewDrivesInformPipe[0], &dummy, 1); filterNewDrives(&listDrives, &listNewDrives); // filter and copy to app logic vector printDrives(&listDrives); mxDrives.unlock(); } if (FD_ISSET(fdShredInformPipe[0], &selectSet)) { char dummy; read(fdShredInformPipe[0], &dummy, 1); updateShredMetrics(&listDrives); #ifdef LOG_LEVEL_HIGH Logger::logThis()->info("got progress signal from a shred task"); #endif } ui->updateTUI(&listDrives, u8SelectedEntry); } // endless loop thDevices.join(); thUserInput.join(); thCheckFrozenDrives.join(); } Drive *reHDD::getSelectedDrive() { if (u8SelectedEntry < listDrives.size()) { list::iterator it = listDrives.begin(); advance(it, u8SelectedEntry); return &(*it); } else { Logger::logThis()->warning("selected drive not present"); return {}; } } void reHDD::ThreadScanDevices() { while (true) { mxDrives.lock(); listNewDrives.clear(); searchDrives(&listNewDrives); // search for new drives and store them in list filterIgnoredDrives(&listNewDrives); // filter out ignored drives addSMARTData(&listNewDrives); // add S.M.A.R.T. Data to the drives filterInvalidDrives(&listNewDrives); // filter out drives that report zero capacity mxDrives.unlock(); write(fdNewDrivesInformPipe[1], "A", 1); sleep(5); // sleep 5 sec } } void reHDD::ThreadCheckFrozenDrives() { while (true) { mxDrives.lock(); for (auto it = begin(listDrives); it != end(listDrives); ++it) { if (it->state == Drive::SHRED_ACTIVE) { it->checkFrozenDrive(); } } mxDrives.unlock(); sleep(13); // sleep 13 sec } } void reHDD::ThreadUserInput() { while (true) { // cout << TUI::readUserInput() << endl; switch (TUI::readUserInput()) { case TUI::UserInput::DownKey: // cout << "Down" << endl; handleArrowKey(TUI::UserInput::DownKey); ui->updateTUI(&listDrives, u8SelectedEntry); break; case TUI::UserInput::UpKey: // cout << "Up" << endl; handleArrowKey(TUI::UserInput::UpKey); ui->updateTUI(&listDrives, u8SelectedEntry); break; case TUI::UserInput::Undefined: // cout << "Undefined" << endl; break; case TUI::UserInput::Abort: // cout << "Abort" << endl; handleAbort(); ui->updateTUI(&listDrives, u8SelectedEntry); break; case TUI::UserInput::Delete: // cout << "Delete" << endl; if (getSelectedDrive() != nullptr) { if (getSelectedDrive()->state == Drive::NONE) { getSelectedDrive()->state = Drive::DELETE_SELECTED; } } ui->updateTUI(&listDrives, u8SelectedEntry); break; case TUI::UserInput::Shred: // cout << "Shred" << endl; if (getSelectedDrive() != nullptr) { if (getSelectedDrive()->state == Drive::NONE) { getSelectedDrive()->state = Drive::SHRED_SELECTED; } } ui->updateTUI(&listDrives, u8SelectedEntry); break; case TUI::UserInput::ShredAll: // cout << "ShredAll" << endl; startShredAllDrives(&listDrives); ui->updateTUI(&listDrives, u8SelectedEntry); break; case TUI::UserInput::Enter: // cout << "Enter" << endl; handleEnter(); ui->updateTUI(&listDrives, u8SelectedEntry); break; case TUI::UserInput::ESC: // cout << "ESC" << endl; handleESC(); ui->updateTUI(&listDrives, u8SelectedEntry); break; default: break; } } } void reHDD::ThreadShred(Drive *const pDrive) { if (pDrive != nullptr) { pDrive->setActionStartTimestamp(); // save timestamp at start of shredding Shred *pShredTask = new Shred(); // create new shred task pShredTask->shredDrive(pDrive, &fdShredInformPipe[1]); // start new shred task delete pShredTask; // delete shred task ui->updateTUI(&listDrives, u8SelectedEntry); } } void reHDD::ThreadDelete() { if (getSelectedDrive() != nullptr) { getSelectedDrive()->setActionStartTimestamp(); // save timestamp at start of deleting Delete::deleteDrive(getSelectedDrive()); // blocking, no thread getSelectedDrive()->state = Drive::TaskState::NONE; // delete finished getSelectedDrive()->bWasDeleted = true; Logger::logThis()->info("Finished delete for: " + getSelectedDrive()->getModelName() + "-" + getSelectedDrive()->getSerial()); ui->updateTUI(&listDrives, u8SelectedEntry); } } void reHDD::filterNewDrives(list *plistOldDrives, list *plistNewDrives) { list::iterator itOld; // Iterator for current (old) drive list list::iterator itNew; // Iterator for new drive list that was created from to scan thread // remove offline old drives from previously run for (itOld = plistOldDrives->begin(); itOld != plistOldDrives->end();) { if (itOld->bIsOffline == true) { Logger::logThis()->warning("Offline drive found: " + itOld->getPath()); itOld = plistOldDrives->erase(itOld); /* if(plistOldDrives->size() > 0){ //This can be a risk if the user starts a task for the selected drive and the selected drive changes u8SelectedEntry = 0U; } */ } else { ++itOld; } } // search offline drives and mark them for (itOld = plistOldDrives->begin(); itOld != plistOldDrives->end(); ++itOld) { itOld->bIsOffline = true; // set offline before searching in the new list for (itNew = plistNewDrives->begin(); itNew != plistNewDrives->end();) { if ((itOld->getSerial() == itNew->getSerial()) || (itOld->getPath() == itNew->getPath())) { 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()); #ifdef LOG_LEVEL_HIGH Logger::logThis()->info("Delete new drive, because already attached: " + itNew->getModelName()); #endif itNew = plistNewDrives->erase(itNew); // This drive is already attached, remove from new list } else { ++itNew; } } } // mark offline old drives for (itOld = plistOldDrives->begin(); itOld != plistOldDrives->end(); ++itOld) { if (itOld->bIsOffline == true) { // cout << "offline drive found: " << itOld->getPath() << endl; Logger::logThis()->warning("Mark offline drive found: " + itOld->getPath()); itOld->state = Drive::NONE; // clear state --> shred task will terminate } } // add new drives to drive list for (itNew = plistNewDrives->begin(); itNew != plistNewDrives->end(); ++itNew) { plistOldDrives->push_back(*itNew); // Logger::logThis()->info("Add new drive: " + itNew->getModelName()); } plistNewDrives->clear(); } /** * \brief search attached drives on /dev/sd* * \param pointer of list * plistDrives * \return void */ void reHDD::searchDrives(list *plistDrives) { // Logger::logThis()->info("--> search drives <--"); char *cLine = NULL; size_t len = 0; FILE *outputfileHwinfo = popen("lsblk -e 11 -d -o NAME", "r"); if (outputfileHwinfo == NULL) { Logger::logThis()->error("Unable to scan attached drives"); exit(EXIT_FAILURE); } while ((getline(&cLine, &len, outputfileHwinfo)) != -1) { if (string(cLine).length() == 4) { Drive *tmpDrive = new Drive("/dev/" + string(cLine).substr(0, 3)); tmpDrive->state = Drive::NONE; tmpDrive->bIsOffline = false; plistDrives->push_back(*tmpDrive); // Logger::logThis()->info("SATA drive found: " + tmpDrive->getPath()); } if (string(cLine).length() == 8) { Drive *tmpDrive = new Drive("/dev/" + string(cLine).substr(0, 7)); tmpDrive->state = Drive::NONE; tmpDrive->bIsOffline = false; plistDrives->push_back(*tmpDrive); // Logger::logThis()->info("NVME drive found: " + tmpDrive->getPath()); } } pclose(outputfileHwinfo); } /** * \brief filter out drives that are listed in "ignoreDrives.conf" * \param pointer of list * plistDrives * \return void */ void reHDD::filterIgnoredDrives(list *plistDrives) { list> vtlIgnoredDevices; // store drives from ignore file ifstream input("ignoreDrives.conf"); // read ignore file string systemDrivePath; if (getSystemDrive(systemDrivePath)) { Logger::logThis()->info("Found system drive: " + systemDrivePath); } for (string sLine; getline(input, sLine);) { // Logger::logThis()->info("read uuid: " + sLine); vtlIgnoredDevices.emplace_back(sLine); // add found path and uuid from ignore file to vector } // loop through found entries in ignore file for (auto row : vtlIgnoredDevices) { list::iterator it; for (it = plistDrives->begin(); it != plistDrives->end(); ++it) { string sUUID; char *cLine = NULL; size_t len = 0; string sCMD = "blkid "; sCMD.append(it->getPath()); // cout << "cmd: " << sCMD << endl; FILE *outputfileBlkid = popen(sCMD.c_str(), "r"); // get UUID from drive if (outputfileBlkid == NULL) { exit(EXIT_FAILURE); } while ((getline(&cLine, &len, outputfileBlkid)) != -1) // parse UUID from blkid { if (string(cLine).find("PTUUID") != string::npos) { string sBlkidOut = string(cLine); sBlkidOut.erase(0, 18); sBlkidOut.erase(8, sBlkidOut.length()); sUUID = sBlkidOut; // cout << "blkid uuid:" << sUUID << endl; } } pclose(outputfileBlkid); // cout << "blkid uuid:" << sUUID << endl; if (!get<0>(row).compare(sUUID)) // compare uuid from ignore file and uuid from drive { // same uuid found than in ignore file --> ignore this drive #ifdef LOG_LEVEL_HIGH Logger::logThis()->info("same uuid found than in ignore file --> ignore this drive: " + it->getPath()); #endif it = plistDrives->erase(it); it--; } } } } /** * \brief filter out drives that are not indented for processing * \param pointer of list * plistDrives * \return void */ void reHDD::filterInvalidDrives(list *plistDrives) { list::iterator it; for (it = plistDrives->begin(); it != plistDrives->end(); ++it) { if (it->getCapacity() == 0U) { #ifdef LOG_LEVEL_HIGH Logger::logThis()->info("Drive reports zero capacity --> ignore this drive: " + it->getPath()); #endif it = plistDrives->erase(it); it--; } } } /** * \brief start shred for all drives * \param pointer of list * plistDrives * \return void */ void reHDD::startShredAllDrives(list *plistDrives) { list::iterator it; mxDrives.lock(); for (it = plistDrives->begin(); it != plistDrives->end(); ++it) { if (it->state == Drive::NONE) { Drive *pTmpDrive = iterator_to_pointer::iterator>(it); #ifdef LOG_LEVEL_HIGH ostringstream address; address << (void const *)&(*pTmpDrive); Logger::logThis()->info("Started shred (all) for: " + pTmpDrive->getModelName() + "-" + pTmpDrive->getSerial() + " @" + address.str()); #endif pTmpDrive->state = Drive::TaskState::SHRED_ACTIVE; thread(ThreadShred, pTmpDrive).detach(); } } mxDrives.unlock(); } /** * \brief print drives with all information * \param pointer of list * plistDrives * \return void */ void reHDD::printDrives(list *plistDrives) { #ifdef LOG_LEVEL_HIGH Logger::logThis()->info("------------DRIVES START------------"); // cout << "------------DRIVES---------------" << endl; list::iterator it; uint8_t u8Index = 0; for (it = plistDrives->begin(); it != plistDrives->end(); ++it) { /* cout << " Drive: " << distance(pvecDrives->begin(), it) << endl; cout << "Path: " << it->getPath() << endl; cout << "ModelFamily: " << it->getModelFamily() << endl; cout << "ModelName: " << it->getModelName() << endl; cout << "Capacity: " << it->getCapacity() << endl; cout << "Serial: " << it->getSerial() << endl; cout << "PowerOnHours: " << it->getPowerOnHours() << endl; cout << "PowerCycle: " << it->getPowerCycles() << endl; cout << "ErrorCount: " << it->getErrorCount() << endl; cout << endl;*/ ostringstream address; address << (void const *)&(*it); Logger::logThis()->info(to_string(u8Index++) + ": " + it->getPath() + " - " + it->getModelFamily() + " - " + it->getSerial() + " @" + address.str()); } Logger::logThis()->info("------------DRIVES END--------------"); // cout << "---------------------------------" << endl; #endif } /** * \brief update shred metrics for all drives * \param pointer of list * plistDrives * \return void */ void reHDD::updateShredMetrics(list *plistDrives) { list::iterator it; for (it = plistDrives->begin(); it != plistDrives->end(); ++it) { if (it->state == Drive::SHRED_ACTIVE) { Drive *pTmpDrive = iterator_to_pointer::iterator>(it); // set metrics for calculating shred speed std::chrono::time_point chronoCurrentTimestamp = std::chrono::system_clock::now(); time_t u32ShredTimeDelta = (chronoCurrentTimestamp - pTmpDrive->sShredSpeed.chronoShredTimestamp).count(); if (u32ShredTimeDelta > METRIC_THRESHOLD) { pTmpDrive->sShredSpeed.u32ShredTimeDelta = u32ShredTimeDelta; pTmpDrive->sShredSpeed.chronoShredTimestamp = std::chrono::system_clock::now(); pTmpDrive->sShredSpeed.ulWrittenBytes = pTmpDrive->sShredSpeed.ulSpeedMetricBytesWritten; pTmpDrive->sShredSpeed.ulSpeedMetricBytesWritten = 0U; } } } } /** * \brief add S.M.A.R.T data from SMART * \param pointer of list * plistDrives * \return void */ void reHDD::addSMARTData(list *plistDrives) { list::iterator it; for (it = plistDrives->begin(); it != plistDrives->end(); ++it) { Drive *pTmpDrive = iterator_to_pointer::iterator>(it); SMART::readSMARTData(pTmpDrive); } } void reHDD::handleArrowKey(TUI::UserInput userInput) { int8_t u8EntrySize = (int8_t)listDrives.size(); switch (userInput) { case TUI::UserInput::DownKey: u8SelectedEntry++; if (u8SelectedEntry >= u8EntrySize) { u8SelectedEntry = 0; } break; case TUI::UserInput::UpKey: if (u8SelectedEntry == 0) { u8SelectedEntry = (u8EntrySize - 1); } else { u8SelectedEntry--; } break; default: u8SelectedEntry = 0; break; } // Logger::logThis()->info("ArrowKey - selected drive: " + to_string(u8SelectedEntry)); } void reHDD::handleEnter() { if (getSelectedDrive() != nullptr) { if (getSelectedDrive()->state == Drive::TaskState::SHRED_SELECTED) { Logger::logThis()->info("Started shred/check for: " + getSelectedDrive()->getModelName() + "-" + getSelectedDrive()->getSerial()); getSelectedDrive()->state = Drive::TaskState::SHRED_ACTIVE; // task for drive is running --> don't show more task options Drive *pTmpDrive = getSelectedDrive(); thread(ThreadShred, pTmpDrive).detach(); } if (getSelectedDrive()->state == Drive::TaskState::DELETE_SELECTED) { Logger::logThis()->info("Started delete for: " + getSelectedDrive()->getModelName() + "-" + getSelectedDrive()->getSerial()); getSelectedDrive()->state = Drive::TaskState::DELETE_ACTIVE; // task for drive is running --> don't show more task options thread(ThreadDelete).detach(); } } } void reHDD::handleESC() { if (getSelectedDrive() != nullptr) { if (getSelectedDrive()->state == Drive::TaskState::SHRED_SELECTED) { getSelectedDrive()->state = Drive::TaskState::NONE; // task for drive is selected --> remove selection } if (getSelectedDrive()->state == Drive::TaskState::DELETE_SELECTED) { getSelectedDrive()->state = Drive::TaskState::NONE; // task for drive is selected --> remove selection } } } void reHDD::handleAbort() { if (getSelectedDrive() != nullptr) { if (getSelectedDrive()->state == Drive::SHRED_ACTIVE || getSelectedDrive()->state == Drive::DELETE_ACTIVE) { getSelectedDrive()->state = Drive::NONE; Logger::logThis()->info("Abort-Shred-Signal for: " + getSelectedDrive()->getModelName() + "-" + getSelectedDrive()->getSerial()); // task for drive is running --> remove selection } } } bool reHDD::getSystemDrive(string &systemDrive) { Logger::logThis()->info("--> search system drive <--"); char *cLine = NULL; size_t len = 0; bool systemDriveFound = false; FILE *outputfileHwinfo = popen("lsblk -e 11 -o NAME,MOUNTPOINT", "r"); if (outputfileHwinfo == NULL) { Logger::logThis()->error("Unable to scan attached drives for system drive"); exit(EXIT_FAILURE); } while ((getline(&cLine, &len, outputfileHwinfo)) != -1) { string currentLine = cLine; if (currentLine.find("NAME") != std::string::npos) { continue; } // Logger::logThis()->info(currentLine); if ((cLine[0U] != '|') && (cLine[0U] != '`')) { systemDrive = currentLine; // Logger::logThis()->info("Drive found: " + systemDrive); } if (currentLine.ends_with(" /boot/efi\n"s)) { systemDriveFound = true; break; } if (currentLine.ends_with(" /\n"s)) { systemDriveFound = true; break; } } pclose(outputfileHwinfo); return systemDriveFound; }