/** * @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 scann 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(ThreadScannDevices); //start thread that scanns 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::ThreadScannDevices() { 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 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()->bWasDeleteted = 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 scann 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 allready attached: " + itNew->getModelName()); #endif itNew = plistNewDrives->erase(itNew); //This drive is allready 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 -I 8 -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("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 ingnore file ifstream input( "ignoreDrives.conf" ); //read ingnore file 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 ingnore 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 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 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 } } }